Python3網路爬蟲(十一):爬蟲黑科技之讓你的爬蟲程式更像人類使用者的行為(代理IP池等)

圓方圓PYTHON學院發表於2019-01-07

原文連結: Jack-Cui,blog.csdn.net/c406495762

執行平臺: Windows Python版本: Python3.x IDE: Sublime text3

1 前言

近期,有些朋友問我一些關於如何應對反爬蟲的問題。由於好多朋友都在問,因此決定寫一篇此類的部落格。把我知道的一些方法,分享給大家。博主屬於小菜級別,玩爬蟲也完全是處於興趣愛好,如有不足之處,還望指正。

在網際網路上進行自動資料採集(抓取)這件事和網際網路存在的時間差不多一樣長。今天大眾好像更傾向於用“網路資料採集”,有時會把網路資料採集程式稱為網路機器人(bots)。最常用的方法是寫一個自動化程式向網路伺服器請求資料(通常是用 HTML 表單或其他網頁檔案),然後對資料進行解析,提取需要的資訊。

說句實在話,如果我的網站總是讓人爬來爬取的,經常被虛擬訪問者騷擾,我也是蠻煩的,而且如果遇到“霸道”一點的爬蟲,都能直接把伺服器卡死。因此,我們在爬取別人網站的時候,也多為對方考慮考慮。不過話說回來,我卻沒有這個煩惱,為什麼呢?因為我根本就沒有自己的網站。=.=

Python3網路爬蟲(十一):爬蟲黑科技之讓你的爬蟲程式更像人類使用者的行為(代理IP池等)

2 黑科技

網站防採集的前提就是要正確地區分人類訪問使用者和網路機器人。現在網站有很多技術來防止爬蟲,比如驗證碼,對於一些簡單的數字驗證碼,可以使用訓練好的caffemodel諸如此類的模型去識別,準確率還是可以的。當然,也可以在Github搜一搜關於驗證碼識別的東西,看一看大牛們是怎麼玩的。除了這些高大上的,還有一些十分簡單的方法可以讓你的網路機器人看起來更像人類訪問使用者。

2.1 構造合理的HTTP請求頭

除了處理網站表單,requests 模組還是一個設定請求頭的利器。HTTP 的請求頭是在你每次向網路伺服器傳送請求時,傳遞的一組屬性和配置資訊。HTTP 定義了十幾種古怪的請求頭型別,不過大多數都不常用。

每個網站都有不同的請求頭,如何獲取這個請求頭呢?可以用我從前提到過的Fiddler或者審查元素的方法,我們可以根據實際情況進行配置。例如,GET百度根目錄的時候,需要新增的請求頭資訊如下:

Python3網路爬蟲(十一):爬蟲黑科技之讓你的爬蟲程式更像人類使用者的行為(代理IP池等)

部分引數說明:

  • Upgrade-Insecure-Requests:引數為1。該指令用於讓瀏覽器自動升級請求從http到https,用於大量包含http資源的http網頁直接升級到https而不會報錯。簡潔的來講,就相當於在http和https之間起的一個過渡作用。就是瀏覽器告訴伺服器,自己支援這種操作,我能讀懂你伺服器發過來的上面這條資訊,並且在以後發請求的時候不用http而用https;

  • User-Agent:有一些網站不喜歡被爬蟲程式訪問,所以會檢測連線物件,如果是爬蟲程式,也就是非人點選訪問,它就會不讓你繼續訪問,所以為了要讓程式可以正常執行,我們需要設定一個瀏覽器的User-Agent;

  • Accept:瀏覽器可接受的MIME型別,可以根據實際情況進行設定;

  • Accept-Encoding:瀏覽器能夠進行解碼的資料編碼方式,比如gzip。Servlet能夠向支援gzip的瀏覽器返回經gzip編碼的HTML頁面。許多情形下這可以減少5到10倍的下載時間;

  • Accept-Language:瀏覽器所希望的語言種類,當伺服器能夠提供一種以上的語言版本時要用到;

  • Cookie:這是最重要的請求頭資訊之一。中文名稱為“小型文字檔案”或“小甜餅“,指某些網站為了辨別使用者身份而儲存在使用者本地終端(Client Side)上的資料(通常經過加密)。定義於RFC2109。是網景公司的前僱員盧·蒙特利在1993年3月的發明。

