<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          5分鐘,自己做一個隧道代理

          共 13570字,需瀏覽 28分鐘

           ·

          2021-07-28 03:23


          什么是隧道代理?我們來看下面這張截圖:

          所謂隧道代理,就是一個能幫你自動更換代理 IP 的代理服務(wù)

          在你的代碼里面,你只需要把一個入口代理地址寫死,然后正常發(fā)起請求,而目標服務(wù)器接收到的請求,每一次都是不同的代理地址

          在某代理網(wǎng)站上,隧道代理 50 并發(fā)每秒的價格是 4000 元/月:

          而常規(guī)的,先請求接口拿到一批代理 IP,再選一個發(fā)起請求的原始代理服務(wù)器,一個月價格才 600 多元:

          所以,如果我們能自己做一個隧道代理,將會省下很多錢!

          隧道代理的原理,跟常規(guī)代理的不同之處,用下面這兩張圖就能說清楚:

          傳統(tǒng)代理服務(wù)
          隧道代理

          要自己開發(fā)一個這樣的隧道代理,我們需要做兩步:

          1. 構(gòu)建一個代理池
          2. 實現(xiàn)代理自動轉(zhuǎn)發(fā)

          構(gòu)建代理池

          假設(shè)你從代理供應(yīng)商手上買到的便宜代理地址為:http://xxx.com/ips,直接在瀏覽器上面請求,頁面效果如下圖所示:

          現(xiàn)在,你需要做的就是寫一個程序,周期性訪問這個url,拉取當(dāng)前最新可用的IP地址,然后把它放到Redis中。

          這里,我們使用 Redis 的Hash這個數(shù)據(jù)結(jié)構(gòu),其中Hash的字段名就是IP:端口,里面的值就是跟每個IP相關(guān)的一些信息。

          你這個程序需要確保,當(dāng)前在Redis里面的代理地址,全部都是可用的。這里,我給出了一個示例程序:

          """
          ProxyManager.py
          ~~~~~~~~~~~~~~~~~~~~~
          簡易代理池管理工具,直接從URL中讀取所有
          最新的代理,并寫入Redis。
          """

          import yaml
          import time
          import json
          import redis
          import datetime
          import requests


          class ProxyManager:
              def __init__(self):
                  self.config = self.read_config()
                  self.redis_config = self.config['redis']
                  self.client = redis.Redis(host=self.redis_config['host'],
                                            password=self.redis_config['password'],
                                            port=self.redis_config['port'])
                  self.instance_dict = {}

              def read_config(self):
                  with open('config.yaml'as f:
                      config = yaml.safe_load(f.read())
                      return config

              def read_ip(self):
                  resp = requests.get(self.config['proxy']).text
                  if '{' in resp:
                      return []
                  proxy_list = resp.split()
                  return proxy_list

              def delete_ip(self, live_ips, pool_ips):
                  ip_to_removed = set(pool_ips) - set(live_ips)
                  if ip_to_removed:
                      print('ip to be removed:', ip_to_removed)
                      self.client.hdel(self.redis_config['key'], *list(ip_to_removed))

              def add_new_ips(self, live_ips, pool_ips):
                  ip_to_add = set(live_ips) - set(pool_ips)
                  if ip_to_add:
                      print('ip to add:', ip_to_add)
                      ips = {}
                      for ip in ip_to_add:
                          ips[ip] = json.dumps({'private_ip': ip,
                                                'ts': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')})
                      self.client.hset(self.redis_config['key'], mapping=ips)

              def run(self):
                  while True:
                      live_ips = self.read_ip()
                      pool_ips = [x.decode() for x in self.client.hgetall(self.redis_config['key'])]
                      self.delete_ip(live_ips, pool_ips)
                      self.add_new_ips(live_ips, pool_ips)
                      time.sleep(40)


          if __name__ == '__main__':
              manager = ProxyManager()
              manager.run()

          其中,我把Redis相關(guān)的配置、代理供應(yīng)商的URL寫到了一個yaml配置文件中,防止被你們看到。配置文件的格式如下圖所示:

          由于我這個代理供應(yīng)商提供的IP,有效期是1-5分鐘,所以保險起見,我每40秒更換一次IP。更換的時候,采用了增量更換的方式。把當(dāng)前拉取的IP和Redis里面的已有IP進行對比。不在這次拉取的IP全部從Redis移除,然后把新增的IP加到Redis中。

          大家在實際過程中,還可以加一些代理校驗的邏輯,確保從URL拉下來的代理也進行有效性檢查,發(fā)現(xiàn)無效的立刻移除。

          實現(xiàn)自動轉(zhuǎn)發(fā)

          要實現(xiàn)自動轉(zhuǎn)發(fā),我們可以使用OpenResty[1]

          它是一個基于Nginx和Lua實現(xiàn)的高性能Web平臺。通過它,我們可以使用Lua語言實現(xiàn)一些邏輯,例如從Redis讀取數(shù)據(jù),把來源請求轉(zhuǎn)發(fā)到上游代理服務(wù)器……

          因此,我們使用OpenResty搭建一個轉(zhuǎn)發(fā)服務(wù)。并把這個轉(zhuǎn)發(fā)服務(wù)所在服務(wù)器的IP地址作為我們的入口IP地址。在使用Requests等等網(wǎng)絡(luò)請求客戶端發(fā)送請求的時候,只需要把這個入口IP地址設(shè)置為代理。那么,當(dāng)客戶端發(fā)送請求的時候,請求首先到了OpenResty。然后它從Redis中隨機選一個代理IP來作為上游代理,并把剛剛發(fā)來的請求轉(zhuǎn)發(fā)到上游代理。從而實現(xiàn)隧道代理的效果。

          Lua是一門非常老的語言,它的語法不少地方跟Python不太一樣。不過你不用擔(dān)心,這個配置文件我已經(jīng)寫好了。大家拿過來改一改就能用。

          對應(yīng)的配置文件如下圖所示:

          worker_processes  16;        #nginx worker 數(shù)量
          error_log /usr/local/openresty/nginx/logs/error.log;   #指定錯誤日志文件路徑
          events {
              worker_connections 1024;
          }


          stream {
              ## TCP 代理日志格式定義
              log_format tcp_proxy '$remote_addr [$time_local] '
                                   '$protocol $status $bytes_sent $bytes_received '
                                   '$session_time "$upstream_addr" '
                                   '"$upstream_bytes_sent" "$upstream_bytes_received" "$upstream_connect_time"';
              ## TCP 代理日志配置
              access_log /usr/local/openresty/nginx/logs/access.log tcp_proxy;
              open_log_file_cache off;

              ## TCP 代理配置
              upstream backend{
                  server 127.0.0.2:1101;# 愛寫啥寫啥  反正下面的代碼也給你改了
                  balancer_by_lua_block {
                      -- 初始化balancer
                      local balancer = require "ngx.balancer"
                      local host = ""
                      local port = 0
                      host = ngx.ctx.proxy_host
                      port = ngx.ctx.proxy_port
                      -- 設(shè)置 balancer
                      local ok, err = balancer.set_current_peer(host, port)
                      if not ok then
                          ngx.log(ngx.ERR, "failed to set the peer: ", err)
                      end
                  }
              }


              server {
                  preread_by_lua_block{

                      local redis = require("resty.redis")
                      --創(chuàng)建實例
                      local redis_instance = redis:new()
                      --設(shè)置超時(毫秒)
                      redis_instance:set_timeout(3000)
                      --建立連接,請在這里配置Redis 的 IP 地址、端口號、密碼和用到的 Key
                      local rhost = "123.45.67.89"
                      local rport = 6739
                      local rpass = "abcdefg"
                      local rkey = "proxy:pool"
                      local ok, err = redis_instance:connect(rhost, rport)
                      ngx.log(ngx.ERR, "1111111 ", ok, " ", err)

                      -- 如果沒有密碼,移除下面這一行
                      local res, err = redis_instance:auth(rpass)
                      local res, err = redis_instance:hkeys(rkey)
                      if not res then
                          ngx.log(ngx.ERR,"res num error : ", err)
                          return redis_instance:close()
                      end
                      math.randomseed(tostring(ngx.now()):reverse():sub(1, 6))
                      local proxy = res[math.random(#res)]
                      local colon_index = string.find(proxy, ":")
                      local proxy_ip = string.sub(proxy, 1, colon_index - 1)
                      local proxy_port = string.sub(proxy, colon_index + 1)
                      ngx.log(ngx.ERR,"redis data = ", proxy_ip, ":", proxy_port);
                      ngx.ctx.proxy_host = proxy_ip
                      ngx.ctx.proxy_port = proxy_port
                      redis_instance:close()
                  }
                  #  下面是本機的端口,也就是爬蟲固定寫死的端口
                 listen 0.0.0.0:9976; #監(jiān)聽本機地址和端口,當(dāng)使用keeplived的情況下使用keeplived VIP
                 proxy_connect_timeout 3s;
                 proxy_timeout 10s;
                 proxy_pass backend; #這里填寫對端的地址
              }
          }

          需要修改的地方,我在配置文件里面已經(jīng)做好的注釋。具體而言,需要修改地方包含:

          • Redis的地址、端口、密碼和 Key。如果你的Redis沒有密碼,可以把設(shè)置密碼的這一行刪掉
          • 入口代理的端口

          設(shè)置好了這些配置以后,我們就可以使用Docker來啟動它。Docker的配置文件極其簡單:

          from openresty/openresty:centos

          copy nginx_redis.conf /usr/local/openresty/nginx/conf/nginx.conf

          然后,執(zhí)行命令構(gòu)建和運行:

          docker build --network host -t tunnel_proxy:0.01 .
          docker run --name tunnel_proxy --network host -it tunnel_proxy:0.01

          運行以后,你會看到Docker的命令行似乎卡住了。這是正常請求。因為需要你有了請求,它才會輸出內(nèi)容。

          現(xiàn)在,你可以用Requests趕快寫一段代碼來進行驗證:

          import requests
          import time

          proxies = {'http''http://13.88.220.207:9976'}
          for _ in range(10):
              resp = requests.get('http://httpbin.org/ip', proxies=proxies).text
              print(resp)
              time.sleep(1)

          運行效果如下圖所示:

          說明隧道代理搭建成功

          目前隧道代理我已經(jīng)穩(wěn)定運行了半年,從來沒有出過問題,大家可以放心使用

          參考資料

          [1]

          OpenResty: https://openresty.org/cn/

          [2]

          openresty正向代理搭建 - 簡書: https://www.jianshu.com/p/7808ee6395ab


          瀏覽 96
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  丁香五月天激情网 | 在线黄网| 中文字幕成人无码 | 少妇久久久久久久久久 | 四虎影院在线无码 |