從零開始寫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之家新聞的鏈接
https://www.ithome.com/html/it/323076.htm一開始肯定是直接嘗試用requests訪問新聞的鏈接,
並用解析工具解析出熱評的位置,在抓取就好可是我嘗試後發現,requests抓去下來的源文件
並沒有評論的內容仔細一想,那一般是通過ajax來動態載入內容的
用到ajax的情形一般是這樣的:你訪問一個網頁,不用刷新跳轉,只要點一個按鈕,或者滑鼠往下滾,新的信息就會自動出來,例如Facebook的timeline
猜到了大概方向之後,
我們 切換到開發者工具的 Network
並勾選Preserve log 最後切換到 XHR這個標籤頁這個時候我們在刷新頁面,就能捕捉到網頁的各種請求了。
熱評是在一個名為(getajaxdata.aspx)的連接里出來的
通過這個名字,大家也能看出來這是一個ajax請求吧?我們再來看一下這個請求的headers
仔細分析一下,不難得出,
這是一個向:https://dyn.ithome.com/ithome/getajaxdata.aspx
發送的POST請求,PSOT的參數是:newsID:323076 type:hotcommentn
再仔細的分析一下,發現這個newsid 就明文寫在新聞的url之中
我們重新整理一下思路
- 熱評是通過ajax請求獲取的
- 該POST請求需要兩個參數:newsid 、type
那是不是意味著,我們不需要訪問新聞的頁面,
直接向it之家ajax伺服器發送符合要求的請求就可以獲取到數據呢?我直接告訴你們,是可以的!
那麼,只剩下一個問題了:
newsid如何獲取呢?這個其實更簡單了,我們只要點開隨便一個新聞分類
我這裡用蘋果作為演示分類
url: http://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對於數據分析 我是有所逃避的因為在我的潛意識裡,我就是個文科生,
拼 演算法、思路、最優解、對數據的敏感程度我是肯定拼不過那些理科工科生的幫助我入門的@路人甲就是做數據分析的
大家都喊他數據帝
甲哥他在我最迷茫的時候,(剛剛入門)幫助過我,讓我走出了迷茫,想知道的可以看這裡: https://www.zhihu.com/question/46509697/answer/177104589
現在在他的建議下,我不在逃避這方面的弱點,
開始經常刷leetcode上的演算法題,雖然過程很難熬,有的時候半個小時都沒有思路。但畢竟會是有用的對吧?咳咳,其實說了這麼多,
一是想告訴大家,不要為了寫爬蟲而寫爬蟲
二是,我其實已經將這數據做出了一點點分析,回頭我整理好會發出來 題目大概叫 幫你上熱評大家要多來點贊哦
逃 ~每天的學習記錄都會 同步更新到:
微信公眾號: findyourownway知乎專欄:https://zhuanlan.zhihu.com/Ehco-pythonblog : www.ehcoblog.mlGithub: https://github.com/Ehco1996/Python-crawler推薦閱讀:
※新媒體人必會的傻瓜式爬蟲工具:上手 Web Scraper 的 5 個步驟
※人生苦短,我用Python(寫在前面)
※我這樣破解pexels獲取的高清原圖
※60秒GET小技能-爬蟲快速構建post參數法
※Python爬蟲技巧一之設置ADSL撥號伺服器代理