2.2 設定Cookie的學問

雖然 cookie 是一把雙刃劍,但正確地處理 cookie 可以避免許多采集問題。網站會用 cookie 跟蹤你的訪問過程,如果發現了爬蟲異常行為就會中斷你的訪問,比如特別快速地填寫表單,或者瀏覽大量頁面。雖然這些行為可以通過關閉並重新連線或者改變 IP 地址來偽裝,但是如果 cookie 暴露了你的身份,再多努力也是白費。

在採集一些網站時 cookie 是不可或缺的。要在一個網站上持續保持登入狀態,需要在多個頁面中儲存一個 cookie。有些網站不要求在每次登入時都獲得一個新 cookie,只要儲存一箇舊的“已登入”的 cookie 就可以訪問。

如果你在採集一個或者幾個目標網站,建議你檢查這些網站生成的 cookie,然後想想哪一個 cookie 是爬蟲需要處理的。有一些瀏覽器外掛可以為你顯示訪問網站和離開網站時 cookie 是如何設定的。例如:EditThisCookie,該外掛可以谷歌商店進行下載。URL:www.editthiscookie.com/

Cookie資訊,也可以根據實際情況填寫。不過requests已經封裝好了很多操作,自動管理cookie,session保持連線。我們可以先訪問某個目標網站,建立一個session連線之後,獲取cookie。程式碼如下:

# -*- coding:UTF-8 -*-
import requests

if __name__ == '__main__':
    url = 'https://www.baidu.com/'
    headers = {'Upgrade-Insecure-Requests':'1',
    'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36',
    'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
    'Accept-Encoding':'gzip, deflate, sdch, br',
    'Accept-Language':'zh-CN,zh;q=0.8',
    }
    s = requests.Session()
    req = s.get(url=url,headers=headers)
    print(s.cookies)
複製程式碼

執行結果如下:

Python3網路爬蟲(十一):爬蟲黑科技之讓你的爬蟲程式更像人類使用者的行為(代理IP池等)

使用 requests.Session 會話物件讓你能夠跨請求保持某些引數,它也會在同一個 Session 例項發出的所有請求之間保持 cookie, 期間使用 urllib3 的 connection pooling 功能。詳細內容參見requests高階用法:docs.python-requests.org/zh_CN/lates…

因為 requests 模組不能執行 JavaScript,所以它不能處理很多新式的跟蹤軟體生成的 cookie,比如 Google Analytics,只有當客戶端指令碼執行後才設定 cookie(或者在使用者瀏覽頁面時基於網頁事件產生 cookie,比如點選按鈕)。要處理這些動作,需要用 Selenium 和 PhantomJS 包。

Selenium的安裝已經在之前的文章中講到,今天就說下PhantomJS吧。URL:phantomjs.org/ PhantomJS 是一個“無頭”(headless)瀏覽器。它會把網站載入到記憶體並執行頁面上的 JavaScript,但不會向使用者展示網頁的圖形介面。將 Selenium 和 PhantomJS 結合在一起,就可以執行一個非常強大的網路爬蟲了,可以處理 cookie、JavaScript、headers,以及任何你需要做的事情。

PhantomJS可以依據自己的開發平臺選擇不同的包進行下載:phantomjs.org/download.ht… 解壓即用,很方便。

接下來呢,還是以例項出發,對 pythonscraping.com 網站呼叫 webdriver 的 get_cookie()方法來檢視 cookie(D:/phantomjs-2.1.1-windows/bin/phantomjs.exe是我的PhantomJS路徑,這裡需要更改成你自己的):

# -*- coding:UTF-8 -*-
from selenium import webdriver

