爬虫框架:scrapy

一 背景知识

    爬虫的本质就是一个socket客户端与服务端的通信过程,如果我们有多个url待爬取,采用串行的方式执行,只能等待爬取一个结束后才能继续下一个,效率会非常低。

需要强调的是:串行并不意味着低效,如果串行的都是纯计算的任务,那么cpu的利用率仍然会很高,之所以爬虫程序的串行低效,是因为爬虫程序是明显的IO密集型程序。

关于IO模型详见链接:http://www.cnblogs.com/linhaifeng/articles/7454717.html

    那么该如何提高爬取性能呢?

二 同步、异步、回调机制

1、同步调用:即提交一个任务后就在原地等待任务结束,等到拿到任务的结果后再继续下一行代码,效率低下

import requestsdef get_page(url):response=requests.get(url)if response.status_code == 200:return response.texturls=['https://www.baidu.com/','http://www.sina.com.cn/','https://www.python.org']
for url in urls:res=get_page(url) #调用一个任务,就在原地等待任务结束拿到结果后才继续往后执行print(len(res))
同步调用

2、一个简单的解决方案:多线程或多进程

#在服务器端使用多线程(或多进程)。多线程(或多进程)的目的是让每个连接都拥有独立的线程(或进程),这样任何一个连接的阻塞都不会影响其他的连接。
from multiprocessing import Process
from threading import Thread
import requestsdef get_page(url):response=requests.get(url)if response.status_code == 200:return response.textif __name__ == '__main__':urls=['https://www.baidu.com/','http://www.sina.com.cn/','https://www.python.org']for url in urls:p=Process(target=get_page,args=(url,))p.start()# t=Thread(target=get_page,args=(url,))# t.start()
多进程或多线程

    该方案的问题是:

#开启多进程或都线程的方式,我们是无法无限制地开启多进程或多线程的:在遇到要同时响应成百上千路的连接请求,则无论多线程还是多进程都会严重占据系统资源,降低系统对外界响应效率,而且线程与进程本身也更容易进入假死状态。

3、改进方案: 线程池或进程池+异步调用:提交一个任务后并不会等待任务结束,而是继续下一行代码

#很多程序员可能会考虑使用“线程池”或“连接池”。“线程池”旨在减少创建和销毁线程的频率,其维持一定合理数量的线程,并让空闲的线程重新承担新的执行任务。“连接池”维持连接的缓存池,尽量重用已有的连接、减少创建和关闭连接的频率。这两种技术都可以很好的降低系统开销,都被广泛应用很多大型系统,如websphere、tomcat和各种数据库等。
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
import requestsdef get_page(url):print('GET : %s' %url)response=requests.get(url)if response.status_code == 200:return response.textif __name__ == '__main__':p=ProcessPoolExecutor()# p=ThreadPoolExecutor()
urls=['https://www.baidu.com/','http://www.sina.com.cn/','https://www.python.org']for url in urls:p.submit(get_page,url)p.shutdown(wait=True)
进程池或线程池
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
import requests
import osdef get_page(url):print('%s GET : %s' %(os.getpid(),url))response=requests.get(url)if response.status_code == 200:return response.textdef parse_page(res):res=res.result()print('%s parsing' %os.getpid())if __name__ == '__main__':p=ProcessPoolExecutor()# p=ThreadPoolExecutor()
urls=['https://www.baidu.com/','http://www.sina.com.cn/','https://www.python.org']for url in urls:p.submit(get_page,url).add_done_callback(parse_page)p.shutdown(wait=True)
异步调用+回调机制 

    改进后方案其实也存在着问题:

#“线程池”和“连接池”技术也只是在一定程度上缓解了频繁调用IO接口带来的资源占用。而且,所谓“池”始终有其上限,当请求大大超过上限时,“池”构成的系统对外界的响应并不比没有池的时候效果好多少。所以使用“池”必须考虑其面临的响应规模,并根据响应规模调整“池”的大小。

    对应上例中的所面临的可能同时出现的上千甚至上万次的客户端请求,“线程池”或“连接池”或许可以缓解部分压力,但是不能解决所有问题。总之,多线程模型可以方便高效的解决小规模的服务请求,但面对大规模的服务请求,多线程模型也会遇到瓶颈,可以用非阻塞接口来尝试解决这个问题。

三 高性能

    上述无论哪种解决方案其实没有解决一个性能相关的问题:IO阻塞,无论是多进程还是多线程,在遇到IO阻塞时都会被操作系统强行剥夺走CPU的执行权限,程序的执行效率因此就降低了下来。

    解决这一问题的关键在于,我们自己从应用程序级别检测IO阻塞然后切换到我们自己程序的其他任务执行,这样把我们程序的IO降到最低,我们的程序处于就绪态就会增多,以此来迷惑操作系统,操作系统便以为我们的程序是IO比较少的程序,从而会尽可能多的分配CPU给我们,这样也就达到了提升程序执行效率的目的

    1、在python3.3之后新增了asyncio模块,可以帮我们检测IO(只能是网络IO),实现应用程序级别的切换

import asyncio@asyncio.coroutine
def task(task_id,senconds):print('%s is start' %task_id)yield from asyncio.sleep(senconds) #只能检测网络IO,检测到IO后切换到其他任务执行print('%s is end' %task_id)tasks=[task(task_id=1,senconds=3),task(task_id=2,senconds=4)]loop=asyncio.get_event_loop()
loop.run_until_complete(asyncio.gather(*tasks))
loop.close()
基本使用

    2、但asyncio模块只能发tcp级别的请求,不能发http协议,因此,在我们需要发送http请求的时候,需要我们自定义http报头

#我们爬取一个网页的过程,以https://www.python.org/doc/为例,将关键步骤列举如下
#步骤一:向www.python.org这台主机发送tcp三次握手,是IO阻塞操作
#步骤二:封装http协议的报头
#步骤三:发送http协议的请求包,是IO阻塞操作
#步骤四:接收http协议的响应包,是IO阻塞操作
import asyncio@asyncio.coroutine
def get_page(host,port=80,url='/'):#步骤一(IO阻塞):发起tcp链接,是阻塞操作,因此需要yield fromrecv,send=yield from asyncio.open_connection(host,port)#步骤二:封装http协议的报头,因为asyncio模块只能封装并发送tcp包,因此这一步需要我们自己封装http协议的包requset_headers="""GET %s HTTP/1.0\r\nHost: %s\r\n\r\n""" % (url, host,)# requset_headers="""POST %s HTTP/1.0\r\nHost: %s\r\n\r\nname=egon&password=123""" % (url, host,)requset_headers=requset_headers.encode('utf-8')#步骤三(IO阻塞):发送http请求包
    send.write(requset_headers)yield from send.drain()#步骤四(IO阻塞):接收http协议的响应包text=yield from recv.read()#其他处理print(host,url,text)send.close()print('-===>')return 1tasks=[get_page(host='www.python.org',url='/doc'),get_page(host='www.cnblogs.com',url='linhaifeng'),get_page(host='www.openstack.org')]loop=asyncio.get_event_loop()
results=loop.run_until_complete(asyncio.gather(*tasks))
loop.close()print('=====>',results) #[1, 1, 1]
asyncio+自定义http协议报头

    3、自定义http报头多少有点麻烦,于是有了aiohttp模块,专门帮我们封装http报头,然后我们还需要用asyncio检测IO实现切换

import aiohttp
import asyncio@asyncio.coroutine
def get_page(url):print('GET:%s' %url)response=yield from aiohttp.request('GET',url)data=yield from response.read()print(url,data)response.close()return 1tasks=[get_page('https://www.python.org/doc'),get_page('https://www.cnblogs.com/linhaifeng'),get_page('https://www.openstack.org')
]loop=asyncio.get_event_loop()
results=loop.run_until_complete(asyncio.gather(*tasks))
loop.close()print('=====>',results) #[1, 1, 1]
asyncio+aiohttp

    4、此外,还可以将requests.get函数传给asyncio,就能够被检测了

