作為一個初學爬蟲的小白,還是決定開始寫一些文章來紀念自己的學習過程,希望自己能有進步.
前幾周學習了python基礎模塊urlllib的使用, 有很多功能實現(xiàn)起來都很復雜, 直到現(xiàn)在學了第三方模塊requests后驚嘆于它的簡潔性與實用性, 但是requests模塊的底層實現(xiàn)還是調用了urllib的有關模塊的.? 不扯遠了, 下面是我用requests庫抓取搜狗圖片的詳細過程:
一般瀏覽器發(fā)起的請求有兩種: get請求和 post請求, 根據(jù)我們的需要使用 get請求就可以實現(xiàn)這個功能了.? 這里稍微區(qū)分下get和post: get一般是把發(fā)送給服務器的信息放在請求頭中, 它的請求有長度限制, 而且不可以處理敏感數(shù)據(jù), 因為它的 傳輸是不加密的 , 所以我們在模擬登錄請求時候一般用的都是post.? post一般是把發(fā)送給服務器的信息放在http消息主體中, 請求長度是沒有限制的, 同時post信息是加密傳輸?shù)? 所以可以用來模擬登錄.. . 粗略介紹了下 get 和 post.?
然后我的想法就是實現(xiàn)一個能根據(jù)輸入關鍵詞爬取圖片的python程序, 那么在傳給搜狗服務的請求參數(shù)中就需要添加這個關鍵詞, 請求長度不是特別長而且可以明文傳輸, 我們用get足矣.??
1. 首先打開搜狗圖片首頁我們需要在里面找到我們的目標url:
(1). 訪問主頁頁面:
很顯然我們要輸入關鍵字的話直接用這個url肯定是不行的, 我們首先嘗試下人工搜索, 看看地址欄url有什么變化(我們首先需要明確一點: 爬蟲是灰色產業(yè), 也就是說既不能說不違法, 也不能說違法(不要太過分就好). 那么為什么說它不違法呢? --- 爬蟲只能訪問用戶可以訪問到的內容, 比如愛奇藝 VIP視頻 , 你普通用戶是看不了的, 那有人問用爬蟲可不可以爬VIP視頻呢? 答案是可以的, 但是有個前提 -> 你必須要用VIP的身份去登陸它的網站再進行爬取. )
這里說這個的意思是, 你爬一個東西前, 最好人工去訪問下你想要爬的內容, 如果你人工都訪問不了, 那么你還是放棄吧! 爬蟲也爬不了的 (如果強行獲取用戶訪問不了的數(shù)據(jù) -> 那就是黑客了, 是犯法了. 我們是spider, 不是 hacker...)?
人工模擬搜索:
上面的url我復制過來是這個:?
這個一看就知道是個get請求, 也就是主url 加上若干個參數(shù)的拼接, 其中參數(shù)用&分開, 我們發(fā)現(xiàn)一共有6個參數(shù):
query, w, p, _asf, _ast, sc, sut, ssto? 這些參數(shù)中肯定有我們有用的, 也肯定有我們不需要的, 我們來分析下, 首先query的意思是查詢, 它其實就是我們要查詢的關鍵字,?%D0%C7%BF%D5 的意思是星空, 只不過是特殊的url編碼表示的"星空", 一般是一種urlencoded格式(用于網絡信息傳輸). 我們可以驗證下:
結果是
發(fā)現(xiàn)不是我們想要的, 其實這是utf-8和gbk格式數(shù)據(jù)的差異, 我們看看搜狗圖片網站的編碼:?
發(fā)現(xiàn)它是gbk格式的, 那么傳輸數(shù)據(jù)的轉換格式也應該用gbk, 我們用gbk試試:
結果:
完全一致,那我們的猜想就對了 query 對應的確實是查詢關鍵字, 那么其他關鍵字呢? 在這里我們可以采取"控制變量法" 去"試", 嘗試去掉某些關鍵字看網頁是否有所改變, 最后我們發(fā)現(xiàn)其他關鍵字都去掉了網頁還是不變:
下一步是分析網頁構造:
我們首先選定一張圖片, 右鍵點擊它, 選擇復制圖片地址, 然后我們用Google提供的查看網頁源代碼功能, 在源代碼界面按 ctrl + F 查找元素, 然后粘貼圖片地址:?
然后根據(jù)匹配項(橙色部分)前面的屬性值確定圖片是放在哪里的, 這里圖片是放在?"thumbUrl"這個屬性里面, 我們再用同樣的方法搜索這個屬性名, 看看有幾個匹配項:
這里有48個圖片, 也就是48個匹配項, 48張圖片, 我們來檢查下是不是一一對應的:?
然后在新窗口中輸入這個url:
發(fā)現(xiàn)確實是對的, 我們屬性也找對了. 現(xiàn)在來說說兩個"問題":
一 : 我們發(fā)現(xiàn), 搜狗圖片的顯示好像是"無窮無盡"的, 為什么我們這個源代碼頁面只有48個? 其實當我們把滑條向下拉動時會一直有新的圖片出來, 沒有"下一頁"的選項, 這里其實用到了web開發(fā)一種技術: AJAX, 我們不需要具體去學AJAX, 我們只要知道它是啥東西就行了, AJAX技術實現(xiàn)了網頁的動態(tài)加載(網頁不是一次性加載出全部資源, 而是動態(tài)加載資源, 避免了因為網頁過于臃腫而影響加載速度). 我們如何從動態(tài)加載的網頁中找出"目標url"? --> 這個就是我們的目的.? 這里我們可以借助Google瀏覽器的開發(fā)者工具:?
打開開發(fā)者工具的快捷鍵是 ctrl + shift + i (或者 ctrl + shift + c), 進到開發(fā)者工具后我們點擊 network選項:
里面就會有網頁所有的元素, 這里我們需要關注的是一個XHR的選項:
XHR全程XMLHttpRequest, 意思是可擴展超文本傳輸請求, XHR對象可以在不向服務器提交整個頁面的情況下實現(xiàn)局部更新網頁. 當頁面全部加載完畢后, 客戶端通過該對象向服務器請求數(shù)據(jù), 服務器端接收數(shù)據(jù)并處理后向客戶端反饋數(shù)據(jù), XHR對象提供了對HTTP協(xié)議的完全訪問, 包括POST和HEAD請求以及普通的GET請求的能力, XHR可以同步或異步返回web服務器的響應, 能以文本或DOM文檔的形式返回內容..........?
不說遠了, 它對我們完成程序至關重要, 現(xiàn)在我們的XHR里面的內容是空的:
是因為我們在打開開發(fā)者工具抓包前它已經返回了請求的數(shù)據(jù), 所以這里抓包沒抓到, 我們可以在保持開發(fā)者工具打開的情況下刷新下網頁來獲得初始數(shù)據(jù), Google瀏覽器按ctrl + r或點擊左上角的刷新按鈕實現(xiàn)刷新:
現(xiàn)在XHR里面已經有了一個數(shù)據(jù), 我們來看看它是什么:
首先左鍵點擊它:
在這里選擇Preview或者左鍵連點兩次可以打開在新標簽頁打開服務器返回的數(shù)據(jù)(可能更方便查找數(shù)據(jù)).
我們右鍵在新標簽頁打開這個url:
我們首先跟上面一樣, 第一步分析url結構:
這樣得到一個url:?https://pic.sogou.com/pics?query=%D0%C7%BF%D5&mode=1&start=48&reqType=ajax&reqFrom=result&tn=0
我們還是可以找出這個get請求由哪些關鍵詞組成: query, mode, start, reqType, reqFrom, tn
我們繼續(xù)用"控制變量法"分析它, query肯定需要, 是查詢的關鍵詞, 分析過程如下:
一: 先把出了query的關鍵詞全部去掉:?https://pic.sogou.com/pics?query=%D0%C7%BF%D5,?
發(fā)現(xiàn)它跳到了這個界面,不是我們需要的數(shù)據(jù):
然后我們可以保留一個關鍵詞,例如start , 猜測它是很有用的:https://pic.sogou.com/pics?query=%D0%C7%BF%D5&start=48,?
發(fā)現(xiàn)它還是跳到了一個圖片網站, 只是起始圖片不同了:
證明它是有用的, 可以告訴服務器從第幾張圖片開始返回, 我們保留它, 剩下的關鍵詞我們猜測reqType是有用的, 因為它等于AJAX, 正好是前面講的一種web開發(fā)技術, 我們試試這個url:?https://pic.sogou.com/pics?query=%D0%C7%BF%D5&start=48&reqType=ajax:
我們檢驗下這個頁面的第一個圖片:
發(fā)現(xiàn)不是最初頁面的第一張, 然后我們剛剛不是分析了start嗎? 我們可以嘗試修改start=0, 試試返回的第一張應該就是最初頁面的第一張圖了:
驗證下,果然就是第一張圖:
到這里我們分析差不多結束了, 我們分析得到的最終url類似這樣:
https://pic.sogou.com/pics?query=%D0%C7%BF%D5&start=0&reqType=ajax
這里我說明下,其他參數(shù)什么意思我也不知道, 但是不影響我們爬圖片, 其實加上也可以, 沒必要去試, 就把所有的參數(shù)都帶上, 省時間.??
經過前面的分析, 怎么爬取多個圖片? 好像我們每次請求, 他只返回給我們48張圖片(不管我們的start變成多少), 這里我們怎么辦呢? 很簡單 ---- > 我們可以在python代碼里面循環(huán)更改start的值, 就可以從不同的起始圖開始爬取數(shù)據(jù)了 . 比如,? 我們可以讓start 最初為 0 , 然后爬取完一組數(shù)據(jù)后讓start += 48 , 就可以啦 .. 為什么要加48, emmm,? 因為搜狗服務器一次只返回給我們48張....??
綜上所述, url,也找出來了,? 結果也分析出來了,? 本來我們可以開始代碼了, 但是我后來代碼寫完才發(fā)現(xiàn)爬取到的圖片好小,? 根本不是那種特別清晰的,? 總之比起我們單擊搜狗圖片里面某個圖片后打開的圖片來說完全就是"縮小版",?
像這樣:
左邊的是我們爬到的, 右邊的是點開圖片后搜狗圖片展示的, 我們怎么辦? --> 我們肯定要追求最好是不? 然后我果斷選擇繼續(xù): 我首先就在搜狗圖片詳情頁復制了下圖片地址然后去返回的請求里找看有沒有:
詳情頁就是這個頁面(單機圖片后的):
我們最后找到了, 而且還有好幾個匹配的項:
我們發(fā)現(xiàn)這個屬性名不是前面那個"thumbUrl", 但是它們確實都在同一個頁面, 只不過是在屬性名不同的值里面, 我們最后只要稍微修改下代碼就好了.?
注意點:
這里我代碼里面是把所有的參數(shù)都拼接上去了, 其實只需要下面兩個參數(shù)即可, 一個query, 一個start,? 這是我們分析出來的.? ?url? 就是去掉所有參數(shù)后剩下的, user-agent 用來辨別用戶身份, 如果不自己修改的話, 最后的請求頭python會自動給你加上 帶有python字樣的user-agent,? 那別人就很容易知道你是爬蟲來處理你. 所以每次爬的時候都要帶上.? 然后請求數(shù)據(jù)就兩行:
這里講下用response的好處, 我們之前不是看到 關鍵詞 "星空" 在url里是有著urlencoded格式的特殊類型, 所以本來我們在請求的時候需要將url里不符合規(guī)范的格式全部改成urlencoded格式(在urllib底層庫里我們需要手動改),? 當然在第三方模塊 requests中 它會自動將不是標準格式的url改成標準格式, 很方便, 而且相比urllib的請求, requests多了個 params參數(shù):
它可以自動將python字典,元組組成的列表或者bytes類型轉化為標準格式并拼接到url后面, 也是很方便.?
這里最后說下返回值的問題: 我們打開請求網頁也看到了, 服務器返回給我們的其實是一個json對象, 遇到這種情況, 我們也不需要將返回的改成字符串然后用正則或者xpath去解析, 直接將它轉化為json對象(在python代碼里表現(xiàn)為字典格式), 然后直接從字典去找需要的元素. 怎么轉換成json格式, 有兩種方法, 第一種就是利用強大的requests模塊的json方法:
def json(self,**kwargs):
? ? r"""Returns the json-encoded content of a response, if any.
:param \*\*kwargs: Optional arguments that ``json.loads`` takes.
:raises ValueError: If the response body does not contain valid json.
? ? """
這是源碼里的, 就是如果返回的response對象包含一個合法的json, 就可以直接將返回對象轉換成json格式(也就是個字典); 第二種方法就是, import json? 然后將返回值轉換成字符串或者bytes 然后用 response = json.loads(response.content(或者response.text)), 也可以實現(xiàn), 原理是一樣的, 只不過requests別人幫我們封裝好了.?
之后就沒啥難點了, 就是保存文件是用二進制將返回的數(shù)據(jù)保存到本地, 原理是所有mp4, mp3, jpg, mkv, png等等等等 都是將視頻,音樂等對象用不同的壓縮算法將它們壓縮成計算機能保存的(0, 1流)二進制文件, 自然我們只要取得這二進制文件完全相同的所有字節(jié)就相當于"下載" 了.?
運行結果如下:
(從第50張開始下是因為之前下過前50張, 不會重復下)