强大的异步爬虫 with aiohttp

这是崔斯特的第四十七篇原创文章

异步了解下 (๑• . •๑)

看到现在网络上大多讲的都是requests、scrapy,却没有说到爬虫中的神器: aiohttp

aiohttp 介绍

aiohttp是什么,官网上有这样一句话介绍: Async HTTP client/server for asyncio and Python ,翻译过来就是 asyncio和Python的异步HTTP客户端/服务器

主要特点是:

  1. 支持客户端和HTTP服务器。
  2. 无需使用Callback Hell即可支持Server WebSockets和Client WebSockets。
  3. Web服务器具有中间件,信号和可插拔路由。

emmmm,好吧,还是来看代码吧

Client example:

import aiohttp
import asyncio

async def fetch(session, url):
    async with session.get(url) as response:
        return await response.text()

async def main():
    async with aiohttp.ClientSession() as session:
        html = await fetch(session, 'http://httpbin.org/headers')
        print(html)

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

output:

{"headers":{"Accept":"*/*","Accept-Encoding":"gzip, deflate","Connection":"close","Host":"httpbin.org","User-Agent":"Python/3.6 aiohttp/3.2.1"}}

Server example:

from aiohttp import web

async def handle(request):
    name = request.match_info.get('name', "Anonymous")
    text = "Hello, " + name
    return web.Response(text=text)

app = web.Application()
app.add_routes([web.get('/', handle),
                web.get('/{name}', handle)])

web.run_app(app)

output:

======== Running on http://0.0.0.0:8080 ========
(Press CTRL+C to quit)

aiohttp 与 requests

去翻一下官方文档 Client Quickstart ,让我感觉非常熟悉,很多用法和requests相似。

async with aiohttp.ClientSession() as session:
    async with session.get('http://httpbin.org/get') as resp:
        print(resp.status)
        print(await resp.text())

首先,官方推荐使用 ClientSession 来管理会话,这不就是 requests 中的 session 吗?用法也类似,使用 session.get() 去发送 get 请求,返回的 resp 中就有我们所需要的数据了,用法也和 requests 一样, text() 文本, .json() 直接打印返回的 json 数据, headers 什么的也一样,更多内容参考官方文档 Response object

既然已经有 requests 了,那为什么还要说 aiohttp 了?重点来了, aiohttp 是异步的。在python3.5中,加入了 asyncio/await 关键字,使得回调的写法更加直观和人性化。而 aiohttp 是一个提供异步web服务的库, asyncio 可以实现单线程并发IO操作。

requests 写爬虫是同步的,是等待网页下载好才会执行下面的解析、入库操作,如果在下载网页时间太长会导致阻塞,使用 multiprocessing 或者 threading 加速爬虫也是一种方法。

我们现在使用的 aiohttp 是异步的,简单来说,就是不需要等待,你尽管去下载网页就好了,我不用傻傻的等待你完成才进行下一步,我还有别的活要干。这样就极大的提高了下载网页的效率。

另外, Scrapy 也是异步的,是基于Twisted事件驱动的。在任何情况下,都不要写阻塞的代码。阻塞的代码包括:

  1. 访问文件、数据库或者Web
  2. 产生新的进程并需要处理新进程的输出,如运行shell命令
  3. 执行系统层次操作的代码,如等待系统队列

代码实例

这里是使用 aiohttp 的一个爬虫实例

import asyncio

import aiohttp
from bs4 import BeautifulSoup

import logging

class AsnycGrab(object):

    def __init__(self, url_list, max_threads):

        self.urls = url_list
        self.results = {}
        self.max_threads = max_threads

    def __parse_results(self, url, html):

        try:
            soup = BeautifulSoup(html, 'html.parser')
            title = soup.find('title').get_text()
        except Exception as e:
            raise e

        if title:
            self.results[url] = title

    async def get_body(self, url):
        async with aiohttp.ClientSession() as session:
            async with session.get(url, timeout=30) as response:
                assert response.status == 200
                html = await response.read()
                return response.url, html

    async def get_results(self, url):
        url, html = await self.get_body(url)
        self.__parse_results(url, html)
        return 'Completed'

    async def handle_tasks(self, task_id, work_queue):
        while not work_queue.empty():
            current_url = await work_queue.get()
            try:
                task_status = await self.get_results(current_url)
            except Exception as e:
                logging.exception('Error for {}'.format(current_url), exc_info=True)

    def eventloop(self):
        q = asyncio.Queue()
        [q.put_nowait(url) for url in self.urls]
        loop = asyncio.get_event_loop()
        tasks = [self.handle_tasks(task_id, q, ) for task_id in range(self.max_threads)]
        loop.run_until_complete(asyncio.wait(tasks))
        loop.close()


if __name__ == '__main__':
    async_example = AsnycGrab(['http://edmundmartin.com',
               'https://www.udemy.com',
               'https://github.com/',
               'https://zhangslob.github.io/',
               'https://www.zhihu.com/'], 5)
    async_example.eventloop()
    print(async_example.results)

需要注意的是,你需要时刻在你的代码中使用异步操作,你如果在代码中使用同步操作,爬虫并不会报错,但是速度可能会受影响。

其他异步库

因为爬虫不仅仅只有下载这块,还会有操作数据库,这里提供两个异步库: aioredismotor

import asyncio
import aioredis

loop = asyncio.get_event_loop()

async def go():
    conn = await aioredis.create_connection(
        'redis://localhost', loop=loop)
    await conn.execute('set', 'my-key', 'value')
    val = await conn.execute('get', 'my-key')
    print(val)
    conn.close()
    await conn.wait_closed()
loop.run_until_complete(go())
# will print 'value'

文档: aioredis

import motor.motor_asyncio

client = motor.motor_asyncio.AsyncIOMotorClient('mongodb://localhost:27017')

db = client['test_database']
collection = db['test_collection']

async def do_insert():
    document = {'key': 'value'}
    result = await db.test_collection.insert_one(document)
    print('result %s' % repr(result.inserted_id))
    
async def do_find_one():
    document = await db.test_collection.find_one({'i': {'$lt': 1}})
    pprint.pprint(document)

文档: motor

本文仅仅介绍了 aiohttp 作为 Client 的用法, 有兴趣的朋友可以去研究下作为 Server 的用法,同样很强大。

责编内容来自:崔斯特的博客 (源链) | 更多关于

阅读提示:酷辣虫无法对本内容的真实性提供任何保证,请自行验证并承担相关的风险与后果!
本站遵循[CC BY-NC-SA 4.0]。如您有版权、意见投诉等问题,请通过eMail联系我们处理。
酷辣虫 » 综合编程 » 强大的异步爬虫 with aiohttp

喜欢 (0)or分享给?

专业 x 专注 x 聚合 x 分享 CC BY-NC-SA 4.0

使用声明 | 英豪名录