從零開始寫Python爬蟲 --- 爬蟲應用:IT之家熱門段子(評論)爬取

不知道這裡有沒有喜歡刷it之家的小夥伴,我反正每天早上醒來第一件事就是打開it之家,看看有沒有新鮮的段子<熱評> 逃~

其實這次是要來抓取it之家的熱門評論,因為數量較多(上萬),所以我們這次採用MongoDB來存數數據

關鍵詞:

這次爬蟲不像原本的小腳本啦,

對速度和質量都有比較高的要求,

所以會涉及到一些我原本沒有介紹的知識:

  • Ajax
  • 多進程
  • MongoDB
  • 生成器...

先來看一下成果

資料庫展示:

這是MongoDB的GUI軟體(RoBo 3T)的截圖

可以看到 在 ithome這個資料庫里我們

抓到了 21745條object 即21745條熱門評論

點開一條記錄是這樣顯示的:

既抓取的內容為:

發帖人、評論內容、手機廠商、手機型號、位置、時間

爬蟲運行的時候是這樣的:

思路分析

首先看一下項目目錄:

.n├── __pycache__n│ ├── config.cpython-36.pycn│ └── pipeline.cpython-36.pycn├── apple.json # 導出的數據結果n├── config.py # 資料庫的配置信息n├── datahandleer.py # 數據後期處理的文件n├── pipeline.py # 數據處理n└── spider.py # 爬蟲主文件n

如和獲取熱評呢

我們先隨便點開一篇it之家新聞的鏈接

ithome.com/html/it/3230

一開始肯定是直接嘗試用requests訪問新聞的鏈接,

並用解析工具解析出熱評的位置,在抓取就好

可是我嘗試後發現,requests抓去下來的源文件

並沒有評論的內容

仔細一想,那一般是通過ajax來動態載入內容的

用到ajax的情形一般是這樣的:你訪問一個網頁,不用刷新跳轉,只要點一個按鈕,或者滑鼠往下滾,新的信息就會自動出來,例如Facebook的timeline

猜到了大概方向之後,

我們 切換到開發者工具的 Network

並勾選Preserve log

最後切換到 XHR這個標籤頁

這個時候我們在刷新頁面,就能捕捉到網頁的各種請求了。

熱評是在一個名為(getajaxdata.aspx)的連接里出來的

通過這個名字,大家也能看出來這是一個ajax請求吧?

我們再來看一下這個請求的headers

仔細分析一下,不難得出,

這是一個向:dyn.ithome.com/ithome/g

發送的POST請求,PSOT的參數是:

newsID:323076 type:hotcommentn

再仔細的分析一下,發現這個newsid 就明文寫在新聞的url之中

我們重新整理一下思路

  • 熱評是通過ajax請求獲取的
  • 該POST請求需要兩個參數:newsid 、type

那是不是意味著,我們不需要訪問新聞的頁面,

直接向it之家ajax伺服器發送符合要求的請求就可以獲取到數據呢?

我直接告訴你們,是可以的!

那麼,只剩下一個問題了:

newsid如何獲取呢?

這個其實更簡單了,我們只要點開隨便一個新聞分類

我這裡用蘋果作為演示分類

url: it.ithome.com/apple/

打開這個頁面,發現所有的新聞鏈接都包裹在li 標籤之中,

我們只需要找到並用辦法解析出最後的newsid就可以了。

等等,最下面那個紅色按鈕是不是感到很眼熟?

是不是又是通過Ajax請求來獲取下一頁的新聞?

當然是的!(這裡我就不截圖演示了,大家可以自己去嘗試一下分析)

爬蟲流程

當所有的思路都通順之後,

我們的熱評爬蟲是這樣運行的:

  • spider不停的訪問某一新聞分類下的page頁面,並獲取newsid
  • 將newisd 交給hotcomment爬蟲來爬取熱評數據
  • 將數據交給pipeline里的函數,
  • 最後將數據存入資料庫

代碼展示:

由於前面的思路已經詳細的講過了

代碼里也有比較詳細的注釋,

我就不一一說明了

獲取newsid的部分

def parse_news_id(categoryid, page_start):n n 找到當前分類下首頁的文章的idnn retrun newsid <str>n n data = {n categoryid: categoryid,n type: pccategorypage,n page: 1,n }nn # 循環獲取newsid 最早可到2014年12月n # 默認每次取10頁n for page in range(page_start, page_start + 11):n data[page] = str(page)n try:n r = requests.post(n http://it.ithome.com/ithome/getajaxdata.aspx, data=data)n soup = BeautifulSoup(r.text, lxml)n news_list = soup.find_all(a, class_=list_thumbnail)n # 找到當前頁的所有新聞鏈接之後,用生成器返回newsidn for news in news_list:n yield news[href].split(/)[-1].replace(.htm, )n except:n return Nonen

這裡我稍微說一下,

這裡用到了關鍵字 yeild

這樣,就將我們的parse_news_id()函數變成了一個生成器函數

用起來是這樣的:每當抓到一個newsid,就返回給parse_hot_comment使用,當熱評抓完了之後,調回頭來據需抓取下一個newsid。這樣動態的抓取,既節省了內存,又能提高效率,Scrapy框架也是這麼設計的喲

獲取熱評的部分

