如何寫一個簡單的分布式知乎爬蟲?

前言

很早就有采集知乎用戶數(shù)據(jù)的想法揽乱,要實現(xiàn)這個想法名眉,需要寫一個網(wǎng)絡爬蟲(Web Spider)。因為在學習 python锤窑,正好 python 寫爬蟲也是極好的選擇璧针,于是就寫了一個基于 python 的網(wǎng)絡爬蟲。

幾個月前寫了爬蟲的初版渊啰,后來因為一些原因探橱,暫時擱置了下來,最近重新拾起這個想法绘证。首先優(yōu)化了代碼的結(jié)構(gòu)隧膏,然后在學弟的提醒下,從多線程改成了多進程嚷那,一臺機器上運行一個爬蟲程序胞枕,會啟動幾百個子進程加速抓取。

但是一臺機器的性能是有極限的魏宽,所以后來我使用 MongoDB 和 Redis 搭建了一個主從結(jié)構(gòu)的分布式爬取系統(tǒng)腐泻,來進一步加快抓取的速度。

然后我就去好幾個服務器廠商申請免費的試用队询,比如百度云派桩、騰訊云、Ucloud…… 加上自己的筆記本蚌斩,斷斷續(xù)續(xù)抓取了一個多周铆惑,才采集到300萬知乎用戶數(shù)據(jù)。中間還跑壞了運行網(wǎng)站的云主機送膳,還好 自動備份 起作用员魏,數(shù)據(jù)沒有丟失,但那又是另外一個故事了……

完整版的爬蟲鏈接在這兒:windcode/zhihu-crawler-people叠聋,走過路過別忘了點個 star ~

廢話不多說撕阎,下面我介紹一下如何寫一個簡單的分布式知乎爬蟲。

抓取知乎用戶的個人信息

我們要抓取知乎用戶數(shù)據(jù)碌补,首先要知道在哪個頁面可以抓取到用戶的數(shù)據(jù)闻书。知乎用戶的個人信息在哪里呢名斟,當然是在用戶的主頁啦,我們以輪子哥為例 ~

紅框里的便我們要抓取的用戶關(guān)鍵信息(的一部分)。

最上面是我們的目標URL:https://www.zhihu.com/people/excited-vczh/answers钉稍。

觀察一下這個URL的組成:

http://www.zhihu.com + /people + /excited-vczh + /answer

可以發(fā)現(xiàn)只有 excited-vczh 這部分是會變化的粱檀,它代表著知乎用戶的唯一ID,在知乎的數(shù)據(jù)格式中律胀,它的鍵名叫做 urlToken

所以我們可以用拼接字符串的形式,得到我們待抓取頁面的URL:

url = '%s/people/%s/answers'%(host,urlToken)

頁面URL有了晃择,而且從上圖我們可以發(fā)現(xiàn) 不登錄 也可以訪問用戶主頁,這說明我們可以不用考慮模擬登陸的問題也物,可以自由的獲取用戶主頁面源碼宫屠。

那么我們?nèi)绾螐挠脩糁黜摰脑创a中獲取用戶的數(shù)據(jù)呢?一開始我以為需要挨個匹配頁面中對應的部分滑蚯,但我查看源碼的時候發(fā)現(xiàn)知乎把用戶數(shù)據(jù)集集中放到了源碼的一個地方浪蹂,那就是 id="data" 的 div 的 data-state 屬性的值中,看下圖:

從上圖我們可以發(fā)現(xiàn)告材,date-state 的屬性值中藏有用戶的信息坤次,比如我們可以依次找到用戶的教育經(jīng)歷(educations)、簡介(headline)斥赋、參與的 Live 數(shù)量(participatedLiveCount)缰猴、關(guān)注的收藏夾數(shù)量(followingFavlistsCount)、被收藏的次數(shù)(favoritedCount)疤剑、關(guān)注他的用戶數(shù)(followerCount)滑绒、關(guān)注的話題數(shù)量(followingTopicCount)、用戶描述(description)等信息隘膘。通過觀察我們也可以發(fā)現(xiàn)疑故,數(shù)據(jù)應該是以 JSON 格式存儲。