if __name__ == '__main__':
    url = 'http://pythonscraping.com'
    driver = webdriver.PhantomJS(executable_path='D:/phantomjs-2.1.1-windows/bin/phantomjs.exe')
    driver.get(url)
    driver.implicitly_wait(1)
    print(driver.get_cookies())
複製程式碼

這樣就可以獲得一個非常典型的 Google Analytics 的 cookie 列表:

Python3網路爬蟲(十一):爬蟲黑科技之讓你的爬蟲程式更像人類使用者的行為(代理IP池等)

還可以呼叫 delete_cookie()、add_cookie() 和 delete_all_cookies() 方法來處理 cookie。另外,還可以儲存 cookie 以備其他網路爬蟲使用。

通過Selenium和PhantomJS,我們可以很好的處理一些需要事件執行後才能獲得的cookie。

2.3 正常的訪問速度

有一些防護措施完備的網站可能會阻止你快速地提交表單,或者快速地與網站進行互動。即使沒有這些安全措施,用一個比普通人快很多的速度從一個網站下載大量資訊也可能讓自己被網站封殺。

因此,雖然多程式程式可能是一個快速載入頁面的好辦法——在一個程式中處理資料,另一個程式中載入頁面——但是這對編寫好的爬蟲來說是恐怖的策略。還是應該儘量保證一次載入頁面載入且資料請求最小化。如果條件允許,儘量為每個頁面訪問增加一點兒時間間隔,即使你要增加兩行程式碼:

import time
time.sleep(1)
複製程式碼

合理控制速度是你不應該破壞的規則。過度消耗別人的伺服器資源會讓你置身於非法境地,更嚴重的是這麼做可能會把一個小型網站拖垮甚至下線。拖垮網站是不道德的,是徹頭徹尾的錯誤。所以請控制採集速度!

2.4 注意隱含輸入欄位

在 HTML 表單中,“隱含”欄位可以讓欄位的值對瀏覽器可見,但是對使用者不可見(除非看網頁原始碼)。隨著越來越多的網站開始用 cookie 儲存狀態變數來管理使用者狀態,在找到另一個最佳用途之前,隱含欄位主要用於阻止爬蟲自動提交表單。

下圖顯示的例子就是 Facebook 登入頁面上的隱含欄位。雖然表單裡只有三個可見欄位(username、password 和一個確認按鈕),但是在原始碼裡表單會向伺服器傳送大量的資訊。

Python3網路爬蟲(十一):爬蟲黑科技之讓你的爬蟲程式更像人類使用者的行為(代理IP池等)

用隱含欄位阻止網路資料採集的方式主要有兩種。第一種是表單頁面上的一個欄位可以用伺服器生成的隨機變數表示。如果提交時這個值不在表單處理頁面上,伺服器就有理由認為這個提交不是從原始表單頁面上提交的,而是由一個網路機器人直接提交到表單處理頁面的。繞開這個問題的最佳方法就是,首先採集表單所在頁面上生成的隨機變數,然後再提交到表單處理頁面。

第二種方式是“蜜罐”(honey pot)。如果表單裡包含一個具有普通名稱的隱含欄位(設定蜜罐圈套),比如“使用者名稱”(username)或“郵箱地址”(email address),設計不太好的網路機器人往往不管這個欄位是不是對使用者可見,直接填寫這個欄位並向伺服器提交,這樣就會中伺服器的蜜罐圈套。伺服器會把所有隱含欄位的真實值(或者與表單提交頁面的預設值不同的值)都忽略,而且填寫隱含欄位的訪問使用者也可能被網站封殺。

總之,有時檢查表單所在的頁面十分必要,看看有沒有遺漏或弄錯一些伺服器預先設定好的隱含欄位(蜜罐圈套)。如果你看到一些隱含欄位,通常帶有較大的隨機字串變數,那麼很可能網路伺服器會在表單提交的時候檢查它們。另外,還有其他一些檢查,用來保證這些當前生成的表單變數只被使用一次或是最近生成的(這樣可以避免變數被簡單地儲存到一個程式中反覆使用)。

