Python异步模块asyncio/aiohttp(链家爬虫实例)内附教程分享

一、写在开头


虽然用scrapy框架来爬信息已经够快了,再用aiohttp来爬链家有点重复造轮子的嫌疑,但还是有助于我对异步编程的理解。以下内容都是出于自己对于异步的理解写出来的,毕竟不是计算机专业,没法用专业的语言来表述,用的都是通俗口语化的文字,其中肯定有些地方也写的并不对,但目前只能这样了,待以后有更深入理解之后再来完善吧。

这是最终的效果(代码放在最后):

Python异步模块asyncio/aiohttp(链家爬虫实例)内附教程分享

同步方式

Python异步模块asyncio/aiohttp(链家爬虫实例)内附教程分享

异步方式


二、几个概念


为了尽可能的发挥出cpu的性能,使程序运行的更有效率,软件跑的更快,可以通过多种方法来实现,比如多进程、多线程或者异步等等。

先解释一下这几个概念:进程线程协程。专业的可以参照《进程、线程与协程的比较》一文。

举个例子来粗浅的理解这几个概念,假设在有一间屋子需要打扫卫生,打扫卫生包含了擦桌子、扫地、拖地、擦窗户。

  • 如果是按顺序一件一件的完成这些事,那么这就是同步编程,打扫卫生的人就是进程;
  • 如果有2个人来一起来打扫卫生,那么就有了2个进程,打扫的速度自然就快了一倍;
  • 如果每个人的左手和右手也可以分别擦桌子和擦窗户,那么每个进程就有了2个线程,打扫速度又快了很多;
  • 但上面都是按顺序来打扫卫生的(同步编程),先擦桌子,再扫地,再拖地,最后擦窗户。假设完成每件事情需要5分钟,其中拖地要拖5分钟然后等地面干燥10分钟,那么1个人完成所有事情的时间就是5+5+15+5=30分钟,2个进程则需要15分钟。
  • 如果需要更快呢?
  • 那就可以在等地面干燥时,先去擦窗户,擦完窗户之后再看看地面有没有干,如果干了,则事情就完成了。如果是1个人打扫房间,整个时间就直接缩短为擦桌子5分钟,扫地5分钟,5分钟拖地,5分钟擦窗户,再等待5分钟,共计25分钟。
  • 这就是异步编程。

但对于程序来说,cpu的计算速度实在是太快了,速度只能用快的飞起来形容,大部分任务中耗时的是磁盘读取和写入、等待服务器响应等等,所以cpu通常都是1秒就做完了自己的事情(其实远没有1秒,可能只要毫秒或者纳秒),而要傻傻的等磁盘或者网络加载到天荒地老。

此时在编程中就需要采取异步的思想,cpu做完自己的事情后,可以先去干别的事情,让磁盘或者网络慢慢的去写入和加载,cpu只需要偶尔过来瞄一眼看看磁盘有没有做完,如果磁盘做完了事情,那么cpu就继续往下做事,如果磁盘还没做完,cpu就继续做别的事情,直到磁盘做完。

Python异步模块asyncio/aiohttp(链家爬虫实例)内附教程分享


三、Python中实现异步


Python中关于异步的一个模块是asyncio,是英文asynchronous异步的,input输入和output输出三个单词的缩写。

Python3.6以前,async def只能用装饰器的方式来实现,await要通过yield from来实现。

import asyncio
@asyncio.coroutine
def to_do_something():
 print('do something')
 yield from asyncio.sleep(2)
 print('something done!')

在Python3.6后,可以通过关键词async def来定义一个coroutine协程,协程就相当于未来需要完成的任务,多个协程就是多个需要完成的任务,多个协程可以进一步封装到一个task对象中,task就是一个储存任务的盒子。此时,装在盒子里的任务并没有真正的运行,需要把它接入到一个监视器中使它运行,同时监视器还要持续不断的盯着盒子里的任务运行到了哪一步,这个持续不断的监视器就用一个循环对象loop来实现。

那么,整个过程应该就是通过下面这种方式来实现:

import asyncio
import time
#定义第1个协程,协程就是将要具体完成的任务,该任务耗时3秒,完成后显示任务完成
async def to_do_something(i):
 print('第{}个任务:任务启动...'.format(i))
 #遇到耗时的操作,await就会使任务挂起,继续去完成下一个任务
 await asyncio.sleep(i)
 print('第{}个任务:任务完成!'.format(i))
