1. 程式人生 > >python 爬蟲 入門 commit by commit -- commit2

python 爬蟲 入門 commit by commit -- commit2

python 爬蟲 入門 commit by commit -- commit2

"每一個commit都是程式設計師的心酸,哦不,心路歷程的最好展示。" -- by 我自己

最近寫好了一組文章,來這裡,當然一如我以前一樣,主要是宣傳。但是,最近發現gitbook老是掛掉,除了宣傳,我覺得,在這裡全部貼一遍,這樣就算是gitbook那邊不穩定,至少這裡還能看到。不過說實話,如果有興趣的話,我還是推薦去gitbook那邊看,因為部落格園的結構,貌似不適合這種系列型的文章。

目前所有完結版本都已經可以在https://rogerzhu.gitbooks.io/python-commit-by-commit/content/ 看到,因為部落格園一天只能貼一篇首頁的文章,所以我可能需要一點時間把所有的都貼完。當然,你可以去gitbook上看已經完結的。而程式碼,我放在了

https://github.com/rogerzhu/relwarcDJ ,裡面有我完整的commit記錄。有興趣的話可以盡情star。 而且我覺得這裡扯淡和準備篇的文字我就不貼了,有興趣可以從上面的gitbook地址看到。

廢話少說,搬運工作開始:

 

“程式設計師是最不會偽裝但是又是最會偽裝的群體”--by 我自己

在運行了第一個commit的程式我估計也就三分鐘之內,你會覺得索然無味。圖書的標題和連結到底有什麼用呢?當然,我也有同樣的疑問,所以,我決定在第二個commit中爬取這個首頁我覺得我最關心的資訊,那就是錢——圖書的價格。

對於這個commit,當你輸入如下命令開始執行時:

你應該能看到如下的結果:

有了第一個commit中的三板斧,我感覺我已經信心與感覺並存,動力與技術齊飛了。於是我熟練的使用了選取工具,選到了價格的方框。火狐的工具給我顯示了,價格是在class名為p-price的div之內的。照葫蘆畫瓢一般的,使用BeautifulSoup的find,直接找到每個li中的這個div,熟練的儲存好檔案,開始用python執行。現實狠狠的給我了一個耳光,無論我怎麼輸出,這個價格都是拿不到的。

為什麼?我對著螢幕思考了3分鐘,畢竟如果思考再長的時間的話那隻能說明我的拖延症犯了。我重新回到我需要的頁面上,重新整理了下頁面,會看到價格資訊會比其他的資訊後出來,我又試了幾次,這不是偶然,每次都不是同時出來的。這個時候憑藉著我對web程式設計的一點粗淺的瞭解,我已經知道了,至少價格這個資訊不是和html資訊一起返回的。用在任何軟體語言裡都有的概念,這裡一點存在有回撥——callback。其實我在初學c++的時候對於這個概念不是很理解,但是如果你是第一次聽到這個概念,在這裡就特別形象,在某一件事情做完之後,又回頭呼叫了一個什麼介面或者檔案等等來取得結果。

到了這一步,就需要一點大膽猜測小心求證的哲學了,當然,還有得知道一點webapi的基本概念。其實簡單的說,就是呼叫一個url來獲得返回的結果,這個url中可以使用&傳入引數,而結果是一個檔案的方式傳到客戶端。而繼續前面所說的贈人玫瑰,手有餘香的邏輯,你要爬取的這個網站的程式設計師們也要考慮維護問題,加上業界對於某些反覆會出現的東西一定會有一套約定俗成的模式。說了這麼多,到底想表達什麼?既然我說webapi一般都是以檔案的方式返回結果,那麼怎麼看到這些從伺服器返回的檔案呢?很完美的事,這件事,又可以使用F12來解決。

當你按下F12的時候會有很多tab,其中有一個叫network,這個下面會記錄客戶端與伺服器端互動的所有內容。

既然是所有,那麼確實有點多,而且在大多數情況下,他會在不停的滾動,讓人很難操作。

這個時候只要你稍微網上看一點,就會發現,這些工具一定都會帶有搜尋功能的,畢竟,任何沒有搜尋功能的列表都是耍流氓。那麼這個時候就到了大膽發揮猜想的時候了,按照我前面的說的,寫程式的人為了維護一定會有某種比較共通的模式,既然價格是靠回撥取出來的,那麼不妨試試callback作為關鍵詞?或者這個是價格,用price作為關鍵詞?我選擇用callback,沒啥原因,只是因為我腦海裡第一反應是想用這個。於是我得到如下的結果,但是很明顯,一個網站上不可能只有一個callback。

