標籤:

【爬蟲】解決封IP問題:1.利用ADSL伺服器

ADSL (Asymmetric Digital Subscriber Line ,非對稱數字用戶環路)是一種新的數據傳輸方式。它因為上行和下行帶寬不對稱,因此稱為非對稱數字用戶線環路。它採用頻分復用技術把普通的電話線分成了電話、上行和下行三個相對獨立的信道,從而避免了相互之間的干擾。

也就是你每撥一次號就換一次IP。

我買的是XX的VPS伺服器,這種撥號伺服器一搜一堆,但一定要注意可靠。不推薦某家,都差不多,大家自己篩選就好。大多平均一個月100左右。

一、界面

註冊購買後進入控制面板,我選擇的是Centos 7.1的系統,以下默認為Centos的操作。

根據他給你的賬號ssh連接後,ls可以看到一個ppp.sh的文件,執行後,輸入你的ADSL賬號和密碼,這些都在你的雲伺服器基本信息里。

當你看到一堆Success後,就是成功了。

adls-startn

既可撥號成功,如果要斷掉,則

adls-stopn

所以斷線重播的命令就是二者組合起來,先執行adsl-stop再執行adsl-start,每撥一次號,ifocnfig命令觀察一下主機的IP,發現主機的IP一直是在變化的,網卡名稱叫做ppp0。

或者可以用pppoe,輸入pppoe-setup,請按照下圖操作,除說明處,其餘都可直接敲回車鍵。

二、設置代理伺服器

接下來我們就親自試驗下怎樣搭建HTTP代理伺服器。

在Linux下搭建HTTP代理伺服器,推薦TinyProxy和Squid,配置都非常簡單,我們分別配置一下。

Squid

首先利用yum安裝squid

yum -y install squidn

設置開機啟動

chkconfig --level 35 squid onn

修改配置文件

vi /etc/squid/squid.confn

修改如下幾個部分:

http_access allow !Safe_ports #deny改成allownhttp_access allow CONNECT !SSL_ports #deny改成allownhttp_access allow all #deny改成allown

其他的不需要過多配置。

啟動squid

sudo service squid startn

如此一來配置就完成了。

代理使用的埠是3128

TinyProxy

命令行執行yum安裝指令:

yum install -y epel-releasenyum update -ynyum install -y tinyproxyn

運行完成之後就可以完成tinyproxy的安裝了。

配置TinyProxy

安裝完成之後還需要配置一下TinyProxy才可以用作代理伺服器,需要編輯配置文件,它一般的路徑是/etc/tinyproxy/tinyproxy.conf。

Allow 127.0.0.1n

這是被允許連接的主機的IP,如果想任何主機都可以連接,那就直接將它注釋即可,所以在這裡我們選擇直接注釋,也就是任何主機都可以使用這台主機作為代理伺服器了。設置完成之後重啟TinyProxy即可。

service tinyproxy startn

驗證TinyProxy

好了,這樣我們就成功搭建好代理伺服器了,首先ifconfig查看下當前主機的IP,比如當前我的主機撥號IP為112.84.118.216,在其他的主機運行測試一下。

比如用curl命令設置代理請求一下httpbin,檢測下代理是否生效。

curl -x 112.84.118.216:8888 httpbin.org/getn

動態獲取IP方法1--flask:

這個方法還需要另一台固定IP的主機或者某個雲伺服器。在這裡我用了另一台有固定IP的螞蟻雲主機。

那麼現在的思路就是,撥號VPS定時撥號換IP,然後請求阿里雲主機,螞蟻雲主機獲取VPS的IP地址即可。

撥號VPS做的事情:

定時撥號,定時請求伺服器。

使用bash腳本,然後crontab定時執行。(實際啟動的時候有問題,改成了python腳本)

遠程伺服器:

接收請求,獲取remote_addr,保存起來。使用Flask搭建伺服器,接收請求。

代碼:AutoProxy

功能

由於DDNS生效時間過長,對於爬蟲等一些時間要求比較緊迫的項目就不太適用,為此本項目根據DDNS基本原理來實現實時獲取ADSL撥號主機IP。

server文件夾是伺服器端運行,利用Python的Flask搭建伺服器,然後接收ADSL撥號客戶機的請求,得到remote_addr,獲取客戶機撥號後的IP。

項目結構

server

  • config.py 配置文件。
  • ip 客戶端請求後獲取的客戶端IP,文本保存。
  • main.py Flask主程序,提供兩個介面,一個是接收客戶端請求,然後將IP保存,另外一個是獲取當前保存的IP。

client

  • crontab 定時任務命令示例。
  • pppoe.sh 撥號腳本,主要是實現重新撥號的幾個命令。
  • request.sh 請求伺服器的腳本,主要是實現撥號後請求伺服器的操作。
  • request.conf 配置文件。

使用

伺服器

伺服器提供兩個功能,record方法是客戶機定時請求,然後獲取客戶機IP並保存。proxy方法是供我們自己用,返回保存的客戶機IP,提取代理。

修改配置

