論如何使用scrapy框架登陸知乎

寫在前面

事情是這樣的,前面幾天報了一個慕課網的爬蟲課程,教授使用python來編寫爬蟲,由於之前有使用過nodejs寫爬蟲的經歷,所以對上手python的scrapy框架也不是那麼生疏,反正思想是差不多的嘛,就是使用框架使爬蟲的開發更加地有結構性,比如說下載鏈接的獲取都是寫在一個獨立的.py文件中,而對獲取的數據的處理,比如獲取圖片的url,將數據存入數據庫等等,都是在專門的pipeline.py中,而對存儲數據結構以及對相應數據的獲取方式都寫在一個item.py中,對爬蟲框架整體的配置,比如數據庫的配置信息,pipeline.py中處理數據的順序,都是在setting.py中完成的,而對於頁面的http請求,圖片的下載,各種文件方法執行的調度,都是交給scrapy框架來完成,這讓開發人員專注於爬蟲的邏輯開發,這是十分高效的


問題起因

由於視頻並不是最新錄製,可能知乎也不想自己的網站像試驗品一樣被爬來爬去的,所以對登陸增加了難度,pc版的是需要點擊倒立漢字,而手機版的是動態加載驗證碼,這對於純請求html頁面的爬蟲登陸是可以做到有效防治的,雖然後面的課程有講如何在scrpay中集成一些自動化測試平臺,但是畢竟我還沒看到嘛,而且要一步一個腳印,做到不跳視頻,所以就卡在這裏了…..


對登陸的理解

在瀏覽器登陸成功後,服務器會返回一段cookie保存在本地,這樣當你再次登陸這個網站時,瀏覽器將這個cookie包含在http請求中發給服務器,服務器在確認這個cookie沒過期後就會讓你保持登陸狀態,不需要輸密碼再次登陸了,cookie會在一段時間內失效,失效後就需要重新登陸了


一個想法

既然我無法做到通過scrapy登陸,那我能否可以獲取那段cookie,然後讓其包含在scrapy發出的http請求中,這樣豈不就算是登陸了嘛

在瀏覽器中輸入知乎網址 ’ www.zhihu.com’,登陸完成後關閉頁面,再次打開時打開開發者工具,查看獲取頁面時瀏覽器發出的http的request請求, 如下:

這裏寫圖片描述

中間有cookie一項,這就是我要的東西!!

先使用一個簡單的request第三方包做了一個測試,將cookie包含在請求的header中,結果成功,證明可以

import requests, os

headers = {
      'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36'# user-agent使用手機版的似乎可以減少被抓的概率
           'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
            'accept-encoding': 'gzip, deflate, br',
            'accept-language': 'en,zh-CN;q=0.8,zh;q=0.6,zh-TW;q=0.4',
            'connection': 'keep-alive',
            'cookie':'aliyungf_tc=AQAAANGG6Xr6fgsARUOVJJhV/Q1zeFk0; .....'}
# z這裏cookie太長了就不全粘出來了
filepath = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'result.html')
r = requests.get('https://www.zhihu.com/', headers=headers)
f = open(filepath, 'w')
print(r.status_code)
f.write(r.text)
f.close()
print('finish') 

這是我得到到頁面

這裏寫圖片描述

雖然無法運行js腳本來動態獲取更多數據,但是登陸到目的還是達到了,而且cookie的有效時間爲一個月。


遇到問題

那麼來到scrapy框架中也不是一樣嘛,header還是之前的那個header,在主文件中把start_requests方法重載一下應該就沒問題了

def start_requests(self):
     return [scrapy.Request(headers=headers, url="https://www.zhihu.com"]

但是運行結果還是未登陸的頁面,wfc!!!

查詢scrapy官方文檔,說是scrapy.Request有一個cookies屬性要單獨設置,而且還要求爲dict類型的,如果不設置就爲空dict

這裏寫圖片描述

複習一下dict類型

    dict_example = {'p1': 's1', 'p2': 's2'}

再觀察header中cookie的結構,如下

    p1=s1; p2=s2; p3="s3=z3"

問題就是結構大大的不同,需要做格式轉化,還有就是字符串轉dict的問題


問題解決

解決問題一有幾種方案

第一種就是手動書寫dict數據,雖然花不了多久時間但是這樣太不優雅了,而且裏面除了一個cap_id我知道是登陸後動態生成的一個id之外別的我不知道會出現什麼變化,要是登陸不成功還要再次手動登陸來更新cookie數據,那還要再一個數據一個數據輸入一遍,太麻煩了,我希望的是直接全部複製粘貼,然後讓程序自己格式化

第二種就是通過正則替換一步到位,把每個單詞之間的等號替換成冒號,把分號替換成逗號,併爲每個單詞都加上引號。但是我明顯高估了我自己的能力了,廢了大半天的時間整出了一個看似完美的正則替換式,但是沒注意到還有p3="s3=z3" 這樣的格式,所以最終還是任命放棄,選擇了第三種方案

第三種方案代碼包裝成一個函數如下

import re, ast


def util_cookie(arg):
    __string = arg
    regex1 = r'(\s)'
    __string = re.sub(regex1, r'', __string)
    list_string = __string.split(';')
    regex2 = r'(=)'


    def convert_fun(tmp_str):
        result_tmp = re.sub(regex2, ':', tmp_str, 1)
        process_str = result_tmp.split(":")
        result_tmp = ':'.join(list(map(lambda x: "'" + x + "'", process_str)))
        return result_tmp


    result = list(map(convert_fun, list_string))
    result_string = ''
    for v in result:
        result_string += v + ','
    return ast.literal_eval('{' + result_string + '}')
  • 1.先將字符串中所有的空格去掉
  • 2.將字符串以’;’爲間隙切割成一個list
  • 3.對於每一個子字符串,將一個出現的’=’替換爲’:‘,隨後以冒號爲間隔切割子字符串,成爲一個list,再將list中每一個孫字符串開頭街尾都加上引號,再用’:’連接在一起成爲新的子字符串
  • 4.將處理完的每一個子字符串以‘,’鏈接在一起,生成新的字符串,也就是上面代碼的result_string,再在它的開頭與結尾處加上大括號,就完全符合dict格式的

解決問題二的方案

這個就相對簡單了,因爲本來一個python文件就是以字符串的形式處理的嘛,python的標準庫ast, 使用其中的literal_eval方法就行了

解決了以上兩個問題之後,重寫 scrapy.Request 如下

scrapy.Request(headers=headers, url='https://www.zhihu.com', cookies=util_cookie(headers['cookie']))

達到了與之前一樣的效果


總結

如果使用方法二我覺得也是可以達成的,但可能真的是我太菜了,我廢了大半天(真的大半天!!)的時間寫的正則表達式雖然沒有達到目標但對於我來說還是有所收穫的,畢竟我今年暑假之前對於正則表達式還是一竅不通的,後面也許會寫一篇總結正則表達式的筆記吧

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