高性能爬虫原理与应用
目录:
- 一 高性能爬虫本质
- 二 高性能爬虫相关理论点
- 三 Python中高性能相关模块
- 3.1 asyncio模块
- 3.2 aiohttp模块
- 3.3 gevent模块
- 3.4 grequest模块
- 3.5 twisted
- 3.6 tornado
一 高性能爬虫本质
爬虫的本质就是一个socket客户端与服务端的通信过程,如果我们有多个url待爬取,只用一个线程且采用串行的方式执行,那只能等待爬取一个结束后才能继续下一个,效率会非常低。
需要强调的是:对于单线程下串行N个任务,并不完全等同于低效,如果这N个任务都是纯计算的任务,那么该线程对CPU的利用率仍然会很高,之所以单线程下串行多个爬虫任务低效,是因为爬虫任务是明显的IO密集型程序。
二 高性能爬虫相关理论点
同步
同步调用:即提交一个任务后,CPU会等待这个任务结束才会拿到执行这个任务的结果在继续执行其它任务,效率低下
1 import requests 2 3 def parse_page(res): 4 print('解析 %s' %(len(res))) 5 6 def get_page(url): 7 print('下载 %s' %url) 8 response=requests.get(url) 9 if response.status_code == 200: 10 return response.text 11 12 urls=['https://www.baidu.com/','http://www.sina.com.cn/','https://www.python.org'] 13 for url in urls: 14 res=get_page(url) #调用一个任务,就在原地等待任务结束拿到结果后才继续往后执行 15 parse_page(res)
异步
通过多线程或多进程:在服务器端使用多线程(或多进程)。多线程(或多进程)的目的是让每个连接都拥有独立的线程(或进程),这样任何一个连接的阻塞都不会影响其他的连接。
1 #IO密集型程序应该用多线程 2 import requests 3 from threading import Thread,current_thread 4 5 def parse_page(res): 6 print('%s 解析 %s' %(current_thread().getName(),len(res))) 7 8 def get_page(url,callback=parse_page): 9 print('%s 下载 %s' %(current_thread().getName(),url)) 10 response=requests.get(url) 11 if response.status_code == 200: 12 callback(response.text) 13 14 if __name__ == '__main__': 15 urls=['https://www.baidu.com/','http://www.sina.com.cn/','https://www.python.org'] 16 for url in urls: 17 t=Thread(target=get_page,args=(url,)) 18 t.start()
该方案存在的问题:
开启多进程或多线程的方式,我们是无法无限制地开启多线程或多进程的。在遇到要同时响应成百上千路的连接请求,则无论多进程还是多线程都会严重占据系统资源,降低系统的响应效率,而且线程与进程本身也更容易进入假死状态。
回调机制
线程池或简称持+异步调用:提交一个任务后并不会等待任务结束,而是继续其他任务
线程池旨在减少创建和销毁线程的频率,其维持一定合理数量的线程,并让空闲的线程重新承担新的执行任务。连接池维持连接的缓存池,尽量重用已有的连接、减少创建和关闭连接的频率。这两种技术都可以很好的降低系统开销,都被广泛应用很多大型系统,如websphere、tomcat和各种数据库等。
1 import requests 2 from threading import current_thread 3 from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor 4 5 def parse_page(res): 6 res=res.result() 7 print('%s 解析 %s' %(current_thread().getName(),len(res))) 8 9 def get_page(url): 10 print('%s 下载 %s' %(current_thread().getName(),url)) 11 response=requests.get(url) 12 if response.status_code == 200: 13 return response.text 14 15 if __name__ == '__main__': 16 urls=['https://www.baidu.com/','http://www.sina.com.cn/','https://www.python.org'] 17 18 pool=ThreadPoolExecutor(50) 19 # pool=ProcessPoolExecutor(50) 20 for url in urls: 21 pool.submit(get_page,url).add_done_callback(parse_page) 22 23 pool.shutdown(wait=True)
这种方法也存在着一些问题:
线程池和连接池技术也只是在一定程度上缓解了频繁调用IO接口带来的资源占用。而且,所谓“池”始终有上限,当请求大大超过上限时,“池”构成的系统对外界的响应并不比没有池的时候效果好多少。所有使用“池”必须考虑其面临的响应规模,并根据响应规模调整“池”的大小。
对应上例中的所面临的可能同时出现的上千甚至上万次的客户端请求,“线程池”和“连接池”或许可以缓解部分压力,但是不能解决所有问题。总之,多线程模型可以方便高效的解决小规模的服务请求,但面对大规模的服务请求,多线程模型也会遇到瓶颈,可以用非阻塞接口来尝试解决这个问题
三 Python中高性能相关模块
简述
上述无论哪种解决方案其实都没有解决一个性能相关的问题:IO阻塞,无论是多进程还是多线程,在遇到IO阻塞时都会被操作系统强行夺走CPU的执行权限,程序的执行效率因此就降低了下来。
解决这一问题的关键在于,我们自己从应用程序级别检测IO阻塞然后切换到我们自己程序的其他任务,这样我们程序的IO降到最低,我们的程序处于就绪态就会增多,以此来迷惑操作系统,操作系统变以为我们的程序是IO比较少的程序,从而会尽可能的多分配CPU给我们,这样也就达到了提升程序执行效率的目的。
asyncio模块
在Python3.3之后新增了asyhcio模块,可以帮我们检测IO(只能检测网络IO)实现应用程序级别的切换
基本使用
1 import asyncio 2 3 @asyncio.coroutine 4 def task(task_id, seconds): 5 print('%s is start' % task_id) 6 yield from asyncio.sleep(seconds) # 只能检测网络IO, 检测到IO后切换到其他任务执行 7 print('%s is end' % task_id) 8 9 10 tasks = [task(task_id='任务1', seconds=3), task("任务2", 2), task("任务3", seconds=1)] 11 12 loop = asyncio.get_event_loop() 13 loop.run_until_complete(asyncio.wait(tasks)) 14 loop.close()
但是asyncio模块只能发TCP级别的请求,不能发http协议,因此,在我们需要发送http请求的时候,需要我们自定义http报头
1 import asyncio 2 import requests 3 import uuid 4 5 6 user_agent='Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.221 Safari/537.36 SE 2.X MetaSr 1.0' 7 8 9 def parse_page(host, res): 10 print('%s 解析结果 %s' % (host, len(res))) 11 with open('%s.html' % (uuid.uuid1()), 'wb') as f: 12 f.write(res) 13 14 15 @asyncio.coroutine 16 def get_page(host, port=80, url='/', callback=parse_page, ssl=False): 17 print('下载 http://%s:%s%s' % (host, port, url)) 18 # 步骤1 (IO阻塞):发起TCP连接,是阻塞操作,因此需要yield from 19 if ssl: 20 port = 443 21 connect = asyncio.open_connection(host=host, port=port, ssl=ssl) 22 print(connect) 23 recv, send = yield from connect 24 # 步骤2 封装http协议的报头,因为asyncio模块只能封装并发送TCP包,因此这一步需要我们自己封装http协议的包 25 request_headers = """GET %s HTTP/1.0\r\nHost: %s\r\nUser-agent: %s\r\n\r\n""" % (url, host, user_agent) 26 # POST请求 27 # request_headers = """POST %s HTTP/1.0\r\nHost: %s\r\n\r\nname=lucy&password=123""" % (url, host,) 28 request_headers = request_headers.encode('utf-8') 29 30 # 步骤3 (IO阻塞):发送http请求包 31 send.write(request_headers) 32 yield from send.drain 33 34 # 步骤4 (IO阻塞):接收响应头 35 while True: 36 line = yield from recv.readline() 37 if line == b'\r\n': 38 break 39 print('%s Response headers: %s' %(host, line)) 40 41 # 步骤5 (IO阻塞):接收响应体 42 text = yield from recv.read() 43 44 # 步骤6 :执行回调函数 45 callback(host, text) 46 47 # 步骤7: 关闭套接字 48 send.close() # 没有recv.close()方法,因为是四次挥手断开连接,双向连接的两端,一段发完整数据后执行send.close()另外一段就被动地断开 49 50 51 if __name__ == '__main__': 52 tasks = [ 53 get_page('www.baidu.com', url='/s?wd=Python', ssl=True), 54 get_page('www.cnblogs.com', url='/', ssl=True), 55 ] 56 57 loop = asyncio.get_event_loop() 58 loop.run_until_complete(asyncio.wait(tasks)) 59 loop.close()
aiohttp模块
用来封装http报头的一个模块,使用asyncio检测IO实现切换
1 import aiohttp 2 import asyncio 3 4 5 @asyncio.coroutine 6 def get_page(url): 7 print('GET:%s' % url) 8 response = yield from aiohttp.request('GET', url) 9 10 data = yield from response.read() 11 12 print(url, data) 13 response.close() 14 15 16 tasks = [ 17 get_page('https://www.python.org/doc'), 18 # get_page('https://www.baidu.com'), 19 # get_page('https://www.taobao.com') 20 ] 21 22 loop = asyncio.get_event_loop() 23 results = loop.run_until_complete(asyncio.gather(*tasks)) 24 loop.close() 25 26 print("===>", results)
此外,还可以将requests.get函数传给asyncio,就能够被检测了
1 import requests 2 import asyncio 3 4 5 @asyncio.coroutine 6 def get_page(func, *args): 7 print('GET:%s' % args[0]) 8 loop = asyncio.get_event_loop() 9 furture = loop.run_in_executor(None, func, *args) 10 response = yield from furture 11 12 print(response.url, len(response.text)) 13 return 1 14 15 16 tasks = [ 17 get_page(requests.get, 'https://www.python.org/doc'), 18 ] 19 20 loop = asyncio.get_event_loop() 21 results = loop.run_until_complete(asyncio.gather(*tasks)) 22 loop.close() 23 24 print('==>', results)
gevent模块
gevent+requests
1 from gevent import monkey; monkey.patch_all() 2 import gevent 3 import requests 4 5 6 def get_page(url): 7 print('GET: %s' % url) 8 response = requests.get(url) 9 print(url, len(response.text)) 10 return 1 11 12 13 g1 = gevent.spawn(get_page, 'http://www.python.org/doc') 14 g2 = gevent.spawn(get_page, 'http://www.openstack.org') 15 gevent.joinall([g1, g2, ]) 16 print(g1.value, g2.value) # 拿到返回值 17 18 # 协程池 19 from gevent.pool import Pool 20 21 22 pool = Pool(2) 23 g1 = pool.spawn(get_page, 'https://www.python.org/doc') 24 g2 = pool.spawn(get_page, 'https://openstack.org') 25 26 gevent.joinall([g1, g2, ]) 27 print(g1.value, g2.value, ) # 拿到返回值
grequest模块
此模块是封装了gevent+requests得来的
1 import grequests 2 3 4 request_list = [ 5 grequests.get('http://www.pyt.org/doc'), 6 grequests.get('http://www.baidu.com'), 7 ] 8 9 10 # 执行并获取响应列表 11 response_list = grequests.map(request_list) 12 print(response_list) 13 14 # 执行并获取响应列表(处理异常) 15 def exception_handler(request, exception): 16 print(request, exception) 17 print("%s Request failed" % request.url) 18 19 20 response_list = grequests.map(request_list, exception_handler=exception_handler) 21 print(response_list)
twisted
Python写的一个网络框架,其中一个功能是发送异步请求,检测IO并自动切换
1 # # twisted基本用法 2 # 3 # from twisted.web.client import getPage, defer 4 # from twisted.internet import reactor 5 # 6 # 7 # def all_done(arg): 8 # print(arg) 9 # reactor.stop() 10 # 11 # 12 # def callback(res): 13 # print(res) 14 # return 1 15 # 16 # 17 # defer_list = [] 18 # urls = [ 19 # 'http://www.baidu.com', 20 # 'http://www.bing.com', 21 # ] 22 # for url in urls: 23 # obj = getPage(url.encode('utf8'), ) 24 # obj.addCallback(callback) 25 # defer_list.append(obj) 26 # 27 # 28 # defer.DeferredList(defer_list).addBoth(all_done) 29 # 30 # reactor.run() 31 32 33 # twisted的getPage的详细用法 34 from twisted.internet import reactor 35 from twisted.web.client import getPage 36 import urllib.parse 37 38 39 def one_done(arg): 40 print(arg) 41 reactor.stop() 42 43 44 post_data = urllib.parse.urlencode({'check_data': 'adf'}) 45 post_data = bytes(post_data, encoding='utf8') 46 headers = {b'Content=Type': b'application/x-www-from-urlencoded'} 47 response = getPage(bytes('http://dig.chouti.com/login', encoding='utf8'), 48 method=bytes('POST', encoding='utf8'), 49 postdata=post_data, 50 cookies={}, 51 headers=headers) 52 53 response.addBoth(one_done) 54 55 reactor.run()
tornado
1 from tornado.httpclient import AsyncHTTPClient 2 from tornado.httpclient import HTTPRequest 3 from tornado import ioloop 4 5 6 def handle_response(response): 7 """ 8 处理返回值内容(需要维护计数器,来停止IO循环),调用 ioloop.IOLoop.current().stop() 9 :param response: 10 :return: 11 """ 12 if response.error: 13 print("Error:", response.error) 14 else: 15 print(response.body) 16 17 18 def func(): 19 url_list = [ 20 'http://www.baidu.com', 21 'http://www.bing.com', 22 ] 23 for url in url_list: 24 print(url) 25 http_client = AsyncHTTPClient() 26 http_client.fetch(HTTPRequest(url), handle_response) 27 28 29 ioloop.IOLoop.current().add_callback(func) 30 ioloop.IOLoop.current().start() 31 32 33 34 35 #发现上例在所有任务都完毕后也不能正常结束,为了解决该问题,让我们来加上计数器 36 from tornado.httpclient import AsyncHTTPClient 37 from tornado.httpclient import HTTPRequest 38 from tornado import ioloop 39 40 count=0 41 42 def handle_response(response): 43 """ 44 处理返回值内容(需要维护计数器,来停止IO循环),调用 ioloop.IOLoop.current().stop() 45 :param response: 46 :return: 47 """ 48 if response.error: 49 print("Error:", response.error) 50 else: 51 print(len(response.body)) 52 53 global count 54 count-=1 #完成一次回调,计数减1 55 if count == 0: 56 ioloop.IOLoop.current().stop() 57 58 def func(): 59 url_list = [ 60 'http://www.baidu.com', 61 'http://www.bing.com', 62 ] 63 64 global count 65 for url in url_list: 66 print(url) 67 http_client = AsyncHTTPClient() 68 http_client.fetch(HTTPRequest(url), handle_response) 69 count+=1 #计数加1 70 71 ioloop.IOLoop.current().add_callback(func) 72 ioloop.IOLoop.current().start() 73 74 Tornado
转载于:https://www.cnblogs.com/eric_yi/p/8330704.html
高性能爬虫原理与应用相关推荐
- python爬虫原理-python学习之python爬虫原理
原标题:python学习之python爬虫原理 今天我们要向大家详细解说python爬虫原理,什么是python爬虫,python爬虫工作的基本流程是什么等内容,希望对这正在进行python爬虫学习的 ...
- python爬虫原理-python爬虫原理详细讲解
原标题:python爬虫原理详细讲解 一 .爬虫是什么 1.什么是互联网? 互联网是由网络设备(网线,路由器,交换机,防火墙等等)和一台台计算机连接而成,像一张网一样. 2.互联网建立的目的?互联网的 ...
- day 01 python爬虫原理
# 今日内容:爬虫原理# 爬虫课程:# 实训要求:# 一 课下写作业# 二 编写博客 # 一 爬虫基本原理# 1 什么是爬虫# 爬虫就是爬数据# 2 是什么互联网# 3 互联网建立的目的# 数据的传递 ...
- python3反爬虫原理与绕过实战 网盘_Python 3反爬虫原理与绕过实战
第 1章 开发环境配置 1 1.1 操作系统的选择 1 1.1.1 Ubuntu 简介 1 1.1.2 VirtualBox 的安装 2 1.1.3 安装 Ubuntu 3 1.1.4 全屏设置 8 ...
- 搜索引擎蜘蛛爬虫原理
permike 原文 搜索引擎蜘蛛爬虫原理 关于搜索引擎的大话还是少说些,下面开始正文搜索引擎蜘蛛爬虫原理: 1 聚焦爬虫工作原理及关键技术概述 网络爬虫是一个自动提取网页的程序,它为搜索引擎从Int ...
- python原理与架构_Python:爬虫原理和网页构造
入门网络数据爬取,也就是Python爬虫 现实中我们使用浏览器访问网页时,网络是怎么运转的,做了什么呢? 首先,必须了解网络连接基本过程原理,然后,再进入爬虫原理了解就好理解的多了. 1.网络连接原理 ...
- Python 爬虫 性能 相关( asyncio 模块 --- 高性能爬虫 )
From:https://www.cnblogs.com/bravexz/p/7741633.html 爬虫应用 asyncio 模块 ( 高性能爬虫 ):https://www.cnblogs.co ...
- 从抓取豆瓣电影聊高性能爬虫思路(纯干货)
从抓取豆瓣电影聊高性能爬虫思路 本篇文章将以抓取豆瓣电影信息为例来一步步介绍开发一个高性能爬虫的常见思路. 寻找数据地址 爬虫的第一步,首先我们要找到获取数据的地址.可以先到豆瓣电影 首页 去看看. ...
- 从抓取豆瓣电影聊高性能爬虫思路
本篇文章将以抓取豆瓣电影信息为例来一步步介绍开发一个高性能爬虫的常见思路. 寻找数据地址 爬虫的第一步,首先我们要找到获取数据的地址.可以先到豆瓣电影 首页 去看看. 顶部导航为提供了很多种类型的入口 ...
最新文章
- 干货|你的Paper阅读能力合格了吗(硕士生版)
- 阿里P7背调红灯:被前前公司说坏话,修改领导名被查!
- injectionForXcode代码注入步骤
- Apple高规格推3232吋LCD面板XDR
- python工具之myql数据库操作
- AQS(CountdownLatch、CyclicBarrier、Semaphore)、FutureTask、BlockingQueue、ForkJoin
- Linux ALSA声卡驱动之三:PCM设备的创建
- ios如何判断键盘是否已经显示
- 让 WPF 的 RadioButton 支持再次点击取消选中的功能
- 阿里云能耗宝发布,助力中小企业绿色升级,参与碳中和万亿市场
- linux保存编辑信息,linux系统编辑神器 -vim用法大全
- 【Statistics】均值
- Hprose开源的高性能远程对象服务引擎
- transform 的副作用
- 阿里、网易、滴滴共十次前端面试碰到的问题
- spark DataSet与DataFrame的区别
- mysql批量替换 语法
- 兴趣爱好-常用的10种算法
- WorkStation9完美支持Win8
- 【原版教材•中英对照】密度泛函理论的化学家指南(第二版)— 传统量子力学的化学家们将从这篇得到特别的启发
热门文章
- [Python] L1-018. 大笨钟-PAT团体程序设计天梯赛GPLT
- 蓝桥杯 ADV-148算法提高 排队打水问题(贪心)
- python 方差_python统计分析总体方差检验
- eclipse在线安装ivy和ivyde
- BitCoin Gloom系列
- canvas操作图片,进行面板画图,旋转等
- width 、 height 与 box-sizing : border-box ,content-box 的关系
- ubuntu下使用visual studio code来编译和调试C++
- Jquery_异步上传文件多种方式归纳
- 【089】◀▶ Microsoft Office 技巧