但是這已經少多了,最差的結果一個一個暴力尋找,找什麼呢?找返回值,也就是右邊有的response的tab,找什麼返回值?因為每個callback當然都有返回值。當然找價格的數字了,既然你都能看到價格是多少錢,那麼response中含有這個價格當然就是你需要的webapi的地址啦。而很明顯,所有的callback返回的都是json字串,如果你實在沒有聽過json,也沒有關係,最簡單的你可以把他理解成是一個帶有格式的文字,這個文字的格式就是以逗號隔開的key,value字串。於是我就這樣暴力尋找,還真知道一個respons裡面帶有p:正確價格的字串。這個時候可以再回頭自己驗證一下,怎麼驗證?我的方法就是看看這個請求的url,驗證的方法還是本著良心程式設計師一定會把介面設計的另外一個程式設計師一看就懂的模式,吹的大一點,程式碼即文件。如果你看下這個請求的url,很明顯,有個關鍵詞告訴你,啊!這就是你要找的,那就是price,你可以在request URL中看到。你還可以再大膽的進一步,在這條記錄上右鍵,所有的F12工具都有copy url的功能,拷貝下這個地址,放在瀏覽器上,回車,你會發現,你可以看到一條返回的json字串。

仔細看看這些分會的字串,雖然都是縮寫,但是大概都能猜到是什麼意思,比如p後面是價格,id後面是標識,至於op和m的意思,我猜是什麼會員價和原價?不過沒關係,這裡面已經有了我們想要的資訊了。那麼想拿到價格的方法也很簡單了,按照前面了的路子,只要訪問這個網址然後拿到輸出傳給BeautifulSoup物件,就能完成解析了。但是,我們目前的想法是以一個書目,也就是一個list為一個Item,這個json字串似乎一次性傳回了很多個條目的價格。當然,可以通過字串處理然後選取合適的容器來取出每個圖書Item的價格。但這和我們的程式設計邏輯不搭,這東西就和寫文章一樣,行文邏輯不一致,會讓讀的人感到非常困難。放到程式碼上就是難以維護,那麼,有什麼辦法可以一條一條的取出價格就好了。

這個時候,不妨回頭看看獲取到這個json返回值的url,因為webapi,引數就在url上,真正的謎底就在謎面上。你想想,我們想獲取一個條目的價格,那麼如果你寫程式,一定是把這個條目的標識傳進去,然後獲取到價格。而我們現在使用的這個url有點長。

但仔細一看,很多都不知道是什麼意思,職業的敏感讓我把眼光放在skuIds這個引數上面,再看看傳回來的引數,很明顯,每一條單獨的json條目都對應了這個傳進去的一個Id。這個時候大膽嘗試的念頭又在我心中泛起,試試看只傳進去一個引數。在瀏覽器中輸入這條修改後的地址。

啊!你會發現就返回了一條skuIds的記錄!再試試把Id改為其他的,發現也能行!並且每次都能得到正確的結果!所以說,勇敢嘗試是成功的第一步。這個時候就可以使用這個URL了。但是這個URL中還有很多不知道幹啥的引數,作為一個強迫症患者,試試看全部刪除這些不需要的引數,就留下一個SkuIds試試。如果你真的試過,會發現不行。會給你返回一個error "pdos_captcha"。這看起來是步子邁大了,扯著蛋了。但是不多打一個字的惰性促使著我想看看能不能少一點,於是先從第一個callback=JQueryxxx的加起。

你會驚奇的發現,成了!但是你如果你多試幾次,你可能會發現,你會失敗!為什麼?這就是在網路爬蟲中的一個重要問題。如果一個網站的任意url可以被人任意的訪問,那麼勢必會造成很多問題,不然驗證碼有不會被髮明出來了。當你不能訪問的時候,大多數時候因為對方網站的某種反爬蟲機制已經將你的某種標識標記為機器人,然後給你返回一個錯誤的或者是無法獲取的資訊。

(好奇心重的人在這裡可能會問,這個JQuery後面的數字是幹啥的?我怎麼知道這個數字從哪來?每一個書呼叫的callback=JQueryxxx的數字都不一樣嗎?那我要在哪裡搞到這個數字?絕對的好問題,但是,如果你是那種手賤的人,你會發現這個數字任意改成啥都能得到結果,我改成了1,一樣可以有結果。如果你對這個技術有興趣的話,可以看這裡。)

