如何用Python爬數據?(一)網頁抓取

你期待已久的Python網路數據爬蟲教程來了。本文為你演示如何從網頁里找到感興趣的鏈接和說明文字,抓取並存儲到Excel。

需求

我在公眾號後台,經常可以收到讀者的留言。

很多留言,是讀者的疑問。只要有時間,我都會抽空嘗試解答。

但是有的留言,乍看起來就不明所以了。

例如下面這個:

一分鐘後,他可能覺得不妥(大概因為想起來,我用簡體字寫文章),於是又用簡體發了一遍。

我恍然大悟。

這位讀者以為我的公眾號設置了關鍵詞推送對應文章功能。所以看了我的其他數據科學教程後,想看「爬蟲」專題。

不好意思,當時我還沒有寫爬蟲文章。

而且,我的公眾號暫時也沒有設置這種關鍵詞推送。

主要是因為我懶。

這樣的消息接收得多了,我也能體察到讀者的需求。不止一個讀者表達出對爬蟲教程的興趣。

之前提過,目前主流而合法的網路數據收集方法,主要分為3類:

  • 開放數據集下載;
  • API讀取;
  • 爬蟲。

前兩種方法,我都已經做過一些介紹,這次說說爬蟲。

概念

許多讀者對爬蟲的定義,有些混淆。咱們有必要辨析一下。

維基百科是這麼說的:

網路爬蟲(英語:web crawler),也叫網路蜘蛛(spider),是一種用來自動瀏覽萬維網的網路機器人。其目的一般為編纂網路索引。

這問題就來了,你又不打算做搜索引擎,為什麼對網路爬蟲那麼熱心呢?

其實,許多人口中所說的爬蟲(web crawler),跟另外一種功能「網頁抓取」(web scraping)搞混了。

維基百科上,對於後者這樣解釋:

Web scraping, web harvesting, or web data extraction is data scraping used for extracting data from websites. Web scraping software may access the World Wide Web directly using the Hypertext Transfer Protocol, or through a web browser.

看到沒有,即便你用瀏覽器手動拷貝數據下來,也叫做網頁抓取(web scraping)。是不是立刻覺得自己強大了很多?

但是,這定義還沒完:

While web scraping can be done manually by a software user, the term typically refers to automate processes implemented using a bot or web crawler.

也就是說,用爬蟲(或者機器人)自動替你完成網頁抓取工作,才是你真正想要的。

數據抓下來幹什麼呢?

一般是先存儲起來,放到資料庫或者電子表格中,以備檢索或者進一步分析使用。

所以,你真正想要的功能是這樣的:

找到鏈接,獲得Web頁面,抓取指定信息,存儲。

這個過程有可能會往複循環,甚至是滾雪球。

你希望用自動化的方式來完成它。

了解了這一點,你就不要老盯著爬蟲不放了。爬蟲研製出來,其實是為了給搜索引擎編製索引資料庫使用的。你為了抓取點兒數據拿來使用,已經是大炮轟蚊子了。

要真正掌握爬蟲,你需要具備不少基礎知識。例如HTML, CSS, Javascript, 數據結構……

這也是為什麼我一直猶豫著沒有寫爬蟲教程的原因。

不過這兩天,看到王爍主編的一段話,很有啟發:

我喜歡講一個另類二八定律,就是付出兩成努力,了解一件事的八成。

既然我們的目標很明確,就是要從網頁抓取數據。那麼你需要掌握的最重要能力,是拿到一個網頁鏈接後,如何從中快捷有效地抓取自己想要的信息。

掌握了它,你還不能說自己已經學會了爬蟲。

但有了這個基礎,你就能比之前更輕鬆獲取數據了。特別是對「文科生」的很多應用場景來說,非常有用。這就是賦能

而且,再進一步深入理解爬蟲的工作原理,也變得輕鬆許多。

這也算「另類二八定律」的一個應用吧。

Python語言的重要特色之一,就是可以利用強大的軟體工具包(許多都是第三方提供)。你只需要編寫簡單的程序,就能自動解析網頁,抓取數據。

本文給你演示這一過程。

目標

要抓取網頁數據,我們先制訂一個小目標。

目標不能太複雜。但是完成它,應該對你理解抓取(Web Scraping)有幫助。

