Python網絡爬蟲反爬破解策略實戰

我們經常會寫一 些網絡爬蟲,想必大家都會有一個感受,寫爬蟲雖然不難,但是反爬處理卻很難,因爲現在大部分的網站都有自己的反爬機制,所以我們要爬取這些數據會比較難。但是,每一種反爬機制其實我們都會有相應的解決方案,作爲爬蟲方的我們,重點需要處理這些反爬機制,所以,今天我們在這裏就爲大家分析常見的反爬策略以及破解的手段。

1. 知己知彼-常見的反爬策略有哪些?

首先,既然要破解這些常見的反爬策略,就首先需要知道常見的反爬策略有哪些,所謂知己知彼,百戰百勝。

常見的反爬策略主要有這些:

A. 有些網站會通過用戶代理對爬蟲進行限制,只要不是瀏覽器訪問或者一直都是某個瀏覽器訪問,那麼就限制該用戶不能對網站進行訪問;遇到這種情況,我們一般會採用用戶代理池的方式進行解決,我們將在2中進行詳細講解。

B. 還有些網站會通過用戶訪問站點時的ip進行限制,比如某一個ip在短時間內大量的訪問該網站上的網頁,則封掉該ip,封掉之後使用該IP就無法訪問該站點了;如果遇到這種情況,我們一般會通過IP代理池的方式進行解決,難點在於如何找到可靠的代理IP並構建好IP代理池,我們將在3中進行詳細講解。

C. 除此之外,有的網站會通過驗證碼對用戶的訪問請求進行限制,比如當一個用戶多次訪問該站點之後,會出現驗證碼,輸入驗證碼之後纔可以繼續訪問,而怎麼樣讓爬蟲自動的識別驗證碼是一個關鍵問題;如果遇到驗證碼從而阻擋了爬蟲的運行,我們可以使用驗證碼自動識別的方式去處理驗證碼,我們將在4中進行詳細講解。

D. 還有的網站會通過數據屏蔽的方式進行反爬,比如用戶訪問時出現的數據並不會出現在源碼中,此時這些數據會隱藏在js文件中,以此避免爬蟲對這些數據的抓取。如果遇到這種情況,我們一般會採用抓包分析去找到被屏蔽的數據,並自動獲取。我們將在5中進行詳細講解。

2. 解決UA限制-瀏覽器僞裝與用戶代理池構建實戰

我們先來爲大家講解如何解決UA限制。剛纔已經提到,我們可以採用用戶代理池構建的方式來解決這個問題。有新朋友可能會問,爲什麼採用用戶代理就能夠解決UA限制呢? 原理是這樣的,我們不妨打開任意一個網頁並按F12調用調試工具,然後在傳遞的數據中會發現頭信息中有如下所示的一項信息:

enter image description here

這一項字段爲User-Agent,也就是我們俗稱的用戶代理(UA),對方服務器就是通過這一項字段的內容來識別我們訪問的終端是什麼的。不同瀏覽器的User-Agent的值是不一樣的,我們可以使用不同瀏覽器的User-Agent的值構建爲一個池子,然後每次訪問都隨機調用該池子中的一個UA,這樣,也就意味着我們每次訪問都使用的是不同的瀏覽器,這樣的話,對方的服務器就很難通過用戶代理來識別我們是否是爬蟲了。

那麼用戶代理池應該怎麼構建呢?我們以Scrapy爬蟲爲案例進行講解。

在Scrapy裏面,如果我們需要使用用戶代理池,就需要使用下載中間件,所謂下載中間件,就是處於爬蟲和從互聯網中下載網頁中間的一個部件。

首先我們在爬蟲項目中建立一個文件作爲下載中間件,名字隨意,只需要在設置文件中設置爲對應的即可。比如在此,我們建立了一個名爲“downloader_middlerwares.py”的文件,如下所示:

enter image description here

然後在該文件中寫入如下代碼:

