当前位置:   article > 正文

爬虫之scrapy、scrapy-redis_scrapy scrapy-reids

scrapy scrapy-reids

目录

一、Scrapy的简介

二、Scrapy的使用

1、安装

2、Scrapy工程的基本操作以及命令

3、项目结构

5、配置

6、scrapy持久化存储

7、scrapy中间件(下载中间件)

三、去重源码解析

四、scrapy-redis分布式爬虫

1、介绍

 2、使用方法


一、Scrapy的简介

  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的响应输入和请求输出。

二、Scrapy的使用

1、安装

  1. #Windows平台
  2. 1、pip3 install wheel #安装后,便支持通过wheel文件安装软件,wheel文件官网:https://www.lfd.uci.edu/~gohlke/pythonlibs
  3. 3、pip3 install lxml
  4. 4、pip3 install pyopenssl
  5. 5、下载并安装pywin32:https://sourceforge.net/projects/pywin32/files/pywin32/
  6. 6、下载twisted的wheel文件:http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
  7. 7、执行pip3 install 下载目录\Twisted-17.9.0-cp36-cp36m-win_amd64.whl
  8. 8、pip3 install scrapy
  9. #Linux平台
  10. 1、pip3 install scrapy

2、Scrapy工程的基本操作以及命令

  1. #注意:都是在cmd下进行命令操作
  2. # 1、创建scrapy工程
  3. scrapy startproject 工程名字
  4. # 2、切换到scrapy攻略目录下,创建爬虫文件
  5. scrapy genspider 爬虫文件名字 www.xxx.com(允许爬取的域名)
  6. # 3、执行scrapy工程
  7. scrapy crawl 爬虫文件名字(不用带.py)
  8. # 也可以创建一个py文件,内容如下,每次执行该py文件就可以
  9. from scrapy.cmdline import execute
  10. execute(['scrapy','crawl','爬虫文件名'])
  1. '''
  2. 用法: scrapy <命令> [options] [args]
  3. 可用命令:
  4. bench # scrapy压力测试
  5. check # 检测项目有无语法错误
  6. commands
  7. crawl # 运行爬虫
  8. edit # 编辑器,一般不用
  9. fetch #独立于程单纯地爬取一个页面,可以拿到请求头
  10. genspider #创建爬虫程序
  11. list #列出项目中所包含的爬虫名
  12. parse #scrapy parse url地址 --callback 回调函数 #以此可以验证我们的回调函数是否正确
  13. runspider #运行一个独立的python文件,不必创建项目
  14. settings #如果是在项目目录下,则得到的是该项目的配置
  15. shell #scrapy shell url地址 在交互式调试,如选择器规则正确与否
  16. startproject #创建项目
  17. version #scrapy version 查看scrapy的版本,scrapy version -v查看scrapy依赖库的版本
  18. view #下载完毕后直接弹出浏览器,以此可以分辨出哪些数据是ajax请求
  19. '''
  20. '''
  21. 例子:
  22. 注意:执行项目命令,要切到项目的目录下(check、list、parse、bench)
  23. '''
  24. scrapy settings --get XXX #如果切换到项目目录下,看到的则是该项目的配置
  25. scrapy runspider baidu.py
  26. scrapy shell https://www.baidu.com
  27. response
  28. response.status
  29. response.body
  30. view(response)
  31. scrapy view https://www.taobao.com #如果页面显示内容不全,不全的内容则是ajax请求实现的,以此快速定位问题
  32. scrapy fetch --nolog --headers https://www.taobao.com
  33. scrapy version #scrapy的版本
  34. scrapy version -v #依赖库的版本

