本文轉自網絡爬蟲與反爬蟲實戰,由作者韋玮首發自GitChat
我們經常會寫一 些網絡爬蟲,想必大家都會有一個感受,寫爬蟲雖然不難,但是反爬處理卻很難,因為現在大部分的網站都有自己的反爬機制,是以我們要爬取這些資料會比較難。但是,每一種反爬機制其實我們都會有相應的解決方案,作為爬蟲方的我們,重點需要處理這些反爬機制,是以,今天我們在這裡就為大家分析常見的反爬政策以及破解的手段。
1. 知己知彼-常見的反爬政策有哪些?
首先,既然要破解這些常見的反爬政策,就首先需要知道常見的反爬政策有哪些,所謂知己知彼,百戰百勝。
常見的反爬政策主要有這些:
A. 有些網站會通過使用者代理對爬蟲進行限制,隻要不是浏覽器通路或者一直都是某個浏覽器通路,那麼就限制該使用者不能對網站進行通路;遇到這種情況,我們一般會采用使用者代理池的方式進行解決,我們将在2中進行詳細講解。
B. 還有些網站會通過使用者通路站點時的ip進行限制,比如某一個ip在短時間内大量的通路該網站上的網頁,則封掉該ip,封掉之後使用該IP就無法通路該站點了;如果遇到這種情況,我們一般會通過IP代理池的方式進行解決,難點在于如何找到可靠的代理IP并建構好IP代理池,我們将在3中進行詳細講解。
C. 除此之外,有的網站會通過驗證碼對使用者的通路請求進行限制,比如當一個使用者多次通路該站點之後,會出現驗證碼,輸入驗證碼之後才可以繼續通路,而怎麼樣讓爬蟲自動的識别驗證碼是一個關鍵問題;如果遇到驗證碼進而阻擋了爬蟲的運作,我們可以使用驗證碼自動識别的方式去處理驗證碼,我們将在4中進行詳細講解。
D. 還有的網站會通過資料屏蔽的方式進行反爬,比如使用者通路時出現的資料并不會出現在源碼中,此時這些資料會隐藏在js檔案中,以此避免爬蟲對這些資料的抓取。如果遇到這種情況,我們一般會采用抓包分析去找到被屏蔽的資料,并自動擷取。我們将在5中進行詳細講解。
2. 解決UA限制-浏覽器僞裝與使用者代理池建構實戰
我們先來為大家講解如何解決UA限制。剛才已經提到,我們可以采用使用者代理池建構的方式來解決這個問題。有新朋友可能會問,為什麼采用使用者代理就能夠解決UA限制呢? 原理是這樣的,我們不妨打開任意一個網頁并按F12調用調試工具,然後在傳遞的資料中會發現頭資訊中有如下所示的一項資訊:
這一項字段為User-Agent,也就是我們俗稱的使用者代理(UA),對方伺服器就是通過這一項字段的内容來識别我們通路的終端是什麼的。不同浏覽器的User-Agent的值是不一樣的,我們可以使用不同浏覽器的User-Agent的值建構為一個池子,然後每次通路都随機調用該池子中的一個UA,這樣,也就意味着我們每次通路都使用的是不同的浏覽器,這樣的話,對方的伺服器就很難通過使用者代理來識别我們是否是爬蟲了。
那麼使用者代理池應該怎麼建構呢?我們以Scrapy爬蟲為案例進行講解。
在Scrapy裡面,如果我們需要使用使用者代理池,就需要使用下載下傳中間件,所謂下載下傳中間件,就是處于爬蟲和從網際網路中下載下傳網頁中間的一個部件。
首先我們在爬蟲項目中建立一個檔案作為下載下傳中間件,名字随意,隻需要在設定檔案中設定為對應的即可。比如在此,我們建立了一個名為“downloader_middlerwares.py”的檔案,如下所示:
然後在該檔案中寫入如下代碼:
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,
}
這樣,對應的使用者代理其就會生效,我們運作之後如下所示,可以看到,隻采用了如下所示的使用者代理進行爬取。
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池呢?我們可以這樣做:
首先,建立一個下載下傳中間件(各使用者代理池建構時所建立的中間件類似,名字同樣自定義),在此我們建立的下載下傳中間件檔案如下所示:
然後在該中間件中編寫如下程式:
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,
}
随後,我們可以運作對應爬蟲,結果如下所示:
可以看到此時使用代理ip:107.170.26.74:8088對網站進行爬取。
4. 解決驗證碼限制-驗證碼三種處理手段及實戰講解
接下來我們為大家講解,如何解決驗證碼限制。
我們使用爬蟲爬取一些網站的時候,經常會遇到驗證碼,那麼遇到驗證碼之後應該怎麼處理呢? 一般來說,遇到驗證碼時,處理思路有三種:
- 半自動識别
- 通過接口自動識别
- 通過機器學習等知識自動識别
由于第三種方法涉及AI領域的新知識,是以第三種方法在此不具體講解(有興趣的同學可以關注我AI方面的課程),在此,我們主要會為大家講解如何使用前兩種方式去處理驗證碼。
首先為大家講解如何通過半自動識别的方式去處理驗證碼,我們以豆瓣登陸爬蟲為例進行講解。比如,在登入豆瓣時,我們經常會遇到如下所示的驗證碼:
那麼此時如果通過半自動處理的方式來做,我們可以這樣:先爬一遍登入頁,然後得到驗證碼圖檔所在的網址,并将該驗證碼圖檔下載下傳到本地,然後等待輸入,此時我們可以在本地檢視該驗證碼圖檔,并輸入對應的驗證碼,然後就可以自動登入。
通過半自動處理的方式登入豆瓣網站,完整代碼如下所示,關鍵部分一給出注釋,關鍵點就在于找到驗證碼圖檔所在的位址并下載下傳到本地,然後等待我們手動輸入并将輸入結果傳遞給豆瓣伺服器。
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鍵,此時便可正常登陸,如下所示:
顯然,通過半自動的處理方式不足以處理驗證碼,此時,我們可以通過接口的方式,自動的識别驗證碼并進行處理,這樣就完全自動化操作了。
具體方式如下,首先我們在雲打碼注冊一個賬号并申請對應接口,然後将程式驗證碼處理部分做以下修改:
cmd="D:/python27/python D:/python27/yzm/YDMPythonDemo.py"
r = os.popen(cmd)
captcha_value = r.read()
r.close()
print("此時已經自動處理驗證碼完畢!處理的驗證碼結果為:"+str(captcha_value))
然後運作該程式,結果如下所示:
可以看到,事實已經能夠自動的識别驗證碼并能夠成功登陸。
接口自動處理驗證碼的完整代碼如下所示:
# -*- 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”,把下方的全勾上,如下圖所示:
然後,點選Action,選擇将CA憑證導入到桌面,即第二項,導出後,點選上圖的ok儲存配置。
然後在桌面上就有了導出的證書,如下所示:
随後,我們可以在浏覽器中導入該證書。我們打開火狐浏覽器,打開“選項--進階--證書--導入”,選擇桌面上的證書,導入即可。随後,Fiddler就可以抓HTTPS協定的網頁了。如下圖所示。
能夠抓取HTTPS的資料之後,我們便可以對大多數網站進行抓包分析。
由于抓包分析的内容比較多,是以我們為大家配了配套視訊進行講解,配套視訊中講解了如何解決淘寶網站商品資料的屏蔽問題。
6. 其他反爬政策及應對思路
我們使用上面幾種反爬處理的方式,基本上可以應付大多數網站了。當然,有些網站的資料所在位址是随機生成的,根本沒有規律可循,如果遇到這種情況,我們采用PhantomJS将對應的位址擷取到,再交給爬蟲處理即可。
随着時代的發展,也會出現越來越多的反爬方式,我們掌握了這些基礎之後,在遇到新的反爬方式的時候,稍微研究便可以處理了。