Scrapy反爬蟲之521異常

引子

最近在爬取一個網站時, 遇到了521錯誤, 這是一種網站的反爬技術, 瀏覽器會渲染很多東西, 代碼爬數據會漏掉瀏覽器渲染的信息

思路

可以嘗試複製瀏覽器的cookie信息, 加在請求頭中, 但是這樣只能獲取單個域名的網頁。恰巧我需要爬取的網站下面有多個二級域名的網頁(二級域名網頁的鏈接可以通過一級域名獲取), 複製每個二級域名的cookie來爬取每個二級域名的網頁是不太可能的

進一步的方案是通過PhantomJS的無頭瀏覽器發送兩次請求, 第一次請求獲取一部分Cookie(__jsl_clearance)的信息, 第二次請求再將這部分Cookie加在Header信息中, 就可以獲得網頁內容

第一次嘗試

urls = request.url.split("/")
host = urls[-2]

headers = {
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
    "Accept-Language": "zh-CN,zh;q=0.8,en;q=0.6",
    "Connection": "keep-alive",
    "Host": host,
    "Referer": request.url,
    "Upgrade-Insecure-Requests": 1
}
ua = random.choice(spider.settings.get("UAPOOL"))
headers["User-Agent"] = ua

cap = DesiredCapabilities.PHANTOMJS.copy()
for key, value in headers.items():
    cap['phantomjs.page.customHeaders.{}'.format(key)] = value
driver = webdriver.PhantomJS(desired_capabilities=cap)
driver.get(request.url)
cj = driver.get_cookies()

上面的代碼, 構造請求的頭信息, 通過PhantomJS發起第一次請求, 獲得cookies

cookie = ''
for c in cj:
    cookie = cookie + c['name'] + '=' + c['value'] + ';'
cookie = cookie[:-1]
headers['Cookie'] = cookie

dcap = DesiredCapabilities.PHANTOMJS.copy()
for key, value in headers.items():
    dcap['phantomjs.page.customHeaders.{}'.format(key)] = value

time.sleep(10)
driver2 = webdriver.PhantomJS(desired_capabilities=dcap)
driver2.get(request.url)
content = driver2.page_source.encode('utf-8')
return HtmlResponse(request.url, encoding='utf-8', body=content, request=request)

通過第一次請求返回的cookie, 構造頭部的Cookie, 併發起第二次請求, 注意第一次和第二次發起的請求的頭部信息相同, 只是第二次加入了Cookie的信息

通過發起二次請求的方式可以獲取到網頁的內容, 但是這種方式不太穩定, 經常連續發送幾次就會有一段時間獲取不到網頁, 讓人感覺很崩潰。於是嘗試過濾掉這部分拉取不到的請求, 將這部分請求的url記錄在數據庫, 並重復爬取。但又遇到一個新的問題, PhantomJS連續拉取到一定的次數, 就會長時間的沒有響應, 加超時時間也不管用

將PhantomJS和Scrapy整合在一起會導致網頁一次都拉取不到, 猜想可能因爲PhantomJS是一種串行的爬取方式, 而Scrpay是一種異步的拉取的方式, 這兩種方式有衝突。

第二次嘗試

通過第一次嘗試得出兩個結論, 第一: PhantomJS不太可靠, 網上得知PhantomJS已經不再維護, 於是考慮使用PhantomJS的替代品Chrome Driver. 第二: Scrapy多線程的拉取方式和PhantomJS/Chrome Driver無頭的串行拉取方式沒法融合. 於是想到單獨使用Phython寫一個原生的爬蟲

這裏只共享通過Chrome Driver + selenium解決521反爬的方案

headers = {
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
    "Accept-Language": "zh-CN,zh;q=0.8,en;q=0.6",
    "Connection": "keep-alive",
    "Upgrade-Insecure-Requests": 1
}
ua = random.choice(UAPOOL)
headers["User-Agent"] = ua
host = url.split("/")[2]
headers["Host"] = host
headers["Referer"] = url

cj = Content.get_cookie(url, ua)

get_cookie方法

chrome_options = Options()
chrome_options.add_argument('--headless')
chrome_options.add_argument('--disable-gpu')
user_agent = 'user-agent='+ua
chrome_options.add_argument(user_agent)
driver = webdriver.Chrome(chrome_options=chrome_options, executable_path="/home/bearyb/chromedriver")

try:
    driver.get(url)
    cj = driver.get_cookies()
    driver.quit()
    return cj
except TimeoutException:
    print("first time get " + url + " timeout")
    driver.quit()
    return ''

還是和之前一樣, 構建第一次請求通過get_cookie獲取cookie, 可以看到在get_cookie方法裏已經將PhantomJS換成了chrome driver的headless方式

cookie = '_ga=GA1.2.413717745.1574286560;UM_distinctid=16e8aca983f9-09c74e905834908-76256753-13c680' \
         '-16e8aca984088;Hm_lvt_dfa5478034171cc641b1639b2a5b717d=1576274996,1576361248,1576673944,' \
         '1576794342;g=HDF.145.5deb431611b32;_gid=GA1.2.742943599.1576794342;CNZZDATA-FE=CNZZDATA-FE;' \
         'Hm_lpvt_dfa5478034171cc641b1639b2a5b717d=1576800627;' \
         'CNZZDATA1256706712=' + '36179960-1576796605-https%253A%252F%252F' + host + '%252F%7C1576796605;'

for c in cj:
    cookie = cookie + c['name'] + '=' + c['value'] + ';'
cookie = cookie[:-1]

headers['Cookie'] = cookie
return Content.get_content(url, headers)

get_content方法

def get_content(url, headers):
    dcap = DesiredCapabilities.PHANTOMJS.copy()
    for key, value in headers.items():
        dcap['phantomjs.page.customHeaders.{}'.format(key)] = value
    time.sleep(10)

    session = requests.Session()
    html = session.get(url, headers=headers).content.decode('gbk')
    content = etree.HTML(html)
    return content

將第一次請求的Cookie加入到header中再次請求, 就能獲得返回的網頁, 第二次請求沒有使用Chrome的請求方式, 而是使用了python框架的requests模塊

後話:

所謂道高一尺, 魔高一丈, 這個問題前前後後一直困擾了我兩個多星期, 不斷的嘗試, 不斷的失敗, 不停的調整方向, 還好最後終於解決了問題. 在這裏分享給大家解決521反爬的思路, 望學習的道路上共同進步!

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