修改config.py文件

  • KEY 是客戶端請求伺服器時的憑證,在client的request.conf也有相同的配置,二者保持一致即可。
  • NEED_AUTH 在獲取當前保存的IP(即代理的IP)的時候,為防止自己的主機代理被濫用,在獲取IP的時候,需要加許可權驗證。
  • AUTH_USER和AUTH_PASSWORD分別是認證用戶名密碼。
  • PORT默認埠,返回保存的結果中會自動添加這個埠,組成一個IP:PORT的代理形式。

運行

cd servernnohup python main.pyn

ADSL客戶機 修改配置

修改reqeust.conf文件

  • KEY 是客戶端請求伺服器時的憑證,在server的config.py也有相同的配置,二者保持一致即可。
  • SERVER是伺服器項目運行後的地址,一般為http://<伺服器IP>:<伺服器埠>/record。如120.27.14.24:5000/recor

運行

設置定時任務

crontab -en

輸入crontab的實例命令

1*/5 * * * * /var/py/AutoProxy/client/request.sh /var/py/AutoProxy/client/request.conf >> /var/py/AutoProxy/client/request.logn

注意修改路徑,你的項目在哪裡,都統一修改成自己項目的路徑。

最前面的*/5是5分鐘執行一次。

好了,保存之後,定時任務就會開啟。

驗證結果

這樣一來,訪問伺服器地址,就可以得到ADSL撥號客戶機的IP了。

import requestsnurl = http://120.27.14.24:5000nproxy = requests.get(url, auth=(admin, 123)).textnprint(proxy)n

實例結果:

1116.208.97.22:8888

代理設置

urllib2

