aiohttp異步爬蟲

python在3.4版本中將asyncio引入內置模塊,即支持了異步操作,在3.5版本后引入async/await關鍵字簡化了異步操作。aiohttp是基于asyncio的一個異步http框架,可以基于此框架編寫異步爬蟲。
關于asyncio需要提前明確幾個概念:

  • event_loop事件循環:協程函數必須注冊到事件循環中,由事件循環運行。
  • coroutine協程:本質是一個函數,但是需要由async指定為協程函數。
  • task任務:coroutine本質是函數,需要將其封裝為任務,任務可以包含各種狀態。
  • future:較低層的可等待(awaitable)對象,表示異步操作的最終結果。當一個Future對象被等待的時候協程會一直等待,直到Future運行完畢。Future是Task的父類,它和task沒有本質區別。

學習異步前強烈建立看一下asyncio的基本概念和核心架構,推薦看:http://www.360doc.com/content/19/0123/07/58006001_810729573.shtml
也可以看一下這個博客理解協程的各種操作http://www.lxweimin.com/p/50384d0d3940

關于aiohttp,做爬蟲最重要的是ClientSession對象,由這個對象進行請求發送。aiohttp的中文文檔:https://hubertroy.gitbooks.io/aiohttp-chinese-documentation/content/

開始編寫爬蟲(以豆瓣電影top250為例)
先編寫解析模塊,這里用pyquery模塊進行解析。
pyquery 是類似jQuery 的python實現。pyquery是基于lxml(c編寫)模塊,所以要比純python編寫的Beautifulsoup解析速度快。

from pyquery import PyQuery as pq

def parse(resp_html):
    doc = pq(resp_html)
    for li in doc('div.article ol li').items(): 
        title = li('div > div.info > div.hd > a > span:nth-child(1)').text()  # 電影標題
        score = li('span.rating_num').text()  # 評分
        print(title, score)

編寫協程函數

import asyncio
import aiohttp

# 協程函數
async def crawl(url):
    async with aiohttp.ClientSession() as session:  # ClinetSession對象
        # 發送get請求,里面可以像requests.get()一樣傳遞headers或者proxy等,詳情可查看文檔
        async with session.get(url) as resp:
            parse(await resp.text())

url = 'https://movie.douban.com/top250/'
loop = asyncio.get_event_loop()   # 獲取事件循環
loop.run_until_complete(crawl(url))   # 執行

多url請求

url = 'https://movie.douban.com/top250/?start={}'
# tasks = [asyncio.ensure_future(crawl(url.format(i))) for i in range(0, 250, 25)]
tasks = []
for i in range(0, 250, 25):
    # ensure_future()創建任務,3.7版本可以使用asyncio.create_task()創建
    task = asyncio.ensure_future(crawl(url.format(i)))
    tasks.append(tasks)

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))  # 將task添加到事件循環

異步請求非常快,可以限制連接池容量。linux打開文件的最大數默認是1024,windows默認是509,如果異步操作文件的數量超過最大值會引起報錯ValueError: too many file descriptors in select(),可以用asyncio.Semaphore(100)限制并發數量。

async def crawl(url):
    # async with asyncio.Semaphore(30):  # 限制并發數量
    conn = aiohttp.TCPConnector(limit=30)   # 限制連接池數量,limit=0表示不限制,默認為100
    async with aiohttp.ClientSession(connector=conn) as session:
        async with session.get(url) as resp:
            return parse(await resp.text())

或者用協程嵌套

async def main():
    url = 'https://movie.douban.com/top250/?start={}'
    tasks = [asyncio.ensure_future(crawl(url.format(i))) for i in range(0, 250, 25)]
    await asyncio.wait(tasks)   # await asyncio.gather(*tasks)也可以

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

如果嵌套的協程函數有return返回值

async def main():
    url = 'https://movie.douban.com/top250/?start={}'
    tasks = [asyncio.ensure_future(crawl(url.format(i))) for i in range(0, 250, 25)]
    dones, pendings = await asyncio.wait(tasks)
    for task in dones:
        print(task.result())
    # 或者
    # results = await asyncio.gather(*tasks)
    # for result in results:
    #     print(result)

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

完整代碼

import asyncio
import aiohttp, async_timeout
from pyquery import PyQuery as pq


def parse(resp_html):
    res = []
    doc = pq(resp_html)
    for li in doc('div.article ol li').items():
        title = li('div > div.info > div.hd > a > span:nth-child(1)').text()
        score = li('span.rating_num').text()
        res.append({'title': title, 'score': score})
    return res

async def crawl(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as resp:
            return parse(await resp.text())   # 有返回值

async def main():
    url = 'https://movie.douban.com/top250/?start={}'
    tasks = [asyncio.ensure_future(crawl(url.format(i))) for i in range(0, 250, 25)]
    await asyncio.wait(tasks)
    results = await asyncio.gather(*tasks)   # 獲取返回值
    for result in results:
        print(result)

loop = asyncio.get_event_loop()
loop.run_until_complete(main())
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。