知道了用戶數(shù)據(jù)都藏在 date-state 中棘幸,我們 用 BeautifulSoup 把該屬性的值取出來焰扳,然后作為 JSON 格式讀取,再把數(shù)據(jù)集中存儲用戶數(shù)據(jù)的部分提取出來即可误续,看代碼:

# 解析html
s = BS(html,'html.parser')
# 獲得該用戶藏在主頁面中的json格式數(shù)據(jù)集
data = s.find('div',attrs={'id':'data'})['data-state']
data = json.loads(data)
data = data['entities']['users'][urlToken]

如此吨悍,我們便得到了某一個用戶的個人信息。

抓取知乎用戶的關(guān)注者列表

剛剛我們討論到可以通過抓取用戶主頁面源碼來獲取個人信息蹋嵌,而用戶主頁面可以通過拼接字符串的形式得到 URL育瓜,其中拼接的關(guān)鍵是 如何獲取用戶唯一ID —— urlToken

我采用的方法是 抓取用戶的關(guān)注者列表栽烂。

每個用戶都會有關(guān)注者列表躏仇,比如輪子哥的:

和獲取個人信息同樣的方法恋脚,我們可以在該頁面源碼的 date-state 屬性值中找到關(guān)注他的用戶(一部分):

名為 ids 的鍵值中存儲有當前列表頁的所有用戶的 urlToken,默認列表的每一頁顯示20個用戶焰手,所以我們寫一個循環(huán)便可以獲取當前頁該用戶的所有關(guān)注者的 urlToken糟描。

# 解析當前頁的 html   
url = '%s/people/%s/followers?page=%d'%(host,urlToken,page)
html = c.get_html(url)
s = BS(html,'html.parser')

# 獲得當前頁的所有關(guān)注用戶
data = s.find('div',attrs={'id':'data'})['data-state']
data = json.loads(data)
items = data['people']['followersByUser'][urlToken]['ids']
for item in items:
    if item!=None and item!=False and item!=True and item!='知乎用戶'.decode('utf8'):
        node = item.encode('utf8')
        follower_list.append(node)

再寫一個循環(huán)遍歷關(guān)注者列表的所有頁,便可以獲取用戶的所有關(guān)注者的 urlToken书妻。

有了每個用戶在知乎的唯一ID船响,我們便可以通過拼接這個ID得到每個用戶的主頁面URL,進一步獲取到每個用戶的個人信息躲履。

我選擇抓取的是用戶的關(guān)注者列表见间,即關(guān)注這個用戶的所有用戶(follower)的列表,其實你也可以選擇抓取用戶的關(guān)注列表(following)工猜。我希望抓取更多知乎非典型用戶(潛水用戶)米诉,于是選擇了抓取關(guān)注者列表。當時抓取的時候有這樣的擔心篷帅,萬一這樣抓不到主流用戶怎么辦史侣?畢竟很多知乎大V雖然關(guān)注者很多,但是主動關(guān)注的人相對都很少犹褒,而且關(guān)注的很可能也是大V抵窒。但事實證明,主流用戶基本都抓取到了叠骑,看來基數(shù)提上來后李皇,總有縫隙出現(xiàn)。

反爬蟲機制

頻繁抓取會被知乎封IP宙枷,也就是常說的反爬蟲手段之一掉房,不過俗話說“道高一尺,魔高一丈”慰丛,既然有反爬蟲手段卓囚,那么就一定有反反爬蟲手段,咳诅病,我自己起的名……

言歸正傳哪亿,如果知乎封了你的IP,那么怎么辦呢贤笆?很簡單蝇棉,換一個IP。這樣的思想催生了 代理IP池 的誕生芥永。所謂代理IP池篡殷,是一個代理IP的集合,使用代理IP可以偽裝你的訪問請求埋涧,讓服務器以為你來自不同的機器板辽。

