你期待已久的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://juejin.im/post/5b339e99f265da597d0aac5f'
複製程式碼
下面的語句,利用 session 的 get 功能,把這個連結對應的網頁整個兒取回來。
r = session.get(url)
複製程式碼
網頁裡面都有什麼內容呢?
我們告訴Python,請把伺服器傳回來的內容當作HTML檔案型別處理。我不想要看HTML裡面那些亂七八糟的格式描述符,只看文字部分。
於是我們執行:
print(r.html.text)
複製程式碼
這就是獲得的結果了:
我們心裡有數了。取回來的網頁資訊是正確的,內容是完整的。
好了,我們來看看怎麼趨近自己的目標吧。
我們先用簡單粗暴的方法,嘗試獲得網頁中包含的全部連結。
把返回的內容作為HTML檔案型別,我們檢視 links 屬性:
r.html.links
複製程式碼
這是返回的結果:
這麼多連結啊!
很興奮吧?
不過,你發現沒有?這裡許多連結,看似都不完全。例如第一條結果,只有:
'/'
複製程式碼
這是什麼東西?是不是連結抓取錯誤啊?
不是,這種看著不像連結的東西,叫做相對連結。它是某個連結,相對於我們採集的網頁所在域名(https://www.jianshu.com)的路徑。
這就好像我們在國內郵寄快遞包裹,填單子的時候一般會寫“XX省XX市……”,前面不需要加上國家名稱。只有國際快遞,才需要寫上國名。
但是如果我們希望獲得全部可以直接訪問的連結,怎麼辦呢?
很容易,也只需要一條 Python 語句。
r.html.absolute_links
複製程式碼
這裡,我們要的是“絕對”連結,於是我們就會獲得下面的結果:
這回看著是不是就舒服多了?
我們的任務已經完成了吧?連結不是都在這裡嗎?
連結確實都在這裡了,可是跟我們的目標是不是有區別呢?
檢查一下,確實有。
我們不光要找到連結,還得找到連結對應的描述文字呢,結果裡包含嗎?
沒有。
結果列表中的連結,都是我們需要的嗎?
不是。看長度,我們就能感覺出許多連結並不是文中描述其他資料科學文章的網址。
這種簡單粗暴直接羅列HTML檔案中所有連結的方法,對本任務行不通。
那麼我們該怎麼辦?
我們得學會跟 Python 說清楚我們要找的東西。這是網頁抓取的關鍵。
想想看,如果你想讓助手(人類)幫你做這事兒,怎麼辦?
你會告訴他:
“尋找正文中全部可以點選的藍色文字連結,拷貝文字到Excel表格,然後右鍵複製對應的連結,也拷貝到Excel表格。每個連結在Excel佔一行,文字和連結各佔一個單元格。”
雖然這個操作執行起來麻煩,但是助手聽懂後,就能幫你執行。
同樣的描述,你試試說給電腦聽……不好意思,它不理解。
因為你和助手看到的網頁,是這個樣子的。
電腦看到的網頁,是這個樣子的。
為了讓你看得清楚原始碼,瀏覽器還特意對不同型別的資料用了顏色區分,對行做了編號。
資料顯示給電腦時,上述輔助可視功能是沒有的。它只能看見一串串字元。
那可怎麼辦?
仔細觀察,你會發現這些HTML原始碼裡面,文字、圖片連結內容前後,都會有一些被尖括號括起來的部分,這就叫做“標記”。
所謂HTML,就是一種標記語言(超文字標記語言,HyperText Markup Language)。
標記的作用是什麼?它可以把整個的檔案分解出層次來。
(圖片來源:https://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://juejin.im/post/5b34a409f265da599f68dc3b')]
複製程式碼
檢驗完畢,函式沒有問題。
下一步做什麼?
你還打算去找第三個連結,仿照剛才的方法做?
那你還不如全文手動摘取資訊算了,更省事兒一些。
我們要想辦法把這個過程自動化。
對比一下剛剛兩次我們找到的標記路徑:
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)。
如果你對資料科學感興趣,不妨閱讀我的系列教程索引貼《如何高效入門資料科學?》,裡面還有更多的有趣問題及解法。