import requests
import asyncio@asyncio.coroutine
def get_page(func,*args):print('GET:%s' %args[0])loog=asyncio.get_event_loop()furture=loop.run_in_executor(None,func,*args)response=yield from furtureprint(response.url,len(response.text))return 1tasks=[get_page(requests.get,'https://www.python.org/doc'),get_page(requests.get,'https://www.cnblogs.com/linhaifeng'),get_page(requests.get,'https://www.openstack.org')
]loop=asyncio.get_event_loop()
results=loop.run_until_complete(asyncio.gather(*tasks))
loop.close()print('=====>',results) #[1, 1, 1]
asyncio+requests模块的方法

   

   

    5、还有之前在协程时介绍的gevent模块

from gevent import monkey;monkey.patch_all()
import gevent
import requestsdef get_page(url):print('GET:%s' %url)response=requests.get(url)print(url,len(response.text))return 1# g1=gevent.spawn(get_page,'https://www.python.org/doc')
# g2=gevent.spawn(get_page,'https://www.cnblogs.com/linhaifeng')
# g3=gevent.spawn(get_page,'https://www.openstack.org')
# gevent.joinall([g1,g2,g3,])
# print(g1.value,g2.value,g3.value) #拿到返回值#协程池
from gevent.pool import Pool
pool=Pool(2)
g1=pool.spawn(get_page,'https://www.python.org/doc')
g2=pool.spawn(get_page,'https://www.cnblogs.com/linhaifeng')
g3=pool.spawn(get_page,'https://www.openstack.org')
gevent.joinall([g1,g2,g3,])
print(g1.value,g2.value,g3.value) #拿到返回值
gevent+requests

    6、封装了gevent+requests模块的grequests模块

#pip3 install grequestsimport grequestsrequest_list=[grequests.get('https://wwww.xxxx.org/doc1'),grequests.get('https://www.cnblogs.com/linhaifeng'),grequests.get('https://www.openstack.org')
]##### 执行并获取响应列表 #####
# response_list = grequests.map(request_list)
# print(response_list)##### 执行并获取响应列表(处理异常) #####
def exception_handler(request, exception):# print(request,exception)print("%s Request failed" %request.url)response_list = grequests.map(request_list, exception_handler=exception_handler)
print(response_list)
grequests

    7、twisted:是一个网络框架,其中一个功能是发送异步请求,检测IO并自动切换

'''
#问题一:error: Microsoft Visual C++ 14.0 is required. Get it with "Microsoft Visual C++ Build Tools": http://landinghub.visualstudio.com/visual-cpp-build-tools
https://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
pip3 install C:\Users\Administrator\Downloads\Twisted-17.9.0-cp36-cp36m-win_amd64.whl
pip3 install twisted#问题二:ModuleNotFoundError: No module named 'win32api'
https://sourceforge.net/projects/pywin32/files/pywin32/#问题三:openssl
pip3 install pyopenssl
'''#twisted基本用法
from twisted.web.client import getPage,defer
from twisted.internet import reactordef all_done(arg):# print(arg)
    reactor.stop()def callback(res):print(res)return 1defer_list=[]
urls=['http://www.baidu.com','http://www.bing.com','https://www.python.org',
]
for url in urls:obj=getPage(url.encode('utf=-8'),)obj.addCallback(callback)defer_list.append(obj)defer.DeferredList(defer_list).addBoth(all_done)reactor.run()#twisted的getPage的详细用法
from twisted.internet import reactor
from twisted.web.client import getPage
import urllib.parsedef 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()
twisted的用法

    8、tornado