def parse_hot_comment(newsid):n n 找到it之家新聞的熱評nn return :info_list <list>n n info_list = []n data = {n newsID: newsid,n type: hotcommentn }n try:n r = requests.post(n https://dyn.ithome.com/ithome/getajaxdata.aspx, data=data)n r.raise_for_status()n r.encoding = r.apparent_encodingn soup = BeautifulSoup(r.text, lxml)n comment_list = soup.find_all(li, class_=entry)n for comment in comment_list:n # 評論內容n content = comment.find(p).textn # 用戶名n name = comment.find(strong, class_=nick).get_text()n # 其他信息n info = comment.find(div, class_=info rmp).find_all(span)n # 判斷用戶是否填寫了手機尾巴n # 對信息做出鹹蛋的處理n # 抓取到 手機廠商、型號、位置、時間n # 方便最後做數據分析n if len(info) > 1:n phone_com = info[0].text.split( )[0]n phone_model = info[0].text.split( )[1]n loc = info[1].text.replace(IT之家, ).replace(n 網友, ).replace(xa0, ).split( )[0]n time = info[1].text.replace(IT之家, ).replace(n 網友, ).replace(xa0, ).split( )[2]n else:n phone_com = 暫無n phone_model = 暫無n loc = info[0].text.replace(IT之家, ).replace(n 網友, ).replace(xa0, ).split( )[0]n time = info[0].text.replace(IT之家, ).replace(n 網友, ).replace(xa0, ).split( )[2]nn info_list.append(n {name: name, content: content, phone_com: phone_com, phone_model: phone_model, loc: loc, time: time, })nn return info_listn except:n return Nonen

數據存儲的部分

# config.pyn# 資料庫urlnMONGO_URL = localhostn# 資料庫名nMONGO_DB = ithomen# 資料庫表nMONGO_TABLE = hotcomment_itnn# pipeline.pynfrom pymongo import MongoClientnfrom config import *nnclient = MongoClient(MONGO_URL, connect=True)ndb = client[MONGO_DB]nn# 將記錄寫入資料庫ndef save_to_mongo(result):n if db[MONGO_TABLE].insert(result):n print(存儲成功, result)n return Truen return Falsen

有可以優化的地方么?

有!速度!

# 寫了一個檢測函數運行時間的裝飾器ndef clock(func):n def clocked(*args):n t0 = time.perf_counter()n result = func(*args) # 裝飾被裝飾的函數nn timepassed = time.perf_counter() - t0n name = func.__name__n arg_str = , .join(repr(arg) for arg in args)nn print([{:.8f}s] {}({}) -> {}.format(timepassed, name, arg_str, result))n return clockedn n@clockndef main(page_start):n # 新聞分類的idn ID = 31n # 建立蘋果新聞分類對象n apple = parse_news_id(ID, page_start)nn # 利用迭代器抓取熱評n for newsid in apple:n hot_comment_dic = parse_hot_comment(newsid)n if hot_comment_dic:n for comment in hot_comment_dic:n save_to_mongo(comment)n else:n print(沒有抓取到熱評,一般是文章太過久遠)n nn開啟多進程之前 ,抓取一頁新聞的所有熱評所話費的時間n[8.45930967s] main() -> Nonenn抓取10頁:n[112.86940903s] main(61) -> Nonenn

利用進程池開啟多進程

if __name__ == __main__:nn # 單進程模式n # main(1)n n # 開啟多進程模式n from multiprocessing import Pooln pool = Pool() n # 進程池,每個進程抓取10頁新聞的熱評n groups = ([x for x in range(111, 191,10)])n pool.map(main, groups)n pool.close()n pool.join()nnn開啟後:n不能使用裝飾器測時間了nAttributeError: Cant pickle local object clock.<locals>.clockedn改為第三方秒錶計時:nn爬取1~40頁:ntime:1:56.54n可以看到 速度快了三倍!nn

由於整體的代碼太長了,

就不再貼上來了,有需要全部代碼的,

可以去我的github(放在文末了)上看

最後總結一下

這次一共抓取了2W+條數據

但如果僅僅是有這些數據,

而不去分析,整理,那麼數據就沒有任何意義。

我們很多人是通過學爬蟲入門Python的。

但爬蟲重要的並不是爬蟲本身,而是爬出來的數據。

那麼我們是不是應該想想,

有什麼辦法能夠將數據整理並展示出來呢?

熟悉我的小夥伴都知道,

我現在主要在發展的方向是Python Web

對於數據分析 我是有所逃避的

因為在我的潛意識裡,我就是個文科生,

拼 演算法、思路、最優解、對數據的敏感程度

我是肯定拼不過那些理科工科生的

幫助我入門的@路人甲就是做數據分析的

大家都喊他數據帝

甲哥他在我最迷茫的時候,(剛剛入門)幫助過我,讓我走出了迷茫,想知道的可以看這裡: zhihu.com/question/4650

現在在他的建議下,我不在逃避這方面的弱點,

開始經常刷leetcode上的演算法題,

雖然過程很難熬,有的時候半個小時都沒有思路。

但畢竟會是有用的對吧?

咳咳,其實說了這麼多,

一是想告訴大家,不要為了寫爬蟲而寫爬蟲

二是,我其實已經將這數據做出了一點點分析,回頭我整理好會發出來

題目大概叫 幫你上熱評

大家要多來點贊哦

逃 ~

每天的學習記錄都會 同步更新到:

微信公眾號: findyourownway

知乎專欄:zhuanlan.zhihu.com/Ehco

blog : www.ehcoblog.ml

Github: github.com/Ehco1996/Pyt


推薦閱讀:

新媒體人必會的傻瓜式爬蟲工具:上手 Web Scraper 的 5 個步驟
人生苦短,我用Python(寫在前面)
我這樣破解pexels獲取的高清原圖
60秒GET小技能-爬蟲快速構建post參數法
Python爬蟲技巧一之設置ADSL撥號伺服器代理

TAG:爬虫 | Python | 数据分析 |