Python3網絡爬蟲(十一):爬蟲黑科技之讓你的爬蟲程序更像人類用戶的行爲(代理IP池等)

轉載請註明作者和出處:http://blog.csdn.net/c406495762
運行平臺: Windows
Python版本: Python3.x
IDE: Sublime text3

1 前言

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

     在互聯網上進行自動數據採集(抓取)這件事和互聯網存在的時間差不多一樣長。今天大衆好像更傾向於用“網絡數據採集”,有時會把網絡數據採集程序稱爲網絡機器人(bots)。最常用的方法是寫一個自動化程序向網絡服務器請求數據(通常是用 HTML 表單或其他網頁文件),然後對數據進行解析,提取需要的信息。

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

2 黑科技

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

2.1 構造合理的HTTP請求頭

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

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

    部分參數說明:

  • 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:http://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)

    運行結果如下:

    使用 requests.Session 會話對象讓你能夠跨請求保持某些參數,它也會在同一個 Session 實例發出的所有請求之間保持 cookie, 期間使用 urllib3 的 connection pooling 功能。詳細內容參見requests高級用法:http://docs.python-requests.org/zh_CN/latest/user/advanced.html

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

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

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

    接下來呢,還是以實例出發,對 http://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 列表:

    還可以調用 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 和一個確認按鈕),但是在源代碼裏表單會向服務器傳送大量的信息。

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

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

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

2.5 爬蟲如何避開蜜罐

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

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

    下面的例子所用的網頁在 http://pythonscraping.com/pages/itsatrap.html,這是一個給我們python爬蟲學習的一個網站。這個頁面包含了兩個鏈接,一個通過 CSS 隱含了,另一個是可見的。另外,頁面上還包括兩個隱含字段:

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

  • 第一個鏈接是通過簡單的 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 抓取出了每個隱含的鏈接和字段,結果如下所示:

    雖然你不太可能會去訪問你找到的那些隱含鏈接,但是在提交前,記得確認一下那些已經在表單中、準備提交的隱含字段的值(或者讓 Selenium 爲你自動提交)。

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:http://www.xicidaili.com/,如果想方便一些,可以使用他們提供的API,直接獲取IP。但是這些IP的更新速度有些慢,15分鐘更新一次,如果滿足需求,使用這個API無妨,如果需求得不到滿足呢?呃…需求…不能滿足…咳咳!

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

    比如,我想爬取國內高匿代理,第一頁的URL爲:www.xicidaili.com/nn/1,第二頁的URL爲:www.xicidaili.com/nn/2,其他頁面一次類推,一頁IP正好100個,夠我們用了。

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

    我們可以使用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()方法,就可以提取出信息。

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

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

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

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

    從上文可知,通過測試本機和代理IP地址的連通性,我們能夠大致知道這個代理 IP的健康情況。如果,本機能夠ping通這個代理 IP,那麼我們也就可以使用這個代理 IP去訪問其他網站。這個過程是在cmd中執行的,那麼python有沒有提供一個方法,通過程序來實現這樣的操作呢?答案是肯定的,有!Subprocess.Popen()可以創建一個進程,當shell參數爲true時,程序通過shell來執行:

  • 參數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)

    運行結果如下:

    能都得到返回結果,跟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也拋棄。當然,我這個要求有點嚴格,可以視情況放寬規則:

    從打印結果中可以看出,第一個隨機選取的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:https://github.com/Jack-Cherish/python-spider

PS: 如果覺得本篇本章對您有所幫助,歡迎關注、評論、頂!

發佈了107 篇原創文章 · 獲贊 2581 · 訪問量 166萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章