用 Python 抓網頁?你想問的都幫答好了,你還有不懂的嗎?


用 Python 抓網頁?你想問的都幫答好了,你還有不懂的嗎?


近年來,隨着大數據、人工智能、機器學習等技術的興起,Python 語言也越來越爲人們所喜愛。但早在這些技術普及之前,Python 就一直擔負着一個重要的工作:自動化抓取網頁內容。

舉個栗子,飛機票的價格每時每刻都在變化,甚至有些 app,你搜索的越多,價格就越貴。那不搜又不行啊,怎麼樣才能知道確切的價格呢?

這就是 Python 大顯身手的時候啦~ 我們可以用Python寫一段程序,讓它自動幫你從網絡上獲取需要的數據——這就是所謂的“爬蟲程序”——它能從你指定的一個或多個網站上讀取並記錄數據(比如從某個航班數據網站上讀取指定日期和航線的機票信息),並根據數據進行一些自動操作,比如記錄下最低價,並通知用戶。

總結一下:

網頁抓取是一種通過自動化程序從網頁上獲取頁面內容的計算機軟件技術。

我們這裏說的“爬蟲”,正式名稱叫做“網頁抓取”。按照維基百科的說法,網頁抓取和大多數搜索引擎採用的網頁索引的爬蟲技術不同,網頁抓取更側重於將網絡上的非結構化數據(常見的是HTML格式)轉換成爲能在一箇中央數據庫中儲存和分析的結構化數據。“網頁抓取也涉及到網絡自動化,它利用計算機軟件模擬了人的瀏覽。網頁抓取的用途包括在線比價,聯繫人數據抓取,氣象數據監測,網頁變化檢測,以及各類科研和Web數據集成等。”

用 Python 抓網頁?你想問的都幫答好了,你還有不懂的嗎?


對於一般用戶,我們主要關注的就是網頁抓取。因此,以下提到的“爬蟲”一律指網頁抓取所用的自動化程序。

在今天的文章裏,我們將帶你從最基礎的工具和庫入手,詳細瞭解一下一個爬蟲程序的常用結構,爬取網絡數據時應該遵循哪些規則,存在哪些陷阱;最後,我們還將解答一些常見的問題,比如反追蹤,該做什麼不該做什麼,以及如何採用並行處理技術加速你的爬蟲等等。

文中介紹的每項內容都會附上 Python 的實例代碼,方便你可以直接上手試玩。同時,我們還會介紹幾個非常有用的 Python 庫。

本教程主要分爲5個部分:

1. 常用的代碼庫和工具

2. 從最簡單的例子開始

3. 小心陷阱

4. 一些規則

5. 利用並行加速爬蟲程序

在開始之前,請記住:務必善待服務器,我們並不希望把人家網站弄掛了,是吧。

1. 常用的代碼庫和工具

總的來說,網頁抓取並沒有一個一成不變的解決方案,畢竟通常每個網站的數據都因爲網站自身結構的不同而具有各不相同的特性。事實上,如果你希望從某個網站上抓取數據,你需要對這個網站的結構有足夠的理解,針對這個網站自己寫出對應的腳本,或將某個腳本設置到符合網站的結構,纔可能成功。不過,你也無須重新發明輪子:已經有很多不同的代碼庫,能幫你完成絕大多數底層的工作,它們多多少少都能幫上你一點忙。

1.1“檢查”選項

大部分時候,在實際爬取之前,你都需要熟悉網站的 HTML 代碼。你可以簡單地在你想查看的網頁元素上點擊右鍵,選擇“檢查”(Chrome)或者“查看元素”(火狐)

用 Python 抓網頁?你想問的都幫答好了,你還有不懂的嗎?


之後,系統就會彈出一個調試工具區,高亮你剛選中的網頁元素。以 Medium 網站的作者信息頁爲例:

用 Python 抓網頁?你想問的都幫答好了,你還有不懂的嗎?


