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
也可以看一下这个博客理解协程的各种操作https://www.jianshu.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())
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352