記python3爬取東方財(cái)富股吧滬深帖子及用戶信息
一. 簡(jiǎn)介
爬蟲并沒有使用流行的框架,原因是以前并沒有接觸過爬蟲 想通過自己慢慢實(shí)現(xiàn)來找找感覺。
程序使用了多進(jìn)程+多線程统抬,實(shí)現(xiàn)了首次全部爬取和增量爬取引颈。
按我的理解蛉抓,爬蟲主要要干的就是:
- 下載網(wǎng)頁
- 解析網(wǎng)頁
- 存儲(chǔ)內(nèi)容
下載請(qǐng)求使用的是requests伺糠,解析和獲取內(nèi)容使用的是lxml檀轨、xpath和re胸竞。
存儲(chǔ)方面我則是用了mongodb,這也是我第一次使用NoSQL参萄。其實(shí)使用mongodb部分原因是想用用非關(guān)系數(shù)據(jù)庫卫枝,借機(jī)了解學(xué)習(xí)= =。還有使用了redis作為消息隊(duì)列讹挎。
二. 數(shù)據(jù)庫存儲(chǔ)
使用非關(guān)系數(shù)據(jù)庫的話校赤,就要學(xué)會(huì)摒棄關(guān)系型數(shù)據(jù)庫的思維吆玖,反范式化。
不得不說马篮,開頭的時(shí)候我還是傻傻地把表按關(guān)系型的思維去設(shè)計(jì)了沾乘。
東方財(cái)富股吧數(shù)據(jù)表字段:
User集合
字段名 | 類型 | 描述 |
---|---|---|
_id | ObjectId | ObjectID |
id | string | 網(wǎng)站用戶id |
url | string | 用戶主頁鏈接 |
nickname | string | 用戶昵稱 |
avator | string | 用戶頭像鏈接 |
reg_date | ISODate | 注冊(cè)日期 |
following_count | int | 關(guān)注數(shù) |
fans_count | int | 粉絲數(shù) |
influence | int | 影響力 |
introduce | string | 用戶簡(jiǎn)介 |
visit_count | int | 總訪問數(shù) |
post_count | int | 發(fā)帖數(shù) |
comment_count | int | 評(píng)論數(shù) |
optional_count | int | 自選數(shù) |
capacity_circle | List(存放code) | 能力圈 |
source | string | 所屬論壇 |
Post 集合
字段名 | 類型 | 描述 |
---|---|---|
_id | ObjectId | Objectid |
id | string | 帖子id |
url | string | 帖子鏈接 |
user_nickname | string | 作者昵稱 |
title | string | 帖子標(biāo)題 |
created_at | ISODate | 發(fā)表時(shí)間 |
content | string | 帖子內(nèi)容(見下) |
type | string | 帖子類型 |
code | string | 所屬板塊代碼 |
comment_count | int | 評(píng)論數(shù) |
comments | List | 評(píng)論列表,里面存放comment文檔(見下) |
last_update_at | ISODate | 最后更新時(shí)間 |
like_count | int | 點(diǎn)贊數(shù) |
page_count | int | 帖子頁數(shù) |
source | string | 來源(eastmoney) |
type | string | 帖子類型(有hinfo浑测、normal還有qa) |
user_id | string | 作者id |
user_influence | string | 作者影響力 |
user_age | string | 作者吧齡 |
view_count | int | 瀏覽量 |
文章與評(píng)論采用內(nèi)嵌形式
采用內(nèi)嵌而不是引用的理由:
- 場(chǎng)景是使用爬蟲爬取文本內(nèi)容做分析而不是作為論壇數(shù)據(jù)庫使用翅阵,多數(shù)帖子評(píng)論較少,最多的大概是幾千條迁央,基本不會(huì)超過16MB的限制掷匠。
- 且未來文本分析主要操作是讀取,讀取內(nèi)容為主帖+每條評(píng)論岖圈,使用內(nèi)嵌形式的話進(jìn)行一次查詢即可讹语。
- 缺點(diǎn)是占用更多空間。
Comment 評(píng)論文檔
字段名 | 類型 | 描述 |
---|---|---|
id | string | 評(píng)論id(唯一) |
user_nickname | string | 評(píng)論用戶昵稱 |
user_id | String | 評(píng)論用戶id |
created_at | ISODate | 發(fā)表時(shí)間 |
content | string | 評(píng)論內(nèi)容 |
like_count | int | 點(diǎn)贊數(shù) |
user_influence | int | 評(píng)論用戶影響力 |
user_age | String | 評(píng)論用戶吧齡 |
reply_to | document | 回復(fù)的評(píng)論的內(nèi)容(見下表) |
reply_to 文檔
字段名 | 類型 | 描述 |
---|---|---|
reply_to_user_nickname | string | 回復(fù)的評(píng)論的用戶昵稱 |
reply_to_comment | string | 回復(fù)的評(píng)論的內(nèi)容 |
reply_to_comment_id | string | 回復(fù)的評(píng)論的id |
當(dāng)帖子類型為qa(問董秘)時(shí)蜂科,帖子的內(nèi)容形式有變化顽决。此時(shí)帖子的content則存放question和answer,其中question為string导匣,而answer為文檔集侯,如下速妖。
answer 文檔
字段名 | 類型 | 描述 |
---|---|---|
content | string | 答復(fù)內(nèi)容 |
from | string | 答復(fù)來自 |
time | ISODate | 答復(fù)時(shí)間 |
實(shí)例如下:
{
"_id" : ObjectId("5af30cc7e99de146ba385258"),
"url" : "http://guba.eastmoney.com/news,600000,758450707.html",
"code" : "600000",
"comment_count" : "1",
"comments" : [
{
"id" : "8692649074",
"user_nickname" : "很S很天真",
"user_id" : "6303084653682694",
"created_at" : ISODate("2018-05-07T18:51:56.000Z"),
"content" : "浦發(fā)的每一次反彈都是撤離的好機(jī)會(huì)冀墨!",
"reply_to" : "",
"like_count" : 0,
"user_influence" : 5,
"user_age" : "1.9年"
}
],
"content" : {
"question" : "為何2017年的現(xiàn)金分紅對(duì)比2016年大幅縮減钥屈?導(dǎo)致5月2號(hào)股票價(jià)格大跌。",
"answer" : {
"from" : "上證e互動(dòng)",
"time" : "2018-05-07 17:32:47",
"content" : "公司2017年度利潤(rùn)分配預(yù)案主要基于:一是在國家持續(xù)推進(jìn)供給側(cè)改革和去杠桿過程中厕氨,銀行業(yè)風(fēng)險(xiǎn)管控壓力持續(xù)上升进每,對(duì)銀行抗風(fēng)險(xiǎn)能力提出更高的要求;二是2018年為資本達(dá)標(biāo)過渡期最后一年命斧,對(duì)商業(yè)銀行資本充足水平提出了更高要求田晚;三是隨著公司集團(tuán)化、國際化戰(zhàn)略的不斷推進(jìn)国葬,集團(tuán)及各子公司健康快速發(fā)展贤徒,對(duì)于資本補(bǔ)充的需求也顯著上升;四是公司加快結(jié)構(gòu)轉(zhuǎn)型汇四,注重科技引領(lǐng)接奈,加快建設(shè)數(shù)字生態(tài)銀行,科研投入占比有所增加通孽,需要充足的資本支撐序宦。公司綜合考慮監(jiān)管機(jī)構(gòu)的相關(guān)要求、自身盈利水平和資本充足狀況背苦、以及轉(zhuǎn)型發(fā)展的需要互捌,適當(dāng)提高了利潤(rùn)留存比例以補(bǔ)充資本潘明,提升公司防范金融風(fēng)險(xiǎn)、服務(wù)實(shí)體經(jīng)濟(jì)秕噪、深化金融改革的能力钳降。感謝您的關(guān)注!"
}
},
"created_at" : ISODate("2018-05-03T11:15:25.000Z"),
"id" : "758450707",
"last_update_at" : ISODate("2018-05-07T18:51:56.000Z"),
"like_count" : 0,
"page_count" : 1,
"source" : "eastmoney",
"title" : "為何2017年的現(xiàn)金分紅對(duì)比2016年大",
"type" : "qa",
"uesr_nickname" : "順民izeoyl",
"user_age" : "2.8年",
"user_id" : "4661094379663388",
"user_influence" : 0,
"view_count" : "2642"
}
三.爬取過程
爬取的思路大概是這樣的:
1.首先要獲取到所有的股票代碼
在東方財(cái)富個(gè)股吧里有所有的股票信息腌巾,我只選取了滬A和深A(yù)的股票作為爬取對(duì)象遂填。
所以的話,第一步就是就將這些股票代碼爬取下來澈蝙,保存到redis中城菊。
2.能夠獲取一個(gè)股票代碼版塊的所有帖子鏈接
可以選擇頁數(shù)較少的股票先作為目標(biāo),比如 浙江美大002677 這只股票碉克,其版塊鏈接為http://guba.eastmoney.com/list,002677.html
大概有167頁,每頁最多有80個(gè)帖子并齐。要能夠把這么多個(gè)帖子鏈接記錄下來漏麦,然后后面再挨個(gè)爬取里面的詳細(xì)內(nèi)容。
3.能夠獲取一個(gè)帖子里面的所有內(nèi)容
每獲取完一個(gè)帖子里的所有內(nèi)容况褪,就把它保存到數(shù)據(jù)庫中撕贞。
在這個(gè)過程中,把所有發(fā)帖留言的用戶id都記錄下來测垛。
4.能夠獲取用戶的信息
爬取前先判斷用戶id是否已經(jīng)保存在redis的set中捏膨。若不存在,則獲取用戶信息食侮,然后保存到數(shù)據(jù)庫号涯,并且把用戶id插入到redis的set中(set能夠自動(dòng)去重)。
這四步都完成后锯七,爬蟲就完成啦链快。
第二步就可以使用多進(jìn)程去操作,每個(gè)進(jìn)程從redis里取一個(gè)任務(wù)眉尸,然后執(zhí)行第三步域蜗。
而第三、四步則可以用多線程噪猾,因?yàn)檫@兩步屬于IO密集型的任務(wù)霉祸。
四. 細(xì)節(jié)
我在爬取東方財(cái)富股吧的過程中發(fā)現(xiàn)它好像沒有設(shè)置反爬機(jī)制?對(duì)新手很友好哇袱蜡。但是我們還是秉著原則time.sleep(x)
一下丝蹭。
關(guān)于第一步 獲取所有股票代碼
爬蟲的第一步?jīng)]有什么坑,按照靜態(tài)頁面思路爬取即可戒劫,主要是熟悉了xpath的使用半夷。
關(guān)于第二步 獲取某個(gè)版塊的所有帖子
要獲取所有帖子婆廊,第一反應(yīng)就是想到遍歷每一頁,那得先知道版塊頁數(shù)對(duì)吧巫橄?然后發(fā)現(xiàn)版塊下面就有了淘邻,如下圖。
但是發(fā)現(xiàn)這個(gè)是屬于動(dòng)態(tài)內(nèi)容湘换,所以我是用到了selenium無頭模式獲取這個(gè)版塊的頁數(shù)宾舅,比如浙江美大版塊 共 167 頁。其中彩倚,在服務(wù)器中如果要用selenium得要用庫pyvirtualdisplay筹我。然后在使用selenium的地方加上
display = Display(visible=0, size=(800, 800))
display.start()
獲取到頁面數(shù)之后,就開始用for循環(huán)獲取每一頁內(nèi)容帆离,把頁面上基本的信息保存下來蔬蕊。
尤其是帖子的鏈接,在第三步獲取帖子里面的內(nèi)容哥谷,就得請(qǐng)求帖子鏈接岸夯。
頁數(shù)范圍 = 用selenium獲取的頁數(shù)
for i in 頁數(shù)范圍:
獲取(http://guba.eastmoney.com/list,002677_i.html)的帖子信息
其實(shí)后面我想到?jīng)]必要用selenium獲取這個(gè)頁數(shù)们妥,直接在while循環(huán)內(nèi)讓頁數(shù)遞增猜扮,當(dāng)獲取不到頁面元素時(shí)直接捕捉異常退出while循環(huán)就可以了吧= =。
這步遇到的坑就是监婶,當(dāng)爬取內(nèi)容前旅赢,得先了解要爬取的目標(biāo)!惑惶!
比如帖子原來有公告煮盼,比賽,問董秘集惋,研報(bào)孕似,新聞及普通帖子多種形式。
所以要分析這些帖子里面的內(nèi)容形式是不是相同的9涡獭喉祭!否則解析部分就會(huì)不對(duì)= =。比如一開始我看了幾頁數(shù)據(jù)雷绢,都沒注意到有問董秘這種東西泛烙。。翘紊。
關(guān)于第三步 獲取帖子詳細(xì)內(nèi)容
帖子的標(biāo)題蔽氨,內(nèi)容還有評(píng)論內(nèi)容都是靜態(tài)內(nèi)容,可以很容易獲取到的。
但是作者鹉究、評(píng)論用戶的吧齡及影響力宇立,還有帖子及評(píng)論的點(diǎn)贊數(shù)則都是要用ajax獲取到的。
用chrome打開控制臺(tái)Network得到這些鏈接自赔。如下圖所示妈嘹,以下得到的是每個(gè)用戶的id、吧齡和影響力绍妨。
請(qǐng)求鏈接Requests URL如下圖所示润脸,可以看到關(guān)鍵就是要構(gòu)造action=xx后面的id和replyids,
所以的話他去,每次想要獲取帖子里所有用戶的吧齡和影響力毙驯,就是先獲取到他們的用戶id,以及評(píng)論id灾测,構(gòu)造出類似上面的鏈接獲取信息爆价。
構(gòu)造形式大概就是/../guba.aspx?action=getreplylikegd&id=xxx&replyids=xxx%7Cxxx%2Cxxx%7Cxxx….
7C后面接的是評(píng)論的id,2C后面接的是評(píng)論用戶的id媳搪。即對(duì)于評(píng)論允坚,是要有這條評(píng)論本身的id以及評(píng)論用戶的id一起才行的。
返回結(jié)果為如上蛾号,把最外層的括號(hào)去掉,然后用json庫解析數(shù)據(jù)獲取里面的內(nèi)容即可涯雅。
再后面就遇到坑了O式帷!;钅妗:
有的帖子有很多的評(píng)論用戶精刷,構(gòu)造出來的鏈接就會(huì)包括很多用戶和評(píng)論id。此時(shí)就可能超過了一定的上限蔗候,比如幾百條回復(fù)怒允,你構(gòu)造了超級(jí)長(zhǎng)的鏈接請(qǐng)求,結(jié)果可能只返回了前30條锈遥。纫事。。(具體多少我沒去留意)
所以所灸,我就分開多次獲取丽惶,每次獲取30條再保存起來。
其它的部分也類似上述過程啦爬立。
最最后
程序可以跑啦钾唬!
結(jié)果,卻發(fā)現(xiàn)還是會(huì)有報(bào)錯(cuò)。抡秆。奕巍。(痛苦)
后面嘗試輸出錯(cuò)誤頁面鏈接,發(fā)現(xiàn)了東財(cái)很多奇怪的地方儒士。的止。。
比如坑1:
中科信息吧的帖子打開竟然是中科曙光的UЧ稹冲杀?= =
坑2:
有的帖子出現(xiàn)在了版塊中,打開卻會(huì)是不存在睹酌。
坑3:
這雖然是問董秘類型的帖子权谁,但其實(shí)應(yīng)該是公告類型的。憋沿。旺芽。(仔細(xì)看,會(huì)發(fā)現(xiàn)和上面兩條的公告是一樣的)
進(jìn)去里面并沒有問答的內(nèi)容辐啄,所以解析頁面的時(shí)候就會(huì)報(bào)錯(cuò)采章!
坑4:
好吧,原來還有“上海手機(jī)網(wǎng)友”這種玩意壶辜。悯舟。。
你沒法獲取到它的用戶id(壓根沒有)和昵稱(內(nèi)容不再是在a標(biāo)簽里砸民,而是在span標(biāo)簽里)抵怎。評(píng)論里也一樣會(huì)出現(xiàn)“上海手機(jī)網(wǎng)友”。岭参。反惕。
針對(duì)上述情況,修改下代碼即可演侯。
ok姿染,單進(jìn)程單線程能正常跑了,加上多線程和多進(jìn)程提高爬蟲速度秒际。
五.增量爬取
首次爬取完全部的內(nèi)容了悬赏,假設(shè)后面想獲取更新內(nèi)容呢?立馬想到的是根據(jù)時(shí)間來判斷娄徊。
取版塊中帖子的最后更新時(shí)間與數(shù)據(jù)庫中該板塊的最新時(shí)間作比較舷嗡,如果發(fā)現(xiàn)頁面中的時(shí)間比數(shù)據(jù)庫里的新,就把帖子爬下來嵌莉。
結(jié)果還是遇到坑了进萄,首先,東財(cái)版塊頁面每條帖子后面顯示的最后更新時(shí)間是不包含年份的,這樣的話可能遇到某些特殊情況會(huì)出問題中鼠?于是可婶,我就嘗試獲取帖子的最新評(píng)論的發(fā)表時(shí)間,這個(gè)時(shí)間是包含年月日的援雇。
請(qǐng)求鏈接為:http://guba.eastmoney.com/news,xxxxxx,xxxxxxxx,d.html#storeply
請(qǐng)求帶d的帖子鏈接矛渴,第一條評(píng)論就是最新評(píng)論。
本以為這就可以了惫搏,實(shí)際運(yùn)行發(fā)現(xiàn)爬沒幾條就停止了具温。發(fā)現(xiàn)這些帖子都有一個(gè)問題,就是它們在版塊中顯示的最后更新時(shí)間和最新一條評(píng)論的發(fā)表時(shí)間并不相同筐赔!
后面細(xì)心點(diǎn)才發(fā)現(xiàn)問題所在:
版塊頁面每條帖子后面顯示的最后更新時(shí)間并不一定代表帖子有新評(píng)論铣猩,也可能是帖子中有點(diǎn)贊行為(猜測(cè)是吧,實(shí)際中我嘗試回帖茴丰、點(diǎn)贊达皿,帖子都不是立即被置頂?shù)?/strong>)。
而當(dāng)時(shí)保存在數(shù)據(jù)庫中的最后更新時(shí)間 其實(shí)是 主帖中最后一條評(píng)論的發(fā)表時(shí)間贿肩,這個(gè)不一定和版塊頁面中顯示的最后更新時(shí)間是相同的峦椰。
所以就出現(xiàn)了這樣的情況:有的帖子沒有新評(píng)論,但是有點(diǎn)贊行為汰规,帖子被頂上去了汤功,然而程序如果看到這樣的帖子,就以為爬完了(因?yàn)榘鎵K頁面的時(shí)間是最新的溜哮,但最后一條評(píng)論的發(fā)表時(shí)間和數(shù)據(jù)庫中的最后更新時(shí)間相同)冤竹。
后面想了一個(gè)解決方法是:設(shè)定一個(gè)上限N,只有連續(xù)獲取的N條帖子情況都這樣(連續(xù)N條帖子都是由純點(diǎn)贊行為導(dǎo)致的可能性是很小的吧)才認(rèn)為把最新的內(nèi)容都爬取完茬射。
這么弄了后,觀察下來增量爬取好像沒什么問題了冒签。