在頁面上,這個被選中的元素包含了作者的姓名、標籤及個人介紹。這個元素的 class 是 hero hero--profile u-flexTOP。然後在這個元素裏還有幾個子元素,其中顯示作者姓名的是 <h1> 標籤,它的 class 是 ui-h2 hero-title,顯示作者個人信息的 <p>,它的 class 是 ui-body hero-description。

你可以在 Mozilla 的開發者學院裏找到更多關於 HTML 標記,以及 class 和 id 的區別等的詳細介紹。

1.2 Scrapy 庫

有個可獨立運行,開箱即用的數據抓取框架,名叫 Scrapy。除了抓取並輸出 HTML 外,這個庫還提供了許多額外的功能,比如按特定的格式輸出數據,記錄日誌等。同時,它的可定製性也很高,你可以在多個不同的進程上運行不同的爬蟲,禁用 cookie ¹,設置下載延時²等。

¹ 有些站點會用 cookie 來識別爬蟲。

² 數量過多的爬取請求會給網站帶來額外的負擔,甚至可能會導致網站宕機。

但對我個人而言,這個庫有點太大太全面了:我只不過是想讀取站點每個頁面上的鏈接,按順序訪問每個鏈接並導出頁面上的數據而已。

1.3 BeautifulSoup 和 Requests 庫

BeautifulSoup 庫能讓你優雅地處理 HTML 源碼。同時你還需要一個 Request 庫,用於從指定URL獲取內容。不過,你需要自己處理其他的細節問題,包括錯誤捕獲與處理,導出數據,並行處理等。

我個人特別喜歡 BeautifulSoup 因爲它迫使我自己探索許多 Scrapy 可能已經幫我處理好了的技術細節,讓我從自己動手開始,從錯誤中學習。

2. 從最簡單的例子開始

從網站上抓取數據其實還是蠻直截了當的。大部分時候我們要關注的就是 HTML 源碼,找到你需要的內容所對應的 class 和 id。

下面是一個示例的網頁 HTML 代碼,假設我們要抓取到原價和折後價,那我們需要關注的就是 main_price 和 discounted_price 兩個元素。請注意,discounted_price 元素並不總是出現。

用 Python 抓網頁?你想問的都幫答好了,你還有不懂的嗎?


於是,我們從最基本的代碼開始:先導入需要用的 BeautifulSoup 和 Requests 庫,然後發起查詢請求( requests.get() ),接着處理 html 源碼,最後找到所有 class 爲 main_price 的元素。

用 Python 抓網頁?你想問的都幫答好了,你還有不懂的嗎?


有的時候,網頁的其他地方可能也有 main_price 的元素。爲了避免導出無關的信息,我們可以先找到我們需要的 id='listings_prices',然後只在這個元素的子元素中查找 main_price 元素。

3. Pitfalls 小心陷阱

3.1 檢查 robots.txt

許多網站會將爬取規則和限制寫在 robots.txt 裏,這個文件通常是在根域名下,你可以直接在域名後面加上 /robots.txt 來獲取這個文件。例如: http://www.example.com/robots.txt

robots.txt 裏一般會規定哪些網頁不允許被自動抓取,或者限定某個頁面被機器人訪問的頻率。雖然大部分人確實都不理會這些,不過就算你真的不打算遵守這個規定,起碼也先看一看它的內容,給點表面的尊重吧,哈哈。

Google官方的幫助文檔中,對此的解釋是:“robots.txt 文件中的命令並不能強制抓取工具對您的網站採取具體的操作;對於訪問您網站的抓取工具來說,這些命令僅作爲指令。Googlebot 和其他正規的網頁抓取工具都會遵循 robots.txt 文件中的命令,但其他抓取工具未必也會如此。”

3.2 小心 HTML 裏的坑

HTML 標籤中可能包含 id 或 class,或二者兼有。 HTML id 是一個獨一無二的標記,而 HTML class 可能在多個元素中被重用。class 名或元素內容可能會改變,而這種改變可能會讓你的代碼崩潰,或是返回錯誤的結果。