2.5 爬蟲如何避開蜜罐

雖然在進行網路資料採集時用 CSS 屬性區分有用資訊和無用資訊會很容易(比如,通過讀取 id和 class 標籤獲取資訊),但這麼做有時也會出問題。如果網路表單的一個欄位通過 CSS 設定成對使用者不可見,那麼可以認為普通使用者訪問網站的時候不能填寫這個欄位,因為它沒有顯示在瀏覽器上。如果這個欄位被填寫了,就可能是機器人乾的,因此這個提交會失效。

這種手段不僅可以應用在網站的表單上,還可以應用在連結、圖片、檔案,以及一些可以被機器人讀取,但普通使用者在瀏覽器上卻看不到的任何內容上面。訪問者如果訪問了網站上的一個“隱含”內容,就會觸發伺服器指令碼封殺這個使用者的 IP 地址,把這個使用者踢出網站,或者採取其他措施禁止這個使用者接入網站。實際上,許多商業模式就是在幹這些事情。

下面的例子所用的網頁在 pythonscraping.com/pages/itsat… CSS 隱含了,另一個是可見的。另外,頁面上還包括兩個隱含欄位:

Python3網路爬蟲(十一):爬蟲黑科技之讓你的爬蟲程式更像人類使用者的行為(代理IP池等)

這三個元素通過三種不同的方式對使用者隱藏:

  • 第一個連結是通過簡單的 CSS 屬性設定 display:none 進行隱藏;
  • 電話號碼欄位 name=”phone” 是一個隱含的輸入欄位;
  • 郵箱地址欄位 name=”email” 是將元素向右移動 50 000 畫素(應該會超出電腦顯示器的邊界)並隱藏滾動條。

因為 Selenium 可以獲取訪問頁面的內容,所以它可以區分頁面上的可見元素與隱含元素。通過 is_displayed() 可以判斷元素在頁面上是否可見。

例如,下面的程式碼示例就是獲取前面那個頁面的內容,然後查詢隱含連結和隱含輸入欄位(同樣,需要更改下PhantomJS路徑):

# -*- coding:UTF-8 -*-
from selenium import webdriver

if __name__ == '__main__':
    url = 'http://pythonscraping.com/pages/itsatrap.html'
    driver = webdriver.PhantomJS(executable_path='D:/phantomjs-2.1.1-windows/bin/phantomjs.exe')
    driver.get(url)
    links = driver.find_elements_by_tag_name('a')
    for link in links:
        if not link.is_displayed():
            print('連線:' + link.get_attribute('href') + ',是一個蜜罐圈套.')

    fields = driver.find_elements_by_tag_name('input')
    for field in fields:
        if not field.is_displayed():
            print('不要改變' + field.get_attribute('name') + '的值.')
複製程式碼

Selenium 抓取出了每個隱含的連結和欄位,結果如下所示:

Python3網路爬蟲(十一):爬蟲黑科技之讓你的爬蟲程式更像人類使用者的行為(代理IP池等)

2.6 建立自己的代理IP池

啟用遠端平臺的人通常有兩個目的:對更大計算能力和靈活性的需求,以及對可變 IP 地址的需求。

有一些網站會設定訪問閾值,也就是說,如果一個IP訪問速度超過這個閾值,那麼網站就會認為,這是一個爬蟲程式,而不是使用者行為。為了避免遠端伺服器封鎖IP,或者想加快爬取速度,一個可行的方法就是使用代理IP,我們需要做的就是建立一個自己的代理IP池。

思路:通過免費IP代理網站爬取IP,構建一個容量為100的代理IP池。從代理IP池中隨機選取IP,在使用IP之前,檢查IP是否可用。如果可用,使用該IP訪問目標頁面,如果不可用,捨棄該IP。當代理IP池中IP的數量小於20的時候,更新整個代理IP池,即重新從免費IP代理網站爬取IP,構建一個新的容量為100的代理IP池。

