python爬虫日记本Python开发者程序员

python爬虫:基于gevent异步爬虫的原理及实现

2018-01-18  本文已影响695人  Tony带不带水
这张真的很好看啊
  当你写爬虫写了一段时间,你开始觉得这个爬虫怎么那么慢,明明代码优美没有bug。所以你不会去想方设法降低你爬虫的时间复杂度或者空间复杂度,你清楚的知道机器的大部分时间花在了网络IO上。想提速怎么办?
  加钱买带宽买机器啊!好的本文结束,大家散了散了。
  哎哎哎,你们刀放下我好好说话。
  看标题猜到,本文爬虫提速方式是用异步机制。先看看这个与你的同步爬虫有什么差别?你需要先了解两(四)个概念:

  你一突然一拍脑袋,完蛋怎么跟线程有关系,不是说python有GIL,多线程都是假的。
  对啊对啊,快来学golang吧。哎哎哎?怎么又是你,把刀放下好好说话。
  python因为GIL并不能做到并行,但可以做到并发。对于计算密集型应用,python的多线程确实没啥用。但对于向网页提交多个request这种IO密集型应用,并发就很有用了。嗯...说你的爬虫不是cpu密集型,是IO密集型你没什么意见吧。
  简单说三个大家应该多多少少了解的概念(为不影响阅读,详细概念我会放在本文最后附录部分)。

  别急,马上引出gevent,基础知识还是要讲讲的。之前说python的多线程其实是串行,但是的确可以提高IO密集型应用的速度,为什么这里不用多线程而要基于gevent(协程)?

Gevent安装:

  直接输入pip install gevent

Gevent核心部分:

  gevent中的主要模式, 它是以C扩展模块形式接入Python的轻量级协程。 全部运行在主程序操作系统进程的内部,但它们被程序员协作式地调度

  先看代码吧,结合代码说:

import gevent
import greenlet
def callback(event, args):
    print event, args[0], '===:>>>>', args[1]

# 想象成你的爬虫1
def foo():
    print('Running in foo')
    # 这个时候做了网络IO
    gevent.sleep(0)
    print('Explicit context switch to foo again')

# 想象成你的爬虫2
def bar():
    print('Explicit context to bar')
    # 这个时候做了网络IO
    gevent.sleep(0)
    print('Implicit context switch back to bar')

print 'main greenlet info: ', greenlet.greenlet.getcurrent()
print 'hub info', gevent.get_hub()
oldtrace = greenlet.settrace(callback)
        
gevent.joinall([
    gevent.spawn(foo),
    gevent.spawn(bar),
])
greenlet.settrace(oldtrace)

  你可以直接代码拷过去运行一下,你可以看到gevent的调度方式。我将其转换成图片方便大家阅读理解。你会发现多了个hub,每次从hub切换到一个greenlet后,都会回到hub,然而这就是gevent的关键。

Gevent中调度方式

  采用这种模式个人理解是:

涉及数据结构:

  嗯...有兴趣深入了解的看官方文档吧?这里主要讲爬虫,爬虫用的到的地方给了解释。

  实际应用到你的爬虫中:
  实在抱歉啊,我尽可能的少说概念了,可是直接上代码就跟网上其他我看的教程一样云里雾里,我觉得这样不是很好,好了快看代码吧。

import gevent
from gevent import Greenlet
from gevent import monkey
import gevent.pool
# 在进行IO操作时,默认切换协程
monkey.patch_all()

# 假设我在这里调用了你的爬虫类接口
def run_Spider(url):
    # do anything what u want
    pass
    
if __name__ == '__main__':
    # 假如你的url写在文件中 用第一个参数传进来
    import sys
    # 限制并发数20
    pool = gevent.pool.Pool(20)
    # 这里也可以用pool.map,我这么写比较无脑
    threads = []
    with open(sys.argv[1], "r") as f:
        for line in f:
            threads.append(pool.spawn(run_Spider,line.strip()))
    gevent.joinall(threads)
    print "finish"

  这样就实现一个基本异步爬虫更加复杂的异步也逃不过这些基础的东西。如果说的不到位,大家指正啊没事,评论私信都行,不想写那么多概念的,可是好像不写不行,会更加云里雾里。


附录:

进程

线程

协程

上一篇 下一篇

猜你喜欢

热点阅读