3、项目结构

  1. """
  2. ├── project_name/
  3. ├── scrapy.cfg/ #项目的主配置信息,用来部署scrapy时使用,爬虫相关的配置信息在settings.py文件中
  4. ├── project_name/
  5. ├── __init__.py/
  6. ├── items.py/ # 设置数据存储模板,用于结构化数据,类似Django的Model
  7. ├── settings.py # 配置文件,如:递归的层数、并发数,延迟下载等。强调:配置文件的选项必须大写否则视为无效,正确写法USER_AGENT='xxxx'
  8. ├── pipelines.py/ # 数据处理行为,如:一般结构化的数据持久化
  9. ├── spiders/ # 爬虫目录,如:创建文件,编写爬虫规则
  10. ├── __init__.py/
  11. ├── 爬虫文件1.py # 项目开发时的本地配置
  12. ├── 爬虫文件2.py # 项目开发时的本地配置
  13. ......
  14. """

4、数据解析

xpath解析和css解析

  1. #1 //与/
  2. response.xpath('//body/a/')#
  3. response.css('div a::text')
  4. response.xpath('//body/a') #开头的//代表从整篇文档中寻找,body之后的/代表body的儿子
  5. >>> []
  6. response.xpath('//body//a') #开头的//代表从整篇文档中寻找,body之后的//代表body的子子孙孙
  7. >>> [<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="
  8. 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 <'>]
  9. #2 text 获取文本值
  10. response.xpath('//body//a/text()')
  11. response.css('body a::text')
  12. '''
  13. 3、extract与extract_first:从selector对象中解出内容
  14. extract()取出的是一个列表
  15. extract_first()取出的是列表第一个元素
  16. '''
  17. response.xpath('//div/a/text()').extract()
  18. >>> ['Name: My image 1 ', 'Name: My image 2 ', 'Name: My image 3 ', 'Name: My image 4 ', 'Name: My image 5 ']
  19. response.css('div a::text').extract()
  20. >>> ['Name: My image 1 ', 'Name: My image 2 ', 'Name: My image 3 ', 'Name: My image 4 ', 'Name: My image 5 ']
  21. response.xpath('//div/a/text()').extract_first()
  22. >>> 'Name: My image 1 '
  23. response.css('div a::text').extract_first()
  24. >>> 'Name: My image 1 '
  25. #4、属性:xpath的属性加前缀@
  26. response.xpath('//div/a/@href').extract_first()
  27. >>> 'image1.html'
  28. response.css('div a::attr(href)').extract_first()
  29. >>> 'image1.html'
  30. #4、嵌套查找
  31. response.xpath('//div').css('a').xpath('@href').extract_first()
  32. >>> 'image1.html'
  33. #5、设置默认值
  34. response.xpath('//div[@id="xxx"]').extract_first(default="not found")
  35. >>> 'not found'
  36. #4、按照属性查找
  37. response.xpath('//div[@id="images"]/a[@href="image3.html"]/text()').extract()
  38. response.css('#images a[@href="image3.html"]/text()').extract()
  39. #5、按照属性模糊查找
  40. response.xpath('//a[contains(@href,"image")]/@href').extract()
  41. response.css('a[href*="image"]::attr(href)').extract()
  42. response.xpath('//a[contains(@href,"image")]/img/@src').extract()
  43. response.css('a[href*="imag"] img::attr(src)').extract()
  44. response.xpath('//*[@href="image1.html"]')
  45. response.css('*[href="image1.html"]')
  46. #6、正则表达式
  47. response.xpath('//a/text()').re(r'Name: (.*)')
  48. response.xpath('//a/text()').re_first(r'Name: (.*)')
  49. #7、xpath相对路径
  50. res=response.xpath('//a[contains(@href,"3")]')[0]
  51. res.xpath('img')
  52. >>> [<Selector xpath='img' data='<img src="image3_thumb.jpg">'>]
  53. res.xpath('./img')
  54. >>> [<Selector xpath='./img' data='<img src="image3_thumb.jpg">'>]
  55. res.xpath('.//img')
  56. >>> [<Selector xpath='.//img' data='<img src="image3_thumb.jpg">'>]
  57. res.xpath('//img') #这就是从头开始扫描
  58. >>> [<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
  59. th='//img' data='<img src="image4_thumb.jpg">'>, <Selector xpath='//img' data='<img src="image5_thumb.jpg">'>]
  60. #8、带变量的xpath
  61. response.xpath('//div[@id=$xxx]/a/text()',xxx='images').extract_first()
  62. >>> 'Name: My image 1 '
  63. response.xpath('//div[count(a)=$yyy]/@id',yyy=5).extract_first() #求有5个a标签的div的id
  64. >>> 'images'

5、配置

  1. # robosttxt 协议设置为false
  2. ROBOTSTXT_OBEY = False
  3. # 日志配置成错误级别
  4. LOG_LEVEL='ERROR'
  5. # 请求头配置
  6. USER_AGENT = '浏览器标识'
  7. '''
  8. 提升scrapy爬取数据效率的配置
  9. #1 增加并发:
  10. 默认scrapy开启的并发线程为32个,可以适当进行增加。在settings配置文件中修改CONCURRENT_REQUESTS = 100值为100,并发设置成了为100。
  11. #2 降低日志级别:
  12. 在运行scrapy时,会有大量日志信息的输出,为了减少CPU的使用率。可以设置log输出信息为INFO或者ERROR即可。在配置文件中编写:LOG_LEVEL = ‘INFO’
  13. # 3 禁止cookie:
  14. 如果不是真的需要cookie,则在scrapy爬取数据时可以禁止cookie从而减少CPU的使用率,提升爬取效率。在配置文件中编写:COOKIES_ENABLED = False
  15. # 4禁止重试:
  16. 对失败的HTTP进行重新请求(重试)会减慢爬取速度,因此可以禁止重试。在配置文件中编写:RETRY_ENABLED = False
  17. # 5 减少下载超时:
  18. 如果对一个非常慢的链接进行爬取,减少下载超时可以能让卡住的链接快速被放弃,从而提升效率。在配置文件中进行编写:DOWNLOAD_TIMEOUT = 10 超时时间为10s
  19. '''
  20. '''
  21. 其他配置可以根据需要进行相应配置,也可以参考下面的其他配置
  22. '''
  1. #==>第一部分:基本配置<===
  2. #1、项目名称,默认的USER_AGENT由它来构成,也作为日志记录的日志名
  3. BOT_NAME = 'Amazon'
  4. #2、爬虫应用路径
  5. SPIDER_MODULES = ['Amazon.spiders']
  6. NEWSPIDER_MODULE = 'Amazon.spiders'
  7. #3、客户端User-Agent请求头
  8. #USER_AGENT = 'Amazon (+http://www.yourdomain.com)'
  9. #4、是否遵循爬虫协议
  10. # Obey robots.txt rules
  11. ROBOTSTXT_OBEY = False
  12. #5、是否支持cookie,cookiejar进行操作cookie,默认开启
  13. #COOKIES_ENABLED = False
  14. #6、Telnet用于查看当前爬虫的信息,操作爬虫等...使用telnet ip port ,然后通过命令操作
  15. #TELNETCONSOLE_ENABLED = False
  16. #TELNETCONSOLE_HOST = '127.0.0.1'
  17. #TELNETCONSOLE_PORT = [6023,]
  18. #7、Scrapy发送HTTP请求默认使用的请求头
  19. #DEFAULT_REQUEST_HEADERS = {
  20. # 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
  21. # 'Accept-Language': 'en',
  22. #}
  23. #===>第二部分:并发与延迟<===
  24. #1、下载器总共最大处理的并发请求数,默认值16
  25. #CONCURRENT_REQUESTS = 32
  26. #2、每个域名能够被执行的最大并发请求数目,默认值8
  27. #CONCURRENT_REQUESTS_PER_DOMAIN = 16
  28. #3、能够被单个IP处理的并发请求数,默认值0,代表无限制,需要注意两点
  29. #I、如果不为零,那CONCURRENT_REQUESTS_PER_DOMAIN将被忽略,即并发数的限制是按照每个IP来计算,而不是每个域名
  30. #II、该设置也影响DOWNLOAD_DELAY,如果该值不为零,那么DOWNLOAD_DELAY下载延迟是限制每个IP而不是每个域
  31. #CONCURRENT_REQUESTS_PER_IP = 16
  32. #4、如果没有开启智能限速,这个值就代表一个规定死的值,代表对同一网址延迟请求的秒数
  33. #DOWNLOAD_DELAY = 3
  34. #===>第三部分:智能限速/自动节流:AutoThrottle extension<===
  35. #一:介绍
  36. from scrapy.contrib.throttle import AutoThrottle #http://scrapy.readthedocs.io/en/latest/topics/autothrottle.html#topics-autothrottle
  37. 设置目标:
  38. 1、比使用默认的下载延迟对站点更好
  39. 2、自动调整scrapy到最佳的爬取速度,所以用户无需自己调整下载延迟到最佳状态。用户只需要定义允许最大并发的请求,剩下的事情由该扩展组件自动完成
  40. #二:如何实现?
  41. 在Scrapy中,下载延迟是通过计算建立TCP连接到接收到HTTP包头(header)之间的时间来测量的。
  42. 注意,由于Scrapy可能在忙着处理spider的回调函数或者无法下载,因此在合作的多任务环境下准确测量这些延迟是十分苦难的。 不过,这些延迟仍然是对Scrapy(甚至是服务器)繁忙程度的合理测量,而这扩展就是以此为前提进行编写的。
  43. #三:限速算法
  44. 自动限速算法基于以下规则调整下载延迟
  45. #1、spiders开始时的下载延迟是基于AUTOTHROTTLE_START_DELAY的值
  46. #2、当收到一个response,对目标站点的下载延迟=收到响应的延迟时间/AUTOTHROTTLE_TARGET_CONCURRENCY
  47. #3、下一次请求的下载延迟就被设置成:对目标站点下载延迟时间和过去的下载延迟时间的平均值
  48. #4、没有达到200个response则不允许降低延迟
  49. #5、下载延迟不能变的比DOWNLOAD_DELAY更低或者比AUTOTHROTTLE_MAX_DELAY更高
  50. #四:配置使用
  51. #开启True,默认False
  52. AUTOTHROTTLE_ENABLED = True
  53. #起始的延迟
  54. AUTOTHROTTLE_START_DELAY = 5
  55. #最小延迟
  56. DOWNLOAD_DELAY = 3
  57. #最大延迟
  58. AUTOTHROTTLE_MAX_DELAY = 10
  59. #每秒并发请求数的平均值,不能高于 CONCURRENT_REQUESTS_PER_DOMAIN或CONCURRENT_REQUESTS_PER_IP,调高了则吞吐量增大强奸目标站点,调低了则对目标站点更加”礼貌“
  60. #每个特定的时间点,scrapy并发请求的数目都可能高于或低于该值,这是爬虫视图达到的建议值而不是硬限制
  61. AUTOTHROTTLE_TARGET_CONCURRENCY = 16.0
  62. #调试
  63. AUTOTHROTTLE_DEBUG = True
  64. CONCURRENT_REQUESTS_PER_DOMAIN = 16
  65. CONCURRENT_REQUESTS_PER_IP = 16
  66. #===>第四部分:爬取深度与爬取方式<===
  67. #1、爬虫允许的最大深度,可以通过meta查看当前深度;0表示无深度
  68. # DEPTH_LIMIT = 3
  69. #2、爬取时,0表示深度优先Lifo(默认);1表示广度优先FiFo
  70. # 后进先出,深度优先
  71. # DEPTH_PRIORITY = 0
  72. # SCHEDULER_DISK_QUEUE = 'scrapy.squeue.PickleLifoDiskQueue'
  73. # SCHEDULER_MEMORY_QUEUE = 'scrapy.squeue.LifoMemoryQueue'
  74. # 先进先出,广度优先
  75. # DEPTH_PRIORITY = 1
  76. # SCHEDULER_DISK_QUEUE = 'scrapy.squeue.PickleFifoDiskQueue'
  77. # SCHEDULER_MEMORY_QUEUE = 'scrapy.squeue.FifoMemoryQueue'
  78. #3、调度器队列
  79. # SCHEDULER = 'scrapy.core.scheduler.Scheduler'
  80. # from scrapy.core.scheduler import Scheduler
  81. #4、访问URL去重
  82. # DUPEFILTER_CLASS = 'step8_king.duplication.RepeatUrl'
  83. #===>第五部分:中间件、Pipelines、扩展<===
  84. #1、Enable or disable spider middlewares
  85. # See http://scrapy.readthedocs.org/en/latest/topics/spider-middleware.html
  86. #SPIDER_MIDDLEWARES = {
  87. # 'Amazon.middlewares.AmazonSpiderMiddleware': 543,
  88. #}
  89. #2、Enable or disable downloader middlewares
  90. # See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html
  91. DOWNLOADER_MIDDLEWARES = {
  92. # 'Amazon.middlewares.DownMiddleware1': 543,
  93. }
  94. #3、Enable or disable extensions
  95. # See http://scrapy.readthedocs.org/en/latest/topics/extensions.html
  96. #EXTENSIONS = {
  97. # 'scrapy.extensions.telnet.TelnetConsole': None,
  98. #}
  99. #4、Configure item pipelines
  100. # See http://scrapy.readthedocs.org/en/latest/topics/item-pipeline.html
  101. ITEM_PIPELINES = {
  102. # 'Amazon.pipelines.CustomPipeline': 200,
  103. }
  104. #===>第六部分:缓存<===
  105. """
  106. 1. 启用缓存
  107. 目的用于将已经发送的请求或相应缓存下来,以便以后使用
  108. from scrapy.downloadermiddlewares.httpcache import HttpCacheMiddleware
  109. from scrapy.extensions.httpcache import DummyPolicy
  110. from scrapy.extensions.httpcache import FilesystemCacheStorage
  111. """
  112. # 是否启用缓存策略
  113. # HTTPCACHE_ENABLED = True
  114. # 缓存策略:所有请求均缓存,下次在请求直接访问原来的缓存即可
  115. # HTTPCACHE_POLICY = "scrapy.extensions.httpcache.DummyPolicy"
  116. # 缓存策略:根据Http响应头:Cache-Control、Last-Modified 等进行缓存的策略
  117. # HTTPCACHE_POLICY = "scrapy.extensions.httpcache.RFC2616Policy"
  118. # 缓存超时时间
  119. # HTTPCACHE_EXPIRATION_SECS = 0
  120. # 缓存保存路径
  121. # HTTPCACHE_DIR = 'httpcache'
  122. # 缓存忽略的Http状态码
  123. # HTTPCACHE_IGNORE_HTTP_CODES = []
  124. # 缓存存储的插件
  125. # HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'
  126. #===>第七部分:线程池<===
  127. REACTOR_THREADPOOL_MAXSIZE = 10
  128. #Default: 10
  129. #scrapy基于twisted异步IO框架,downloader是多线程的,线程数是Twisted线程池的默认大小(The maximum limit for Twisted Reactor thread pool size.)
  130. #关于twisted线程池:
  131. http://twistedmatrix.com/documents/10.1.0/core/howto/threading.html
  132. #线程池实现:twisted.python.threadpool.ThreadPool
  133. twisted调整线程池大小:
  134. from twisted.internet import reactor
  135. reactor.suggestThreadPoolSize(30)
其他配置

6、scrapy持久化存储

(1)基于终端指令

  1. '''
  2. 要求:只可以将parse方法的返回值存储到本地的文本文件中
  3. 注意:持久化存储对应的文本文件的类型只可以为:‘json','jsonlines','jl',’csv',‘xml',’marshal','pickle‘
  4. 指令:scrapy crawl 爬虫文件名 -o filePath
  5. 优点:简介高效便捷
  6. 缺点:局限性比较强(数据只可以存储到指定后缀的文本文件中)
  7. '''

(2)基于管道

  1. '''
  2. items.py: 数据结构模版文件,定义数据属性
  3. pipelines.py:管道文件,用来接收数据(items),进行持久化操作
  4. 持久化流程:
  5. 1.爬虫文件爬取到数据后,需要将数据封装到items对象中。需要导入items.py文件里面的类
  6. 2.使用yield关键字将items对象提交给pipelines管道进行持久化操作。
  7. 3.在管道文件中的process_item方法中接收爬虫文件提交过来的item对象,然后编写持久化存储的代码将item对象中存储的数据进行持久化存储
  8. 4.settings.py配置文件中开启管道
  9. '''
  10. # pipline.py 管道类
  11. class BlogPostPipeline:
  12. # 开始
  13. def open_spider(self,spider):
  14. self.conn=pymysql.connect(host='127.0.0.1', user='root', password="root",database='pcdb', charset='utf8mb4',port=3306,)
  15. # 持久化过程
  16. def process_item(self,item,spider):
  17. cursor=self.conn.cursor()
  18. sql = 'insert into article (title,url,content,`desc`) values (%s,%s,%s,%s)'
  19. cursor.execute(sql, [item['title'], item['url'], item['content'], item['desc']])
  20. self.conn.commit()
  21. return item
  22. # 结束
  23. def close_spider(self,spider):
  24. self.conn.close()
  25. print('爬虫结束了')
  26. #items文件
  27. import scrapy
  28. class PostItem(scrapy.Item):
  29. title=scrapy.Field()
  30. url=scrapy.Field()
  31. desc=scrapy.Field()
  32. content=scrapy.Field()

图片存储 :

1、在setting文件配置:IMAGES_STORE = ‘./imgs’:表示最终图片存储的目录,并在ITEM_PIPELINES中增加管道类中新增的类

2、重写一个管道类(继承ImagesPipeline)

3、基于ImagesPipeLine类的管道类三个方法:get_media_request()、file_path()、tiem_completed()

  1. from scrapy.pipelines.images import ImagesPipeline
  2. # 继承ImagesPipeline
  3. class myPipeline(ImagesPipeline):
  4. def get_media_requests(self, item, info):
  5. print('下载开始')
  6. return scrapy.Request(item['img_url'],headers={'referer':item['img_referer']},meta={'item':item})
  7. def file_path(self, request, response=None, info=None):
  8. item=request.meta['item']
  9. url = request.url
  10. file_name=url.split('/')[-1]
  11. return file_name
  12. def item_completed(self, results, item, info):
  13. print('下载结束')
  14. return item

7、scrapy中间件(下载中间件)

(1)拦截请求:修改user-agent、请求头,加入代理ip(可以方法process_exception方法,或者process_request方法)等

(2)拦截响应:修改响应对象或者响应数据(例子:比如一些动态加载的页面)

  1. def process_request(self, request, spider):
  2. #1、更改请求头的user-agent
  3. from fake_useragent import UserAgent
  4. ua=UserAgent()
  5. request.headers['User-Agent']=ua.random
  6. # 2、设置代理ip
  7. import requests
  8. r = requests.get('http://127.0.0.1:5010/get/')
  9. proxy=r.json().get('proxy')
  10. request.meta['download_timeout'] = 5
  11. request.meta["proxy"] = 'http://'+proxy
  12. print(request)
  13. return None
  14. # 在中间件使用selenium处理ajax请求问题
  15. def process_response(self, request, response, spider):
  16. from scrapy.http import HtmlResponse
  17. spider.bro.get('https://dig.chouti.com/')
  18. response = HtmlResponse(url='https://dig.chouti.com/', body=spider.bro.page_source.encode('utf-8'),
  19. request=request)
  20. return response

三、去重源码解析

1、BaseDupeFilter源码

  1. class BaseDupeFilter:
  2. @classmethod
  3. def from_settings(cls, settings):
  4. return cls()
  5. # 去重方法
  6. def request_seen(self, request):
  7. return False
  8. def open(self): # can return deferred
  9. pass
  10. def close(self, reason): # can return a deferred
  11. pass
  12. def log(self, request, spider): # log that a request has been filtered
  13. pass

2、RFPDupeFilter源码

  1. class RFPDupeFilter(BaseDupeFilter):
  2. """Request Fingerprint duplicates filter"""
  3. def __init__(self, path=None, debug=False):
  4. self.file = None
  5. self.fingerprints = set() #集合
  6. self.logdupes = True
  7. self.debug = debug
  8. self.logger = logging.getLogger(__name__)
  9. if path:
  10. self.file = open(os.path.join(path, 'requests.seen'), 'a+')
  11. self.file.seek(0)
  12. self.fingerprints.update(x.rstrip() for x in self.file)
  13. @classmethod
  14. def from_settings(cls, settings):
  15. debug = settings.getbool('DUPEFILTER_DEBUG')
  16. return cls(job_dir(settings), debug)
  17. # 去重的主要方法
  18. def request_seen(self, request):
  19. '''
  20. from scrapy.utils.request import request_fingerprint
  21. 利用request_fingerprint对请求取指纹(md5)
  22. 判断如果在集合中,返回True,不在则添加到集合
  23. '''
  24. fp = self.request_fingerprint(request)
  25. if fp in self.fingerprints:
  26. return True
  27. self.fingerprints.add(fp)
  28. if self.file:
  29. self.file.write(fp + '\n')
  30. def request_fingerprint(self, request):
  31. return request_fingerprint(request)
  32. def close(self, reason):
  33. if self.file:
  34. self.file.close()
  35. def log(self, request, spider):
  36. if self.debug:
  37. msg = "Filtered duplicate request: %(request)s (referer: %(referer)s)"
  38. args = {'request': request, 'referer': referer_str(request)}
  39. self.logger.debug(msg, args, extra={'spider': spider})
  40. elif self.logdupes:
  41. msg = ("Filtered duplicate request: %(request)s"
  42. " - no more duplicates will be shown"
  43. " (see DUPEFILTER_DEBUG to show all duplicates)")
  44. self.logger.debug(msg, {'request': request}, extra={'spider': spider})
  45. self.logdupes = False
  46. spider.crawler.stats.inc_value('dupefilter/filtered', spider=spider)

3、通过BaseDupeFilter和RFPDupeFilter对比,我们可以看出去重规则主要在request_seen()方法中,则我们可以仿照写一个自定义去重类

(1) 新建一个去重文件dupfilter.py

  1. from scrapy.dupefilters import BaseDupeFilter
  2. class UrlFilter(BaseDupeFilter):
  3. def __init__(self):
  4. self.visited = set() #或者放到数据库
  5. @classmethod
  6. def from_settings(cls, settings):
  7. return cls()
  8. def request_seen(self, request):
  9. if request.url in self.visited:
  10. return True
  11. self.visited.add(request.url)
  12. def open(self): # can return deferred
  13. pass
  14. def close(self, reason): # can return a deferred
  15. pass
  16. def log(self, request, spider): # log that a request has been filtered
  17. pass

(2) 在配置文件settings中

DUPEFILTER_CLASS = '项目名.dupfilter.UrlFilter'

四、scrapy-redis分布式爬虫

1、介绍

scrapy-redis是在原来的scrapy的基础上,重写Scheduler,让调度器到共享队列中取Request(该request请求时去重的)。去重主要是利用了redis的集合类型

 

 2、使用方法

(1)安装scrapy-redis模块,pip install scrapy-redis

(2)配置settings文件

  1. # scheduler调度器配置,参考Scheduler源码
  2. class Scheduler(object):
  3. """
  4. 在scrapy中settings的配置信息
  5. --------
  6. SCHEDULER_PERSIST : bool (default: False)
  7. 关闭的时候,是否保留redis队列(也就是起始地址).
  8. SCHEDULER_FLUSH_ON_START : bool (default: False)
  9. 是否在启动时刷新redis队列。
  10. SCHEDULER_IDLE_BEFORE_CLOSE : int (default: 0)
  11. 如果没有收到任何消息,在关闭之前要等待多少秒(超时时间)。
  12. SCHEDULER_QUEUE_KEY : str
  13. 调度器中请求存放在redis中的key
  14. SCHEDULER_QUEUE_CLASS : str
  15. 调度程序队列类s.
  16. SCHEDULER_DUPEFILTER_KEY : str
  17. 去重规则中,在redis中保存对应的key值.
  18. SCHEDULER_DUPEFILTER_CLASS : str
  19. 去重规则对应的处理类
  20. SCHEDULER_SERIALIZER : str
  21. 对保存到redis中的数据进行序列化.默认使用pickle
  22. # SCHEDULER_SERIALIZER ="scrapy_redis.picklecompat"
  23. """
  24. # Redis配置,参考connection源码
  25. def get_redis_from_settings(settings):
  26. # ----------------
  27. # 在settings中的配置
  28. # ----------------
  29. REDIS_URL : str, optional
  30. # 例子:REDIS_URL='redis://:12345@127.0.0.1:6379'
  31. #redis的连接url,优先去url配置(等同的host\post的配置),.
  32. #注意:配置了url,就不用配置主机号跟端口了
  33. REDIS_HOST : str, optional
  34. #redis的主机名.
  35. REDIS_PORT : str, optional
  36. #redis的端口.
  37. REDIS_ENCODING : str, optional
  38. #redis的编码.
  39. REDIS_PARAMS : dict, optional
  40. #redis的其他参数,是字典类型.
  41. #如: REDIS_PARAMS = {'password':'12345'}
  42. # 持久化 配置,参考pipeline源码
  43. class RedisPipeline(object):
  44. Settings
  45. --------
  46. REDIS_ITEMS_KEY : str
  47. # redis关键字
  48. REDIS_ITEMS_SERIALIZER : str
  49. # 序列化函数
  50. # 参考spiders源码配置
  51. class RedisSpider(RedisMixin, Spider):
  52. Settings
  53. --------
  54. REDIS_START_URLS_KEY : str (default: "<spider.name>:start_urls")
  55. # 起始rurl从对应的redis的key.
  56. REDIS_START_URLS_BATCH_SIZE : int (deprecated by CONCURRENT_REQUESTS)
  57. # 每次尝试从reids获取的url数,默认从配置中获取.
  58. REDIS_START_URLS_AS_SET : bool (default: False)
  59. # 获取起始URL时,如果为True,则使用self.server.spop;如果为False,则使用self.server.lpop
  60. REDIS_ENCODING : str (default: "utf-8")
  61. #编码

(3) 参考案例(在原来的scrapy的源码上进行修改)

  1. '''
  2. spdies文件
  3. 继承RedisSpider
  4. '''
  5. from scrapy_redis.spiders import RedisSpider
  6. class BlogSpider(RedisSpider):
  7. name = 'redis_blog'
  8. redis_key = 'myspider:start_urls'
  9. def parse(self, response):
  10. print('---redis-blog-----')
  11. article_list = response.xpath('//div[@id="post_list"]/article')
  12. ....
  1. #settings文件配置
  2. #redis的连接
  3. REDIS_HOST='localhost'
  4. REDIS_PORT=6379
  5. # REDIS_PASSWD='12345'
  6. REDIS_PARAMS = {'password':'12345'}
  7. # REDIS_URL='redis://:12345@127.0.0.1:6379'
  8. # from scrapy_redis.scheduler import Scheduler
  9. DUPEFILTER_CLASS="scrapy_redis.dupefilter.RFPDupeFilter"
  10. SCHEDULER = 'scrapy_redis.scheduler.Scheduler'
  11. SCHEDULER_PERSIST = True

最后,在redis的数据库中插入一个起始地址:lpush myspider:start_urls https://www.cnblogs.com/ 就可以运行了

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/433002
推荐阅读
相关标签
  

闽ICP备14008679号