from tornado.httpclient import AsyncHTTPClient
from tornado.httpclient import HTTPRequest
from tornado import ioloopdef 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:print(url)http_client = AsyncHTTPClient()http_client.fetch(HTTPRequest(url), handle_response)ioloop.IOLoop.current().add_callback(func)
ioloop.IOLoop.current().start()
Tornado

 

    

 

 

 

 

 

 

 

 

 

转载于:https://www.cnblogs.com/richiewlq/p/8318703.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/369786.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

为Openshift + MongoDb应用程序编写验收测试

验收测试用于确定是否满足规范要求。 它应该在与生产环境尽可能相似的环境中运行。 因此,如果您的应用程序已部署到Openshift中,则您将需要一个与生产环境中使用的帐户平行的帐户,以运行测试。 在这篇文章中,我们将为部署到Opensh…

CSS 自适应布局

前言 本篇文章将介页面布局中的自适应布局,常见的自适应布局有以下2种:左列固定右列自适应、左右两列固定中间自适应。 1. 左列固定右列自适应布局方案 说明:左列固定右列自适应,也可以为右列固定左列自适应,常见于中…

mysql的表导出er关系图_使用Navicat生成ER关系图并导出的方法

平时管理数据库一般都是用cmd命令提示符,或是IDEA Intellij自带的Data source,使用Navicat比较少。这段时间,由于要对前后端交互的数据结构进行设计,直接写文档联系多表时有些困难,想着如果有关系图就直观很多。想到Na…

Scude导入MySQL_FM2017_FMF赛季更新和真实修正数据库[更新至9.9,超过89000个更新]

FM2017_FMF赛季更新和真实修正数据库[更新至9.9,超过89000个更新]FM2017_FMF赛季更新和真实修正数据库[更新至9.9,超过89000个更新](2)这是国外玩家制作的一款FM2017_FMF冬季更新和真实修正数据库,更新至9月9日,超过89000个更新内…

音视频和表单的详情

网页中的音视频 <audio> 和 <vedio> 标签属性&#xff1a;autoplay 自动播放 controls 控制播放 loop 循环播放 表单 HTML 表单用于收集用户输入。 标签<form> 标签属性 action 数据的路径 enctype 传输文件 enctype"multipart/form-data" method …

mysql otter 数据同步_MySQL数据同步之otter

一、otter介绍基于日志数据&#xff0c;用于MySQL或者ORACLE之间准实时同步数据。用途&#xff1a;mysql/oracle互相同步中间表/行记录同步二、原理及架构图otter整体模块manager (提供web页面进行同步管理)arbitrate (分布式调度&#xff0c;可跨IDC机房)node (同步过程setl)c…

ubuntu中获取文件名称并生成txt文件

简介&#xff1a; 在机器视觉学习过程中&#xff0c;通常会经常批量处理一些图片&#xff0c;在&#xff35;&#xff42;&#xff55;&#xff4e;&#xff54;&#xff55;下可以使用find命令&#xff0c;来实现将文件名全部读取出来&#xff0c;生成列表txt文件&#xff0c;…

HTMLCSS

HTML xml &#xff08;标签名&#xff09;可扩展标记语言 <Stu> </Stu> Html 超文本标记语言&#xff08;文本&#xff0c;图片&#xff0c;链接&#xff09; <> </> Internet网上编写页面&#xff08;H5版本&#xff1a;支持多种标签特性&…

java中i+=2什么意思_三分钟看懂Java中i++与++i的性能差别以及循环中如何使用

在Java中&#xff0c;自增是一种非常常见的操作&#xff0c;在自增中&#xff0c;有两种写法&#xff0c;一种是前缀自增(i)&#xff0c;一种是后缀自增(i)。这里主要简单介绍两种自增的差别。一、含义差别前缀自增和后缀自增是不同的。前缀自增(i)是从内存中加载i&#xff0c;…

动态规划:从新手到专家

作者&#xff1a;Hawstein出处&#xff1a;http://hawstein.com/posts/dp-novice-to-advanced.html前言 本文翻译自TopCoder上的一篇文章&#xff1a; Dynamic Programming: From novice to advanced &#xff0c;并非严格逐字逐句翻译&#xff0c;其中加入了自己的一些理解。水…