就選擇我最近發布的一篇簡書文章作為抓取對象好了。題目叫做《如何用《玉樹芝蘭》入門數據科學?》。

這篇文章里,我把之前的發布的數據科學系列文章做了重新組織和串講。

文中包含很多之前教程的標題和對應鏈接。例如下圖紅色邊框圈起來的部分。

假設你對文中提到教程都很感興趣,希望獲得這些文章的鏈接,並且存儲到Excel里,就像下面這個樣子:

你需要把非結構化的分散信息(自然語言文本中的鏈接),專門提取整理,並且存儲下來。

該怎麼辦呢?

即便不會編程,你也可以全文通讀,逐個去找這些文章鏈接,手動把文章標題、鏈接都分別拷貝下來,存到Excel表裡面。

但是,這種手工採集方法沒有效率

我們用Python。

環境

要裝Python,比較省事的辦法是安裝Anaconda套裝。

請到這個網址下載Anaconda的最新版本。

請選擇左側的 Python 3.6 版本下載安裝。

如果你需要具體的步驟指導,或者想知道Windows平台如何安裝並運行Anaconda命令,請參考我為你準備的視頻教程。

安裝好Anaconda之後,請到這個網址下載本教程配套的壓縮包。

下載後解壓,你會在生成的目錄(下稱「演示目錄」)裡面看到以下三個文件。

打開終端,用cd命令進入該演示目錄。如果你不了解具體使用方法,也可以參考視頻教程。

我們需要安裝一些環境依賴包。

首先執行:

pip install pipenv

這裡安裝的,是一個優秀的 Python 軟體包管理工具 pipenv 。

安裝後,請執行:

pipenv install

看到演示目錄下兩個Pipfile開頭的文件了嗎?它們就是 pipenv 的設置文檔。

pipenv 工具會依照它們,自動為我們安裝所需要的全部依賴軟體包。

上圖裡面有個綠色的進度條,提示所需安裝軟體數量和實際進度。

裝好後,根據提示我們執行:

pipenv shell

此處請確認你的電腦上已經安裝了 Google Chrome 瀏覽器。

我們執行:

jupyter notebook

默認瀏覽器(Google Chrome)會開啟,並啟動 Jupyter 筆記本界面:

你可以直接點擊文件列表中的第一項ipynb文件,可以看到本教程的全部示例代碼。

你可以一邊看教程的講解,一邊依次執行這些代碼。

但是,我建議的方法,是回到主界面下,新建一個新的空白 Python 3 筆記本。

請跟著教程,一個個字元輸入相應的內容。這可以幫助你更為深刻地理解代碼的含義,更高效地把技能內化。

準備工作結束,下面我們開始正式輸入代碼。

代碼

讀入網頁加以解析抓取,需要用到的軟體包是 requests_html 。我們此處並不需要這個軟體包的全部功能,只讀入其中的 HTMLSession 就可以。

from requests_html import HTMLSession

然後,我們建立一個會話(session),即讓Python作為一個客戶端,和遠端伺服器交談。

session = HTMLSession()

前面說了,我們打算採集信息的網頁,是《如何用《玉樹芝蘭》入門數據科學?》一文。

我們找到它的網址,存儲到url變數名中。

url = https://www.jianshu.com/p/85f4624485b9

下面的語句,利用 session 的 get 功能,把這個鏈接對應的網頁整個兒取回來。

r = session.get(url)

網頁裡面都有什麼內容呢?

我們告訴Python,請把伺服器傳回來的內容當作HTML文件類型處理。我不想要看HTML裡面那些亂七八糟的格式描述符,只看文字部分。

於是我們執行:

print(r.html.text)

這就是獲得的結果了:

我們心裡有數了。取回來的網頁信息是正確的,內容是完整的。

好了,我們來看看怎麼趨近自己的目標吧。

我們先用簡單粗暴的方法,嘗試獲得網頁中包含的全部鏈接。

把返回的內容作為HTML文件類型,我們查看 links 屬性:

r.html.links

這是返回的結果:

這麼多鏈接啊!

很興奮吧?

不過,你發現沒有?這裡許多鏈接,看似都不完全。例如第一條結果,只有:

/

這是什麼東西?是不是鏈接抓取錯誤啊?