一般來說,有兩種辦法避免這種情況出現:

● 採用 id 來獲取元素內容,而不是 class,因爲 id 一般來說不那麼容易改變。

● 記得檢查返回值,如果返回了 None,那很可能有什麼地方出了問題。

用 Python 抓網頁?你想問的都幫答好了,你還有不懂的嗎?


不過,因爲有一些 class 可能並不總是出現(例如前面例子中的 discounted_price ),相關的元素並不一定在每個列表中都有。所以你需要統計某個元素在所有列表中出現的比例,比如計算返回 None 的次數。如果每次都返回 None,那也許你需要檢查代碼或者是 HTML 源碼,看看是不是這個元素在網站的 HTML 中就已經改變了。

3.3 對 User agent 進行僞裝

每當你訪問一個網站時,網站都會通過瀏覽器的 user agent 獲取到你的瀏覽器信息。有些網站如果沒收到 user agent 信息,就不會返回任何內容,還有些網站會根據不同的 user agent,給不同的瀏覽器提供不同的內容。

網站並不會阻止正常用戶的訪問,但如果你用同一個 user agent 發起每秒 200 次的請求,那看起來也太可疑了一點。怎麼解決呢?你可以通過 user_agent 庫,產生(幾乎是)隨機的 user agent,也可以自定義一個特殊的 user agent。

用 Python 抓網頁?你想問的都幫答好了,你還有不懂的嗎?


3.4 給 request 請求設置一個超時時間

在默認狀態,request 庫會無止境地等待某個請求返回對應的響應內容。所以,給它設置一個參數,等待超時就斷開連接,還是很有必要的。

用 Python 抓網頁?你想問的都幫答好了,你還有不懂的嗎?


文字版見:https://gist.github.com/jkokatjuhha/64cecefa0bf31c2b21111373c11fcc66

3.5 我是不是剛被屏蔽了?

如果你拿到的返回值經常是 404(找不到頁面)、403(被禁止)、408(訪問超時),就應該考慮你是不是被這個站點屏蔽了。

如果你對 HTTP 返回值不熟悉,看看我們之前解釋 HTTP 返回值的漫畫吧~

同樣,你也應該在返回的響應中對這類錯誤進行處理。

用 Python 抓網頁?你想問的都幫答好了,你還有不懂的嗎?


文字版見:https://gist.github.com/jkokatjuhha/a33467fae4c9f7fac64f067501b484ac

3.6 切換 IP 地址

就算你採用了隨機生成的 user agent,程序發起的所有連接都還用的是同一個 IP 地址:你的地址。雖然這通常並不會引起太多重視,畢竟很多圖書館、大學以及企業分別都只有少數幾個 IP 地址,由這些機構內的所有計算機共同使用。然而,如果在短時間內從某一個 IP 地址發出了巨量的請求,還是會被服務器發現的。

這時候,你多年珍藏的科學上網工具就能大顯身手啦。

用 Python 抓網頁?你想問的都幫答好了,你還有不懂的嗎?


當你採用了代理、×××或者其他技術之後,對應的網站會將你發起的請求識別爲來自相應的服務器,而不是你的。

3.7 蜜罐×××

蜜罐是引誘網頁爬蟲對其進行抓取或索引,從而進行偵測的一種技術手段。

比如,網頁上可能會存在一些“隱藏”鏈接,正常用戶在訪問的時候看不到這個鏈接,但爬蟲在處理 HTML 源代碼的時候會把它當作正常鏈接進行處理。此類鏈接有可能用 CSS 樣式設置了 display:none,或者設置成和背景相同的顏色,甚至採用比如藏在頁面中的不可見位置等手段。一旦你的爬蟲訪問了這類鏈接,你的 IP 地址可能就被記錄日誌,甚至服務器可能直接將你屏蔽。

另外一種蜜罐,是用超鏈接建立一串近乎無限深度的目錄樹,如果有人訪問了足夠深位置的內容,那基本上可以確定這人不是個普通用戶。因此,在編寫爬蟲時,需要限制爬蟲取回的頁面數量,或控制遍歷深度。