小程序 foreach_【第2106期】小程序依赖分析实践

前言这种可视化分析图还是很直观的&#xff0c;很有趣。今日早读文章由自然醒授权分享。正文从这开始~~用过 webpack 的同学肯定知道 webpack-bundle-analyzer &#xff0c;可以用来分析当前项目 js 文件的依赖关系。webpack-bundle-analyzer因为最近一直在做小程序业务&#x…

python----模块

collections---------------------------------------->扩展数据类型 re-------------------------------------------------->正则相关操作&#xff0c;正则 匹配字符串 time----------------------------------------------->时间相关 三种格式&#xff1a;时间戳&…

css渲染(二) 文本

一、文本样式 首行缩进  text-indent 首行缩进是将段落的第一行缩进&#xff0c;这是常用的文本格式化效果。一般地&#xff0c;中文写作时开头空两格。[注意]该属性可以为负值&#xff1b;应用于: 块级元素(包括block和inline-block)  <div><p style"text-in…

28岁学python转行_28岁转行程序员,学Java还是Python?码农:想快点月薪过万就选它...

为什么要学Java&#xff1f;Python给人的印象简单是因为我们在用Python的时候&#xff0c;可以直接调用别人已经写好的代码接口就可以&#xff0c;相对于傻瓜模式&#xff0c;Java的许多处理都要原生很多&#xff0c;写的代码可能会多一些&#xff0c;但一旦完成封装&#xff0…

CSS布局(五) 网页布局方式

网页实质是块与块之间的位置&#xff0c;块挨着块&#xff0c;块嵌套块&#xff0c;块叠着块。 三种关系&#xff1a;相邻&#xff0c;嵌套&#xff0c;重叠。 下面介绍网页布局的常用几种方式 1.一列布局&#xff1a; 一般都是固定的宽高&#xff0c;设置margin : 0 auto来水…

变量声明declare,简单运算符运算,变量测试与内容替换

declare -/ 选项 变量名 - 设类型 取消类型 -i 设为整型 -x 设为环境变量 -p 显示类型属性&#xff08;property&#xff09; [rootlocalhost ~]# a1 [rootlocalhost ~]# declare -p a declare -- a"1" [rootlocalhost ~]# export a [rootlocalhost ~]# declare -p …

如何水平居中一个元素

在项目中经常会遇到居中问题&#xff0c;这里借鉴度娘的众多答案&#xff0c;做一个总结&#xff1a; 一、元素的水平居中 1、行级元素的水平居中 <div style"width: 200px;height: 100px;border: 1px solid cyan; text-align: center;"><span>行级元素…

Yammer Metrics,一种监视应用程序的新方法

当您运行诸如Web应用程序之类的长期应用程序时&#xff0c;最好了解一些关于它们的统计信息&#xff0c;例如&#xff0c;服务的请求数&#xff0c;请求持续时间或活动请求数。 但是还有一些更通用的信息&#xff0c;例如内部集合的状态&#xff0c;代码的某些部分被执行了多少…

mysql教程目录_MySql目录(二)

MySql索引(二) 转自&#xff1a; http://www.cnblogs.com/dreamhome/archive/2013/04/16/3025304.html 所有MySQL列类型可以被索引。根据存储引擎定义每个表的最大索引数和最大索引长度。 所有存储引擎支持每个表至少16个索引&#xff0c;总索引长度至少为256字节。大多数存储引…

solr和Lucene的配置方式和应用

solr字段类型 类说明BinaryField二进制数据BoolField布尔值&#xff0c;其中’t’/’T’/’1’都是trueCollationFiled支持Unicode排序CurrencyField支持货币和汇率DateRangeFiled支持索引date rangeExternamFiledFiledpull磁盘上的文件EnumField支持定义枚举值ICUCollationFie…