赞
踩
目录
Scrapy 是基于twisted框架开发而来,twisted是一个流行的事件驱动的python网络框架。因此Scrapy使用了一种非阻塞(又名异步)的代码来实现并发。整体架构大致如下
它主要由五大组件和两中间件组成:
五大组件:
1、引擎(ENGINE):用来处理整个系统的数据流处理, 触发事务(框架核心)
2、调度器(SCHEDULER) : 用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL(抓取网页的网址或者说是链接)的优先队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址
3、下载器(DOWLOADER):用于下载网页内容, 并将网页内容返回给ENGINE,下载器是建立在twisted这个高效的异步模型上的
4、爬虫(Spiders):爬虫是主要干活的, 用于从特定的网页中提取自己需要的信息, 即所谓的实体(Item)。用户也可以从中提取出链接,让Scrapy继续抓取下一个页面
5、项目管道(Pipeline):负责处理爬虫从网页中抽取的实体(item),主要的功能是对item进行持久化、验证有效性、清理。当页面被操作。
两大中间件:
1、下载中间件:位于Scrapy引擎和下载器之间的框架,主要是处理Scrapy引擎与下载器之间的请求及响应。比如:添加请求头,更换user-agent、代理ip等
2、爬虫中间件:介于Scrapy引擎和spiders之间的框架,主要工作是处理spider的响应输入和请求输出。
- #Windows平台
- 1、pip3 install wheel #安装后,便支持通过wheel文件安装软件,wheel文件官网:https://www.lfd.uci.edu/~gohlke/pythonlibs
- 3、pip3 install lxml
- 4、pip3 install pyopenssl
- 5、下载并安装pywin32:https://sourceforge.net/projects/pywin32/files/pywin32/
- 6、下载twisted的wheel文件:http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
- 7、执行pip3 install 下载目录\Twisted-17.9.0-cp36-cp36m-win_amd64.whl
- 8、pip3 install scrapy
-
- #Linux平台
- 1、pip3 install scrapy
- #注意:都是在cmd下进行命令操作
- # 1、创建scrapy工程
- scrapy startproject 工程名字
-
- # 2、切换到scrapy攻略目录下,创建爬虫文件
- scrapy genspider 爬虫文件名字 www.xxx.com(允许爬取的域名)
-
- # 3、执行scrapy工程
- scrapy crawl 爬虫文件名字(不用带.py)
-
- # 也可以创建一个py文件,内容如下,每次执行该py文件就可以
- from scrapy.cmdline import execute
- execute(['scrapy','crawl','爬虫文件名'])
- '''
- 用法: scrapy <命令> [options] [args]
- 可用命令:
- bench # scrapy压力测试
- check # 检测项目有无语法错误
- commands
- crawl # 运行爬虫
- edit # 编辑器,一般不用
- fetch #独立于程单纯地爬取一个页面,可以拿到请求头
- genspider #创建爬虫程序
- list #列出项目中所包含的爬虫名
- parse #scrapy parse url地址 --callback 回调函数 #以此可以验证我们的回调函数是否正确
- runspider #运行一个独立的python文件,不必创建项目
- settings #如果是在项目目录下,则得到的是该项目的配置
- shell #scrapy shell url地址 在交互式调试,如选择器规则正确与否
- startproject #创建项目
- version #scrapy version 查看scrapy的版本,scrapy version -v查看scrapy依赖库的版本
- view #下载完毕后直接弹出浏览器,以此可以分辨出哪些数据是ajax请求
- '''
- '''
- 例子:
- 注意:执行项目命令,要切到项目的目录下(check、list、parse、bench)
- '''
- scrapy settings --get XXX #如果切换到项目目录下,看到的则是该项目的配置
-
- scrapy runspider baidu.py
-
- scrapy shell https://www.baidu.com
- response
- response.status
- response.body
- view(response)
-
- scrapy view https://www.taobao.com #如果页面显示内容不全,不全的内容则是ajax请求实现的,以此快速定位问题
-
- scrapy fetch --nolog --headers https://www.taobao.com
-
- scrapy version #scrapy的版本
-
- scrapy version -v #依赖库的版本
- """
- ├── project_name/
- ├── scrapy.cfg/ #项目的主配置信息,用来部署scrapy时使用,爬虫相关的配置信息在settings.py文件中
- ├── project_name/
- ├── __init__.py/
- ├── items.py/ # 设置数据存储模板,用于结构化数据,类似Django的Model
- ├── settings.py # 配置文件,如:递归的层数、并发数,延迟下载等。强调:配置文件的选项必须大写否则视为无效,正确写法USER_AGENT='xxxx'
- ├── pipelines.py/ # 数据处理行为,如:一般结构化的数据持久化
- ├── spiders/ # 爬虫目录,如:创建文件,编写爬虫规则
- ├── __init__.py/
- ├── 爬虫文件1.py # 项目开发时的本地配置
- ├── 爬虫文件2.py # 项目开发时的本地配置
- ......
- """
4、数据解析
xpath解析和css解析
- #1 //与/
- response.xpath('//body/a/')#
- response.css('div a::text')
-
- response.xpath('//body/a') #开头的//代表从整篇文档中寻找,body之后的/代表body的儿子
- >>> []
- response.xpath('//body//a') #开头的//代表从整篇文档中寻找,body之后的//代表body的子子孙孙
- >>> [<Selector xpath='//body//a' data='<a href="image1.html">Name: My image 1 <'>, <Selector xpath='//body//a' data='<a href="image2.html">Name: My image 2 <'>, <Selector xpath='//body//a' data='<a href="
- image3.html">Name: My image 3 <'>, <Selector xpath='//body//a' data='<a href="image4.html">Name: My image 4 <'>, <Selector xpath='//body//a' data='<a href="image5.html">Name: My image 5 <'>]
-
- #2 text 获取文本值
- response.xpath('//body//a/text()')
- response.css('body a::text')
- '''
- 3、extract与extract_first:从selector对象中解出内容
- extract()取出的是一个列表
- extract_first()取出的是列表第一个元素
- '''
- response.xpath('//div/a/text()').extract()
- >>> ['Name: My image 1 ', 'Name: My image 2 ', 'Name: My image 3 ', 'Name: My image 4 ', 'Name: My image 5 ']
- response.css('div a::text').extract()
- >>> ['Name: My image 1 ', 'Name: My image 2 ', 'Name: My image 3 ', 'Name: My image 4 ', 'Name: My image 5 ']
-
- response.xpath('//div/a/text()').extract_first()
- >>> 'Name: My image 1 '
- response.css('div a::text').extract_first()
- >>> 'Name: My image 1 '
-
- #4、属性:xpath的属性加前缀@
- response.xpath('//div/a/@href').extract_first()
- >>> 'image1.html'
- response.css('div a::attr(href)').extract_first()
- >>> 'image1.html'
-
- #4、嵌套查找
- response.xpath('//div').css('a').xpath('@href').extract_first()
- >>> 'image1.html'
-
- #5、设置默认值
- response.xpath('//div[@id="xxx"]').extract_first(default="not found")
- >>> 'not found'
-
- #4、按照属性查找
- response.xpath('//div[@id="images"]/a[@href="image3.html"]/text()').extract()
- response.css('#images a[@href="image3.html"]/text()').extract()
-
- #5、按照属性模糊查找
- response.xpath('//a[contains(@href,"image")]/@href').extract()
- response.css('a[href*="image"]::attr(href)').extract()
-
- response.xpath('//a[contains(@href,"image")]/img/@src').extract()
- response.css('a[href*="imag"] img::attr(src)').extract()
-
- response.xpath('//*[@href="image1.html"]')
- response.css('*[href="image1.html"]')
-
- #6、正则表达式
- response.xpath('//a/text()').re(r'Name: (.*)')
- response.xpath('//a/text()').re_first(r'Name: (.*)')
-
- #7、xpath相对路径
- res=response.xpath('//a[contains(@href,"3")]')[0]
- res.xpath('img')
- >>> [<Selector xpath='img' data='<img src="image3_thumb.jpg">'>]
- res.xpath('./img')
- >>> [<Selector xpath='./img' data='<img src="image3_thumb.jpg">'>]
- res.xpath('.//img')
- >>> [<Selector xpath='.//img' data='<img src="image3_thumb.jpg">'>]
- res.xpath('//img') #这就是从头开始扫描
- >>> [<Selector xpath='//img' data='<img src="image1_thumb.jpg">'>, <Selector xpath='//img' data='<img src="image2_thumb.jpg">'>, <Selector xpath='//img' data='<img src="image3_thumb.jpg">'>, <Selector xpa
- th='//img' data='<img src="image4_thumb.jpg">'>, <Selector xpath='//img' data='<img src="image5_thumb.jpg">'>]
-
- #8、带变量的xpath
- response.xpath('//div[@id=$xxx]/a/text()',xxx='images').extract_first()
- >>> 'Name: My image 1 '
- response.xpath('//div[count(a)=$yyy]/@id',yyy=5).extract_first() #求有5个a标签的div的id
- >>> 'images'
- # robosttxt 协议设置为false
- ROBOTSTXT_OBEY = False
- # 日志配置成错误级别
- LOG_LEVEL='ERROR'
- # 请求头配置
- USER_AGENT = '浏览器标识'
-
- '''
- 提升scrapy爬取数据效率的配置
- #1 增加并发:
- 默认scrapy开启的并发线程为32个,可以适当进行增加。在settings配置文件中修改CONCURRENT_REQUESTS = 100值为100,并发设置成了为100。
- #2 降低日志级别:
- 在运行scrapy时,会有大量日志信息的输出,为了减少CPU的使用率。可以设置log输出信息为INFO或者ERROR即可。在配置文件中编写:LOG_LEVEL = ‘INFO’
- # 3 禁止cookie:
- 如果不是真的需要cookie,则在scrapy爬取数据时可以禁止cookie从而减少CPU的使用率,提升爬取效率。在配置文件中编写:COOKIES_ENABLED = False
- # 4禁止重试:
- 对失败的HTTP进行重新请求(重试)会减慢爬取速度,因此可以禁止重试。在配置文件中编写:RETRY_ENABLED = False
- # 5 减少下载超时:
- 如果对一个非常慢的链接进行爬取,减少下载超时可以能让卡住的链接快速被放弃,从而提升效率。在配置文件中进行编写:DOWNLOAD_TIMEOUT = 10 超时时间为10s
- '''
-
- '''
- 其他配置可以根据需要进行相应配置,也可以参考下面的其他配置
- '''
- #==>第一部分:基本配置<===
- #1、项目名称,默认的USER_AGENT由它来构成,也作为日志记录的日志名
- BOT_NAME = 'Amazon'
-
- #2、爬虫应用路径
- SPIDER_MODULES = ['Amazon.spiders']
- NEWSPIDER_MODULE = 'Amazon.spiders'
-
- #3、客户端User-Agent请求头
- #USER_AGENT = 'Amazon (+http://www.yourdomain.com)'
-
- #4、是否遵循爬虫协议
- # Obey robots.txt rules
- ROBOTSTXT_OBEY = False
-
- #5、是否支持cookie,cookiejar进行操作cookie,默认开启
- #COOKIES_ENABLED = False
-
- #6、Telnet用于查看当前爬虫的信息,操作爬虫等...使用telnet ip port ,然后通过命令操作
- #TELNETCONSOLE_ENABLED = False
- #TELNETCONSOLE_HOST = '127.0.0.1'
- #TELNETCONSOLE_PORT = [6023,]
-
- #7、Scrapy发送HTTP请求默认使用的请求头
- #DEFAULT_REQUEST_HEADERS = {
- # 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
- # 'Accept-Language': 'en',
- #}
-
-
-
- #===>第二部分:并发与延迟<===
- #1、下载器总共最大处理的并发请求数,默认值16
- #CONCURRENT_REQUESTS = 32
-
- #2、每个域名能够被执行的最大并发请求数目,默认值8
- #CONCURRENT_REQUESTS_PER_DOMAIN = 16
-
- #3、能够被单个IP处理的并发请求数,默认值0,代表无限制,需要注意两点
- #I、如果不为零,那CONCURRENT_REQUESTS_PER_DOMAIN将被忽略,即并发数的限制是按照每个IP来计算,而不是每个域名
- #II、该设置也影响DOWNLOAD_DELAY,如果该值不为零,那么DOWNLOAD_DELAY下载延迟是限制每个IP而不是每个域
- #CONCURRENT_REQUESTS_PER_IP = 16
-
- #4、如果没有开启智能限速,这个值就代表一个规定死的值,代表对同一网址延迟请求的秒数
- #DOWNLOAD_DELAY = 3
-
- #===>第三部分:智能限速/自动节流:AutoThrottle extension<===
- #一:介绍
- from scrapy.contrib.throttle import AutoThrottle #http://scrapy.readthedocs.io/en/latest/topics/autothrottle.html#topics-autothrottle
- 设置目标:
- 1、比使用默认的下载延迟对站点更好
- 2、自动调整scrapy到最佳的爬取速度,所以用户无需自己调整下载延迟到最佳状态。用户只需要定义允许最大并发的请求,剩下的事情由该扩展组件自动完成
-
- #二:如何实现?
- 在Scrapy中,下载延迟是通过计算建立TCP连接到接收到HTTP包头(header)之间的时间来测量的。
- 注意,由于Scrapy可能在忙着处理spider的回调函数或者无法下载,因此在合作的多任务环境下准确测量这些延迟是十分苦难的。 不过,这些延迟仍然是对Scrapy(甚至是服务器)繁忙程度的合理测量,而这扩展就是以此为前提进行编写的。
-
- #三:限速算法
- 自动限速算法基于以下规则调整下载延迟
- #1、spiders开始时的下载延迟是基于AUTOTHROTTLE_START_DELAY的值
- #2、当收到一个response,对目标站点的下载延迟=收到响应的延迟时间/AUTOTHROTTLE_TARGET_CONCURRENCY
- #3、下一次请求的下载延迟就被设置成:对目标站点下载延迟时间和过去的下载延迟时间的平均值
- #4、没有达到200个response则不允许降低延迟
- #5、下载延迟不能变的比DOWNLOAD_DELAY更低或者比AUTOTHROTTLE_MAX_DELAY更高
-
- #四:配置使用
- #开启True,默认False
- AUTOTHROTTLE_ENABLED = True
- #起始的延迟
- AUTOTHROTTLE_START_DELAY = 5
- #最小延迟
- DOWNLOAD_DELAY = 3
- #最大延迟
- AUTOTHROTTLE_MAX_DELAY = 10
- #每秒并发请求数的平均值,不能高于 CONCURRENT_REQUESTS_PER_DOMAIN或CONCURRENT_REQUESTS_PER_IP,调高了则吞吐量增大强奸目标站点,调低了则对目标站点更加”礼貌“
- #每个特定的时间点,scrapy并发请求的数目都可能高于或低于该值,这是爬虫视图达到的建议值而不是硬限制
- AUTOTHROTTLE_TARGET_CONCURRENCY = 16.0
- #调试
- AUTOTHROTTLE_DEBUG = True
- CONCURRENT_REQUESTS_PER_DOMAIN = 16
- CONCURRENT_REQUESTS_PER_IP = 16
-
-
-
- #===>第四部分:爬取深度与爬取方式<===
- #1、爬虫允许的最大深度,可以通过meta查看当前深度;0表示无深度
- # DEPTH_LIMIT = 3
-
- #2、爬取时,0表示深度优先Lifo(默认);1表示广度优先FiFo
-
- # 后进先出,深度优先
- # DEPTH_PRIORITY = 0
- # SCHEDULER_DISK_QUEUE = 'scrapy.squeue.PickleLifoDiskQueue'
- # SCHEDULER_MEMORY_QUEUE = 'scrapy.squeue.LifoMemoryQueue'
- # 先进先出,广度优先
-
- # DEPTH_PRIORITY = 1
- # SCHEDULER_DISK_QUEUE = 'scrapy.squeue.PickleFifoDiskQueue'
- # SCHEDULER_MEMORY_QUEUE = 'scrapy.squeue.FifoMemoryQueue'
-
- #3、调度器队列
- # SCHEDULER = 'scrapy.core.scheduler.Scheduler'
- # from scrapy.core.scheduler import Scheduler
-
- #4、访问URL去重
- # DUPEFILTER_CLASS = 'step8_king.duplication.RepeatUrl'
-
-
-
- #===>第五部分:中间件、Pipelines、扩展<===
- #1、Enable or disable spider middlewares
- # See http://scrapy.readthedocs.org/en/latest/topics/spider-middleware.html
- #SPIDER_MIDDLEWARES = {
- # 'Amazon.middlewares.AmazonSpiderMiddleware': 543,
- #}
-
- #2、Enable or disable downloader middlewares
- # See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html
- DOWNLOADER_MIDDLEWARES = {
- # 'Amazon.middlewares.DownMiddleware1': 543,
- }
-
- #3、Enable or disable extensions
- # See http://scrapy.readthedocs.org/en/latest/topics/extensions.html
- #EXTENSIONS = {
- # 'scrapy.extensions.telnet.TelnetConsole': None,
- #}
-
- #4、Configure item pipelines
- # See http://scrapy.readthedocs.org/en/latest/topics/item-pipeline.html
- ITEM_PIPELINES = {
- # 'Amazon.pipelines.CustomPipeline': 200,
- }
-
-
-
- #===>第六部分:缓存<===
- """
- 1. 启用缓存
- 目的用于将已经发送的请求或相应缓存下来,以便以后使用
- from scrapy.downloadermiddlewares.httpcache import HttpCacheMiddleware
- from scrapy.extensions.httpcache import DummyPolicy
- from scrapy.extensions.httpcache import FilesystemCacheStorage
- """
- # 是否启用缓存策略
- # HTTPCACHE_ENABLED = True
-
- # 缓存策略:所有请求均缓存,下次在请求直接访问原来的缓存即可
- # HTTPCACHE_POLICY = "scrapy.extensions.httpcache.DummyPolicy"
- # 缓存策略:根据Http响应头:Cache-Control、Last-Modified 等进行缓存的策略
- # HTTPCACHE_POLICY = "scrapy.extensions.httpcache.RFC2616Policy"
-
- # 缓存超时时间
- # HTTPCACHE_EXPIRATION_SECS = 0
-
- # 缓存保存路径
- # HTTPCACHE_DIR = 'httpcache'
-
- # 缓存忽略的Http状态码
- # HTTPCACHE_IGNORE_HTTP_CODES = []
-
- # 缓存存储的插件
- # HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'
-
- #===>第七部分:线程池<===
- REACTOR_THREADPOOL_MAXSIZE = 10
-
- #Default: 10
- #scrapy基于twisted异步IO框架,downloader是多线程的,线程数是Twisted线程池的默认大小(The maximum limit for Twisted Reactor thread pool size.)
-
- #关于twisted线程池:
- http://twistedmatrix.com/documents/10.1.0/core/howto/threading.html
-
- #线程池实现:twisted.python.threadpool.ThreadPool
- twisted调整线程池大小:
- from twisted.internet import reactor
- reactor.suggestThreadPoolSize(30)
(1)基于终端指令
- '''
- 要求:只可以将parse方法的返回值存储到本地的文本文件中
- 注意:持久化存储对应的文本文件的类型只可以为:‘json','jsonlines','jl',’csv',‘xml',’marshal','pickle‘
- 指令:scrapy crawl 爬虫文件名 -o filePath
- 优点:简介高效便捷
- 缺点:局限性比较强(数据只可以存储到指定后缀的文本文件中)
- '''
(2)基于管道
- '''
- items.py: 数据结构模版文件,定义数据属性
- pipelines.py:管道文件,用来接收数据(items),进行持久化操作
- 持久化流程:
- 1.爬虫文件爬取到数据后,需要将数据封装到items对象中。需要导入items.py文件里面的类
- 2.使用yield关键字将items对象提交给pipelines管道进行持久化操作。
- 3.在管道文件中的process_item方法中接收爬虫文件提交过来的item对象,然后编写持久化存储的代码将item对象中存储的数据进行持久化存储
- 4.settings.py配置文件中开启管道
- '''
- # pipline.py 管道类
- class BlogPostPipeline:
- # 开始
- def open_spider(self,spider):
- self.conn=pymysql.connect(host='127.0.0.1', user='root', password="root",database='pcdb', charset='utf8mb4',port=3306,)
- # 持久化过程
- def process_item(self,item,spider):
- cursor=self.conn.cursor()
- sql = 'insert into article (title,url,content,`desc`) values (%s,%s,%s,%s)'
- cursor.execute(sql, [item['title'], item['url'], item['content'], item['desc']])
- self.conn.commit()
- return item
- # 结束
- def close_spider(self,spider):
- self.conn.close()
- print('爬虫结束了')
-
-
- #items文件
- import scrapy
- class PostItem(scrapy.Item):
-
- title=scrapy.Field()
- url=scrapy.Field()
- desc=scrapy.Field()
- content=scrapy.Field()
图片存储 :
1、在setting文件配置:IMAGES_STORE = ‘./imgs’:表示最终图片存储的目录,并在ITEM_PIPELINES中增加管道类中新增的类
2、重写一个管道类(继承ImagesPipeline)
3、基于ImagesPipeLine类的管道类三个方法:get_media_request()、file_path()、tiem_completed()
- from scrapy.pipelines.images import ImagesPipeline
- # 继承ImagesPipeline
- class myPipeline(ImagesPipeline):
-
- def get_media_requests(self, item, info):
- print('下载开始')
- return scrapy.Request(item['img_url'],headers={'referer':item['img_referer']},meta={'item':item})
-
- def file_path(self, request, response=None, info=None):
- item=request.meta['item']
- url = request.url
- file_name=url.split('/')[-1]
- return file_name
-
- def item_completed(self, results, item, info):
- print('下载结束')
- return item
(1)拦截请求:修改user-agent、请求头,加入代理ip(可以方法process_exception方法,或者process_request方法)等
(2)拦截响应:修改响应对象或者响应数据(例子:比如一些动态加载的页面)
- def process_request(self, request, spider):
- #1、更改请求头的user-agent
- from fake_useragent import UserAgent
- ua=UserAgent()
- request.headers['User-Agent']=ua.random
- # 2、设置代理ip
- import requests
- r = requests.get('http://127.0.0.1:5010/get/')
- proxy=r.json().get('proxy')
- request.meta['download_timeout'] = 5
- request.meta["proxy"] = 'http://'+proxy
- print(request)
- return None
- # 在中间件使用selenium处理ajax请求问题
- def process_response(self, request, response, spider):
-
- from scrapy.http import HtmlResponse
-
- spider.bro.get('https://dig.chouti.com/')
-
- response = HtmlResponse(url='https://dig.chouti.com/', body=spider.bro.page_source.encode('utf-8'),
- request=request)
-
- return response
1、BaseDupeFilter源码
- class BaseDupeFilter:
- @classmethod
- def from_settings(cls, settings):
- return cls()
- # 去重方法
- def request_seen(self, request):
- return False
- def open(self): # can return deferred
- pass
- def close(self, reason): # can return a deferred
- pass
- def log(self, request, spider): # log that a request has been filtered
- pass
2、RFPDupeFilter源码
- class RFPDupeFilter(BaseDupeFilter):
- """Request Fingerprint duplicates filter"""
-
- def __init__(self, path=None, debug=False):
- self.file = None
- self.fingerprints = set() #集合
- self.logdupes = True
- self.debug = debug
- self.logger = logging.getLogger(__name__)
- if path:
- self.file = open(os.path.join(path, 'requests.seen'), 'a+')
- self.file.seek(0)
- self.fingerprints.update(x.rstrip() for x in self.file)
-
- @classmethod
- def from_settings(cls, settings):
- debug = settings.getbool('DUPEFILTER_DEBUG')
- return cls(job_dir(settings), debug)
- # 去重的主要方法
- def request_seen(self, request):
- '''
- from scrapy.utils.request import request_fingerprint
- 利用request_fingerprint对请求取指纹(md5)
- 判断如果在集合中,返回True,不在则添加到集合
- '''
- fp = self.request_fingerprint(request)
- if fp in self.fingerprints:
- return True
- self.fingerprints.add(fp)
- if self.file:
- self.file.write(fp + '\n')
-
- def request_fingerprint(self, request):
- return request_fingerprint(request)
-
- def close(self, reason):
- if self.file:
- self.file.close()
-
- def log(self, request, spider):
- if self.debug:
- msg = "Filtered duplicate request: %(request)s (referer: %(referer)s)"
- args = {'request': request, 'referer': referer_str(request)}
- self.logger.debug(msg, args, extra={'spider': spider})
- elif self.logdupes:
- msg = ("Filtered duplicate request: %(request)s"
- " - no more duplicates will be shown"
- " (see DUPEFILTER_DEBUG to show all duplicates)")
- self.logger.debug(msg, {'request': request}, extra={'spider': spider})
- self.logdupes = False
-
- spider.crawler.stats.inc_value('dupefilter/filtered', spider=spider)
3、通过BaseDupeFilter和RFPDupeFilter对比,我们可以看出去重规则主要在request_seen()方法中,则我们可以仿照写一个自定义去重类
(1) 新建一个去重文件dupfilter.py
- from scrapy.dupefilters import BaseDupeFilter
- class UrlFilter(BaseDupeFilter):
- def __init__(self):
- self.visited = set() #或者放到数据库
-
- @classmethod
- def from_settings(cls, settings):
- return cls()
-
- def request_seen(self, request):
- if request.url in self.visited:
- return True
- self.visited.add(request.url)
-
- def open(self): # can return deferred
- pass
-
- def close(self, reason): # can return a deferred
- pass
-
- def log(self, request, spider): # log that a request has been filtered
- pass
-
(2) 在配置文件settings中
DUPEFILTER_CLASS = '项目名.dupfilter.UrlFilter'
scrapy-redis是在原来的scrapy的基础上,重写Scheduler,让调度器到共享队列中取Request(该request请求时去重的)。去重主要是利用了redis的集合类型
(1)安装scrapy-redis模块,pip install scrapy-redis
(2)配置settings文件
- # scheduler调度器配置,参考Scheduler源码
- class Scheduler(object):
- """
- 在scrapy中settings的配置信息
- --------
- SCHEDULER_PERSIST : bool (default: False)
- 关闭的时候,是否保留redis队列(也就是起始地址).
- SCHEDULER_FLUSH_ON_START : bool (default: False)
- 是否在启动时刷新redis队列。
- SCHEDULER_IDLE_BEFORE_CLOSE : int (default: 0)
- 如果没有收到任何消息,在关闭之前要等待多少秒(超时时间)。
- SCHEDULER_QUEUE_KEY : str
- 调度器中请求存放在redis中的key
- SCHEDULER_QUEUE_CLASS : str
- 调度程序队列类s.
- SCHEDULER_DUPEFILTER_KEY : str
- 去重规则中,在redis中保存对应的key值.
- SCHEDULER_DUPEFILTER_CLASS : str
- 去重规则对应的处理类
- SCHEDULER_SERIALIZER : str
- 对保存到redis中的数据进行序列化.默认使用pickle
- # SCHEDULER_SERIALIZER ="scrapy_redis.picklecompat"
- """
-
- # Redis配置,参考connection源码
- def get_redis_from_settings(settings):
- # ----------------
- # 在settings中的配置
- # ----------------
- REDIS_URL : str, optional
- # 例子:REDIS_URL='redis://:12345@127.0.0.1:6379'
- #redis的连接url,优先去url配置(等同的host\post的配置),.
- #注意:配置了url,就不用配置主机号跟端口了
- REDIS_HOST : str, optional
- #redis的主机名.
- REDIS_PORT : str, optional
- #redis的端口.
- REDIS_ENCODING : str, optional
- #redis的编码.
- REDIS_PARAMS : dict, optional
- #redis的其他参数,是字典类型.
- #如: REDIS_PARAMS = {'password':'12345'}
-
- # 持久化 配置,参考pipeline源码
- class RedisPipeline(object):
-
- Settings
- --------
- REDIS_ITEMS_KEY : str
- # redis关键字
- REDIS_ITEMS_SERIALIZER : str
- # 序列化函数
-
-
- # 参考spiders源码配置
- class RedisSpider(RedisMixin, Spider):
- Settings
- --------
- REDIS_START_URLS_KEY : str (default: "<spider.name>:start_urls")
- # 起始rurl从对应的redis的key.
- REDIS_START_URLS_BATCH_SIZE : int (deprecated by CONCURRENT_REQUESTS)
- # 每次尝试从reids获取的url数,默认从配置中获取.
- REDIS_START_URLS_AS_SET : bool (default: False)
- # 获取起始URL时,如果为True,则使用self.server.spop;如果为False,则使用self.server.lpop
- REDIS_ENCODING : str (default: "utf-8")
- #编码
(3) 参考案例(在原来的scrapy的源码上进行修改)
- '''
- spdies文件
- 继承RedisSpider
- '''
- from scrapy_redis.spiders import RedisSpider
-
- class BlogSpider(RedisSpider):
- name = 'redis_blog'
- redis_key = 'myspider:start_urls'
- def parse(self, response):
- print('---redis-blog-----')
- article_list = response.xpath('//div[@id="post_list"]/article')
- ....
- #settings文件配置
- #redis的连接
- REDIS_HOST='localhost'
- REDIS_PORT=6379
- # REDIS_PASSWD='12345'
- REDIS_PARAMS = {'password':'12345'}
- # REDIS_URL='redis://:12345@127.0.0.1:6379'
- # from scrapy_redis.scheduler import Scheduler
- DUPEFILTER_CLASS="scrapy_redis.dupefilter.RFPDupeFilter"
- SCHEDULER = 'scrapy_redis.scheduler.Scheduler'
- SCHEDULER_PERSIST = True
最后,在redis的数据库中插入一个起始地址:lpush myspider:start_urls https://www.cnblogs.com/ 就可以运行了
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。