還是使用在之前筆記中提到過的西刺代理,URL:www.xicidaili.com/,如果想方便一些,可以…

Python3網路爬蟲(十一):爬蟲黑科技之讓你的爬蟲程式更像人類使用者的行為(代理IP池等)

我們可以自己爬取IP。但是,注意一點,千萬不要爬太快!很容易被伺服器Block哦!

比如,我想爬取國內高匿代理,第一頁的URL為:www.xicidaili.com/nn/1,第二頁的UR…

Python3網路爬蟲(十一):爬蟲黑科技之讓你的爬蟲程式更像人類使用者的行為(代理IP池等)

通過審查元素可知,這些ip都存放在了id屬性為ip_list的table中。

Python3網路爬蟲(十一):爬蟲黑科技之讓你的爬蟲程式更像人類使用者的行為(代理IP池等)

我們可以使用lxml的xpath和Beutifulsoup結合的方法,爬取所有的IP。當然,也可以使用正規表示式,方法很多。程式碼如下:

# -*- coding:UTF-8 -*-
import requests
from bs4 import BeautifulSoup
from lxml import etree

if __name__ == '__main__':
    #requests的Session可以自動保持cookie,不需要自己維護cookie內容
    page = 1
    S = requests.Session()
    target_url = 'http://www.xicidaili.com/nn/%d' % page
    target_headers = {'Upgrade-Insecure-Requests':'1',
        'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36',
        'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
        'Referer':'http://www.xicidaili.com/nn/',
        'Accept-Encoding':'gzip, deflate, sdch',
        'Accept-Language':'zh-CN,zh;q=0.8',
    }
    target_response = S.get(url = target_url, headers = target_headers)
    target_response.encoding = 'utf-8'
    target_html = target_response.text
    bf1_ip_list = BeautifulSoup(target_html, 'lxml')
    bf2_ip_list = BeautifulSoup(str(bf1_ip_list.find_all(id = 'ip_list')), 'lxml')
    ip_list_info = bf2_ip_list.table.contents

    proxys_list = []
    for index in range(len(ip_list_info)):
        if index % 2 == 1 and index != 1:
            dom = etree.HTML(str(ip_list_info[index]))
            ip = dom.xpath('//td[2]')
            port = dom.xpath('//td[3]')
            protocol = dom.xpath('//td[6]')
            proxys_list.append(protocol[0].text.lower() + '#' + ip[0].text + '#' + port[0].text)
    print(proxys_list)
複製程式碼

可以看到,通過這種方法,很容易的就獲得了這100個IP,包括他們的協議、IP和埠號。這裡我是用”#”符號隔開,使用之前,只需要spilt()方法,就可以提取出資訊。

Python3網路爬蟲(十一):爬蟲黑科技之讓你的爬蟲程式更像人類使用者的行為(代理IP池等)

已經獲取了IP,如何驗證這個IP是否可用呢?一種方案是GET請求一個網頁,設定timeout超市時間,如果超時伺服器沒有反應,說明IP不可用。這裡的實現,可以參見Requests的高階用法:docs.python-requests.org/zh_CN/lates…

這種設定timeout的驗證方法是一種常見的方法,很多人都這樣驗證。所以博主就想了一個問題,有沒有其他的方法呢?經過思考,想出了一個方法,測試了一個,驗證一個IP大約需要3秒左右。呃..當然這種方法是我自己琢磨出來的,沒有參考,所以,如果有錯誤之處,或者更好的方法,還望指正!

在Windows下,可以在CMD中輸入如下指令檢視IP的連通性(mac和linux可以在中斷檢視):

Python3網路爬蟲(十一):爬蟲黑科技之讓你的爬蟲程式更像人類使用者的行為(代理IP池等)

從免費代理網站獲得的代理IP很不穩定,過幾分鐘再測試這個代理IP你可能會發現,這個IP已經不能用了。所以再使用代理IP之前,我們需要測試下代理IP是否可用。