但是我們是真機器人啊!我們要做的是爬蟲啊!所以如何在爬蟲程式中把自己偽裝成類人類上網就很重要。辦法很多,其實總結出來,我個人感覺就記住兩個關鍵詞就行了,偽裝和暫停。

先說偽裝,怎麼把機器人偽裝成人呢?人是通過瀏覽器上網的,大多數現代人類肯定是這樣上網的,你要用curl命令,這也算是一股清流了。那麼回到F12上來,使用F12你是可以看到request的,而在request中間,你可以看到每個http都是有頭部,這個頭部裡面包含了很多資訊,比如使用了什麼版本的瀏覽器,有的網站會要求在頭部有某些特定的欄位或者特定的資訊,伺服器端只有驗證了這些資訊才會返回正確的資訊。如果你有一點了解http協議的話,其實只要我們能按照這裡的格式構造出一個和瀏覽器訪問一摸一樣的http請求頭,那麼就可以模擬瀏覽器去訪問頁面,那伺服器端是不可能分得清是人還是機器人的。

那麼python如何做到這一點?作為一個對爬蟲十分友好的語言,做到這一步也很簡單,只要你把構造好的頭部作為引數傳進相應的函式,就可以完美的做到這一點。至於這些瀏覽器的頭部資訊怎樣構造?搜尋呀!這種模板網上一大堆,整個過程一如程式碼10-15行所示:

偽裝成瀏覽器一般只是爬蟲程式偽裝的一部分,另外一部分是使用不同的代理IP。因為計算機程式訪問一個網站資源的速率遠遠大於人類,這一點很明顯很容易被伺服器端所識別。而同一IP孜孜不倦的訪問某一個網址,除非你是某網站的超級粉絲,不然一般正常人不會有這樣的行為。而解決這個的辦法,是隨機選取一些IP,然後偽裝成這些IP去訪問網站。原理和偽裝成瀏覽器頭部差不多,說實話並不複雜。而我在我的爬蟲程式中並沒有用代理IP,原因很簡單,就像我在前言裡說的,我這並不是一個完全的商業化爬蟲。當然,這就造成了你使用這個爬蟲的時候有可能會導致返回錯誤,但是我可以說一個我用的方法,簡單快速而又方便,用你的手機當熱點,然後執行這些爬蟲,一般都不會因為IP問題而封殺。你要問我為啥知道這樣做,這就多虧了我做過網路程式設計方面的經驗了。你可以把這個當做練習,當然也是因為我懶,實話,不過如果有幸我的這組文章能被廣泛閱讀而又有人要求看看如何使用代理IP的話,我會加上的。

剩下的就是我說的另外一個類別的招數了,因為人類的反應速度有限,你不可能在毫秒級別的不停的點選網頁連結,所以一些超級簡單的sleep同樣也可以讓你唬住伺服器。怎麼樣讓伺服器覺得你不是機器人的方法說句不負責任的話,主要還是靠靈活運用,這裡面頗有一種與人鬥,其樂無窮的感覺。

基本上這個commit裡面的理論扯淡部分就完了,那麼接下來自然就是動手幹程式碼了。

我寫軟體的設計,如果隨意思考也能叫設計的話,邏輯就是一定要符合人的自然思維。而在貌似在第一個commit之內,我們缺少一個圖書類目的唯一標示,而在前面價格的json字串中,又明顯有ID樣的表示,所以,自然而然的我就想在我爬到的資料形成的結構中來這麼一個玩意兒。在price中,可以看到這個id叫dataSku,雖然不知道這個縮寫是什麼意思,但是並不妨礙我去尋找這麼一個東西。在返回的HTML中,有很多地方可以取到這個Id,比如,在第一個commit裡取得的專案連線中就有。但是我一直本著別的程式設計師也得使自己的程式保持一致性的原則,我還是選取了一個叫p-o-btn focus J_focus這樣的class名的一個超連結元素。這個超連結元素中有個key就叫data-sku,看起來抗迭代性最強。也就是程式碼第27,28行所示。

而接下來的程式碼就是我獲取價格的邏輯了,從33行到40行,python的另一個完美之處就是其字典資料結構對json字串完美對接,在去除掉不需要的字元之後,直接就可以得到完美的json字串,通過key直接就能取得value。

再接著後面,我選擇把爬到的資料結構都放到一個字典裡面,但是這樣做其實是有利有弊的。在我這種程式中,其實沒啥比較好優勢,甚至對於互動性上,是不方便的。但是如果你對爬到的資料還有比較多的後續處理,先將其儲存到一個結構中是比較好的做法。

到這裡,這個commit真的是沒啥好講的了。讓我繼續扯淡下一個commit吧。