不是,這種看著不像鏈接的東西,叫做相對鏈接。它是某個鏈接,相對於我們採集的網頁所在域名(jianshu.com)的路徑。

這就好像我們在國內郵寄快遞包裹,填單子的時候一般會寫「XX省XX市……」,前面不需要加上國家名稱。只有國際快遞,才需要寫上國名。

但是如果我們希望獲得全部可以直接訪問的鏈接,怎麼辦呢?

很容易,也只需要一條 Python 語句。

r.html.absolute_links

這裡,我們要的是「絕對」鏈接,於是我們就會獲得下面的結果:

這回看著是不是就舒服多了?

我們的任務已經完成了吧?鏈接不是都在這裡嗎?

鏈接確實都在這裡了,可是跟我們的目標是不是有區別呢?

檢查一下,確實有。

我們不光要找到鏈接,還得找到鏈接對應的描述文字呢,結果里包含嗎?

沒有。

結果列表中的鏈接,都是我們需要的嗎?

不是。看長度,我們就能感覺出許多鏈接並不是文中描述其他數據科學文章的網址。

這種簡單粗暴直接羅列HTML文件中所有鏈接的方法,對本任務行不通。

那麼我們該怎麼辦?

我們得學會跟 Python 說清楚我們要找的東西。這是網頁抓取的關鍵

想想看,如果你想讓助手(人類)幫你做這事兒,怎麼辦?

你會告訴他:

「尋找正文中全部可以點擊的藍色文字鏈接,拷貝文字到Excel表格,然後右鍵複製對應的鏈接,也拷貝到Excel表格。每個鏈接在Excel佔一行,文字和鏈接各佔一個單元格。」

雖然這個操作執行起來麻煩,但是助手聽懂後,就能幫你執行。

同樣的描述,你試試說給電腦聽……不好意思,它不理解。

因為你和助手看到的網頁,是這個樣子的。

電腦看到的網頁,是這個樣子的。

為了讓你看得清楚源代碼,瀏覽器還特意對不同類型的數據用了顏色區分,對行做了編號。

數據顯示給電腦時,上述輔助可視功能是沒有的。它只能看見一串串字元。

那可怎麼辦?

仔細觀察,你會發現這些HTML源代碼裡面,文字、圖片鏈接內容前後,都會有一些被尖括弧括起來的部分,這就叫做「標記」。

所謂HTML,就是一種標記語言(超文本標記語言,HyperText Markup Language)。

標記的作用是什麼?它可以把整個的文件分解出層次來。