import urllib2nproxy_handler = urllib2.ProxyHandler({"http": http:// + proxy})nopener = urllib2.build_opener(proxy_handler)nurllib2.install_opener(opener)nresponse = urllib2.urlopen(http://httpbin.org/get)nprint response.read()n

requests

import requestsnproxies = {nhttp: http:// + proxy,n}nr = requests.get(http://httpbin.org/get, proxies=proxies)nprint(r.text)n

以上便秒級解決了動態IP解析,自己實現了一遍DDNS,以後就可以直接請求你的主機獲取一個最新可用的代理IP了。

動態獲取IP方法2:

要實現這個需要兩台主機,一台主機就是這台動態撥號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):n def __init__(self, host=REDIS_HOST, port=REDIS_PORT):n self.db = redis.Redis(host=host, port=port, password=REDIS_PASSWORD)n self.proxy_key = PROXY_KEYnn def key(self, name):n return {key}:{name}.format(key=self.proxy_key, name=name)nn def set(self, name, proxy):n return self.db.set(self.key(name), proxy)nn def get(self, name):n return self.db.get(self.key(name)).decode(utf-8)n

首先初始化Redis連接,我們可以將Key設計成adsl:vm1這種形式,冒號前面是總的key,冒號後面是主機名稱name,這樣顯得結構更加清晰。

然後指定set()和get()方法,用來存儲代理和獲取代理。

請求處理

撥號主機會一直向遠程主機發送請求,遠程主機當然可以獲取撥號主機的IP,但是代理埠是無法獲得的,我們在撥號主機上設置了TinyProxy或者Squid,但是伺服器不知道是在哪個埠開的,所以埠也是需要客戶端傳給遠程主機的。遠程主機接收到請求後,將解析得到的IP和埠合併就可以作為完整的代理保存了。

所以現在我們知道撥號主機需要傳送給遠程主機的信息已經有兩個了,一是撥號主機本身的名稱,二是代理的埠。

通信秘鑰

為了保證遠程主機不被惡意的請求干擾,可以設置一個傳輸秘鑰,最簡單的方式可以二者共同規定一個秘鑰字元串,撥號主機在傳送這個字元串,遠程主機匹配一下,如果能正確匹配,那就進行下一步的處理,如果不能匹配,那麼可能是惡意請求,就忽略這個請求。

當然肯定有更好的加密傳輸方式,但為了方便起見可以用如上來做。

所以客戶機還需要傳送一個數據,那就是通信秘鑰,一共需要傳送三個數據。

所以我們需要架設一個伺服器,一直監聽客戶端的請求,在這裡我們用tornado實現。

tornado的安裝也非常簡單,利用pip安裝即可:

pip3 install tornadon

定義一個處理撥號主機請求的方法,在這裡我們使用post請求,參考如下。

def post(self):n token = self.get_body_argument(token, default=None, strip=False)n port = self.get_body_argument(port, default=None, strip=False)n name = self.get_body_argument(name, default=None, strip=False)n if token == TOKEN and port:n ip = self.request.remote_ipn proxy = ip + : + portn print(Receive proxy, proxy)n self.redis.set(name, proxy)n self.test_proxies()n elif token != TOKEN:n self.write(Wrong Token)n elif not port:n self.write(No Client Port)n

遠程主機獲取請求的token,也就是上面我們所說的通信密鑰,保證安全。port是撥號機的代理埠,name是撥號主機的名稱。然後我們再獲取請求的remote_ip,也就是撥號主機的IP。然後將IP和埠拼合就可以得到撥號主機的完整代理信息了,將其存入資料庫即可。

代理檢測

在遠程主機端我們需要做一下代理檢測,如果某個代理不可用了,會及時將其去除,以免出現獲取到代理後不可用的情況。

注意:在這裡在撥號主機端驗證是不夠的,因為可能突然遇到某個撥號主機宕機的情況,這樣撥號主機就不會再向遠程主機發送請求,而最後一次得到的代理還會存在於資料庫中,所以在遠程主機端統一驗證比較科學。

驗證方式可以定時檢測,也可以每收到一次請求檢測一次,用獲取到的代理來請求某個網站,檢測一下是否能訪問即可。如果不能,將其從資料庫中刪除。

API

遠程主機已經將撥號主機的IP和埠保存下來了,那也就是說,所有的可用的代理已經在遠程主機保存了,我們需要提供一個介面來將代理獲取下來。

比如我們可以提供這麼幾個方法,獲取所有代理,獲取最新代理,獲取隨機代理等等。

def all(self):n keys = self.keys()n proxies = [{name: key, proxy: self.get(key)} for key in keys]n return proxiesnndef random(self):n items = self.all()n return random.choice(items).get(proxy)nndef list(self):n keys = self.keys()n proxies = [self.get(key) for key in keys]n return proxiesnndef first(self):n return self.get(self.keys()[0])n

然後用tornado搭建API服務,如果可以的話還可以綁定一個域名,更加便捷,舉例如下:

獲取隨機代理:

獲取最新代理:

獲取所有代理:

請求介面獲取可用代理即可,比如獲取一個隨機代理:

import requestsnndef get_random_proxy():n try:n # 遠程主機的服務地址n url = http://xxx.xxx.xxx.xxx:8000/randomn return requests.get(url).textn except requests.exceptions.ConnectionError:n return Nonen

這樣我們拿到的IP都是穩定可用的,而且過段時間重新請求取到的IP就會變化,是一直動態變化的高可用代理。

撥號VPS實現

定時撥號

撥號VPS需要每隔一段時間就撥號一次,我們可以直接執行命令行來撥號,那在Python里我們只需要調用一下這個撥號命令就好了。利用subprocess模塊調用腳本即可,在這裡定義一個變數ADSL_BASH為adsl-stop;adsl-start,這就是撥號的腳本。

import subprocessn(status, output) = subprocess.getstatusoutput(ADSL_BASH)n

通過getstatusoutput方法可以獲取腳本的執行狀態和輸出結果,如果status為0,則證明撥號成功,然後檢測一下撥號介面是否獲取了IP地址。

執行ifconfig命令可以獲取當前的IP,我這台主機介面名稱叫做ppp0,當然網卡名稱可以自己指定,所以將ppp0介面的IP提取出來即可。

def get_ip(self, ifname=ADSL_IFNAME):n (status, output) = subprocess.getstatusoutput(ifconfig)n if status == 0:n pattern = re.compile(ifname + .*?inet.*?(d+.d+.d+.d+).*?netmask, re.S)n result = re.search(pattern, output)n if result:n ip = result.group(1)n return ipn

如果方法正常返回IP,則證明IP存在,撥號成功,接下來向遠程主機發送請求即可,然後sleep一段時間重新再次撥號。

如果方法返回的值為空,那證明IP不存在,我們需要重新撥號。

請求遠程主機

發送的時候需要攜帶這麼幾個信息,一個是通信秘鑰,一個是代理埠,另一個是主機的標識符,用requests發送即可。

requests.post(SERVER_URL, data={token: TOKEN, port: PROXY_PORT, name: CLIENT_NAME})n

所以整體的思路實現可以寫成這樣子:

def adsl(self):n while True:n print(ADSL Start, Please wait)n (status, output) = subprocess.getstatusoutput(ADSL_BASH)n if status == 0:n print(ADSL Successfully)n ip = self.get_ip()n if ip:n print(New IP, ip)n try:n requests.post(SERVER_URL, data={token: TOKEN, port: PROXY_PORT, name: CLIENT_NAME})n print(Successfully Sent to Server, SERVER_URL)n except ConnectionError:n print(Failed to Connect Server, SERVER_URL)n time.sleep(ADSL_CYCLE)n else:n print(Get IP Failed)n else:n print(ADSL Failed, Please Check)n time.sleep(1)n

這樣我們就可以做到定時撥號並向遠程主機發送請求了。

代碼

Talk is cheap, show me the code! 在這裡提供一份完整代碼實現,其中client模塊是在動態VPS主機運行,server模塊在遠程主機運行,具體的操作使用可以參考README。

ADSLProxyPool


推薦閱讀:

對豆瓣《戰狼2》87767 條短評做詞雲
爬取Ajax動態載入和翻頁時url不變的網頁
【Python爬蟲實戰】——爬取今日頭條美女圖片
爬蟲軟體|軟體的簡單使用(二)
數據採集技術指南 第一篇 技術棧總覽

TAG:爬虫 | ADSL | Python |