在Python3中使用asyncio库进行快速数据抓取的教程


Posted in Python onApril 02, 2015

web数据抓取是一个经常在python的讨论中出现的主题。有很多方法可以用来进行web数据抓取,然而其中好像并没有一个最好的办法。有一些如scrapy这样十分成熟的框架,更多的则是像mechanize这样的轻量级库。DIY自己的解决方案同样十分流行:你可以使用requests、beautifulsoup或者pyquery来实现。

方法如此多样的原因在于,数据“抓取”实际上包括很多问题:你不需要使用相同的工具从成千上万的页面中抓取数据,同时使一些Web工作流自动化(例如填一些表单然后取回数据)。我喜欢DIY的原因在于其灵活性,但是却不适合用来做大量数据的抓取,因为需要请求同步,所以大量的请求意味着你不得不等待很长时间。

在本文中,我将会为你展示一个基于新的异步库(aiohttp)的请求的代替品。我使用它写了一些速度的确很快的小数据抓取器,下面我将会为你演示是如何做到的。

asyncio的基本概念
asyncio是在python3.4中被引进的异步IO库。你也可以通过python3.3的pypi来安装它。它相当的复杂,而且我不会介绍太多的细节。相反,我将会解释你需要知道些什么,以利用它来写异步的代码。

简而言之,有两件事情你需要知道:协同程序和事件循环。协同程序像是方法,但是它们可以在代码中的特定点暂停和继续。当在等待一个IO(比如一个HTTP请求),同时执行另一个请求的时候,可以用来暂停一个协同程序。我们使用关键字yield from来设定一个状态,表明我们需要一个协同程序的返回值。而事件循环则被用来安排协同程序的执行。

关于asyncio还有很多很多,但是以上是我们到目前为止需要知道的。可能你还有些不清楚,那么让我们来看一些代码吧。

aiohttp
aiohttp是一个利用asyncio的库,它的API看起来很像请求的API。到目前为止,相关文档还不健全。但是这里有一些非常有用的例子。我们将会演示它的基本用法。

首先,我们会定义一个协同程序用来获取页面,并打印出来。我们使用 asyncio.coroutine将一个方法装饰成一个协同程序。aiohttp.request是一个协同程序,所以它是一个可读方法,我们需要使用yield from来调用它们。除了这些,下面的代码看起来相当直观:
 

@asyncio.coroutine
def print_page(url):
  response = yield from aiohttp.request('GET', url)
  body = yield from response.read_and_close(decode=True)
  print(body)

如你所见,我们可以使用yield from从另一个协同程序中调用一个协同程序。为了从同步代码中调用一个协同程序,我们需要一个事件循环。我们可以通过asyncio.get_event_loop()得到一个标准的事件循环,之后使用它的run_until_complete()方法来运行协同程序。所以,为了使之前的协同程序运行,我们只需要做下面的步骤:
 

loop = asyncio.get_event_loop()
loop.run_until_complete(print_page('http://example.com'))

一个有用的方法是asyncio.wait,通过它可以获取一个协同程序的列表,同时返回一个将它们全包括在内的单独的协同程序,所以我们可以这样写:
 

loop.run_until_complete(asyncio.wait([print_page('http://example.com/foo'),
                   print_page('http://example.com/bar')]))

另一个是asyncio.as_completed,通过它可以获取一个协同程序的列表,同时返回一个按完成顺序生成协同程序的迭代器,因此当你用它迭代时,会尽快得到每个可用的结果。

数据抓取
现在我们知道了如何做异步HTTP请求,因此我们可以来写一个数据抓取器了。我们仅仅还需要一些工具来读取html页面,我使用了beautifulsoup来做这个事情,其余的像 pyquery或lxml也可以实现。

在这个例子中,我们会写一个小数据抓取器来从海盗湾抓取一些linux distributions的torrent 链路(海盗湾(英语:The Pirate Bay,缩写:TPB)是一个专门存储、分类及搜索Bittorrent种子文件的网站,并自称“世界最大的BitTorrent tracker(BT种子服务器)”,提供的BT种子除了有自由版权的收集外,也有不少被著作人声称拥有版权的音频、视频、应用软件与电子游戏等,为网络分享与下载的重要网站之一?译者注来自维基百科)

首先,需要一个辅助协同程序来获取请求:
 

@asyncio.coroutine
def get(*args, **kwargs):
  response = yield from aiohttp.request('GET', *args, **kwargs)
  return (yield from response.read_and_close(decode=True))

解析部分。本文并非介绍beautifulsoup的,所以这部分我会简写:我们获取了这个页面的第一个磁链。
 

def first_magnet(page):
  soup = bs4.BeautifulSoup(page)
  a = soup.find('a', title='Download this torrent using magnet')
  return a['href']

在这个协同程序中,url的结果通过种子的数量进行排序,所以排名第一的结果实际上是种子最多的:
 

@asyncio.coroutine
def print_magnet(query):
  url = 'http://thepiratebay.se/search/{}/0/7/0'.format(query)
  page = yield from get(url, compress=True)
  magnet = first_magnet(page)
  print('{}: {}'.format(query, magnet))

最后,用下面的代码来调用以上所有的方法。
 

distros = ['archlinux', 'ubuntu', 'debian']
loop = asyncio.get_event_loop()
f = asyncio.wait([print_magnet(d) for d in distros])
loop.run_until_complete(f)

结论
好了,现在我们来到了这个部分。你有了一个异步工作的小抓取器。这意味着多个页面可以同时被下载,所以这个例子要比使用请求的相同代码快3倍。现在你应该可以用相同的方法写出你自己的抓取器了。

你可以在这里找到生成的代码,也包括一些额外的建议。