(圖片來源:goo.gl/kWCqS6

如同你要發送包裹給某個人,可以按照「省-市-區-街道-小區-門牌」這樣的結構來寫地址,快遞員也可以根據這個地址找到收件人。

同樣,我們對網頁中某些特定內容感興趣,可以依據這些標記的結構,順藤摸瓜找出來。

這是不是意味著,你必須先學會HTML和CSS,才能進行網頁內容抓取呢?

不是的,我們可以藉助工具,幫你顯著簡化任務複雜度。

這個工具,Google Chrome瀏覽器自帶。

我們在樣例文章頁面上,點擊滑鼠右鍵,在出現的菜單裡面選擇「檢查」。

這時,屏幕下方就會出現一個分欄。

我們點擊這個分欄左上角(上圖紅色標出)的按鈕。然後把滑鼠懸停在第一個文內鏈接(《玉樹芝蘭》)上面,點擊一下。

?

此時,你會發現下方分欄裡面,內容也發生了變化。這個鏈接對應的源代碼被放在分欄區域正中,高亮顯示。

確認該區域就是我們要找的鏈接和文字描述後,我們滑鼠右鍵選擇高亮區域,並且在彈出的菜單中,選擇 Copy -> Copy selector。

找一個文本編輯器,執行粘貼,就可以看見我們究竟複製下來了什麼內容。

body > div.note > div.post > div.article > div.show-content > div > p:nth-child(4) > a

這一長串的標記,為電腦指出了:請你先找到 body 標記,進入它管轄的這個區域後去找 div.note 標記,然後找……最後找到 a 標記,這裡就是要找的內容了。

回到咱們的 Jupyter Notebook 中,用剛才獲得的標記路徑,定義變數sel。

sel = body > div.note > div.post > div.article > div.show-content > div > p:nth-child(4) > a

我們讓 Python 從返回內容中,查找 sel 對應的位置,把結果存到 results 變數中。

results = r.html.find(sel)

我們看看 results 裡面都有什麼。

results

這是結果:

[<Element a href=https://www.jianshu.com/nb/130182 target=_blank>]

results 是個列表,只包含一項。這一項包含一個網址,就是我們要找的第一個鏈接(《玉樹芝蘭》)對應的網址。

可是文字描述「《玉樹芝蘭》」哪裡去了?

別著急,我們讓 Python 顯示 results 結果數據對應的文本。

results[0].text

這是輸出結果:

玉樹芝蘭

我們把鏈接也提取出來:

results[0].absolute_links

顯示的結果卻是一個集合。

{https://www.jianshu.com/nb/130182}

我們不想要集合,只想要其中的鏈接字元串。所以我們先把它轉換成列表,然後從中提取第一項,即網址鏈接。

list(results[0].absolute_links)[0]

這次,終於獲得我們想要的結果了:

https://www.jianshu.com/nb/130182

有了處理這第一個鏈接的經驗,你信心大增,是吧?

其他鏈接,也無非是找到標記路徑,然後照貓畫虎嘛。

可是,如果每找一個鏈接,都需要手動輸入上面這若干條語句,那也太麻煩了。

這裡就是編程的技巧了。重複逐條運行的語句,如果工作順利,我們就要嘗試把它們歸併起來,做個簡單的函數。

對這個函數,只需給定一個選擇路徑(sel),它就把找到的所有描述文本和鏈接路徑都返回給我們。

def get_text_link_from_sel(sel): mylist = [] try: results = r.html.find(sel) for result in results: mytext = result.text mylink = list(result.absolute_links)[0] mylist.append((mytext, mylink)) return mylist except: return None

我們測試一下這個函數。

還是用剛才的標記路徑(sel)不變,試試看。

print(get_text_link_from_sel(sel))

輸出結果如下:

[(玉樹芝蘭, https://www.jianshu.com/nb/130182)]

沒問題,對吧?

好,我們試試看第二個鏈接。

我們還是用剛才的方法,使用下面分欄左上角的按鈕點擊第二個鏈接。

下方出現的高亮內容就發生了變化:

我們還是用滑鼠右鍵點擊高亮部分,拷貝出 selector。

然後我們直接把獲得的標記路徑寫到 Jupyter Notebook 裡面。

sel = body > div.note > div.post > div.article > div.show-content > div > p:nth-child(6) > a

用我們剛才編製的函數,看看輸出結果是什麼?

print(get_text_link_from_sel(sel))

輸出如下:

[(如何用Python做詞雲?, https://www.jianshu.com/p/e4b24a734ccc)]

檢驗完畢,函數沒有問題。

下一步做什麼?

你還打算去找第三個鏈接,仿照剛才的方法做?

那你還不如全文手動摘取信息算了,更省事兒一些。

我們要想辦法把這個過程自動化

對比一下剛剛兩次我們找到的標記路徑:

body > div.note > div.post > div.article > div.show-content > div > p:nth-child(4) > a

以及:

body > div.note > div.post > div.article > div.show-content > div > p:nth-child(6) > a

發現什麼規律沒有?

對,路徑上其他的標記全都是一樣的,唯獨倒數第二個標記("p")後冒號後內容有區別。

這就是我們自動化的關鍵了。

上述兩個標記路徑裡面,因為指定了在第幾個「子」(nth-child)文本段(paragraph,也就是"p"代表的含義)去找"a"這個標記,因此只返回來單一結果。

如果我們不限定"p"的具體位置信息呢?

我們試試看,這次保留標記路徑裡面其他全部信息,只修改"p"這一點。

sel = body > div.note > div.post > div.article > div.show-content > div > p > a

再次運行我們的函數:

print(get_text_link_from_sel(sel))

這是輸出結果:

好了,我們要找的內容,全都在這兒了。

但是,我們的工作還沒完。

我們還得把採集到的信息輸出到Excel中保存起來。

還記得我們常用的數據框工具 Pandas 嗎?又該讓它大顯神通了。

import pandas as pd

只需要這一行命令,我們就能把剛才的列表變成數據框:

df = pd.DataFrame(get_text_link_from_sel(sel))

讓我們看看數據框內容:

df

內容沒問題,不過我們對錶頭不大滿意,得更換為更有意義的列名稱:

df.columns = [text, link]

再看看數據框內容:

df

好了,下面就可以把抓取的內容輸出到Excel中了。

Pandas內置的命令,就可以把數據框變成csv格式,這種格式可以用Excel直接打開查看。

df.to_csv(output.csv, encoding=gbk, index=False)

注意這裡需要指定encoding(編碼)為gbk,否則默認的utf-8編碼在Excel中查看的時候,有可能是亂碼。

我們看看最終生成的csv文件吧。

很有成就感,是不是?

小結

本文為你展示了用Python自動網頁抓取的基礎技能。希望閱讀並動手實踐後,你能掌握以下知識點:

  • 網頁抓取與網路爬蟲之間的聯繫與區別;
  • 如何用 pipenv 快速構建指定的 Python 開發環境,自動安裝好依賴軟體包;
  • 如何用 Google Chrome 的內置檢查功能,快速定位感興趣內容的標記路徑;
  • 如何用 requests-html 包來解析網頁,查詢獲得需要的內容元素;
  • 如何用 Pandas 數據框工具整理數據,並且輸出到 Excel。

或許,你覺得這篇文章過於淺白,不能滿足你的要求。

文中只展示了如何從一個網頁抓取信息,可你要處理的網頁成千上萬啊。

別著急。

本質上說,抓取一個網頁,和抓取10000個網頁,在流程上是一樣的。

而且,從咱們的例子里,你是不是已經嘗試了抓取鏈接?

有了鏈接作為基礎,你就可以滾雪球,讓Python爬蟲「爬」到解析出來的鏈接上,做進一步的處理。

將來,你可能還要應對實踐場景中的一些棘手問題:

  • 如何把抓取的功能擴展到某一范內內的所有網頁?
  • 如何爬取Javascript動態網頁?
  • 假設你爬取的網站對每個IP的訪問頻率做出限定,怎麼辦?
  • ……

這些問題的解決辦法,我希望在今後的教程裡面,一一和你分享。

需要注意的是,網路爬蟲抓取數據,雖然功能強大,但學習與實踐起來有一定門檻。

當你面臨數據獲取任務時,應該先檢查一下這個清單:

  • 有沒有別人已經整理好的數據集合可以直接下載?
  • 網站有沒有對你需要的數據提供API訪問與獲取方式?
  • 有沒有人針對你的需求,編好了定製爬蟲,供你直接調用?

如果答案是都沒有,才需要你自己編寫腳本,調動爬蟲來抓取。

為了鞏固學習的知識,請你換一個其他網頁,以咱們的代碼作為基礎修改後,抓取其中你感興趣的內容。

如果能把你抓取的過程記錄下來,在評論區將記錄鏈接分享給大家,就更好了。

因為刻意練習是掌握實踐技能的最好方式,而教是最好的學

祝順利!

思考

本文主要內容講解完畢。

這裡給你提一個疑問,供你思考:

我們解析並且存儲的鏈接,其實是有重複的:

這並不是我們的代碼有誤,而是在《如何用《玉樹芝蘭》入門數據科學?》一文里,本來就多次引用過一些文章,所以重複的鏈接就都被抓取出來了。

但是你存儲的時候,也許不希望保留重複鏈接。

這種情況下,你該如何修改代碼,才能保證抓取和保存的鏈接沒有重複呢?

討論

你對Python爬蟲感興趣嗎?在哪些數據採集任務上使用過它?有沒有其他更高效的方式,來達成數據採集目的?歡迎留言,把你的經驗和思考分享給大家,我們一起交流討論。

如果你對我的文章感興趣,歡迎點贊,並且微信關注和置頂我的公眾號「玉樹芝蘭」(nkwangshuyi)。

如果本文可能對你身邊的親友有幫助,也歡迎你把本文通過微博或朋友圈分享給他們。讓他們一起參與到我們的討論中來。


推薦閱讀:

pyecharts 更新至 v0.2.6 版本啦
如果只推介一本python3的書籍,你會推介哪一本?
【scikit-learn文檔解析】集成方法 Ensemble Methods(上):Bagging與隨機森林

TAG:Python | 網頁爬蟲 | 數據科學 |