爬虫性能相关解释、补充
使用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\r\nhost:xxxx.com\r\n\r\n') 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()
出处:https://www.cnblogs.com/liujiajia_me/
本文版权归作者和博客园共有,不得转载,未经作者同意参考时必须保留此段声明,且在文章页面明显位置给出原文连接。