你一旦熟悉了这一切,我建议你看一看asyncio的文档和aiohttp的范例,这些都能告诉你 asyncio拥有怎样的潜力。

这种方法(事实上是所有手动的方法)的一个局限在于,没有一个独立的库可以用来处理表单。机械化的方法拥有很多辅助工具,这使得提交表单变得十分简单,但是如果你不使用它们,你将不得不自己去处理这些事情。这可能会导致一些bug的出现,所以同时我可能会写一个这样的库(不过目前为止无需为此担心)。

额外的建议:不要敲打服务器
同时做3个请求很酷,但是同时做5000个就不那么好玩了。如果你打算同时做太多的请求,链接有可能会断掉。你甚至有可能会被禁止链接网络。

为了避免这些,你可以使用semaphore。这是一个可以被用来限制同时工作的协同程序数量的同步工具。我们只需要在建立循环之前创建一个semaphore ,同时把我们希望允许的同时请求的数量作为参数传给它既可:
 

sem = asyncio.Semaphore(5)

然后,我们只需要将下面
 

page = yield from get(url, compress=True)

替换成被semaphore 保护的同样的东西。
 

with (yield from sem):
  page = yield from get(url, compress=True)

这就可以保证同时最多有5个请求会被处理。

额外建议:进度条
这个东东是免费的哦:tqdm是一个用来生成进度条的优秀的库。这个协同程序就像asyncio.wait一样工作,不过会显示一个代表完成度的进度条。
 

@asyncio.coroutine
def wait_with_progress(coros):
  for f in tqdm.tqdm(asyncio.as_completed(coros), total=len(coros)):
    yield from f
Python 相关文章推荐
Python深入学习之上下文管理器
Aug 31 Python
Python实现简单的获取图片爬虫功能示例
Jul 12 Python
Python实现的端口扫描功能示例
Apr 08 Python
对Python中列表和数组的赋值,浅拷贝和深拷贝的实例讲解
Jun 28 Python
Python人工智能之路 之PyAudio 实现录音 自动化交互实现问答
Aug 13 Python
Python笔记之工厂模式
Nov 20 Python
如何在mac环境中用python处理protobuf
Dec 25 Python
树莓派4B安装Tensorflow的方法步骤
Jul 16 Python
Python字典fromkeys()方法使用代码实例
Jul 20 Python
Expected conditions模块使用方法汇总代码解析
Aug 13 Python
Pycharm制作搞怪弹窗的实现代码
Feb 19 Python
Python道路车道线检测的实现
Jun 27 Python
Python中的Classes和Metaclasses详解
Apr 02 #Python
详解Python中的装饰器、闭包和functools的教程
Apr 02 #Python
详解Python的迭代器、生成器以及相关的itertools包
Apr 02 #Python
用Python实现通过哈希算法检测图片重复的教程
Apr 02 #Python
仅用500行Python代码实现一个英文解析器的教程
Apr 02 #Python
python下载文件时显示下载进度的方法
Apr 02 #Python
Python使用正则匹配实现抓图代码分享
Apr 02 #Python
You might like
php学习笔记 [预定义数组(超全局数组)]
2011/06/09 PHP
ZF框架实现发送邮件的方法
2015/12/03 PHP
PHP面向对象详解(三)
2015/12/07 PHP
WordPress中制作导航菜单的PHP核心方法讲解
2015/12/11 PHP
laravel 解决Validator使用中出现的问题
2019/10/25 PHP
php判断IP地址是否在多个IP段内
2020/08/18 PHP
js停止输出代码
2008/07/20 Javascript
JQuery设置文本框和密码框得到焦点时的样式
2013/08/30 Javascript
框架页面高度自动刷新的Javascript脚本
2013/11/01 Javascript
php结合imgareaselect实现图片裁剪
2015/07/05 Javascript
js中javascript:void(0) 真正含义
2020/11/05 Javascript
详解js中class的多种函数封装方法
2016/01/03 Javascript
如何使用Bootstrap的modal组件自定义alert,confirm和modal对话框
2016/03/01 Javascript
详解Node.Js如何处理post数据
2016/09/19 Javascript
详解JavaScript常量定义
2017/01/03 Javascript
js实现简单的二级联动效果
2017/03/09 Javascript
web.js.字符串与正则表达式操作
2017/05/13 Javascript
Angular获取手机验证码实现移动端登录注册功能
2017/05/17 Javascript
Angular2中如何使用ngx-translate进行国际化
2017/05/21 Javascript
jQuery中.attr()和.data()的区别分析
2017/09/03 jQuery
javaScript中的空值和假值
2017/12/18 Javascript
基于Vue实现的多条件筛选功能的详解(类似京东和淘宝功能)
2019/05/07 Javascript
JS前端知识点 运算符优先级,URL编码与解码,String,Math,arguments操作整理总结
2019/06/27 Javascript
Webpack按需加载打包chunk命名的方法
2019/09/22 Javascript
vue实现二级导航栏效果
2019/10/19 Javascript
何时/使用 Vue3 render 函数的教程详解
2020/07/25 Javascript
Python File readlines() 使用方法
2018/03/19 Python
对python中GUI,Label和Button的实例详解
2019/06/27 Python
Stefania Mode英国:奢华设计师和时尚服装
2017/10/23 全球购物
向全球直邮输送天然健康产品:iHerb.com
2020/05/03 全球购物
应届生会计电算化求职信
2013/10/03 职场文书
乔布斯辞职信(中英文对照)
2015/05/12 职场文书
2015年前台文员工作总结
2015/05/18 职场文书
2016年第32个教师节红领巾广播稿
2015/12/18 职场文书
2016学习医德医风心得体会
2016/01/25 职场文书
vue-cropper插件实现图片截取上传组件封装
2021/05/27 Vue.js