from qna.settings import UAPOOLS
import random
from scrapy.contrib.downloadermiddleware.useragent import UserAgentMiddleware
class Uagt(UserAgentMiddleware):
    def __init__(self,ua=''):
        self.user_agent = ua
    def process_request(self,request,spider):
        thisua=random.choice(UAPOOLS)
        self.user_agent=thisua
        print("當前使用的用戶代理是:"+thisua)
        request.headers.setdefault('User-Agent',thisua)

上述代碼中,基本意思爲:首先導入對應需要的模塊,其中UAPOOLS是在設置文件中設置的用戶代理池中各用戶代理(我們稍後再設置),然後導入用戶代理對應的中間件類UserAgentMiddleware,然後建立一個自定義類並繼承該用戶代理中間件類,在主方法process_request中,我們首先從UAPOOLS中隨機選擇一個用戶代理,然後通過self.user_agent=thisua設置好對應的用戶代理,並通過request.headers.setdefault('User-Agent',thisua)在頭文件中添加上該用戶代理,那麼在每次網站的時候,都會隨機的從用戶代理池中選擇一個用戶代理,並以該用戶代理進行訪問,這個時候,對方服務器就會認爲我們每次訪問都是不同的瀏覽器。

然後,我們需要在設置文件中構建用戶代理池,如下所示:

UAPOOLS=["Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.22 Safari/537.36 SE 2.X MetaSr 1.0",
         "Mozilla/5.0 (Windows NT 6.1; rv:49.0) Gecko/20100101 Firefox/49.0",
         "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36",
         ]

當然我們也可以在用戶代理池中添加更多用戶代理,構建好用戶代理池之後,我們需要在設置文件中開啓剛纔設置的對應的中間件,如下所示:

DOWNLOADER_MIDDLEWARES = {
    'scrapy.contrib.downloadermiddleware.useragent.UserAgentMiddleware': 124,
    'qna.downloader_middlerwares.Uagt': 125,
}

這樣,對應的用戶代理其就會生效,我們運行之後如下所示,可以看到,只採用瞭如下所示的用戶代理進行爬取。

enter image description here

3. 解決IP限制-構建穩定可靠的IP代理池

接下來我們爲大家講解如何解決IP限制。

我們知道,在訪問對方網站的時候,有時對方網站會通過我們的ip信息對我們進行識別,如果頻繁訪問的話很可能會把我們這個ip封掉。我們在使用爬蟲自動的抓取大量信息的時候,通常都會由於訪問過於頻繁被禁掉ip,當然有些朋友會採用撥號的方式來切換本機電腦的ip,但始終是治標不治本。

此時我們可以通過代理ip池解決。我們知道,使用某一個代理ip去訪問一個網站,在該網站的服務器中看到的ip就是對應的代理ip,所以當我們本機的ip被禁之後,我們使用代理ip去訪問該網站就可以訪問,但是如果只有一個代理ip,那麼很可能代理ip也被禁掉。

此時,我們可以使用多個代理ip組建成一個池子,每次訪問都從該池子中隨機選擇一個ip出來進行訪問,那麼這樣,對方服務器就很難通過IP限制的方式對我們進行屏蔽了。那麼代理ip應該怎麼找呢?我們可以直接通過下面代碼中的對應地址去申請代理ip。

其次,在實踐中我們研究發現,國外的代理ip有效的程度會更高,所以我們待會會使用國外的代理ip進行實驗,同時也建議各位朋友使用國外的代理ip構建代理ip池。那麼我們怎麼構建代理ip池呢?我們可以這樣做:

首先,建立一個下載中間件(各用戶代理池構建時所創建的中間件類似,名字同樣自定義),在此我們創建的下載中間件文件如下所示:

enter image description here

然後在該中間件中編寫如下程序:

from scrapy.contrib.downloadermiddleware.httpproxy import HttpProxyMiddleware
import urllib.request
IPPOOLS=urllib.request.urlopen("http://tpv.daxiangdaili.com/ip/?tid=559126871522487&num=1&foreign=only").read().decode("utf-8","ignore")
class Ippl(HttpProxyMiddleware):
    def __init__(self,ip=''):
        self.ip=ip
    def process_request(self,request,spider):
        print("當前使用的代理IP是:"+str(IPPOOLS))
        request.meta["proxy"] = "http://" + str(IPPOOLS)

在該程序中,我們使用daxiangdaili.com提供的代理ip,foreign=only代表只是用國外的代理ip,因爲在實踐中得出的經驗是國外的代理ip有效的可能性會比較高,我們每次都從該接口中獲取一個代理ip,然後通過request.meta["proxy"] = "http://" + str(IPPOOLS)將該代理ip添加到meta中,此時該代理ip生效。

然後,我們還需要在設置文件中開啓該下載中間件,如下所示:

DOWNLOADER_MIDDLEWARES = {
    'qna.downloader_middlerwares.Ippl': 121,
    'scrapy.contrib.downloadermiddleware.httpproxy.HttpProxyMiddleware':123,
}

隨後,我們可以運行對應爬蟲,結果如下所示:

enter image description here

可以看到此時使用代理ip:107.170.26.74:8088對網站進行爬取。

4. 解決驗證碼限制-驗證碼三種處理手段及實戰講解

接下來我們爲大家講解,如何解決驗證碼限制。

我們使用爬蟲爬取一些網站的時候,經常會遇到驗證碼,那麼遇到驗證碼之後應該怎麼處理呢? 一般來說,遇到驗證碼時,處理思路有三種:

  1. 半自動識別
  2. 通過接口自動識別
  3. 通過機器學習等知識自動識別

由於第三種方法涉及AI領域的新知識,所以第三種方法在此不具體講解(有興趣的同學可以關注我AI方面的課程),在此,我們主要會爲大家講解如何使用前兩種方式去處理驗證碼。

首先爲大家講解如何通過半自動識別的方式去處理驗證碼,我們以豆瓣登陸爬蟲爲例進行講解。比如,在登錄豆瓣時,我們經常會遇到如下所示的驗證碼:

enter image description here

那麼此時如果通過半自動處理的方式來做,我們可以這樣:先爬一遍登錄頁,然後得到驗證碼圖片所在的網址,並將該驗證碼圖片下載到本地,然後等待輸入,此時我們可以在本地查看該驗證碼圖片,並輸入對應的驗證碼,然後就可以自動登錄。

通過半自動處理的方式登錄豆瓣網站,完整代碼如下所示,關鍵部分一給出註釋,關鍵點就在於找到驗證碼圖片所在的地址並下載到本地,然後等待我們手動輸入並將輸入結果傳遞給豆瓣服務器。

import scrapy
import urllib.request
import ssl
import os
from scrapy.http import Request,FormRequest
ssl._create_default_https_context=ssl._create_unverified_context

