实战 | 用aiohttp和uvloop实现一个高性能爬虫

asyncio于Python3.4引入标准库,增加了对异步I/O的支持,asyncio基于事件循环,可以轻松实现异步I/O操作。接下来,我们用基于asyncio的库实现一个高性能爬虫。

准备工作

Earth View from Google Earth是一款Chrome插件,会在打开新标签页时自动加载一张来自Google Earth的背景图片。

使用Chrome开发者工具观察插件的网络请求,我们发现插件会请求一个地址如https://www.gstatic.com/prettyearth/assets/data/v2/1234.json的JSON文件,文件中包含了经过Base64的图片内容,观察发现,图片的ID范围大致在1000-8000之间,我们的爬虫就要来爬取这些精美的背景图片。

实现主要逻辑

由于爬取目标是JSON文件,爬虫的主要逻辑就变成了爬取JSON-->提取图片-->保存图片

requests是一个常用的http请求库,但是由于requests的请求都是同步的,我们使用aiohttp这个异步http请求库来代替。

 1async def fetch_image_by_id(item_id):
 2    url = f'https://www.gstatic.com/prettyearth/assets/data/v2/{item_id}.json'
 3    # 由于URL是https的,所以选择不验证SSL
 4    async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(verify_ssl=False)) as session:
 5        async with session.get(url) as response:
 6            # 获取后需要将JSON字符串转为对象
 7            try:
 8                json_obj = json.loads(await response.text())
 9            except json.decoder.JSONDecodeError as e:
10                print(f'Download failed - {item_id}.jpg')
11                return
12            # 获取JSON中的图片内容字段,经过Base64解码成二进制内容
13            image_str = json_obj['dataUri'].replace('data:image/jpeg;base64,', '')
14            image_data = base64.b64decode(image_str)
15            save_folder = dir_path = os.path.dirname(
16                os.path.realpath(__file__)) + '/google_earth/'
17            with open(f'{save_folder}{item_id}.jpg', 'wb') as f:
18                f.write(image_data)
19            print(f'Download complete - {item_id}.jpg')

aiohttp基于asyncio,所以在调用时需要使用async/await语法糖,可以看到,由于aiohttp中提供了一个ClientSession上下文,代码中使用了async with的语法糖。

加入并行逻辑

上面的代码是抓取单张图片的逻辑,批量抓取图片,需要再嵌套一层方法:

1async def fetch_all_images():
2    # 使用Semaphore限制最大并发数
3    sem = asyncio.Semaphore(10)
4    ids = [id for id in range(1000, 8000)]
5    for current_id in ids:
6        async with sem:
7            await fetch_image_by_id(current_id)

接下来,将这个方法加入到asyncio的事件循环中。

1event_loop = asyncio.get_event_loop()
2future = asyncio.ensure_future(fetch_all_images())
3results = event_loop.run_until_complete(future)

使用uvloop加速

uvloop基于libuv,libuv是一个使用C语言实现的高性能异步I/O库,uvloop用来代替asyncio默认事件循环,可以进一步加快异步I/O操作的速度。

uvloop的使用非常简单,只要在获取事件循环前,调用如下方法,将asyncio的事件循环策略设置为uvloop的事件循环策略。

1asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())

使用上面的代码,我们可以快速将大约1500张的图片爬取下来。

性能对比

为了验证aiohttp和uvloop的性能,笔者使用requests+concurrent库实现了一个多进程版的爬虫,分别爬取20个id,消耗的时间如图。

可以看到,耗时相差了大概7倍,aiohttp+uvloop的组合在爬虫这种I/O密集型的场景下,可以说具有压倒性优势。相信在不远的将来,基于asyncio的库会将无数爬虫工程师从加班中拯救出来。

原文发布于微信公众号 - Python私房菜(python-fans)

原文发表时间:2018-04-11

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏蜉蝣禅修之道

fs学习笔记之输出格式

1803
来自专栏Petrichor的专栏

tensorflow: 为什么 tensor型参数 可以接受 非tensor型输入

但是 manual 里面已经写明了 tf.multiply函数 的 参数项输入 必须要是 tensor型 的:

1133
来自专栏FreeBuf

爬虫采集去重优化浅谈

以前在做漏洞Fuzz爬虫时,曾做过URL去重相关的工作,当时是参考了seay法师的文章以及网上零碎的一些资料,感觉做的很简单。近来又遇到相关问题,于是乎有了再次...

3426
来自专栏java工会

JVM堆内存使用率持续上升的一种排查思路

最近新版本发布后,在运行一段时间后程序突然无响应了,观察监控,发现JVM堆内存占用在某个时间点突然飙升,最终导致应用无响应:

1240
来自专栏深度学习自然语言处理

这些进程的后台可靠运行命令你都知道了吗

当用户注销(logout)或者网络断开时,终端会收到 HUP(hangup)信号从而关闭其所有子进程。因此,我们的解决办法就有两种途径:要么让进程忽略 HUP ...

711
来自专栏来自地球男人的部落格

使用BibTex格式时缩小参考文献的字体

在使用BibTex写参考文献的时候,所有参考文献通常是写在一个后缀为.bib文件当中。例如:所有的参考文献按照bibtex格式写在references.b...

2989
来自专栏北京马哥教育

酷炫:6个有趣的Linux命令

本文给大家介绍几个有趣的Linux命令。 1. pv 命令 有时候我们在电影屏幕上看到一些字幕一个个匀速显示出来,像有人在边敲键盘,边显示一样。Linux上的p...

3835
来自专栏黑白安全

绕过CDN获取网站IP地址

基于masscan扫描IP端中开放的80端口,程序自动连接每个IP测试,筛选出符合条件的ip保存到result.txt 后续程序会提供”基于扫描子域名获取IP段...

1103
来自专栏编程

前端性能优化指南——网络篇

网络,在我们开发的页面的访问过程中,是最开始的一个环节,同时,也是一个非常重要的环节。 当我们在提及网络优化的时候,我们都会说些什么呢。 事实上来讲,如果可以话...

2259
来自专栏Python中文社区

Django 博客教程(三):创建应用和编写数据库模型

專 欄 ❈追梦人物,Python中文社区专栏作者。电子科技大学计算机学院研究生,从事大数据分析研究方向。主要使用 Python 语言进行相关数据的分析,熟练使...

2079

扫码关注云+社区

领取腾讯云代金券