一、問題發(fā)現(xiàn)
近期我在做代理池的時候,發(fā)現(xiàn)了一種以前沒有見過的反爬蟲機制。當我用常規(guī)的requests.get(url)方法對目標網(wǎng)頁進行爬取時,其返回的狀態(tài)碼(status_code)為521,這是一種以前沒有見過的狀態(tài)碼。再輸出它的爬取內容(text),發(fā)現(xiàn)是一些js代碼??磥硎切聠栴},我們來探索一下。

二、原理分析
打開Fiddler,抓取訪問網(wǎng)站的包,我們發(fā)現(xiàn)瀏覽器對于同一網(wǎng)頁連續(xù)訪問了兩次,第一次的訪問狀態(tài)碼為521,第二次為200(正常訪問)??磥砭W(wǎng)頁加了反爬蟲機制,需要兩次訪問才可返回正常網(wǎng)頁。

下面我們來對比兩次請求的區(qū)別:
521請求:

200請求:

通過對比兩次請求頭,我們發(fā)現(xiàn)第二次訪問帶了新的cookie值。再考慮上面程序對爬取結果的輸出為js代碼,可以考慮其操作過程為:第一次訪問時服務器返回一段可動態(tài)生成cookie值的js代碼;瀏覽器運行js代碼生成cookie值,并帶cookie重新進行訪問;服務器被正常訪問,返回頁面信息,瀏覽器渲染加載。
三、解決流程
弄清楚瀏覽器的執(zhí)行過程后,我們就可以模擬其行為通過python作網(wǎng)頁爬取。操作步驟如下:
用requests.get(url)獲取js代碼
通過正則表達式對代碼進行解析,獲得JS函數(shù)名,JS函數(shù)參數(shù)和JS函數(shù)主體,并將執(zhí)行函數(shù)eval()語句修改為return語句返回cookie值
調用execjs庫的executeJS()功能執(zhí)行js代碼獲得cookie值
將cookie值轉化為字典格式,用requests.get(url, cookies = cookie)方法獲取得到正確的網(wǎng)頁信息
四、代碼實現(xiàn)
實現(xiàn)程序所需要用到的庫:
import re #實現(xiàn)正則表達式
import execjs #執(zhí)行js代碼
import requests #爬取網(wǎng)頁
第一次爬取獲得包含js函數(shù)的頁面信息后,通過正則表達式對代碼進行解析,獲得JS函數(shù)名,JS函數(shù)參數(shù)和JS函數(shù)主體,并將執(zhí)行函數(shù)eval()語句修改為return語句返回cookie值。
# js_html為獲得的包含js函數(shù)的頁面信息
# 提取js函數(shù)名
js_func_name = ''.join(re.findall(r'setTimeout\(\"(\D+)\(\d+\)\"', js_html))
# 提取js函數(shù)參數(shù)
js_func_param = ''.join(re.findall(r'setTimeout\(\"\D+\((\d+)\)\"', js_html))
# 提取js函數(shù)主體
js_func = ''.join(re.findall(r'(function .*?)</script>', js_html))
將執(zhí)行函數(shù)eval()語句修改為return語句返回cookie值
# 修改js函數(shù),返回cookie值
js_func = js_func.replace('eval("qo=eval;qo(po);")', 'return po')
調用execjs庫的executeJS()功能執(zhí)行js代碼獲得cookie值
# 執(zhí)行js代碼的函數(shù),參數(shù)為js函數(shù)主體,js函數(shù)名和js函數(shù)參數(shù)
def executeJS(js_func, js_func_name, js_func_param):
jscontext = execjs.compile(js_func) # 調用execjs.compile()加載js函數(shù)主體內容
func = jscontext.call(js_func_name,js_func_param) # 使用call()通過函數(shù)名和參數(shù)執(zhí)行該函數(shù)
return func
cookie_str = executeJS(js_func, js_func_name, js_func_param)
將cookie值轉化為字典格式
# 將cookie值解析為字典格式,方便后面調用
def parseCookie(cookie_str):
cookie_str = cookie_str.replace("document.cookie='", "")
clearance = cookie_str.split(';')[0]
return {clearance.split('=')[0]: clearance.split('=')[1]}
cookie = parseCookie(cookie_str)
獲得cookie后,采用帶cookie的方式重新進行爬取,即可獲得我們需要的網(wǎng)頁信息了。