4. 一些規則

  • 在抓取之前,先看看目標網站是不是已經提供了公開的 API。畢竟通過 API 能更好更快(也合法)地獲取所需的信息。比如社交網站 Twitter 就提供了許多不同的 API。如果你需要抓取非常大量的數據,你應該考慮用一個數據庫把這些數據整理起來,方便之後進行分析和使用。這裏有一篇用 Python 操作本地數據庫的教程。務必保持禮貌。有時候,甚至建議你直接和對方網站的運維人員取得聯繫,說不定他們能更方便快速地幫你解決你的機器人遇到的問題。

同時,再強調一遍,切記不要貪得無厭地發起太多請求,這會給目標網站帶來不必要的負載。

5. 利用並行加速爬蟲程序

如果你希望讓你的程序並行運行,一定要小心檢查自己的代碼,否則可能你會突然發現自己正在榨乾目標服務器的資源。同時,請一定一定認真看完上一節的幾個規則。最後,你需要確保自己已經理解了並行處理和併發處理,多線程和多進程之間的區別。

如果你在抓取過程中還要對巨量的頁面信息進行預處理,你會發現平均每秒鐘能發起的請求數其實是相當少的。

在我個人的另一個抓取出租房價格的項目裏,因爲抓取時的預處理信息量實在太大,每秒能發起的請求數大約只有1個。處理 4000 個左右的鏈接,需要程序運行上大約一個小時。

爲了並行發送請求,你可能需要採用一個叫做 multiprocessing 的 Python 庫。

假設我們有100個頁面要發起請求,我們希望給將任務量平均分給每個處理器。假設你有 N 個 CPU,你可以把所有的頁面分成 N 個部分,每個 CPU 處理一個部分。每個進程都將有自己的名字,目標函數以及需要處理的參數。每個進程的名字可以在之後被調用,以便將獲取到的信息寫入具體的文件中。

後來,我將 4000 個頁面分成 4 份,我的 4 個 CPU 各分到 1000 個,於是總的請求數增加到 4 個/秒,總的抓取時間就減少到了 17 分鐘左右。

用 Python 抓網頁?你想問的都幫答好了,你還有不懂的嗎?


最後,祝大家爬得開心順利!記得多關注我哦!!

最後的文末知識點摘要:Python解惑之:整數比較

在 Python 中一切都是對象,毫無例外整數也是對象,對象之間比較是否相等可以用 ==,也可以用 is。 ==和 is操作的區別是:

  • is比較的是兩個對象的id值是否相等,也就是比較倆對象是否爲同一個實例對象,是否指向同一個內存地址。

  • ==比較的是兩個對象的內容是否相等,默認會調用對象的 __eq__()方法。

清楚 is和 ==的區別之後,對此也許你有可能會遇到下面的這些困惑,於是就有了這樣一篇文章,試圖把Python中一些隱晦的東西趴出來,希望對你有一定的幫助。我們先來看兩段代碼:

片段一:

>>> a = 256

>>> b = 256

>>> a == b

True

>>>

片段二:

>>> a = 256

>>> b = 256

>>> a is b

True

>>>

在交互式命令行執行上面兩段代碼,代碼片段一中的 a==b返回 True很好理解,因爲兩個對象的值都是256,對於片段二, a is b也返回True,這說明a和b是指向同一個對象的,可以檢查一下他們的id值是否相等:

>>> id(a)

8213296

>>> id(b)

8213296

>>>

結果證明他倆的確是同一個對象,指向的是同一個內存地址。那是不是所有的整數對象只要兩個對象的值(內容)相等,它們就是同一個實例對象呢?換句話說,對於整數對象只要 ==返回 True, is操作也會返回 True嗎?帶着這個問題來看下面這兩段代碼:

片段一:

>>> a = 257

>>> b = 257

>>> a == b

True

>>>

片段二:

>>> a = 257

