使用Tornado+Redis维护ADSL拨号服务器代理池
我們嘗試維護過一個免費的代理池,但是代理池效果用過就知道了,畢竟里面有大量免費代理,雖然這些代理是可用的,但是既然我們能刷到這個免費代理,別人也能呀,所以就導致這個代理同時被很多人使用來抓取網站,所以當我們興致勃勃地拿他來抓取某個網站的時候,會發現它還是被網站封禁的狀態,所以在某些情況下免費代理池的成功率還是比較低的。
當然我們也可以去購買一些代理,比如幾塊錢提取幾百幾千個的代理,然而經過測試后質量也是很一般,也可以去購買專線代理,不過價格也是不菲的。那么目前最穩定而且又保證可用的代理方法就是設置ADSL撥號代理了。
本篇來講解一下ADSL撥號代理服務器的相關設置。
什么是ADSL
大家可能對ADSL比較陌生,ADSL全稱叫做Asymmetric Digital Subscriber Line,非對稱數字用戶環路,因為它的上行和下行帶寬不對稱。它采用頻分復用技術把普通的電話線分成了電話、上行和下行三個相對獨立的信道,從而避免了相互之間的干擾。
有種主機叫做動態撥號VPS主機,這種主機在連接上網的時候是需要撥號的,只有撥號成功后才可以上網,每撥一次號,主機就會獲取一個新的IP,也就是它的IP并不是固定的,而且IP量特別大,幾乎不會撥到相同的IP,如果我們用它來搭建代理,既能保證高度可用,又可以自由控制撥號切換。
經測試發現這也是最穩定最有效的代理方式,本節詳細介紹一下ADSL撥號代理服務器的搭建方法。
購買動態撥號VPS主機
所以在開始之前,我們需要先購買一臺動態撥號VPS主機,這樣的主機在百度搜索一下,服務商還是相當多的,在這里推薦一家云立方http://www.yunlifang.cn/dynamicvps.asp,感覺還是比較良心的,非廣告。
配置的話可以自行選擇,看下帶寬是否可以滿足需求就好了。
購買完成之后,就需要安裝操作系統了,進入撥號主機的后臺,首先預裝一個操作系統。
在這里推薦安裝CentOS7系統。
然后找到遠程管理面板找到遠程連接的用戶名和密碼,也就是SSH遠程連接服務器的信息。
比如我這邊的IP端口分別是 153.36.65.214:20063,用戶名是root。
命令行下輸入:
ssh root@153.36.65.214 -p 20063然后輸入管理密碼,就可以連接上遠程服務器了。
進入之后,可以發現有一個可用的腳本文件,叫做ppp.sh,這是撥號初始化的腳本,運行它會讓我們輸入撥號的用戶名和密碼,然后它就會開始各種撥號配置,一次配置成功,后面的撥號就不需要重復輸入用戶名和密碼了。
運行ppp.sh腳本,輸入用戶名密碼等待它的配置完成。
都提示成功之后就可以進行撥號了。
在撥號之前如果我們測試ping任何網站都是不通的,因為當前網絡還沒聯通,輸入撥號命令:
adsl-start可以發現撥號命令成功運行,沒有任何報錯信息,這就證明撥號成功完成了,耗時約幾秒鐘。接下來如果再去ping外網就可以通了。
如果要停止撥號可以輸入:
adsl-stop停止之后,可以發現又連不通網絡了。
所以只有撥號之后才可以建立網絡連接。
所以斷線重播的命令就是二者組合起來,先執行adsl-stop再執行adsl-start,每撥一次號,ifocnfig命令觀察一下主機的IP,發現主機的IP一直是在變化的,網卡名稱叫做ppp0。
所以,到這里我們就可以知道它作為代理服務器的巨大優勢了,如果將這臺主機作為代理服務器,如果我們一直撥號換IP,就不怕遇到IP被封的情況了,即使某個IP被封了,重新撥一次號就好了。
所以接下來我們要做的就有兩件事,一是怎樣將主機設置為代理服務器,二是怎樣實時獲取撥號主機的IP。
設置代理服務器
之前我們經常聽說代理服務器,也設置過不少代理了,但是可能沒有自己設置吧,自己有一臺主機怎樣設置為代理服務器呢?接下來我們就親自試驗下怎樣搭建HTTP代理服務器。
在Linux下搭建HTTP代理服務器,推薦TinyProxy和Squid,配置都非常簡單,在這里我們以TinyProxy為例來講解一下怎樣搭建代理服務器。
安裝TinyProxy
當然第一步就是安裝TinyProxy這個軟件了,在這里我使用的系統是CentOS,所以使用yum來安裝,如果是其他系統如Ubuntu可以選擇apt-get等命令安裝,都是類似的。
命令行執行yum安裝指令:
yum install -y epel-releaseyum update -y
yum install -y tinyproxy
運行完成之后就可以完成tinyproxy的安裝了。
配置TinyProxy
安裝完成之后還需要配置一下TinyProxy才可以用作代理服務器,需要編輯配置文件,它一般的路徑是/etc/tinyproxy/tinyproxy.conf。
可以看到有一行
Port 8888在這里可以設置代理的端口,默認是8888。
然后繼續向下找,有這么一行
Allow 127.0.0.1這是被允許連接的主機的IP,如果想任何主機都可以連接,那就直接將它注釋即可,所以在這里我們選擇直接注釋,也就是任何主機都可以使用這臺主機作為代理服務器了。
修改為
# Allow127.0.0.1設置完成之后重啟TinyProxy即可。
service tinyproxy start驗證TinyProxy
好了,這樣我們就成功搭建好代理服務器了,首先ifconfig查看下當前主機的IP,比如當前我的主機撥號IP為112.84.118.216,在其他的主機運行測試一下。
比如用curl命令設置代理請求一下httpbin,檢測下代理是否生效。
curl -x 112.84.118.216:8888 httpbin.org/get如果有正常的結果輸出并且origin的值為代理IP的地址,就證明TinyProxy配置成功了。
好,那到現在,我們接下來要做的就是需要動態實時獲取主機的IP了。
動態獲取IP
真正的好戲才開始呢,我們怎樣動態獲取主機的IP呢?可能你首先想到的是DDNS也就是動態域名解析服務,我們需要使用一個域名來解析,也就是雖然IP是變的,但域名解析的地址可以隨著IP的變化而變化。
它的原理其實是撥號主機向固定的服務器發出請求,服務器獲取客戶端的IP,然后再將域名解析到這個IP上就可以了。
國內比較有名的服務就是花生殼:http://hsk.oray.com/了,也提供了免費版的動態域名解析,另外DNSPOD也提供了解析接口來動態修改域名解析設置,DNSPOD:https://www.dnspod.cn/docs/records.html#dns,但是這樣的方式都有一個通病,那就是慢!
原因在于DNS修改后到完全生效是需要一定時間的,所以如果在前一秒撥號了,這一秒的域名解析的可能還是原來的IP,時間長的話可能需要幾分鐘,也就是說這段時間內,服務器IP已經變了,但是域名還是上一次撥號的IP,所以代理是不能用的,對于爬蟲這種秒級響應的需求,是完全不能接受的。
所以根據花生殼的原理,可以完全自己實現一下動態獲取IP的方法。
所以本節重點介紹的就是怎樣來實現實時獲取撥號主機IP的方法。
要實現這個需要兩臺主機,一臺主機就是這臺動態撥號VPS主機,另一臺是具有固定公網IP的主機。動態VPS主機撥號成功之后就請求遠程的固定主機,遠程主機獲取動態VPS主機的IP,就可以得到這個代理,將代理保存下來,這樣撥號主機每撥號一次,遠程主機就會及時得到撥號主機的IP,如果有多臺撥號VPS,也統一發送到遠程主機,這樣我們只需要從遠程主機取下代理就好了,保準是實時可用,穩定高效的。
整體思路大體是這樣子,當然為了更完善一下,我們要做到如下功能:
遠程主機:
監聽主機請求,獲取動態VPS主機IP
將VPS主機IP記錄下來存入數據庫,支持多個客戶端
檢測當前接收到的IP可用情況,如果不可用則刪除
提供API接口,通過API接口可獲取當前可用代理IP
撥號VPS:
定時執行撥號腳本換IP
換IP后立即請求遠程主機
撥號后檢測是否撥號成功,如果失敗立即重新撥號
遠程主機實現
說了這么多,那么我們就梳理一下具體的實現吧,整個項目我們用Python3實現。
數據庫
遠程主機作為一臺服務器,動態撥號VPS會定時請求遠程主機,遠程主機接收到請求后將IP記錄下來存入數據庫。
因為IP是一直在變化的,IP更新了之后,原來的IP就不能用了,所以對于一個主機來說我們可能需要多次更新一條數據。另外我們不能僅限于維護一臺撥號VPS主機,當然是需要支持多臺維護的。在這里我們直接選用Key-Value形式的非關系型數據庫存儲更加方便,所以在此選用Redis數據庫。
既然是Key-Value,Key是什么?Value是什么?首先我們能確定Value就是代理的值,比如112.84.119.67:8888,那么Key是什么?我們知道,這個IP是針對一臺動態撥號VPS的,而且這個值會不斷地變,所以我們需要有一個不變量Key來唯一標識這臺主機,所以在這里我們可以把Key當做主機名稱。名稱怎么來?自己取就好了,只要每臺主機的名字不重復,我們就可以區分出是哪臺主機了,這個名字可以在撥號主機那邊指定,然后傳給遠程主機就好了。
所以,在這里數據庫我們選用Redis,Key就是撥號主機的名稱,可以自己指定,Value就是代理的值。
所以可以寫一個操作Redis數據庫的類,參考如下:
class RedisClient(object):? ?def __init__(self, host=REDIS_HOST, port=REDIS_PORT):
? ? ? ?self.db = redis.Redis(host=host, port=port, password=REDIS_PASSWORD)
? ? ? ?self.proxy_key = PROXY_KEY
? ?def key(self, name):
? ? ? ?return '{key}:{name}'.format(key=self.proxy_key, name=name)
? ?def set(self, name, proxy):
? ? ? ?return self.db.set(self.key(name), proxy)
? ?def get(self, name):
? ? ? ?return self.db.get(self.key(name)).decode('utf-8')
首先初始化Redis連接,我們可以將Key設計成adsl:vm1這種形式,冒號前面是總的key,冒號后面是主機名稱name,這樣顯得結構更加清晰。
然后指定set()和get()方法,用來存儲代理和獲取代理。
請求處理
撥號主機會一直向遠程主機發送請求,遠程主機當然可以獲取撥號主機的IP,但是代理端口是無法獲得的,我們在撥號主機上設置了TinyProxy或者Squid,但是服務器不知道是在哪個端口開的,所以端口也是需要客戶端傳給遠程主機的。遠程主機接收到請求后,將解析得到的IP和端口合并就可以作為完整的代理保存了。
所以現在我們知道撥號主機需要傳送給遠程主機的信息已經有兩個了,一是撥號主機本身的名稱,二是代理的端口。
通信秘鑰
為了保證遠程主機不被惡意的請求干擾,可以設置一個傳輸秘鑰,最簡單的方式可以二者共同規定一個秘鑰字符串,撥號主機在傳送這個字符串,遠程主機匹配一下,如果能正確匹配,那就進行下一步的處理,如果不能匹配,那么可能是惡意請求,就忽略這個請求。
當然肯定有更好的加密傳輸方式,但為了方便起見可以用如上來做。
所以客戶機還需要傳送一個數據,那就是通信秘鑰,一共需要傳送三個數據。
所以我們需要架設一個服務器,一直監聽客戶端的請求,在這里我們用tornado實現。
tornado的安裝也非常簡單,利用pip安裝即可:
pip3 install tornado定義一個處理撥號主機請求的方法,在這里我們使用post請求,參考如下。
def post(self):? ? ? ?token = self.get_body_argument('token', default=None, strip=False)
? ? ? ?port = self.get_body_argument('port', default=None, strip=False)
? ? ? ?name = self.get_body_argument('name', default=None, strip=False)
? ? ? ?if token == TOKEN and port:
? ? ? ? ? ?ip = self.request.remote_ip
? ? ? ? ? ?proxy = ip + ':' + port
? ? ? ? ? ?print('Receive proxy', proxy)
? ? ? ? ? ?self.redis.set(name, proxy)
? ? ? ? ? ?self.test_proxies()
? ? ? ?elif token != TOKEN:
? ? ? ? ? ?self.write('Wrong Token')
? ? ? ?elif not port:
? ? ? ? ? ?self.write('No Client Port')
遠程主機獲取請求的token,也就是上面我們所說的通信密鑰,保證安全。port是撥號機的代理端口,name是撥號主機的名稱。然后我們再獲取請求的remote_ip,也就是撥號主機的IP。然后將IP和端口拼合就可以得到撥號主機的完整代理信息了,將其存入數據庫即可。
代理檢測
在遠程主機端我們需要做一下代理檢測,如果某個代理不可用了,會及時將其去除,以免出現獲取到代理后不可用的情況。
注意:在這里在撥號主機端驗證是不夠的,因為可能突然遇到某個撥號主機宕機的情況,這樣撥號主機就不會再向遠程主機發送請求,而最后一次得到的代理還會存在于數據庫中,所以在遠程主機端統一驗證比較科學。
驗證方式可以定時檢測,也可以每收到一次請求檢測一次,用獲取到的代理來請求某個網站,檢測一下是否能訪問即可。如果不能,將其從數據庫中刪除。
API
遠程主機已經將撥號主機的IP和端口保存下來了,那也就是說,所有的可用的代理已經在遠程主機保存了,我們需要提供一個接口來將代理獲取下來。
比如我們可以提供這么幾個方法,獲取所有代理,獲取最新代理,獲取隨機代理等等。
def all(self):? ?keys = self.keys()
? ?proxies = [{'name': key, 'proxy': self.get(key)} for key in keys]
? ?return proxies
def random(self):
? ?items = self.all()
? ?return random.choice(items).get('proxy')
def list(self):
? ?keys = self.keys()
? ?proxies = [self.get(key) for key in keys]
? ?return proxies
def first(self):
? ?return self.get(self.keys()[0])
然后用tornado搭建API服務,如果可以的話還可以綁定一個域名,更加便捷,舉例如下:
獲取隨機代理:
獲取最新代理:
獲取所有代理:
請求接口獲取可用代理即可,比如獲取一個隨機代理:
import requestsdef get_random_proxy():
? ?try:
? ? ? ?# 遠程主機的服務地址
? ? ? ?url = 'http://xxx.xxx.xxx.xxx:8000/random'
? ? ? ?return requests.get(url).text
? ?except requests.exceptions.ConnectionError:
? ? ? ?return None
這樣我們拿到的IP都是穩定可用的,而且過段時間重新請求取到的IP就會變化,是一直動態變化的高可用代理。
撥號VPS實現
定時撥號
撥號VPS需要每隔一段時間就撥號一次,我們可以直接執行命令行來撥號,那在Python里我們只需要調用一下這個撥號命令就好了。利用subprocess模塊調用腳本即可,在這里定義一個變量ADSL_BASH為adsl-stop;adsl-start,這就是撥號的腳本。
import subprocess(status, output) = subprocess.getstatusoutput(ADSL_BASH)
通過getstatusoutput方法可以獲取腳本的執行狀態和輸出結果,如果status為0,則證明撥號成功,然后檢測一下撥號接口是否獲取了IP地址。
執行ifconfig命令可以獲取當前的IP,我這臺主機接口名稱叫做ppp0,當然網卡名稱可以自己指定,所以將ppp0接口的IP提取出來即可。
def get_ip(self, ifname=ADSL_IFNAME):? ?(status, output) = subprocess.getstatusoutput('ifconfig')
? ?if status == 0:
? ? ? ?pattern = re.compile(ifname + '.*?inet.*?(\d+\.\d+\.\d+\.\d+).*?netmask', re.S)
? ? ? ?result = re.search(pattern, output)
? ? ? ?if result:
? ? ? ? ? ?ip = result.group(1)
? ? ? ? ? ?return ip
如果方法正常返回IP,則證明IP存在,撥號成功,接下來向遠程主機發送請求即可,然后sleep一段時間重新再次撥號。
如果方法返回的值為空,那證明IP不存在,我們需要重新撥號。
請求遠程主機
發送的時候需要攜帶這么幾個信息,一個是通信秘鑰,一個是代理端口,另一個是主機的標識符,用requests發送即可。
requests.post(SERVER_URL, data={'token': TOKEN, 'port': PROXY_PORT, 'name': CLIENT_NAME})所以整體的思路實現可以寫成這樣子:
def adsl(self):? ? ? ?while True:
? ? ? ? ? ?print('ADSL Start, Please wait')
? ? ? ? ? ?(status, output) = subprocess.getstatusoutput(ADSL_BASH)
? ? ? ? ? ?if status == 0:
? ? ? ? ? ? ? ?print('ADSL Successfully')
? ? ? ? ? ? ? ?ip = self.get_ip()
? ? ? ? ? ? ? ?if ip:
? ? ? ? ? ? ? ? ? ?print('New IP', ip)
? ? ? ? ? ? ? ? ? ?try:
? ? ? ? ? ? ? ? ? ? ? ?requests.post(SERVER_URL, data={'token': TOKEN, 'port': PROXY_PORT, 'name': CLIENT_NAME})
? ? ? ? ? ? ? ? ? ? ? ?print('Successfully Sent to Server', SERVER_URL)
? ? ? ? ? ? ? ? ? ?except ConnectionError:
? ? ? ? ? ? ? ? ? ? ? ?print('Failed to Connect Server', SERVER_URL)
? ? ? ? ? ? ? ? ? ?time.sleep(ADSL_CYCLE)
? ? ? ? ? ? ? ?else:
? ? ? ? ? ? ? ? ? ?print('Get IP Failed')
? ? ? ? ? ?else:
? ? ? ? ? ? ? ?print('ADSL Failed, Please Check')
? ? ? ? ? ?time.sleep(1)
這樣我們就可以做到定時撥號并向遠程主機發送請求了。
代碼
Talk is cheap, show me the code! 在這里提供一份完整代碼實現,其中client模塊是在動態VPS主機運行,server模塊在遠程主機運行,具體的操作使用可以參考README。
ADSLProxyPool:
https://github.com/Germey/ADSLProxyPool
總結
以上是生活随笔為你收集整理的使用Tornado+Redis维护ADSL拨号服务器代理池的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何选择一台好的拨号服务器?
- 下一篇: 怎么设置虚拟拨号服务器,如何设置PPPo