Python3網路爬蟲(十一):爬蟲黑科技之讓你的爬蟲程式更像人類使用者的行為(代理IP池等)
從上文可知,通過測試本機和代理IP地址的連通性,我們能夠大致知道這個代理 IP的健康情況。如果,本機能夠ping通這個代理 IP,那麼我們也就可以使用這個代理 IP去訪問其他網站。這個過程是在cmd中執行的,那麼python有沒有提供一個方法,通過程式來實現這樣的操作呢?答案是肯定的,有!Subprocess.Popen()可以建立一個程式,當shell引數為true時,程式通過shell來執行:

Python3網路爬蟲(十一):爬蟲黑科技之讓你的爬蟲程式更像人類使用者的行為(代理IP池等)

  • 引數args可以是字串或者序列型別(如:list,元組),用於指定程式的可執行檔案及其引數。如果是序列型別,第一個元素通常是可執行檔案的路徑。我們也可以顯式的使用executeable引數來指定可執行檔案的路徑。
  • 引數stdin, stdout,stderr分別表示程式的標準輸入、輸出、錯誤控制程式碼。他們可以是PIPE,檔案描述符或檔案物件,也可以設定為None,表示從父程式繼承。
  • 如果引數shell設為true,程式將通過shell來執行。
  • subprocess.PIPE:在建立Popen物件時,subprocess.PIPE可以初始化stdin,stdout或stderr引數。表示與子程式通訊的標準流。
  • subprocess.STDOUT:建立Popen物件時,用於初始化stderr引數,表示將錯誤通過標準輸出流輸出。
  • 瞭解到以上這些,我們就可以寫我們的程式了(ping本機迴環地址):
# -*- coding:UTF-8 -*-
import subprocess as sp

if __name__ == '__main__':
    cmd = "ping -n 3 -w 3 127.0.0.1"
    #執行命令
    p = sp.Popen(cmd, stdin=sp.PIPE, stdout=sp.PIPE, stderr=sp.PIPE, shell=True)
    #獲得返回結果並解碼
    out = p.stdout.read().decode("gbk")
    print(out)
複製程式碼

執行結果如下:

Python3網路爬蟲(十一):爬蟲黑科技之讓你的爬蟲程式更像人類使用者的行為(代理IP池等)

能都得到返回結果,跟cmd中類似,接下來,我們就可以制定相應的規則,根據返回資訊來剔除不滿足要求的ip。

整體程式碼如下:

# -*- coding:UTF-8 -*-
from bs4 import BeautifulSoup
import subprocess as sp
from lxml import etree
import requests
import random
import re

"""
函式說明:獲取IP代理
Parameters:
    page - 高匿代理頁數,預設獲取第一頁
Returns:
    proxys_list - 代理列表
Modify:
    2017-05-27
"""
def get_proxys(page = 1):
    #requests的Session可以自動保持cookie,不需要自己維護cookie內容
    S = requests.Session()
    #西祠代理高匿IP地址
    target_url = 'http://www.xicidaili.com/nn/%d' % page
    #完善的headers
    target_headers = {'Upgrade-Insecure-Requests':'1',
        'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36',
        'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
        'Referer':'http://www.xicidaili.com/nn/',
        'Accept-Encoding':'gzip, deflate, sdch',
        'Accept-Language':'zh-CN,zh;q=0.8',
    }
    #get請求
    target_response = S.get(url = target_url, headers = target_headers)
    #utf-8編碼
    target_response.encoding = 'utf-8'
    #獲取網頁資訊
    target_html = target_response.text
    #獲取id為ip_list的table
    bf1_ip_list = BeautifulSoup(target_html, 'lxml')
    bf2_ip_list = BeautifulSoup(str(bf1_ip_list.find_all(id = 'ip_list')), 'lxml')
    ip_list_info = bf2_ip_list.table.contents
    #儲存代理的列表
    proxys_list = []
    #爬取每個代理資訊
    for index in range(len(ip_list_info)):
        if index % 2 == 1 and index != 1:
            dom = etree.HTML(str(ip_list_info[index]))
            ip = dom.xpath('//td[2]')
            port = dom.xpath('//td[3]')
            protocol = dom.xpath('//td[6]')
            proxys_list.append(protocol[0].text.lower() + '#' + ip[0].text + '#' + port[0].text)
    #返回代理列表
    return proxys_list