>>> b = 257

>>> a is b

False

>>>

對於257, a is b返回的竟然是False,結果可能在你的意料之中,也有可能出乎你的意料,但不管怎麼,我們還是要刨根問底,找出問題的真相。

解惑一

出於對性能的考慮,Python內部做了很多的優化工作,對於整數對象,Python把一些頻繁使用的整數對象緩存起來,保存到一個叫 small_ints的鏈表中,在Python的整個生命週期內,任何需要引用這些整數對象的地方,都不再重新創建新的對象,而是直接引用緩存中的對象。Python把這些可能頻繁使用的整數對象規定在範圍[-5, 256]之間的小對象放在 small_ints中,但凡是需要用些小整數時,就從這裏面取,不再去臨時創建新的對象。因爲257不再小整數範圍內,因此儘管a和b的值是一樣,但是他們在Python內部卻是以兩個獨立的對象存在的,各自爲政,互不干涉。


弄明白第一個問題後,我們繼續在Python交互式命令行中寫一個函數,再來看下面這段代碼:

片段一:

>>> c = 257

>>> def foo():

... a = 257

... b = 257

... print a is b

... print a is c

...

>>> foo()

True

False

呃,什麼情況,是的,你沒看錯,片段一中的這段代碼 a、b 值都是257的情況下,出現了 a is b返回 True,而 a is c 返回的 False,a、b、c的值都爲257,爲什麼會出現不同的結果呢?這對於剛剛好不容易建立起來的認知就被徹底否決了嗎,那這段代碼中究竟發生了什麼?難道解惑一中的結論是錯誤的嗎?

解惑二

A Python program is constructed from code blocks. A block is a piece of Python program text that is executed as a unit. The following are blocks: a module, a function body, and a class definition. Each command typed interactively is a block. A script file (a file given as standard input to the interpreter or specified as a command line argument to the interpreter) is a code block. A script command (a command specified on the interpreter command line with the ‘-c‘ option) is a code block. structure-of-a-program

爲了弄清楚這個問題,我們有必要先理解程序代碼塊的概念。Python程序由代碼塊構成,代碼塊作爲程序的一個最小基本單位來執行。一個模塊文件、一個函數體、一個類、交互式命令中的單行代碼都叫做一個代碼塊。在上面這段代碼中,由兩個代碼塊構成, c = 257作爲一個代碼塊,函數 foo作爲另外一個代碼塊。Python內部爲了將性能進一步的提高,凡是在一個代碼塊中創建的整數對象,如果存在一個值與其相同的對象於該代碼塊中了,那麼就直接引用,否則創建一個新的對象出來。Python出於對性能的考慮,但凡是不可變對象,在同一個代碼塊中的對象,只有是值相同的對象,就不會重複創建,而是直接引用已經存在的對象。因此,不僅是整數對象,還有字符串對象也遵循同樣的原則。所以 a is b就理所當然的返回 True了,而 c和 a不在同一個代碼塊中,因此在Python內部創建了兩個值都是257的對象。爲了驗證剛剛的結論,我們可以借用 dis模塊從字節碼的角度來看看這段代碼。

用 Python 抓網頁?你想問的都幫答好了,你還有不懂的嗎?


可以看出兩個257都是從常量池的同一個位置 co_consts[1]獲取的。

總結

一番長篇大論之後,得出兩點結論:1、小整數對象[-5,256]是全局解釋器範圍內被重複使用,永遠不會被GC回收。2、同一個代碼塊中的不可變對象,只要值是相等的就不會重複創建新的對象。似乎這些知識點對日常的工作一點忙也幫不上,因爲你根本不會用 is來比較兩個整數對象的值是否相等。那爲什麼還要拿出來討論呢?嗯,程序員學知識,不應該淺嘗輒止,要充分發揮死磕到底的精神。

本篇文章分享就到此結束,部分素材來源網絡與自己整理,如有侵權,請聯繫刪除。希望本次的知識點分享對你有所幫助。


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