zoukankan      html  css  js  c++  java
  • 爬虫性能相关解释、补充

    使用requests模块进行爬虫时如何做到高性能,高效利用资源,爬虫其实就是请求网页,那就要合理利用多进程、多线程已经协程了,简单举个例子,比如需要爬取几个网站首页('http://www.baidu.com/','https://www.cnblogs.com/','https://www.cnblogs.com/news/', 'https://cn.bing.com/','https://stackoverflow.com/',),那么如何最大限度利用资源,最快爬取到结果呢?

    最普通写法

    先给出最没有性能的普通写法:

    """
    相当于串行实现
    
    """
    import requests
    
    urls = [
        'http://www.baidu.com/',
        'https://www.cnblogs.com/',
        'https://www.cnblogs.com/news/',
        'https://cn.bing.com/',
        'https://stackoverflow.com/',
    ]
    
    for url in urls:
        response = requests.get(url)
        print(response)

    上面的五个网址,运行之后,在同一个进程中的同一个线程中进行,由于网络连接默认是阻塞的,相当于请求之间是串行的,如果一个请求花费两秒的话,那么总共会花费十秒钟,速度非常慢,如果是多核服务器,这个无法最大限度利用资源了。

    多线程写法

    再给出多线程的写法如下(需要使用线程池的话参考本博客的完整版爬虫):、

    """
    多线程实现
    
    """
    import requests
    import threading
    
    
    urls = [
        'http://www.baidu.com/',
        'https://www.cnblogs.com/',
        'https://www.cnblogs.com/news/',
        'https://cn.bing.com/',
        'https://stackoverflow.com/',
    ]
    
    def task(url):
        response = requests.get(url)
        print(response)
    
    for url in urls:
        t = threading.Thread(target=task,args=(url,))
        t.start()

    上面明显能够大幅度提高性能,如果一个两秒的话,这里使用了多线程,最快的话,五个请求总共花两秒也是可能的;但是这不是最好的选择,因为发请求非常快,几乎不占用什么资源,但是等待请求的响应是要话时间的,这就是典型的IO操作了(IO操作相当消耗性能);这里相当于将五个请求同时发出去了,然后大家同时等待响应回来;其实开五个线程也是会多占用一下内存的,这里就不讲开多进程去解决问题了,那样会占用更多的内存空间;那么如何能够即占用少量的内存,又能减少IO的消耗呢,协程将是一个很好的选择。

    协程写法:

    """
    协程实现IO切换
    pip3 install gevent
    gevent内部调用greenlet(实现了协程)。
    """
    from gevent import monkey; monkey.patch_all()
    import gevent
    import requests
    
    
    def func(url):
        response = requests.get(url)
        print(response)
    
    urls = [
        'http://www.baidu.com/',
        'https://www.cnblogs.com/',
        'https://www.cnblogs.com/news/',
        'https://cn.bing.com/',
        'https://stackoverflow.com/',
    ]
    spawn_list = []
    for url in urls:
        spawn_list.append(gevent.spawn(func, url))
    
    gevent.joinall(spawn_list)

    协程本质上是一个线程,能够在多个任务之间切换来节省一些IO时间,协程中任务之间的切换也消耗时间,但是开销要远远小于进程线程之间的切换,相当于将同一个线程切片,当线程中遇到IO操作时就会切换到其他任务,也就是发其他请求;也就是一瞬间能够将所有请求发出去,然后等待响应回来,这样及节省了内存,又减少了IO消耗性能;但是在处理响应的时候,可能要做运算的东西,那时候就不能够使用协程了,反而会将低效率,因为协程并不能利用多核,这是使用多线程比较好;最后写入文件的时候又是典型的IO操作,这时还是用协程合适。类似的提高爬虫性能的与之有一拼的还可以使用Twisted模块实现。

    基于事件循环的异步非阻塞模块Twisted

    Twisted是用Python实现的基于事件驱动的异步非阻塞网络引擎框架,其实scrapy框架能实现高并发也是基于该模块来实现的;这里给出如何基于Twisted模块解决上面问题的代码:

    """
    基于事件循环的异步非阻塞模块:Twisted
    """
    from twisted.web.client import getPage, defer
    from twisted.internet import reactor
    
    def stop_loop(arg):
        reactor.stop()
    
    
    def get_response(contents):
        print(contents)
    
    deferred_list = []
    
    url_list = [
        'http://www.baidu.com/',
        'https://www.cnblogs.com/',
        'https://www.cnblogs.com/news/',
        'https://cn.bing.com/',
        'https://stackoverflow.com/',
    ]
    
    for url in url_list:
        deferred = getPage(bytes(url, encoding='utf8'))
        deferred.addCallback(get_response)
        deferred_list.append(deferred)
    
    
    dlist = defer.DeferredList(deferred_list)
    dlist.addBoth(stop_loop)
    
    reactor.run()

    模仿版写法

    上述的协程和Twisted内部是如何实现能够切换IO操作的呢;这里给出一个模仿的版本,给出了内部如何建立连接并能够不阻塞还能切换IO操作的方法,这就要使用到IO多路复用了(Python中的select模块):

    # 自定义的异步非阻塞模块
    import socket
    import select
    
    class ChunSheng(object):
    
        def __init__(self):
            self.socket_list = []
            self.conn_list = []
    
            self.conn_func_dict = {}
    
        def add_request(self,url_func):
            conn = socket.socket()
            conn.setblocking(False)  # 将所有连接改为非阻塞的
            try:
                conn.connect((url_func[0],80))
            except BlockingIOError as e:
                pass
            self.conn_func_dict[conn] = url_func[1]
    
            self.socket_list.append(conn)
            self.conn_list.append(conn)
    
        def run(self):
            """
            检测self.socket_list中的5个socket对象是否连接成功
            :return:
            """
            while True:
                #   select.select
                #   第一个参数: 用于检测其中socket是否已经获取到响应内容
                #   第二个参数: 用于检测其中socket是否已经连接成功
    
                # 第一个返回值 r:具体是那一个socket获取到结果
                # 第二个返回值 r:具体是那一个socket连接成功
                r,w,e = select.select(self.socket_list,self.conn_list,[],0.05)
                for sock in w: # [socket1,socket2]
                    sock.send(b'GET / http1.1
    host:xxxx.com
    
    ')
                    self.conn_list.remove(sock)
    
                for sock in r:
                    data = sock.recv(8096)
                    func = self.conn_func_dict[sock]
                    func(data)
                    sock.close()
                    self.socket_list.remove(sock)
    
                if not self.socket_list:
                    break

    向使用协程和Twisted模块一样使用自定义的模块来解决问题的用法:

    from chun import ChunSheng
    
    def callback1(data):
        print('下载完成',data)
    
    def callback2(data):
        print('下载完成',data)
    
    chun = ChunSheng()
    urls = [
        ('www.baidu.com',callback1),
        ('www.cnblogs.com',callback1),
        ('www.pythonav.com',callback2),
        ('www.bing.com',callback2),
        ('www.stackoverflow.com',callback2),
    ]
    for url in urls:
        chun.add_request(url)
    
    chun.run()
    作者:E-QUAL
    出处:https://www.cnblogs.com/liujiajia_me/
    本文版权归作者和博客园共有,不得转载,未经作者同意参考时必须保留此段声明,且在文章页面明显位置给出原文连接。
                                                本文内容参考如下网络文献得来,用于个人学习,如有侵权,请您告知删除修改。
                                                参考链接:https://www.cnblogs.com/linhaifeng/
                                                                 https://www.cnblogs.com/yuanchenqi/
                                                                 https://www.cnblogs.com/Eva-J/
                                                                 https://www.cnblogs.com/jin-xin/
                                                                 https://www.cnblogs.com/liwenzhou/
                                                                 https://www.cnblogs.com/wupeiqi/
  • 相关阅读:
    BBS登入和数据库迁移部分
    Auth组件
    【作业】返回一个整形数组中最大子数组地和——当维度达到二维/*待完善*/
    【作业】返回一个整形数组中最大子数组地和——当数量达到10亿
    软件工程课程周学习进度报告——第三周
    软件工程课程周学习进度报告——第二周
    软件工程第一周开课博客
    【作业】返回一个整形数组中最大子数组地和
    《人月神话》读后感其三——第二个系统问题
    《人月神话》读后感其二——从未考虑过的多人协作问题
  • 原文地址:https://www.cnblogs.com/liujiajia_me/p/12528958.html
Copyright © 2011-2022 走看看