#定义第2个协程,用于通知任务进行状态
async def mission_running():
 print('任务正在执行...')
start = time.time()
#创建一个循环
loop = asyncio.get_event_loop()
#创建一个任务盒子tasks,包含了3个需要完成的任务
tasks = [asyncio.ensure_future(to_do_something(1)),
 asyncio.ensure_future(to_do_something(2)),
 asyncio.ensure_future(mission_running())]
#tasks接入loop中开始运行
loop.run_until_complete(asyncio.wait(tasks))
end = time.time()
print(end-start)

程序运行的过程看起来就像是这个样子:

Python异步模块asyncio/aiohttp(链家爬虫实例)内附教程分享

紫框--tasks中的第一个任务;

黄框--tasks中的第二个任务;

绿框--tasks中的第三个任务;

红线--程序运行路线

虚线--程序阻塞,出让函数的控制权

遇到需要等待的地方时,当前任务就会挂起,先去做第2个任务,然后继续挂起,再继续做第3个任务,最终完成任务时,总耗时则应该是2秒,而不是1秒+2秒=3秒。

程序实际运行的状态也确实如此,时间是2秒多一点,多出来的时间可能就是cpu本身的运算时间。

Python异步模块asyncio/aiohttp(链家爬虫实例)内附教程分享

需要说明的是,3个任务的运行顺序是按照tasks列表里的顺序进行的,如果把顺序换一下,上面的程序运行图就不一样了。

#下面两个tasks运行的结果是不一样的
tasks = [asyncio.ensure_future(to_do_something(1)),
 asyncio.ensure_future(to_do_something(2)),
 asyncio.ensure_future(mission_running())]
####################################################
tasks = [asyncio.ensure_future(to_do_something(1)),
 asyncio.ensure_future(mission_running()),
 asyncio.ensure_future(to_do_something(2))]

如果想通过异步的方式对网络发起请求,则还需要借助aiohttp模块,这个模块相当于requests模块的异步版本,使用方法极其相似,有小部分差异,只要习惯就好。

import asyncio
import aiohttp
async def get_info(url):
 async with aiohttp.ClientSession() as session:
 async with session.get(url,timeout=5) as resp:
 if resp.status != 200:
 url_lst_failed.append(url)
 else:
 url_lst_successed.append(url)
 r = await resp.text()

四、其它一些


为什么要用到循环loop?

程序是按照设定的顺序从头执行到尾,运行的次数也是完全按照设定。当在编写异步程序时,必然其中有部分程序的运行耗时是比较久的,需要先让出当前程序的控制权,让其在背后运行,让另一部分的程序先运行起来。当背后运行的程序完成后,也需要及时通知主程序已经完成任务可以进行下一步操作,但这个过程所需的时间是不确定的,需要主程序不断的监听状态,一旦收到了任务完成的消息,就开始进行下一步。loop就是这个持续不断的监视器。

链家爬虫代码可以看《链家爬虫代码_asyncio》

对比以前的版本,可以手动输入价格区间,采用二分法自动切割价格区间,将各个区间房屋数量缩到3000以内(链家限制了显示条目,最多只有3000)。

另外也还存在一个问题,在对比同步爬取和异步爬取得结果时,发现当url数量比较多时,会有较大一部分的url在采用异步爬取时不会被发起请求,导致异步的结果数量比同步的结果数量少了很多。希望有人能够解答这个问题。

我最后采用了一个笨办法解决了这个问题,将首次运行后未被发起请求的url再放入一个列表中,然后再次循环,直至所有url都被发起请求,最终实现抓取全部信息。


五、参考资料


  • 廖雪峰的官方网站--异步IO
  • Python 的异步 IO:Asyncio 简介
  • python中重要的模块--asyncio
  • aiohttp 简易使用教程
  • Python中单线程、多线程和多进程的效率对比实验

最后,想学习Python的小伙伴们!

请关注+私信回复:“学习”就可以拿到一份我为大家准备的Python学习资料!

Python异步模块asyncio/aiohttp(链家爬虫实例)内附教程分享

pytyhon学习资料

Python异步模块asyncio/aiohttp(链家爬虫实例)内附教程分享

python学习资料

相关推荐