"""
函式說明:檢查代理IP的連通性
Parameters:
    ip - 代理的ip地址
    lose_time - 匹配丟包數
    waste_time - 匹配平均時間
Returns:
    average_time - 代理ip平均耗時
Modify:
    2017-05-27
"""
def check_ip(ip, lose_time, waste_time):
    #命令 -n 要傳送的回顯請求數 -w 等待每次回覆的超時時間(毫秒)
    cmd = "ping -n 3 -w 3 %s"
    #執行命令
    p = sp.Popen(cmd % ip, stdin=sp.PIPE, stdout=sp.PIPE, stderr=sp.PIPE, shell=True)
    #獲得返回結果並解碼
    out = p.stdout.read().decode("gbk")
    #丟包數
    lose_time = lose_time.findall(out)
    #當匹配到丟失包資訊失敗,預設為三次請求全部丟包,丟包數lose賦值為3
    if len(lose_time) == 0:
        lose = 3
    else:
        lose = int(lose_time[0])
    #如果丟包數目大於2個,則認為連線超時,返回平均耗時1000ms
    if lose > 2:
        #返回False
        return 1000
    #如果丟包數目小於等於2個,獲取平均耗時的時間
    else:
        #平均時間
        average = waste_time.findall(out)
        #當匹配耗時時間資訊失敗,預設三次請求嚴重超時,返回平均好使1000ms
        if len(average) == 0:
            return 1000
        else:
            #
            average_time = int(average[0])
            #返回平均耗時
            return average_time

"""
函式說明:初始化正規表示式
Parameters:
    無
Returns:
    lose_time - 匹配丟包數
    waste_time - 匹配平均時間
Modify:
    2017-05-27
"""
def initpattern():
    #匹配丟包數
    lose_time = re.compile(u"丟失 = (\d+)", re.IGNORECASE)
    #匹配平均時間
    waste_time = re.compile(u"平均 = (\d+)ms", re.IGNORECASE)
    return lose_time, waste_time

if __name__ == '__main__':
    #初始化正規表示式
    lose_time, waste_time = initpattern()
    #獲取IP代理
    proxys_list = get_proxys(1)

    #如果平均時間超過200ms重新選取ip
    while True:
        #從100個IP中隨機選取一個IP作為代理進行訪問
        proxy = random.choice(proxys_list)
        split_proxy = proxy.split('#')
        #獲取IP
        ip = split_proxy[1]
        #檢查ip
        average_time = check_ip(ip, lose_time, waste_time)
        if average_time > 200:
            #去掉不能使用的IP
            proxys_list.remove(proxy)
            print("ip連線超時, 重新獲取中!")
        if average_time < 200:
            break

    #去掉已經使用的IP
    proxys_list.remove(proxy)
    proxy_dict = {split_proxy[0]:split_proxy[1] + ':' + split_proxy[2]}
    print("使用代理:", proxy_dict)
複製程式碼

從上面程式碼可以看出,我制定的規則是,如果丟包數大於2個,則認為ip不能用。ping通的平均時間大於200ms也拋棄。當然,我這個要求有點嚴格,可以視情況放寬規則:

Python3網路爬蟲(十一):爬蟲黑科技之讓你的爬蟲程式更像人類使用者的行為(代理IP池等)

從列印結果中可以看出,第一個隨機選取的IP被拋棄了,第二個隨機選取的IP能用。

