爬虫框架: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…

pycaffe简明文档

pycaffe简明文档 by ChrisZZ, imzhuofoxmail.com 2018年01月18日19:00:56 说明 caffe的python接口没有官方说明文档,例如查看一个函数的用法,pytorch能查到所有的用法,而pycaffe则需要自行去查看源码。于是手动写了一个很粗糙的文档&#xff…

Java死锁示例–如何分析死锁情况

死锁是两个或多个线程永远被阻塞的编程情况,这种情况发生在至少两个线程和两个或更多资源的情况下。 在这里,我编写了一个简单的程序,该程序将导致死锁情况,然后我们将看到如何对其进行分析。 Java死锁示例 package com.journald…

项目

不解的问题:表格里td能否用margin?覆盖z-index需要设置背景才能覆盖?表格与表格能否用float? 转载于:https://www.cnblogs.com/ssx5310518/p/7282199.html

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 …

使用Jackson和Super类型令牌进行Json反序列化

Datatables是一个jquery插件&#xff0c;用于显示表格信息–它可以增强简单的表或可以使用基于AJAX的数据并以表格形式显示信息。 数据表要​​求来自服务器的数据遵循特定的JSON格式才能在屏幕上显示。 考虑要显示成员实体列表的情况&#xff0c;那么对于成员而言&#xff0c…

马拉车

O(n)求字符串中的最长回文串的长度 1 char s[SIZE];2 int len[SIZE*2];3 char str[SIZE*2];4 int manacher(){//预处理字符串&#xff0c;将字符串隔开,且开头和结尾字符串要不同,防止越界&#xff0c;如aaa预处理为#a#a#a$5 int l strlen(s);6 int ls 0;7 st…

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;…

使用Google Guava创建收藏和实现不变性

因此&#xff0c;我想看看番石榴提供的一些集合创建模式&#xff0c;以及它提供的某些不可变集合类型。 如果您没有看过我以前的文章&#xff0c;则可能要从这里开始&#xff1a; 番石榴第1部分– MultiMaps 番石榴第2部分– BiMaps 番石榴第3部分–多组 Guava的所有集合实…

HTMLCSS

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

Mysql报错130_mysql 突然报错,连接不上

错误如下&#xff0c; Access denied for user rootlocalhost (using password关掉mysql服务&#xff0c;重新启动如果不行&#xff0c;那应该就是密码被改了&#xff0c;密码不对应1.以系统管理员身份运行cmd.2.查看mysql是否已经启动&#xff0c;如果已经启动&#xff0c;就停…

为内存密集型应用程序转义JVM堆

如果您曾经分配过大的Java堆&#xff0c;您就会知道在某个时候&#xff08;通常从大约4 GiB开始&#xff09;&#xff0c;您将开始遇到垃圾回收暂停的问题。 我不会详细介绍为什么在JVM中会出现暂停&#xff0c;但是总之&#xff0c;当JVM进行完整的收集并且您有很大的堆时&am…

MySQL学习笔记1(增删查改)

创建表&#xff1a; /*创建数据库create database 数据库名; */ CREATE DATABASE mybase; /*使用数据库use 数据库名 */ USE mybase;/*创建数据表的格式create table 表名(列名1 数据类型 约束,列名2 数据类型 约束,列名3 数据类型 约束);创建用户表,用户编号,姓名,用户的地址将…

Angular4 中内置指令的基本用法

ngFor 作用&#xff1a;像 for 循环一样&#xff0c;可以重复的从数组中取值并显示出来。 // .tsthis.userInfo [张三, 李四, 王五];// .html<div class"ui list" *ngFor"let username of userInfo"><div class"item">{{username}}…

初入编程的新世界

准备跨入程序员的行列了&#xff0c; 今天开课第一天&#xff0c; 算起来之前学习的几天&#xff0c; 第一次真正的了解了网页制作包括什么&#xff0c; html&#xff08;结构&#xff09;&#xff0c;css&#xff08;页面美化 层叠样式表&#xff09;&#xff0c;JavaScri…