本篇文章將以抓取豆瓣電影信息爲例來一步步介紹開發一個高性能爬蟲的常見思路。html
爬蟲的第一步,首先咱們要找到獲取數據的地址。能夠先到豆瓣電影 首頁 去看看。前端
頂部導航爲提供了不少種類型的入口,其中和電影有關的有:排行榜、選電影和分類。爲了便於後續更精細的分析,這裏選擇進入分類頁面,地址。經過瀏覽的開發工具,咱們最終能確認數據來源是的vue
https://movie.douban.com/j/new_search_subjects?sort=U&range=0,10&tags=&start=0
複製代碼
注意:若是有朋友熟悉前端並裝有vue瀏覽器插件,就會發現豆瓣電影站點是vue開發的。這些基本web開發技能對於咱們平時開發爬蟲都是頗有幫助的。python
用瀏覽器打開上面的接口地址,咱們就會發現它的返回數據爲json格式。利用python的requests和json庫,就能夠把數據獲取下來了。web
這裏咱們只獲取電影的標題、導演、評分和演員四個字段,代碼以下:編程
import json
import requests
def crawl(url):
response = requests.get(url)
if response.status_code != 200:
raise Exception('http status code is {}'.format(response.status_code))
data = response.json()['data']
items = []
for v in data:
items.append({
'title': v['title'],
'drectors': v['directors'],
'rate': v['rate'],
'casts': v['casts']
})
return items
def main():
url = 'https://movie.douban.com/j/new_search_subjects?sort=U&range=0,10&tags=&start=0'
for item in crawl(url):
print(item)
if __name__ == "__main__":
main()
複製代碼
代碼執行獲得以下這些數據:json
{'title': '綠皮書', 'drectors': ['彼得·法雷裏'], 'rate': '8.9', 'casts': ['維果·莫騰森', '馬赫沙拉·阿里', '琳達·卡德里尼', '塞巴斯蒂安·馬尼斯科', '迪米特·D·馬裏諾夫']}
{'title': '驚奇隊長', 'drectors': ['安娜·波頓', '瑞安·弗雷克'], 'rate': '7.0', 'casts': ['布麗·拉爾森', '裘德·洛', '塞繆爾·傑克遜', '本·門德爾森', '安妮特·貝寧']}
...
{'title': '這個殺手不太冷', 'drectors': ['呂克·貝鬆'], 'rate': '9.4', 'casts': ['讓·雷諾', '娜塔莉·波特曼', '加里·奧德曼', '丹尼·愛羅', '彼得·阿佩爾']}
{'title': '新喜劇之王', 'drectors': ['周星馳', '邱禮濤', '黃驍鵬', '肖鶴'], 'rate': '5.8', 'casts': ['王寶強', '鄂靖文', '張全蛋', '景如洋', '張琪']}
複製代碼
仔細觀察,咱們會發現僅僅抓到了20條數據。電影數據才這麼點,這是不可能的,這是由於正常網站展現信息都會採用分頁方式。再來看下電影的分類頁面,咱們把滾動條拉到底部就會發現底部有個 "加載更多" 的提示按鈕。點擊以後,會加載出更多的電影。瀏覽器
對於各位來講,分頁應該是很好理解的。就像書本同樣,包含信息多了天然就須要分頁,網站也是如此。不過站點根據場景不一樣,分頁規則也會有些不一樣。下面來具體說說:bash
先說說分頁的參數,一般會涉及三個參數,分別是:網絡
分頁主要經過這三種參數的兩種組合實現,哪兩種組合?繼續往下看:
介紹完了常見的兩種分頁規則,來看看咱們的的url:
https://movie.douban.com/j/new_search_subjects?sort=U&range=0,10&tags=&start=0
複製代碼
該頁面經過下拉方式實現翻頁,那麼咱們就會想url中是否有起始位置信息。果真在找到了start參數,此處爲0。而後點擊下拉,經過瀏覽器開發工具監控獲得了新的url,以下:
https://movie.douban.com/j/new_search_subjects?sort=U&range=0,10&tags=&start=20
複製代碼
start的值變成了20,這說明起始位置參數就是start。依照分頁的規則,咱們把main函數修改下,加個while循環就能夠獲取所有電影數據了,代碼以下:
def main():
url = 'https://movie.douban.com/j/new_search_subjects?sort=U&range=0,10&tags=&start={}'
start = 0
total = 0
while True:
items = crawl(url.format(start))
if len(items) <= 0:
break
for item in items:
print(item)
start += 20
total += len(items)
print('已抓取了{}條電影信息'.format(total))
print('共抓取了{}條電影信息'.format(total))
複製代碼
到這裏工做基本完成!把print改成入庫操做把抓取的數據入庫,一個爬蟲就真正完成了。
不知你們注意到沒有,這裏的請求每次只能獲取20條數據,這必然到致使數據請求次數增長。這有什麼問題嗎?三個問題:
那有沒有辦法使請求返回數據量增長?固然是有的。
前面說過度頁規則有兩個,分別是 具體頁碼 + 每頁大小 和 起始位置 + 每頁大小。這兩種規則都和每頁大小,即每頁數量有關。咱們知道上面的接口默認每頁大小爲20。根據前面介紹的分頁規則,咱們分別嘗試在url加上limit和size參數。驗證後發現,limit可用來改變每次請求獲取數量。修改一下代碼,在url上增長參數limit,使其等於100:
url = 'https://movie.douban.com/j/new_search_subjects?sort=U&range=0,10&tags=&start={}&limit=100'
複製代碼
只是增長了一個limit參數就能夠幫助咱們大大減小接口請求次數,提升數據獲取速度。要說明一下,不是每次咱們都有這樣好的運氣,有時候每頁數量是固定的,咱們沒有辦法修改,這點咱們須要知道。
通過上面的優化,咱們的爬蟲性能已經有了必定提高,可是好像仍是很慢。執行它並觀察打印信息,咱們會發現每一個請求之間的延遲很大,必須等待上一個請求響應並處理完成,才能繼續發出下一個請求。若是你們有網絡監控工具,你會發現此時網絡帶寬的利用率很低。由於大部分的時間都被IO請求阻塞了。有什麼辦法能夠解決這個問題?那麼必然要提的就是併發編程。
併發編程是個很大的話題,涉及多線程、多進程以及異步io等,這篇文章的重點不在此。這裏使用python的asyncio來幫助咱們提高高爬蟲性能。咱們來看實現代碼吧。 此處要說明一個問題,由於豆瓣用下拉的方式獲取數據,正如上面介紹的那樣,這是一種不須要提供數據總數的就能夠分頁的方式。可是這種方式會致使我就沒有辦法事先根據limit和total肯定請求的總數,在請求總數未知的狀況下,咱們的請求只能順序執行。因此這裏咱們爲了案例可以繼續,假設獲取數據最多1萬條,代碼以下:
import json
import asyncio
import aiohttp
async def crawl(url):
data = None
async with aiohttp.ClientSession() as s:
async with s.get(url) as r:
if r.status != 200:
raise Exception('http status code is {}'.format(r.status))
data = json.loads(await r.text())['data']
items = []
for v in data:
items.append({
'title': v['title'],
'drectors': v['directors'],
'rate': v['rate'],
'casts': v['casts']
})
return items
async def main():
limit = 100
url = 'https://movie.douban.com/j/new_search_subjects?sort=U&range=0,10&tags=&start={}&limit={}'
start = 0
total = 10000
crawl_total = 0
tasks = [crawl(url.format(start + i * limit, limit)) for i in range(total // limit)]
for r in asyncio.as_completed(tasks):
items = await r
for item in items:
print(item)
crawl_total += len(items)
print('共抓取了{}條電影信息'.format(crawl_total))
if __name__ == "__main__":
ioloop = asyncio.get_event_loop()
ioloop.run_until_complete(main())
複製代碼
最終結果顯示獲取了9900條,感受是豆瓣限制了翻頁的數量,最多隻能獲取9900條數據。
最終的代碼使用了asyncio的異步併發編程來實現爬蟲性能的提升,並且還用到了aiohttp這個庫來實現http的異步請求。跳躍有點大,有一種學會了 1+1 就能夠去作微積分題目的感受。若是想利用多核優點,能夠利用 aio + multiprocess 組合實現。
本文從提升爬蟲抓取速度與減小資源消耗兩個角度介紹了開發一個高性能爬蟲的一些技巧:
最後,你們若是有興趣能夠去看看tornado文檔中實現的一個高併發爬蟲。若是不懂異步io的話,或許會以爲這個代碼很詭異。