一个实时爬取的接口,也是针对这个一直在被我博客的网站,跟他家的抗争让我收益良多
先讲一讲这一次的问题所在,因为时时爬取尽量需要短时间获得到数据,越快越好。按照以前的思维必定是开线程,但是问题来了,此网站的验证机制可谓是十分的变态,只要是换个端口就会失效,需要通过执行一大段js重新获取。所以开线程是基本不可能了,如果每个线程都去获取一次cookie比线性执行还要慢。
首先想到的是用node来重写这个接口,单线程异步可谓十分适合这种情况,但是node的cookie维护的库还是十分不熟悉,用新的语言再去实现着一大堆麻烦的验证机制想必又是一件十分耗时的事情。(当然也是因为比较懒的原因,以后有时间再来慢慢研究)
想到前几天一个同学向我提过一个Python的异步库(所以所有你人为没有意义的事情其实都是在为以后做着准备),又去问了一下,得知了gevent。看了下文档,编写方式类似多线程,执行方式类似node,单线程异步,很简单的避开了io阻塞。安装上遇到些问题,见上一篇笔记。
gevent是第三方库,通过greenlet实现协程,其基本思想是:
当一个greenlet遇到IO操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO。
简单记录下gevent的使用方法备忘。
- 首先是引入
由于切换是在IO操作时自动完成,所以gevent需要修改Python自带的一些标准库,这一过程在启动时通过monkey patch完成:
from gevent import monkey
import gevent
monkey.patch_socket()
- 创建协程
上一段简单的程序,url惯例隐藏
# 一个需要异步执行的函数
def gRequestZ(url, relist):
request = Request(url, headers=zxheaders)
data = zxopener.open(request).read()
relist.append(data)
def getZJson(html):
soup = BeautifulSoup(html, 'lxml')
flag = 0
glist = []
relist = []
for tr in soup.find('table').find_all('tr'):
if flag == 0:
flag = 1
continue
td = tr.find_all('td')
# 向list添加创建的协程
glist.append(
gevent.spawn(gRequestZ, "http://xxxxxxxxxx")
# 类似于多线程,等待所有的携程执行完毕
# 只是提供了一个比较统一的方法,不用再for了
gevent.joinall(glist)
rejson = ",".join(relist)
rejson = "[" + rejson + "]"
return rejson
由于本质是单线程的也不用去加锁什么的,很方便,想到golang对协程的支持非常强大,觉得以后golang一定会大有用武之地,近期学习目标。
- 协程切换
要让greenlet交替运行,可以通过gevent.sleep()交出控制权:
def f(n):
for i in range(n):
print gevent.getcurrent(), i
gevent.sleep(0)
使用gevent,可以获得极高的并发性能,但gevent只能在Unix/Linux下运行,在Windows下不保证正常安装和运行。
由于gevent是基于IO切换的协程,所以最神奇的是,我们编写的Web App代码,不需要引入gevent的包,也不需要改任何代码,仅仅在部署的时候,用一个支持gevent的WSGI服务器,立刻就获得了数倍的性能提升。具体部署方式可以参考后续“实战”-“部署Web App”一节。