我只是實現了,構建代理IP池和檢查IP是否可用,如果你感興趣也可以將獲取的IP放入到資料庫中,不過我沒這樣做,因為感覺免費獲取的代理IP,失效很快,隨用隨取就行。當然,也可以自己寫程式碼試試reqeusts的GET請求,通過設定timeout引數來驗證代理IP是否可用,因為方法簡單,所以在此不再累述。

除此之外,我們也可以個建立一個User-Agent的列表,多羅列點。也是跟代理IP一樣,每次訪問隨機選取一個。這樣在一定程度上,也能避免被伺服器封殺。

3 總結

如果你一直被網站封殺卻找不到原因,那麼這裡有個檢查列表,可以幫你診斷一下問題出在哪裡。

  • 首先,檢查 JavaScript。如果你從網路伺服器收到的頁面是空白的,缺少資訊,或其遇到他不符合你預期的情況(或者不是你在瀏覽器上看到的內容),有可能是因為網站建立頁面的 JavaScript 執行有問題。
  • 檢查正常瀏覽器提交的引數。如果你準備向網站提交表單或發出 POST 請求,記得檢查一下頁面的內容,看看你想提交的每個欄位是不是都已經填好,而且格式也正確。用 Chrome 瀏覽器的網路皮膚(快捷鍵 F12 開啟開發者控制檯,然後點選“Network”即可看到)檢視傳送到網站的 POST 命令,確認你的每個引數都是正確的。
  • 是否有合法的 Cookie?如果你已經登入網站卻不能保持登入狀態,或者網站上出現了其他的“登入狀態”異常,請檢查你的 cookie。確認在載入每個頁面時 cookie 都被正確呼叫,而且你的 cookie 在每次發起請求時都傳送到了網站上。
  • IP 被封禁?如果你在客戶端遇到了 HTTP 錯誤,尤其是 403 禁止訪問錯誤,這可能說明網站已經把你的 IP 當作機器人了,不再接受你的任何請求。你要麼等待你的 IP 地址從網站黑名單裡移除,要麼就換個 IP 地址。如果你確定自己並沒有被封殺,那麼再檢查下面的內容:
    • 確認你的爬蟲在網站上的速度不是特別快。快速採集是一種惡習,會對網管的伺服器造成沉重的負擔,還會讓你陷入違法境地,也是 IP 被網站列入黑名單的首要原因。給你的爬蟲增加延遲,讓它們在夜深人靜的時候執行。切記:匆匆忙忙寫程式或收集資料都是拙劣專案管理的表現;應該提前做好計劃,避免臨陣慌亂。
    • 還有一件必須做的事情:修改你的請求頭!有些網站會封殺任何聲稱自己是爬蟲的訪問者。如果你不確定請求頭的值怎樣才算合適,就用你自己瀏覽器的請求頭吧。
    • 確認你沒有點選或訪問任何人類使用者通常不能點選或接入的資訊。
    • 如果你用了一大堆複雜的手段才接入網站,考慮聯絡一下網管吧,告訴他們你的目的。試試發郵件到 webmaster@< 域名 > 或 admin@< 域名 >,請求網管允許你使用爬蟲採集資料。管理員也是人嘛!

使用免費的代理IP也是有侷限的,就是不穩定。更好的方法是,花錢買一個可以動態切換IP的阿里雲伺服器,這樣IP就可以無限動態變化了!

以上內容整理自《Python網路資料採集》,以及自己的一點小心得。重要的事情再說一遍:我們在爬取別人網站的時候,也為對方考慮考慮!

程式碼獲取:Python3爬蟲的程式,可以在我的Github上檢視。URL:github.com/Jack-Cheris…


相關文章和視訊推薦

圓方圓學院彙集 Python + AI 名師,打造精品的 Python + AI 技術課程。 在各大平臺都長期有優質免費公開課,歡迎報名收看。 公開課地址:ke.qq.com/course/3627…

加入python學習討論群 78486745 ,獲取資料,和廣大群友一起學習。

圓方圓python技術討論群
圓方圓python技術討論群

相關文章