Python 爬虫六 性能相关
前面已经讲过了爬虫的两大基础模块:
requests模块:用来伪造请求爬取数据
bs4模块:用来整理,提取数据
当我们真正的开始有需求的时候通常都是批量爬取url这样的。那如何批量爬取呢?
按照正常的思路,我们开始用不同的实现方式,写一些实例代码。
1、串行
串行,如字面意思,就是写个for 循环一个个执行:
import requests def fetch_async(url):
response = requests.get(url)
return response url_list = ['http://www.github.com', 'http://www.bing.com'] for url in url_list:
fetch_async(url)
时间进度表示:
for 循环每次为url做处理的时候执行fetch_async方法,首先发送请求到目的url的服务器(图片中的请求),之后程序就会阻塞住,等待目的服务器的结果返回(阻塞等待时间),接收到服务器的返回数据后,进行再次数据处理。说到这里,一定都会觉得这是最差的方案了。
2、并行 多线程or多进程
from concurrent.futures import ThreadPoolExecutor # 线程处理池
import requests def fetch_async(url):
response = requests.get(url)
return response url_list = ['http://www.github.com', 'http://www.bing.com']
pool = ThreadPoolExecutor(5) # 设置线程池的容量
for url in url_list:
pool.submit(fetch_async, url) # 提交执行函数和参数
pool.shutdown(wait=True) # 取决于最慢的线程的执行时间来结束线程池
from concurrent.futures import ProcessPoolExecutor # 进程池
import requests def fetch_async(url):
response = requests.get(url)
return response url_list = ['http://www.github.com', 'http://www.bing.com']
pool = ProcessPoolExecutor(5) # 设置进程池的最大容量
for url in url_list:
pool.submit(fetch_async, url) # 提交到进程池,带上目的函数跟参数
pool.shutdown(wait=True) # 取决于最慢的进程的执行时间来结束进程池
上面的两种是相似的,通过多线程或多进程来进行并行处理,增加单位时间段内的并发量来提高效率。
补充:带回调函数的多线程
from concurrent.futures import ThreadPoolExecutor
import requests def fetch_async(url):
response = requests.get(url)
return response def callback(future): # 线程池内函数的返回结果
print(future.result()) url_list = ['http://www.github.com', 'http://www.bing.com']
pool = ThreadPoolExecutor(5)
for url in url_list:
v = pool.submit(fetch_async, url)
v.add_done_callback(callback) # 线程处理完成之后调用此函数
pool.shutdown(wait=True)
线程池回调函数
进程池的回调函数是一样的:
v.add_done_callback(callback)
通过上述方法,均可以实现对请求性能的提交,但是,从之前的解析图中,可以发现,当发送请求后,无论是串行还是并行,放大到每一次请求,都不可避免的遇到了阻塞,等待返回结果这个过程。IO阻塞时会造成线程和进程的浪费。所以这时候我们引入异步IO。
异步IO
不了解异步IO的可以看一下之前的这个博客:http://www.cnblogs.com/wuzdandz/p/7633386.html
异步IO的模块已经有很多封装好的,这里为大家介绍几个:
a、asyncio
import asyncio # python3.3之后的一个内置模块 @asyncio.coroutine
def func1():
print('before...func1......')
# 异步调用
yield from asyncio.sleep(5)
print('end...func1......') tasks = [func1(), func1()] loop = asyncio.get_event_loop() # 获取EventLoop
loop.run_until_complete(asyncio.gather(*tasks)) # 执行coroutine
loop.close()
有一个缺点是asyncio没有提供发送http请求的功能,修改一下代码
import asyncio @asyncio.coroutine
def fetch_async(host, url='/'):
print(host, url)
reader, writer = yield from asyncio.open_connection(host, 80) request_header_content = """GET %s HTTP/1.0\r\nHost: %s\r\n\r\n""" % (url, host,) # 定制http请求字串
request_header_content = bytes(request_header_content, encoding='utf-8') writer.write(request_header_content)
yield from writer.drain()
text = yield from reader.read()
print(host, url, text)
writer.close() tasks = [
fetch_async('www.cnblogs.com', '/wuzdandz/'),
fetch_async('dig.chouti.com', '/pic/show?nid=4073644713430508&lid=10273091')
] loop = asyncio.get_event_loop()
results = loop.run_until_complete(asyncio.gather(*tasks))
loop.close()
b、asyncio + aiohttp
import aiohttp
import asyncio @asyncio.coroutine
def fetch_async(url):
print(url)
response = yield from aiohttp.request('GET', url)
# data = yield from response.read()
# print(url, data)
print(url, response)
response.close() tasks = [fetch_async('http://www.google.com/'), fetch_async('http://www.chouti.com/')] event_loop = asyncio.get_event_loop()
results = event_loop.run_until_complete(asyncio.gather(*tasks))
event_loop.close()
c、asyncio + requests
import asyncio
import requests @asyncio.coroutine
def fetch_async(func, *args):
loop = asyncio.get_event_loop()
future = loop.run_in_executor(None, func, *args)
response = yield from future
print(response.url, response.content) tasks = [
fetch_async(requests.get, 'http://www.cnblogs.com/wuzdandz/'),
fetch_async(requests.get, 'http://dig.chouti.com/pic/show?nid=4073644713430508&lid=10273091')
] loop = asyncio.get_event_loop()
results = loop.run_until_complete(asyncio.gather(*tasks))
loop.close()
d、gevent + requests
import gevent import requests
from gevent import monkey monkey.patch_all() def fetch_async(method, url, req_kwargs):
print(method, url, req_kwargs)
response = requests.request(method=method, url=url, **req_kwargs)
print(response.url, response.content) # ##### 发送请求 #####
gevent.joinall([
gevent.spawn(fetch_async, method='get', url='https://www.python.org/', req_kwargs={}),
gevent.spawn(fetch_async, method='get', url='https://www.yahoo.com/', req_kwargs={}),
gevent.spawn(fetch_async, method='get', url='https://github.com/', req_kwargs={}),
]) # ##### 发送请求(协程池控制最大协程数量) #####
# from gevent.pool import Pool
# pool = Pool(None)
# gevent.joinall([
# pool.spawn(fetch_async, method='get', url='https://www.python.org/', req_kwargs={}),
# pool.spawn(fetch_async, method='get', url='https://www.yahoo.com/', req_kwargs={}),
# pool.spawn(fetch_async, method='get', url='https://www.github.com/', req_kwargs={}),
# ])
e、grequests
import grequests request_list = [
grequests.get('http://httpbin.org/delay/1', timeout=0.001),
grequests.get('http://fakedomain/'),
grequests.get('http://httpbin.org/status/500')
] # ##### 执行并获取响应列表 #####
# response_list = grequests.map(request_list)
# print(response_list) # ##### 执行并获取响应列表(处理异常) #####
# def exception_handler(request, exception):
# print(request,exception)
# print("Request failed") # response_list = grequests.map(request_list, exception_handler=exception_handler)
# print(response_list)
f、Twisted实例
from twisted.web.client import getPage, defer
from twisted.internet import reactor def all_done(arg):
reactor.stop() def callback(contents):
print(contents) deferred_list = [] url_list = ['http://www.bing.com', 'http://www.baidu.com', ]
for url in url_list:
deferred = getPage(bytes(url, encoding='utf8'))
deferred.addCallback(callback)
deferred_list.append(deferred) dlist = defer.DeferredList(deferred_list)
dlist.addBoth(all_done) reactor.run()
g、Tornado
from tornado.httpclient import AsyncHTTPClient
from tornado.httpclient import HTTPRequest
from tornado import ioloop def handle_response(response):
"""
处理返回值内容(需要维护计数器,来停止IO循环),调用 ioloop.IOLoop.current().stop()
:param response:
:return:
"""
if response.error:
print("Error:", response.error)
else:
print(response.body) def func():
url_list = [
'http://www.baidu.com',
'http://www.bing.com',
]
for url in url_list:
http_client = AsyncHTTPClient()
http_client.fetch(HTTPRequest(url), handle_response) ioloop.IOLoop.current().add_callback(func)
ioloop.IOLoop.current().start()
h、Twisted更多
from twisted.internet import reactor
from twisted.web.client import getPage
import urllib.parse def one_done(arg):
print(arg)
reactor.stop() post_data = urllib.parse.urlencode({'check_data': 'adf'})
post_data = bytes(post_data, encoding='utf8')
headers = {b'Content-Type': b'application/x-www-form-urlencoded'}
response = getPage(bytes('http://dig.chouti.com/login', encoding='utf8'),
method=bytes('POST', encoding='utf8'),
postdata=post_data,
cookies={},
headers=headers)
response.addBoth(one_done) reactor.run()
以上都是Python内置或者是第三方模块提供异步IO请求,异步IO请求的本质则是 非阻塞Socket + IO多路复用,之前根据select写过一个异步的FTP,现在来写一个自定义的牛逼的异步爬虫模块
X、自定义
# 并发 自定义异步模块 单线程、异步、回调
import socket
import select class HttpRequest(object):
def __init__(self, sk, item):
self.sock = sk
self.item = item def fileno(self):
return self.sock.fileno() class AsyncHttp(object):
def __init__(self):
self.client_list = []
self.connections_list = [] def start(self, item):
""" :param item: ==> host; url
:return:
"""
try:
sk = socket.socket()
sk.setblocking(False)
sk.connect((item['host']), 80)
except BlockingIOError as e:
pass
self.client_list.append(HttpRequest(sk, item))
self.connections_list.append(HttpRequest(sk, item)) def run(self):
"""
检测self.client_list, self.connections_list里面的socket是否已经连接成功
:return:
"""
while True:
# 之前:select内部传入的参数必须是socket对象[socket对象、socket对象] ==》 调用的就是fileno方法
# 现在:select内部传入的参数可以是任意对象[任意对象(包含fileno方法)、任意对象(包含fileno方法)]
# r:可读,socket对象可以读取接收到的服务端的信息;w:可写,如果又socket和服务端连接成功
r, w, e = select.select(self.client_list, self.connections_list, [], 0.05)
for conn in w: # 连接成功
host = conn.item['host']
url = conn.item['url']
# 构造 'GET / HTTP/1.0\r\nHost: www.baidu.com\r\n\r\n'
temp = 'GET %s HTTP/1.0\r\nHost: %s\r\n\r\n' % (host, url)
conn.sock.sendall(bytes(temp)) # 就发送伪造好的请求给服务端,请求头+请求体
self.connections_list.remove(conn) # 将发送好的socket对象从监测连接的连接池中移除
for conn in r: # 获取响应内容
data = conn.sock.recv(8096)
func = conn.item['fn']
func(data)
conn.sock.close()
self.client_list.remove(conn)
if not self.client_list:
break def done(content):
print(content) reactor = AsyncHttp() url_list = [
{'host': 'www.baidu.com', 'url': '/', 'fn': done},
{'host': 'www.bing.com', 'url': '/', 'fn': done},
{'host': 'www.cnblogs.com', 'url': '/wuzdandz/', 'fn': done},
]
for item in url_list:
reactor.start(item) reactor.run()
关于fileno的解释
select 模块的特性,一般IO操作都有fileno方法,fileno==》文件描述符,网络相关的也是文件描述符;终端是输入输出设备也有文件描述符,但凡有这个的操作系统监听的都是文件描述符,而不是这个对象
所以select本质上就是给什么对象都是去取文件描述符fileno,开始我们没有做封装,用的是socket对象,直接调用了它的fileno方法
当进行封装时,传入的是自己的对象,我们不知道文件描述符是什么,那是操作系统所提供的,此时调用fileno方法就需要直接返回self.sock.fileno(),即socket本身的文件描述符,相当于一次转发,中间商操作
Python 爬虫六 性能相关的更多相关文章
- python爬虫之性能相关
性能相关 在编写爬虫时,性能的消耗主要在IO请求中,当单进程单线程模式下请求URL时必然会引起等待,从而使得请求整体变慢. import requests def fetch_async(url): ...
- python爬虫(六)_urllib2:handle处理器和自定义opener
本文将介绍handler处理器和自定义opener,更多内容请参考:python学习指南 opener和handleer 我们之前一直使用的是urllib2.urlopen(url)这种形式来打开网页 ...
- Python爬虫学习系列教程
最近想学一下Python爬虫与检索相关的知识,在网上看到这个教程,觉得挺不错的,分享给大家. 来源:http://cuiqingcai.com/1052.html 一.Python入门 1. Pyth ...
- Python 爬虫性能相关
性能相关 在编写爬虫时,性能的消耗主要在IO请求中,当单进程单线程模式下请求URL时必然会引起等待,从而使得请求整体变慢. import requests def fetch_async(url): ...
- 【转】Python爬虫(5)_性能相关
爬虫性能相关 一 背景知识 爬虫的本质就是一个socket客户端与服务端的通信过程,如果我们有多个url待爬取,采用串行的方式执行,只能等待爬取一个结束后才能继续下一个,效率会非常低. 需要强调的是: ...
- 孤荷凌寒自学python第六十七天初步了解Python爬虫初识requests模块
孤荷凌寒自学python第六十七天初步了解Python爬虫初识requests模块 (完整学习过程屏幕记录视频地址在文末) 从今天起开始正式学习Python的爬虫. 今天已经初步了解了两个主要的模块: ...
- Python 爬虫十六式 - 第七式:正则的艺术
RE:用匹配来演绎编程的艺术 学习一时爽,一直学习一直爽 Hello,大家好,我是 Connor,一个从无到有的技术小白.上一次我们说到了 pyquery 今天我们将迎来我们数据匹配部分的最后一位 ...
- Python 爬虫十六式 - 第五式:BeautifulSoup-美味的汤
BeautifulSoup 美味的汤 学习一时爽,一直学习一直爽! Hello,大家好,我是Connor,一个从无到有的技术小白.上一次我们说到了 Xpath 的使用方法.Xpath 我觉得还是 ...
- Python爬虫十六式 - 第四式: 使用Xpath提取网页内容
Xpath:简单易用的网页内容提取工具 学习一时爽,一直学习一直爽 ! Hello,大家好,我是Connor,一个从无到有的技术小白.上一次我们说到了 requests 的使用方法.到上节课为止, ...
随机推荐
- log.error("异常:", e);与log.error(e.getMessage());区别
转: log.error("异常:", e);与log.error(e.getMessage());区别 2017年04月28日 14:51:32 行走的soong 阅读数:120 ...
- Vector使用测试
1.测试vector是否自动释放分配的空间 vector有大致两类申请空间的方式,一是vector(n,T()),一是vector(p,p+n)(p是自己申请的空间的指针). 其中第一种估计肯定会释放 ...
- (BFS 二叉树) leetcode 515. Find Largest Value in Each Tree Row
You need to find the largest value in each row of a binary tree. Example: Input: 1 / \ 3 2 / \ \ 5 3 ...
- (BFS/DFS) leetcode 200. Number of Islands
Given a 2d grid map of '1's (land) and '0's (water), count the number of islands. An island is surro ...
- (线性dp 最大连续和)POJ 2479 Maximum sum
Maximum sum Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 44459 Accepted: 13794 Des ...
- M1-Flask-Day2
内容概要: 1.flask - 蓝图 - 中间件 - 闪现 2.扩展 - session - wtfrom 3.上下文管理 - local-threading 4.websocket - 轮训 - 长 ...
- bzoj2004 矩阵快速幂优化状压dp
https://www.lydsy.com/JudgeOnline/problem.php?id=2004 以前只会状压dp和矩阵快速幂dp,没想到一道题还能组合起来一起用,算法竞赛真是奥妙重重 小Z ...
- XML异常
1.com.sun.org.apache.xerces.internal.impl.io.MalformedByteSequenceException: 1 字节的 UTF-8 序列的字节 1 无效 ...
- linux优化之系统参数调优篇
linux优化之系统参数调优篇 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.用户限制配置文件(首先需要编辑/etc/security/limits.conf文件) 大家可以 ...
- Kafka各个版本差异汇总
Kafka各个版本差异汇总 从0.8.x,0.9.x,0.10.0.x,0.10.1.x,0.10.2.x,0.11.0.x,1.0.x或1.1.x升级到2.0.0 Kafka 2.0.0引入了线 ...