我們都見識過requests庫在靜態網頁的爬取上展現的威力,我們日常見得最多的為get和post請求,他們最大的區別在於安全性上: 1、GET是通過URL方式請求,可以直接看到,明文傳輸。 2、POST是通過請求header請求,可以開發者工具或者抓包可以看到,同樣也是明文的。 3.GET請求會保存 ...
我們都見識過requests庫在靜態網頁的爬取上展現的威力,我們日常見得最多的為get和post請求,他們最大的區別在於安全性上:
1、GET是通過URL方式請求,可以直接看到,明文傳輸。
2、POST是通過請求header請求,可以開發者工具或者抓包可以看到,同樣也是明文的。 3.GET請求會保存在瀏覽器歷史紀錄中,還可能會保存在Web的日誌中。
兩者用法上也有顯著差異(援引自知乎):
1、GET用於從伺服器端獲取數據,包括靜態資源(HTML|JS|CSS|Image等等)、動態數據展示(列表數據、詳情數據等等)。
2、POST用於向伺服器提交數據,比如增刪改數據,提交一個表單新建一個用戶、或修改一個用戶等。
對於Post請求,我們可以通過瀏覽器開發者工具或者其他外部工具來進行抓包,得到請求的URL、請求頭(request headers)以及請求的表單data信息,這三樣恰恰是我們用requests模擬post請求時需要的,典型的寫法如下:
response=requests.post(url=url,headers=headers,data=data_search) 由於post請求很多時候是配合Ajax(非同步載入)技術一起使用的,我們抓包時,可以直接選擇XHR(XmlHttpRequest)-ajax的一種對象,幫助我們濾掉其他的一些html、css、js類文件,如下圖所示(截取自Chrome):雙擊點開,就可以在頁面右邊的Headers頁下看到General、Response Headers、Request Headers、Form Data幾個模塊,
其中General模塊能看到請求的方法和請求的URL以及伺服器返回的狀態碼(200(成功) 伺服器已成功處理了請求。通常,這表示伺服器提供了請求的網頁。)
而Response Headers部分,可以看到緩存控制、伺服器類型、返回內容格式、有效期等參數(筆者截圖所示,返回的為json文件):
Request Header模塊是非常重要的,可以有效地將我們的爬取行為模擬成瀏覽器行為,應對常規的伺服器反爬機制:
其中Content-Type、Cookie以及User-Agent欄位較為重要,需要我們構造出來(其他欄位大多數時候,不是必須)
由於Cookie欄位記錄了用戶的登陸信息,每次都不同,且同一個cookie存在一定有效期,當我們結合Selenium來組合爬取頁面信息時,可以通過selenium完成網頁的登陸校驗,然後利用selenium提取出cookie,再轉換為瀏覽器能識別的cookie格式,通常代碼如下所示:
cookies = driver.get_cookies() #利用selenium原生方法得到cookies
ret=''
for cookie in cookies:
cookie_name=cookie['name']
cookie_value=cookie['value']
ret=ret+cookie_name+'='+cookie_value+';' #ret即為最終的cookie,各cookie以“;”相隔開
緊接著,我們需要構造headers部分(即請求頭),我們挑重點的幾個欄位進行構造:
headers={
'Host':'**********.com',
'Referer':'http://****************/check/index.do',
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36',
'X-Requested-With':'XMLHttpRequest',
'Content-Type':'application/x-www-form-urlencoded; charset=UTF-8',
'Cookie':ret #需要登陸後捕獲cookie並調用
}
我們在網頁中點擊“確定”按鈕,網頁則會非同步載入,後臺發出post請求,取到json文件並渲染到網頁表單中,比如我們根據需求填寫了部分欄位(這些就是我們post請求的data信息),然後觀察後臺的form data信息:
後臺Form data 捕獲到的data參數如圖:
類似於字典格式,其中condition鍵對應的value較為複雜——列表中包含字典,字典中還有部分函數,其中字元串中既有單引號又有雙引號交錯。屬於關鍵信息,page決定了網頁的翻頁在第幾頁,而rows則表示每次請求的數據限定的最大行數。
本例中問題的關鍵是,如何把想要的信息(譬如來源於excel配置文件)傳遞到condition欄位對應的值內,確保Form data信息靈活可配置,大抵用法如下:
data_search={ 'page':1, 'rows':15, 'condition': """[\ {"column":"BPM_DEF_NAME","exp":"like","value":""},\ {"column":"DELETE_STATUS","exp":"=","value":0},\ {"column":"TO_CHAR(TO_DATE(CREATE_DATE,'YYYY-MM-DD HH24:MI:SS'),'YYYY-MM-DD')","exp":">=","value":"YYYY-MM-DD"},\ {"column":"TO_CHAR(TO_DATE(CREATE_DATE,'YYYY-MM-DD HH24:MI:SS'),'YYYY-MM-DD')","exp":"<=","value":"YYYY-MM-DD"},\ {"column":"CHECK_TYPE","exp":"like","value":"2"},\ {"column":"LOCKED_STATUS","exp":"=","value":0},\ {"column":"DELETE_STATUS","orderType":"default","orderKey":"","direction":"ASC"}\ ]""", #考慮到該欄位已經有單引號、雙引號,所以只能用三引號來包住這部分代表字元串 'additionalParams':'{}' } data_search_condition=json.loads(data_search['condition']) #將字元串轉為列表,方便更新列表(列表中每個元素都是一個單個字典)元素 #刷新字典 data_search_condition[0]['value']=businessName data_search_condition[2]['value']=str(startDate) data_search_condition[3]['value']=str(endDate) data_search['condition']=json.dumps(data_search_condition) #將列表重新轉回字元串,作為data_search字典中鍵“condition”對應的“value”,然後更新字典
上述代碼中,data_search其實為字典對象,其鍵“condition”對應的值(三引號包住部分)為字元串,本質是json格式,我們如何對這部分動態傳參呢?
這裡需要用到python json包中常用的loads和dumps方法:
1、json.loads()是將json格式對象,轉化Python可識別的字典對象。解碼python json格式,可以用這個模塊的json.loads()函數的解析方法。
2、json.dumps()是將一個Python數據類型列表進行json格式的編碼解析,可以將一個list列表對象,進行了json格式的編碼轉換。
3、json.dump和json.dumps很不同,json.dump主要用來json文件讀寫,和json.load函數配合使用。
上面實例中,就是將data_search['condition'](json,字元串)轉換為列表,然後根據列表定位到底層的每個dict字典,最後根據dict[Key]=value的方法進行更新(傳參),更新完之後的列表,再通過json.dumps反向轉回字元串,這樣整個data_search字典中參數就可以靈活配置,通過外部引入了。
剩下的工作就很簡單,交給強大的Requests包完成就好,示例代碼如下:
def get_page(data_search,url): #定義頁面解析的函數,返回值為json格式 try: response=requests.post(url=url,headers=headers,data=data_search) if response.status_code==200: return response.json() except requests.ConnectionError as e: print('Error',e.args)
我們還可以把json格式內容存到本地(data.json)格式文件或者txt文本,並按照特定縮進(indent=4)進行規則排版,格式化內容,此時要用到json.dump()方法,示例代碼如下:
for pageNum in range(1,1000): data_search['page']=str(pageNum) pageContent=get_page(data_search=data_search,url=url) with open('data.json','w',encoding="utf-8") as json_file: json.dump(pageContent,json_file,ensure_ascii = False,indent=4) if pageContent==None: print("無符合條件的單據!") time.sleep(3) sys.exit(0)
格式化後的json看上去直觀不少:
最後感慨一句:爬蟲是門技術活,任何一個技術理解地不夠透徹,碰到複雜的問題,可能就要花上很長時間去試錯,譬如本文示例中的字典、json包幾個功能的使用,稍微出錯,就無法請求到對的數據!
PS:特別強調一點,有的時候requests.post()方法中data欄位不填或者填寫有誤,伺服器有時也會返回200狀態碼以及相應內容。這種情況下,我們一定要與手工操作得到的json文件進行對比,看看我們的傳參(多測試幾組不同的參數,看返回json內容是否不同)是否真的起到作用,以免空歡喜一場!