class LoginspdSpider(scrapy.Spider):
    name = "loginspd"
    allowed_domains = ["douban.com"]
    #設置頭信息變量,供下面的代碼中模擬成瀏覽器爬取
    header = {"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.122 Safari/537.36 SE 2.X MetaSr 1.0"}
    #編寫start_requests()方法,第一次會默認調取該方法中的請求
    def start_requests(self):
        #首先爬一次登錄頁,然後進入回調函數parse()
        return [Request("https://accounts.douban.com/login", meta={"cookiejar": 1}, callback=self.parse)]

    def parse(self, response):
        #獲取驗證碼圖片所在地址,獲取後賦給captcha變量,此時captcha爲一個列表
        captcha=response.xpath('//img[@id="captcha_image"]/@src').extract()
        #因爲登錄時有時網頁有驗證碼,有時網頁沒有驗證碼
        # 所以需要判斷此時是否需要輸入驗證碼,若captcha列表中有元素,說明有驗證碼信息
        if len(captcha)>0:
            print("此時有驗證碼")
            #設置將驗證碼圖片存儲到本地的本地地址
            localpath="D:/我的教學/Python/CSDN-Python爬蟲/captcha.png"
            #將服務器中的驗證碼圖片存儲到本地,供我們在本地直接進行查看
            urllib.request.urlretrieve(captcha[0], filename=localpath)
            print("請查看本地圖片captcha.png並輸入對應驗證碼:")
            #通過input()等待我們輸入對應的驗證碼並賦給captcha_value變量
            captcha_value=input()
            #設置要傳遞的post信息
            data={
                #設置登錄賬號,格式爲賬號字段名:具體賬號
                "form_email":"[email protected]",
                #設置登錄密碼,格式爲密碼字段名:具體密碼,讀者需要將賬號密碼換成自己的
                #因爲筆者完成該項目後已經修改密碼
                "form_password":"weijc7789",
                #設置驗證碼,格式爲驗證碼字段名:具體驗證碼
                "captcha-solution":captcha_value,
                #設置需要轉向的網址,由於我們需要爬取個人中心頁,所以轉向個人中心頁
                "redir":"https://www.douban.com/people/151968962/",
            }
        #否則說明captcha列表中沒有元素,即此時不需要輸入驗證碼信息
        else:
            print("此時沒有驗證碼")
            #設置要傳遞的post信息,此時沒有驗證碼字段
            data={
                "form_email":"[email protected]",
                "form_password":"weijc7789",
                "redir": "https://www.douban.com/people/151968962/",
            }
        print("登錄中…")
        #通過FormRequest.from_response()進行登陸
        return [FormRequest.from_response(response,
                                          #設置cookie信息
                                          meta={"cookiejar": response.meta["cookiejar"]},
                                          #設置headers信息模擬成瀏覽器
                                          headers=self.header,
                                          #設置post表單中的數據
                                          formdata=data,
                                          #設置回調函數,此時回調函數爲next()
                                          callback=self.next,
                                          )]
    def next(self,response):
        print("此時已經登錄完成並爬取了個人中心的數據")
        #此時response爲個人中心網頁中的數據
        #以下通過Xpath表達式分別提取個人中心中該用戶的相關信息
        #網頁標題Xpath表達式
        xtitle="/html/head/title/text()"
        #日記標題Xpath表達式
        xnotetitle="//div[@class='note-header pl2']/a/@title"
        #日記發表時間Xpath表達式
        xnotetime="//div[@class='note-header pl2']//span[@class='pl']/text()"
        #日記內容Xpath表達式
        xnotecontent="//div[@class='note']/text()"
        #日記鏈接Xpath表達式
        xnoteurl="//div[@class='note-header pl2']/a/@href"

        #分別提取網頁標題、日記標題、日記發表時間、日記內容、日記鏈接
        title=response.xpath(xtitle).extract()
        notetitle = response.xpath(xnotetitle).extract()
        notetime = response.xpath(xnotetime).extract()
        notecontent = response.xpath(xnotecontent).extract()
        noteurl = response.xpath(xnoteurl).extract()
        print("網頁標題是:"+title[0])
        #可能有多篇日記,通過for循環依次遍歷
        for i in range(0,len(notetitle)):
            print("第"+str(i+1)+"篇文章的信息如下:")
            print("文章標題爲:"+notetitle[i])
            print("文章發表時間爲:" + notetime[i])
            print("文章內容爲:" + notecontent[i])
            print("文章鏈接爲:" + noteurl[i])
            print("------------")

然後我們可以運行該程序,如下所示:

enter image description here

可以看到,此時會提示我們輸入驗證碼,我們在本地可以看到驗證碼如下所示:

enter image description here

隨後我們輸入該驗證碼,然後按回車鍵,此時便可正常登陸,如下所示:

enter image description here

顯然,通過半自動的處理方式不足以處理驗證碼,此時,我們可以通過接口的方式,自動的識別驗證碼並進行處理,這樣就完全自動化操作了。

具體方式如下,首先我們在雲打碼註冊一個賬號並申請對應接口,然後將程序驗證碼處理部分做以下修改:

        cmd="D:/python27/python D:/python27/yzm/YDMPythonDemo.py"
        r = os.popen(cmd)
        captcha_value = r.read()
        r.close()
        print("此時已經自動處理驗證碼完畢!處理的驗證碼結果爲:"+str(captcha_value))

然後運行該程序,結果如下所示:

enter image description here

可以看到,事實已經能夠自動的識別驗證碼並能夠成功登陸。

接口自動處理驗證碼的完整代碼如下所示:

# -*- coding: utf-8 -*-
import scrapy
import urllib.request
import ssl
import os
from scrapy.http import Request,FormRequest
ssl._create_default_https_context=ssl._create_unverified_context

class LoginspdSpider(scrapy.Spider):
    name = "loginspd"
    allowed_domains = ["douban.com"]
    #設置頭信息變量,供下面的代碼中模擬成瀏覽器爬取
    header = {"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.122 Safari/537.36 SE 2.X MetaSr 1.0"}
    #編寫start_requests()方法,第一次會默認調取該方法中的請求
    def start_requests(self):
        #首先爬一次登錄頁,然後進入回調函數parse()
        return [Request("https://accounts.douban.com/login", meta={"cookiejar": 1}, callback=self.parse)]

    def parse(self, response):
        #獲取驗證碼圖片所在地址,獲取後賦給captcha變量,此時captcha爲一個列表
        captcha=response.xpath('//img[@id="captcha_image"]/@src').extract()
        #因爲登錄時有時網頁有驗證碼,有時網頁沒有驗證碼
        # 所以需要判斷此時是否需要輸入驗證碼,若captcha列表中有元素,說明有驗證碼信息
        if len(captcha)>0:
            print("此時有驗證碼")
            #設置將驗證碼圖片存儲到本地的本地地址
            localpath="D:/我的教學/Python/CSDN-Python爬蟲/captcha.png"
            #將服務器中的驗證碼圖片存儲到本地,供我們在本地直接進行查看
            urllib.request.urlretrieve(captcha[0], filename=localpath)
            '''
            print("請查看本地圖片captcha.png並輸入對應驗證碼:")
            #通過input()等待我們輸入對應的驗證碼並賦給captcha_value變量
            captcha_value=input()
            '''
            cmd="D:/python27/python D:/python27/yzm/YDMPythonDemo.py"
            r = os.popen(cmd)
            captcha_value = r.read()
            r.close()
            print("此時已經自動處理驗證碼完畢!處理的驗證碼結果爲:"+str(captcha_value))

            #設置要傳遞的post信息
            data={
                #設置登錄賬號,格式爲賬號字段名:具體賬號
                "form_email":"[email protected]",
                #設置登錄密碼,格式爲密碼字段名:具體密碼,讀者需要將賬號密碼換成自己的
                #因爲筆者完成該項目後已經修改密碼
                "form_password":"weijc7789",
                #設置驗證碼,格式爲驗證碼字段名:具體驗證碼
                "captcha-solution":captcha_value,
                #設置需要轉向的網址,由於我們需要爬取個人中心頁,所以轉向個人中心頁
                "redir":"https://www.douban.com/people/151968962/",
            }
        #否則說明captcha列表中沒有元素,即此時不需要輸入驗證碼信息
        else:
            print("此時沒有驗證碼")
            #設置要傳遞的post信息,此時沒有驗證碼字段
            data={
                "form_email":"[email protected]",
                "form_password":"weijc7789",
                "redir": "https://www.douban.com/people/151968962/",
            }
        print("登錄中…")
        #通過FormRequest.from_response()進行登陸
        return [FormRequest.from_response(response,
                                          #設置cookie信息
                                          meta={"cookiejar": response.meta["cookiejar"]},
                                          #設置headers信息模擬成瀏覽器
                                          headers=self.header,
                                          #設置post表單中的數據
                                          formdata=data,
                                          #設置回調函數,此時回調函數爲next()
                                          callback=self.next,
                                          )]
    def next(self,response):
        print("此時已經登錄完成並爬取了個人中心的數據")
        #此時response爲個人中心網頁中的數據
        #以下通過Xpath表達式分別提取個人中心中該用戶的相關信息
        #網頁標題Xpath表達式
        xtitle="/html/head/title/text()"
        #日記標題Xpath表達式
        xnotetitle="//div[@class='note-header pl2']/a/@title"
        #日記發表時間Xpath表達式
        xnotetime="//div[@class='note-header pl2']//span[@class='pl']/text()"
        #日記內容Xpath表達式
        xnotecontent="//div[@class='note']/text()"
        #日記鏈接Xpath表達式
        xnoteurl="//div[@class='note-header pl2']/a/@href"

        #分別提取網頁標題、日記標題、日記發表時間、日記內容、日記鏈接
        title=response.xpath(xtitle).extract()
        notetitle = response.xpath(xnotetitle).extract()
        notetime = response.xpath(xnotetime).extract()
        notecontent = response.xpath(xnotecontent).extract()
        noteurl = response.xpath(xnoteurl).extract()
        print("網頁標題是:"+title[0])
        #可能有多篇日記,通過for循環依次遍歷
        for i in range(0,len(notetitle)):
            print("第"+str(i+1)+"篇文章的信息如下:")
            print("文章標題爲:"+notetitle[i])
            print("文章發表時間爲:" + notetime[i])
            print("文章內容爲:" + notecontent[i])
            print("文章鏈接爲:" + noteurl[i])
            print("------------")