于是我的 應對知乎反爬蟲機制的策略 就很簡單了:全力抓取知乎頁面 --> 被知乎封IP --> 換代理IP --> 繼續(xù)抓 --> 知乎繼續(xù)封 --> 繼續(xù)換 IP..... (手動斜眼)

使用 代理IP池奇瘦,你可以選擇用付費的服務,也可以選擇自己寫一個劲弦,或者選擇用現(xiàn)成的輪子耳标。我選擇用七夜寫的 qiyeboy/IPProxyPool 搭建代理池服務,部署好之后邑跪,修改了一下代碼讓它只保存https協(xié)議的代理IP麻捻,因為 使用http協(xié)議的IP訪問知乎會被拒絕

搭建好代理池服務后呀袱,我們便可以隨時在代碼中獲取以及使用代理 IP 來偽裝我們的訪問請求啦!

(其實反爬手段有很多郑叠,代理池只是其中一種)

簡單的分布式架構(gòu)

多線程/多進程只是最大限度的利用了單臺機器的性能夜赵,如果要利用多臺機器的性能,便需要分布式的支持乡革。

如何搭建一個簡單的分布式爬蟲寇僧?

我采用了 主從結(jié)構(gòu),即一臺主機負責調(diào)度沸版、管理待抓取節(jié)點嘁傀,多臺從機負責具體的抓取工作。

具體到這個知乎爬蟲來說视粮,主機上搭建了兩個數(shù)據(jù)庫MongoDB 和 Redis细办。MongoDB 負責存儲抓取到的知乎用戶數(shù)據(jù),Redis 負責維護待抓取節(jié)點集合蕾殴。從機上可以運行兩個不同的爬蟲程序笑撞,一個是抓取用戶關(guān)注者列表的爬蟲(list_crawler),一個是抓取用戶個人資料的爬蟲(info_crawler)钓觉,他們可以配合使用茴肥,但是互不影響。

我們重點講講主機上維護的集合荡灾,主機的 Redis 數(shù)據(jù)庫中一共維護了5個集合:

  • waiting:待抓取節(jié)點集合
  • info_success:個人信息抓取成功節(jié)點集合
  • info_failed:個人信息抓取失敗節(jié)點集合
  • list_success:關(guān)注列表抓取成功節(jié)點集合
  • list_failed:關(guān)注列表抓取失敗節(jié)點集合

這里插一句瓤狐,之所以采用集合(set),而不采用隊列(queue)批幌,是因為集合天然的帶有唯一性础锐,也就是說可以加入集合的節(jié)點一定是集合中沒有出現(xiàn)過的節(jié)點,這里在5個集合中流通的節(jié)點其實是 urlToken逼裆。

(其實集合可以縮減為3個郁稍,省去失敗集合,失敗則重新投入原來的集合胜宇,但我為了測速所以保留了5個集合的結(jié)構(gòu))

他們的關(guān)系是:


舉個具體的栗子:從一個 urlToken 在 waiting 集合中出現(xiàn)開始耀怜,經(jīng)過一段時間恢着,它被 info_crawler 爬蟲程序從 waiting 集合中隨機獲取到,然后在 info_crawler 爬蟲程序中抓取個人信息财破,如果抓取成功將個人信息存儲到主機的 MongoDB 中掰派,將該 urlToken 放到 info_success 集合中;如果抓取失敗則將該 urlToken 放置到 info_failed 集合中左痢。下一個階段靡羡,經(jīng)過一段時間后,list_crawler 爬蟲程序?qū)?info_success 集合中隨機獲取到該 urlToken俊性,然后嘗試抓取該 urlToken 代表用戶的關(guān)注者列表略步,如果關(guān)注者列表抓取成功,則將抓取到的所有關(guān)注者放入到 waiting 集合中定页,將該 urlToken 放到 list_success 集合中趟薄;如果抓取失敗,將該 urlToken 放置到 list_failed 集合中典徊。

