python实现并发爬虫

在进行单个爬虫抓取的时候,我们不可能按照一次抓取一个url的方式进行网页抓取,这样效率低,也浪费了cpu的资源。目前python上面进行并发抓取的实现方式主要有以下几种:进程,线程,协程。进程不在的讨论范围之内,一般来说,进程是用来开启多个spider,比如我们开启了4进程,同时派发4个spider进行网络抓取,每个spider同时抓取4个url。

所以,我们今天讨论的是,在单个爬虫的情况下,尽可能的在同一个时间并发抓取,并且抓取的效率要高。

一.顺序抓取

顺序抓取是最最常见的抓取方式,一般初学爬虫的朋友就是利用这种方式,下面是一个测试代码,顺序抓取8个url,我们可以来测试一下抓取完成需要多少时间:

HEADERS = {‘Accept‘: ‘text/html,application/xhtml+xml,application/xml;q=0.9‘,
   ‘Accept-Language‘: ‘zh-CN,zh;q=0.8‘,
   ‘Accept-Encoding‘: ‘gzip, deflate‘,}
URLS = [‘http://www.cnblogs.com/moodlxs/p/3248890.html‘,
        ‘https://www.zhihu.com/topic/19804387/newest‘,
        ‘http://blog.csdn.net/yueguanghaidao/article/details/24281751‘,
        ‘https://my.oschina.net/visualgui823/blog/36987‘,
        ‘http://blog.chinaunix.net/uid-9162199-id-4738168.html‘,
        ‘http://www.tuicool.com/articles/u67Bz26‘,
        ‘http://rfyiamcool.blog.51cto.com/1030776/1538367/‘,
        ‘http://itindex.net/detail/26512-flask-tornado-gevent‘]                               

#url为随机获取的一批url                                                                               

def func():
    """
    顺序抓取
    """
    import requests
    import time
    urls = URLS
    headers = HEADERS
    headers[‘user-agent‘] = "Mozilla/5.0+(Windows+NT+6.2;+WOW64)+AppleWebKit/537" \
                            ".36+(KHTML,+like+Gecko)+Chrome/45.0.2454.101+Safari/537.36"
    print(u‘顺序抓取‘)
    starttime= time.time()
    for url in urls:
        try:
            r = requests.get(url, allow_redirects=False, timeout=2.0, headers=headers)
        except:
            pass
        else:
            print(r.status_code, r.url)
    endtime=time.time()
    print(endtime-starttime)                                                                  

func()                                                                                        

我们直接采用内建的time.time()来计时,较为粗略,但可以反映大概的情况。下面是顺序抓取的结果计时:

可以从图片中看到,显示的顺序与urls的顺序是一模一样的,总共耗时为7.763269901275635秒,一共8个url,平均抓取一个大概需要0.97秒。总体来看,还可以接受。

二.多线程抓取

线程是python内的一种较为不错的并发方式,我们也给出相应的代码,并且为每个url创建了一个线程,一共8线程并发抓取,下面的代码:

下面是我们运行8线程的测试代码:

HEADERS = {‘Accept‘: ‘text/html,application/xhtml+xml,application/xml;q=0.9‘,
   ‘Accept-Language‘: ‘zh-CN,zh;q=0.8‘,
   ‘Accept-Encoding‘: ‘gzip, deflate‘,}
URLS = [‘http://www.cnblogs.com/moodlxs/p/3248890.html‘,
        ‘https://www.zhihu.com/topic/19804387/newest‘,
        ‘http://blog.csdn.net/yueguanghaidao/article/details/24281751‘,
        ‘https://my.oschina.net/visualgui823/blog/36987‘,
        ‘http://blog.chinaunix.net/uid-9162199-id-4738168.html‘,
        ‘http://www.tuicool.com/articles/u67Bz26‘,
        ‘http://rfyiamcool.blog.51cto.com/1030776/1538367/‘,
        ‘http://itindex.net/detail/26512-flask-tornado-gevent‘]                                            

def thread():
    from threading import Thread
    import requests
    import time
    urls = URLS
    headers = HEADERS
    headers[‘user-agent‘] = "Mozilla/5.0+(Windows+NT+6.2;+WOW64)+AppleWebKit/537.36+" \
                            "(KHTML,+like+Gecko)+Chrome/45.0.2454.101+Safari/537.36"
    def get(url):
        try:
            r = requests.get(url, allow_redirects=False, timeout=2.0, headers=headers)
        except:
            pass
        else:
            print(r.status_code, r.url)                                                                    

    print(u‘多线程抓取‘)
    ts = [Thread(target=get, args=(url,)) for url in urls]
    starttime= time.time()
    for t in ts:
        t.start()
    for t in ts:
        t.join()
    endtime=time.time()
    print(endtime-starttime)
thread()                                                                                                   

多线程抓住的时间如下:

可以看到相较于顺序抓取,8线程的抓取效率明显上升了3倍多,全部完成只消耗了2.154秒。可以看到显示的结果已经不是urls的顺序了,说明每个url各自完成的时间都是不一样的。线程就是在一个进程中不断的切换,让每个线程各自运行一会,这对于网络io来说,性能是非常高的。但是线程之间的切换是挺浪费资源的。

三.gevent并发抓取

gevent是一种轻量级的协程,可用它来代替线程,而且,他是在一个线程中运行,机器资源的损耗比线程低很多。如果遇到了网络io阻塞,会马上切换到另一个程序中去运行,不断的轮询,来降低抓取的时间
下面是测试代码:

HEADERS = {‘Accept‘: ‘text/html,application/xhtml+xml,application/xml;q=0.9‘,
   ‘Accept-Language‘: ‘zh-CN,zh;q=0.8‘,
   ‘Accept-Encoding‘: ‘gzip, deflate‘,}

URLS = [‘http://www.cnblogs.com/moodlxs/p/3248890.html‘,
        ‘https://www.zhihu.com/topic/19804387/newest‘,
        ‘http://blog.csdn.net/yueguanghaidao/article/details/24281751‘,
        ‘https://my.oschina.net/visualgui823/blog/36987‘,
        ‘http://blog.chinaunix.net/uid-9162199-id-4738168.html‘,
        ‘http://www.tuicool.com/articles/u67Bz26‘,
        ‘http://rfyiamcool.blog.51cto.com/1030776/1538367/‘,
        ‘http://itindex.net/detail/26512-flask-tornado-gevent‘]

def main():
    """
    gevent并发抓取
    """
    import requests
    import gevent
    import time

    headers = HEADERS
    headers[‘user-agent‘] = "Mozilla/5.0+(Windows+NT+6.2;+WOW64)+AppleWebKit/537.36+"                             "(KHTML,+like+Gecko)+Chrome/45.0.2454.101+Safari/537.36"
    urls = URLS
    def get(url):
        try:
            r = requests.get(url, allow_redirects=False, timeout=2.0, headers=headers)
        except:
            pass
        else:
            print(r.status_code, r.url)

    print(u‘基于gevent的并发抓取‘)
    starttime= time.time()
    g = [gevent.spawn(get, url) for url in urls]
    gevent.joinall(g)
    endtime=time.time()
    print(endtime - starttime)
main()

协程的抓取时间如下:

正常情况下,gevent的并发抓取与多线程的消耗时间差不了多少,但是可能是我网络的原因,或者机器的性能的原因,时间有点长......,请各位小主在自己电脑进行跑一下看运行时间

四.基于tornado的coroutine并发抓取

tornado中的coroutine是python中真正意义上的协程,与python3中的asyncio几乎是完全一样的,而且两者之间的future是可以相互转换的,tornado中有与asyncio相兼容的接口。
下面是利用tornado中的coroutine进行并发抓取的代码:

利用coroutine编写并发略显复杂,但这是推荐的写法,如果你使用的是python3,强烈建议你使用coroutine来编写并发抓取。

下面是测试代码:

HEADERS = {‘Accept‘: ‘text/html,application/xhtml+xml,application/xml;q=0.9‘,
   ‘Accept-Language‘: ‘zh-CN,zh;q=0.8‘,
   ‘Accept-Encoding‘: ‘gzip, deflate‘,}

URLS = [‘http://www.cnblogs.com/moodlxs/p/3248890.html‘,
        ‘https://www.zhihu.com/topic/19804387/newest‘,
        ‘http://blog.csdn.net/yueguanghaidao/article/details/24281751‘,
        ‘https://my.oschina.net/visualgui823/blog/36987‘,
        ‘http://blog.chinaunix.net/uid-9162199-id-4738168.html‘,
        ‘http://www.tuicool.com/articles/u67Bz26‘,
        ‘http://rfyiamcool.blog.51cto.com/1030776/1538367/‘,
        ‘http://itindex.net/detail/26512-flask-tornado-gevent‘]
import time
from tornado.gen import coroutine
from tornado.ioloop import IOLoop
from tornado.httpclient import AsyncHTTPClient, HTTPError
from tornado.httpclient import HTTPRequest

#urls与前面相同
class MyClass(object):

    def __init__(self):
        #AsyncHTTPClient.configure("tornado.curl_httpclient.CurlAsyncHTTPClient")
        self.http = AsyncHTTPClient()

    @coroutine
    def get(self, url):
        #tornado会自动在请求首部带上host首部
        request = HTTPRequest(url=url,
                            method=‘GET‘,
                            headers=HEADERS,
                            connect_timeout=2.0,
                            request_timeout=2.0,
                            follow_redirects=False,
                            max_redirects=False,
                            user_agent="Mozilla/5.0+(Windows+NT+6.2;+WOW64)+AppleWebKit/537.36+                            (KHTML,+like+Gecko)+Chrome/45.0.2454.101+Safari/537.36",)
        yield self.http.fetch(request, callback=self.find, raise_error=False)

    def find(self, response):
        if response.error:
            print(response.error)
        print(response.code, response.effective_url, response.request_time)

class Download(object):

    def __init__(self):
        self.a = MyClass()
        self.urls = URLS

    @coroutine
    def d(self):
        print(u‘基于tornado的并发抓取‘)
        starttime = time.time()
        yield [self.a.get(url) for url in self.urls]
        endtime=time.time()
        print(endtime-starttime)

if __name__ == ‘__main__‘:
    dd = Download()
    loop = IOLoop.current()
    loop.run_sync(dd.d)

抓取的时间如下:

可以看到总共花费了128087秒,而这所花费的时间恰恰就是最后一个url抓取所需要的时间,tornado中自带了查看每个请求的相应时间。我们可以从图中看到,最后一个url抓取总共花了1.28087秒,相较于其他时间大大的增加,这也是导致我们消耗时间过长的原因。那可以推断出,前面的并发抓取,也在这个url上花费了较多的时间。

总结:
以上测试其实非常的不严谨,因为我们选取的url的数量太少了,完全不能反映每一种抓取方式的优劣。如果有一万个不同的url同时抓取,那么记下总抓取时间,是可以得出一个较为客观的结果的。
并且,已经有人测试过,多线程抓取的效率是远不如gevent的。所以,如果你使用的是python2,那么我推荐你使用gevent进行并发抓取;如果你使用的是python3,我推荐你使用tornado的http客户端结合coroutine进行并发抓取。从上面的结果来看,tornado的coroutine是高于gevent的轻量级的协程的。但具体结果怎样,我没测试过。

原文地址:https://www.cnblogs.com/sui776265233/p/10051238.html

时间: 2024-08-30 18:21:34

python实现并发爬虫的相关文章

用Python写网络爬虫(高清版)PDF

用Python写网络爬虫(高清版)PDF 百度网盘 链接:https://pan.baidu.com/s/1kdRFAEuze-A9ToWVXHoCXw 提取码:8ib1 复制这段内容后打开百度网盘手机App,操作更方便哦 内容简介  · · · · · · 作为一种便捷地收集网上信息并从中抽取出可用信息的方式,网络爬虫技术变得越来越有用.使用Python这样的简单编程语言,你可以使用少量编程技能就可以爬取复杂的网站. <用Python写网络爬虫>作为使用Python来爬取网络数据的杰出指南,

golang 并发爬虫

golang 并发爬虫 之前的一篇文章中展示了一个使用 python 和 aiohttp 搭建的并发爬虫,这篇文章使用 golang 实现同样的功能,旨在理解 python async 异步和 golang 异步编程之间的差别. 代码 package main import ( json "encoding/json" "fmt" ioutil "io/ioutil" "net/http" ) func httpGet(url

Python 3 并发编程多进程之队列(推荐使用)

Python 3 并发编程多进程之队列(推荐使用) 进程彼此之间互相隔离,要实现进程间通信(IPC),multiprocessing模块支持两种形式:队列和管道,这两种方式都是使用消息传递的. 可以往队列里放任意类型的数据 创建队列的类(底层就是以管道和锁定的方式实现): 1 Queue([maxsize]):创建共享的进程队列,Queue是多进程安全的队列,可以使用Queue实现多进程之间的数据传递. 参数介绍: 1 maxsize是队列中允许最大项数,省略则无大小限制. 方法介绍: 1.主要

[踩坑]python实现并行爬虫

问题背景:指定爬虫depth.线程数, python实现并行爬虫   思路:    单线程 实现爬虫类Fetcher                 多线程 threading.Thread去调Fetcher  方法:Fetcher 中,用urllib.urlopen打开指定url,读取信息: response = urllib.urlopen(self.url) content = response.read() 但是这样有问题, 比如对于www.sina.com来说,读出来的content是

Windows 环境下运用Python制作网络爬虫

import webbrowser as web import time import os i = 0 MAXNUM = 1 while i <= MAXNUM: web.open_new_tab('要刷的网络地址') os.system('taskkill /F /IM 浏览器文件名(chrome.exe)') i += 1 else: print 'happly day!' 代码和简单只要一个第三方的函数和调用系统的文件就OK了.记住给要刷的次数定值,不然电脑就不好受了! Windows

Python 3 并发编程多进程之进程同步(锁)

Python 3 并发编程多进程之进程同步(锁) 进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的,竞争带来的结果就是错乱,如何控制,就是加锁处理. 1.多个进程共享同一打印终端 from multiprocessing import Process import os,time def work(): print('%s is running' %os.getpid()) time.sleep(2) print('%s is done' %os.g

dota玩家与英雄契合度的计算器,python语言scrapy爬虫的使用

首发:个人博客,更新&纠错&回复 演示地址在这里,代码在这里. 一个dota玩家与英雄契合度的计算器(查看效果),包括两部分代码: 1.python的scrapy爬虫,总体思路是page->model->result,从网页中提取数据,组成有意义的数据结构,再拿这数据结构做点什么. 在这个项目中,爬虫的用处是从游久网dota数据库上抓取dota英雄和物品的数据和照片存到本地磁盘,数据存为json格式,方便在网页应用中直接使用. 2.网页应用,使用dota英雄数据.自己编写的小伙

python实现图片爬虫

#encoding:utf-8 import sys reload(sys) sys.setdefaultencoding('utf8') from sgmllib import SGMLParser import re import urllib class URLLister(SGMLParser): def start_a(self, attrs): url = [v for k, v in attrs if k=='href'] if url : urll = url[0] else :

Python Scrapy 自动爬虫注意细节

一.首次爬取模拟浏览器 在爬虫文件中,添加start_request函数.如: def start_requests(self): ua = {"User-Agent": 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.104 Safari/537.36 Core/1.53.2050.400 QQBrowser/9.5.10169.400'} yie