上述代碼中關鍵部分已給出註釋。

5. 解決屏蔽數據問題-抓包分析及異步數據加載分析實戰(解決JS\Ajax等隱藏數據獲取問題)

除此之外,有些網站還會通過數據屏蔽的方式進行反爬。如果是通過這種方式盡反爬,我們可以使用Fiddler進行抓包,分析出數據所在的真實地址,然後直接爬取該數據即可。

當然,Fiddler默認是不能抓取HTTPS協議的,如果要抓取HTTPS協議,需要進行如下設置:

打開Fiddler,點擊“Tools–Fiddler Options–HTTPS”,把下方的全勾上,如下圖所示:

enter image description here

然後,點擊Action,選擇將CA證書導入到桌面,即第二項,導出後,點擊上圖的ok保存配置。

enter image description here

然後在桌面上就有了導出的證書,如下所示:

enter image description here

隨後,我們可以在瀏覽器中導入該證書。我們打開火狐瀏覽器,打開“選項–高級–證書–導入”,選擇桌面上的證書,導入即可。隨後,Fiddler就可以抓HTTPS協議的網頁了。如下圖所示。

enter image description here

能夠抓取HTTPS的數據之後,我們便可以對大多數網站進行抓包分析。

由於抓包分析的內容比較多,所以我們爲大家配了配套視頻進行講解,配套視頻中講解了如何解決淘寶網站商品數據的屏蔽問題。

6. 其他反爬策略及應對思路

我們使用上面幾種反爬處理的方式,基本上可以應付大多數網站了。當然,有些網站的數據所在地址是隨機生成的,根本沒有規律可循,如果遇到這種情況,我們採用PhantomJS將對應的地址獲取到,再交給爬蟲處理即可。

隨着時代的發展,也會出現越來越多的反爬方式,我們掌握了這些基礎之後,在遇到新的反爬方式的時候,稍微研究便可以處理了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章