如此杭煎,主機維護的數(shù)據(jù)庫,配合從機的 info_crawler 和 list_crawler 爬蟲程序卒落,便可以循環(huán)起來:info_crawler 不斷從 waiting 集合中獲取節(jié)點羡铲,抓取個人信息,存入數(shù)據(jù)庫儡毕;list_crawler 不斷的補充 waiting 集合也切。

主機和從機的關(guān)系如下圖:


主機是一臺外網(wǎng)/局域網(wǎng)可以訪問的“服務器”,從機可以是PC/筆記本/Mac/服務器妥曲,這個架構(gòu)可以部署在外網(wǎng)也可以部署在內(nèi)網(wǎng)贾费。

后記

本文分享的是如何寫一個簡單的分布式知乎爬蟲,但愿能帶給你啟發(fā)檐盟。

采用這個分布式爬蟲的思路抓取數(shù)據(jù)然后分析的文章在這里:
大數(shù)據(jù)報告:知乎百萬用戶分析

原創(chuàng)聲明

作者囈語
微信公眾號囈語的黑板報
轉(zhuǎn)載請注明:囈語 ? 如何寫一個簡單的分布式知乎爬蟲褂萧?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市葵萎,隨后出現(xiàn)的幾起案子导犹,更是在濱河造成了極大的恐慌,老刑警劉巖羡忘,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谎痢,死亡現(xiàn)場離奇詭異,居然都是意外死亡卷雕,警方通過查閱死者的電腦和手機节猿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人滨嘱,你說我怎么就攤上這事峰鄙。” “怎么了太雨?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵吟榴,是天一觀的道長。 經(jīng)常有香客問我囊扳,道長吩翻,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任锥咸,我火速辦了婚禮狭瞎,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘搏予。我一直安慰自己脚作,他們只是感情好,可當我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布缔刹。 她就那樣靜靜地躺著,像睡著了一般劣针。 火紅的嫁衣襯著肌膚如雪校镐。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天捺典,我揣著相機與錄音鸟廓,去河邊找鬼。 笑死襟己,一個胖子當著我的面吹牛引谜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播擎浴,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼员咽,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了贮预?” 一聲冷哼從身側(cè)響起贝室,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎仿吞,沒想到半個月后滑频,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡唤冈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年峡迷,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片你虹。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡绘搞,死狀恐怖彤避,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情看杭,我是刑警寧澤忠藤,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站楼雹,受9級特大地震影響模孩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜贮缅,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一榨咐、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧谴供,春花似錦块茁、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至崎场,卻和暖如春佩耳,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背谭跨。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工干厚, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人螃宙。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓蛮瞄,卻偏偏與公主長得像,于是被迫代替她去往敵國和親谆扎。 傳聞我的和親對象是個殘疾皇子挂捅,可洞房花燭夜當晚...
    茶點故事閱讀 45,512評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 本篇文章將是『如何構(gòu)建一個分布式爬蟲』系列文章的最后一篇,擬從實戰(zhàn)角度來介紹如何構(gòu)建一個穩(wěn)健的分布式微博爬蟲堂湖。這里...
    resolvewang閱讀 5,481評論 4 34
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,283評論 25 707
  • 引言 在上篇使用Scrapy爬取知乎用戶信息我們編寫了一個單機的爬蟲籍凝,這篇記錄了使用Scrapy-Redis將其重...
    朱曉飛閱讀 6,707評論 1 24
  • 致第的第一次(讀源碼):突然發(fā)現(xiàn),我不再年輕了苗缩,已經(jīng)到了不得不改變了年紀饵蒂,自己生命的寬度不夠,深度也不夠酱讶,再不非凡...
    快樂的河馬閱讀 267評論 0 1
  • 還要多少時間 才能停止想念 在這樣的夜晚 你是否 和我一樣不知道該找誰聊天 女人哭花了雙眼 男人濕透了衣衫 兩個人...
    抹不掉的傷0702閱讀 650評論 0 0