一 scrapy框架简介

1 介绍

(1) 什么是Scrapy?

  Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架,非常出名,非常强悍。所谓的框架就是一个已经被集成了各种功能(高性能异步下载,队列,分布式,解析,持久化等)的具有很强通用性的项目模板。对于框架的学习,重点是要学习其框架的特性、各个功能的用法即可。

Scrapy一个开源和协作的框架,其最初是为了页面抓取 (更确切来说, 网络抓取 )所设计的,使用它可以以快速、简单、可扩展的方式从网站中提取所需的数据。但目前Scrapy的用途十分广泛,可用于如数据挖掘、监测和自动化测试等领域,也可以应用在获取API所返回的数据(例如 Amazon Associates Web Services ) 或者通用的网络爬虫。Scrapy 是基于twisted框架开发而来,twisted是一个流行的事件驱动的python网络框架。因此Scrapy使用了一种非阻塞(又名异步)的代码来实现并发。

整体架构大致如下:

  1. '''
  2. Components:
  3.  
  4. 1、引擎(EGINE)
  5. 引擎负责控制系统所有组件之间的数据流,并在某些动作发生时触发事件。有关详细信息,请参见上面的数据流部分。
  6.  
  7. 2、调度器(SCHEDULER)
  8. 用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL的优先级队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址
  9. 3、下载器(DOWLOADER)
  10. 用于下载网页内容, 并将网页内容返回给EGINE,下载器是建立在twisted这个高效的异步模型上的
  11. 4、爬虫(SPIDERS)
  12. SPIDERS是开发人员自定义的类,用来解析responses,并且提取items,或者发送新的请求

  13. 5、项目管道(ITEM PIPLINES)
  14. 在items被提取后负责处理它们,主要包括清理、验证、持久化(比如存到数据库)等操作
  15. 下载器中间件(Downloader Middlewares)位于Scrapy引擎和下载器之间,主要用来处理从EGINE传到DOWLOADER的请求request,已经从DOWNLOADER传到EGINE的响应response,
    你可用该中间件做以下几件事:
  16.   (1) process a request just before it is sent to the Downloader (i.e. right before Scrapy sends the request to the website);
  17.   (2) change received response before passing it to a spider;
  18.   (3) send a new Request instead of passing received response to a spider;
  19.   (4) pass response to a spider without fetching a web page;
  20.   (5) silently drop some requests.
  21. 6、爬虫中间件(Spider Middlewares)
  22. 位于EGINE和SPIDERS之间,主要工作是处理SPIDERS的输入(即responses)和输出(即requests)
  23. '''

官网链接

2 安装

  1.   #Linux:
  2.  
  3. pip3 install scrapy
  4.  
  5.   #Windows:
  6.  
  7. a. pip3 install wheel
  8.  
  9. b. 下载twisted http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
  10.  
  11. c. 进入下载目录,执行 pip3 install Twisted17.1.0cp35cp35mwin_amd64.whl
  12.  
  13. d. pip3 install pywin32
  14.  
  15. e. pip3 install scrapy

3 命令行工具

  1. # 1 查看帮助
  2. scrapy -h
  3. scrapy <command> -h
  4.  
  5. # 2 有两种命令:其中Project-only必须切到项目文件夹下才能执行,而Global的命令则不需要
  6. Global commands:
  7. startproject #创建项目
  8. genspider #创建爬虫程序
  9. settings #如果是在项目目录下,则得到的是该项目的配置
  10. runspider #运行一个独立的python文件,不必创建项目
  11. shell #scrapy shell url地址 在交互式调试,如选择器规则正确与否
  12. fetch #独立于程单纯地爬取一个页面,可以拿到请求头
  13. view #下载完毕后直接弹出浏览器,以此可以分辨出哪些数据是ajax请求
  14. version #scrapy version 查看scrapy的版本,scrapy version -v查看scrapy依赖库的版本
  15. Project-only commands:
  16. crawl #运行爬虫,必须创建项目才行,确保配置文件中ROBOTSTXT_OBEY = False
  17. check #检测项目中有无语法错误
  18. list #列出项目中所包含的爬虫名
  19. edit #编辑器,一般不用
  20. parse #scrapy parse url地址 --callback 回调函数 #以此可以验证我们的回调函数是否正确
  21. bench #scrapy bentch压力测试
  22.  
  23. # 3 官网链接
  24. https://docs.scrapy.org/en/latest/topics/commands.html

4 目录结构

  1. '''
  2. project_name/
  3. scrapy.cfg
  4. project_name/
  5. __init__.py
  6. items.py
  7. pipelines.py
  8. settings.py
  9. spiders/
  10. __init__.py
  11. 爬虫1.py
  12. 爬虫2.py
  13. 爬虫3.py
  14.  
  15. '''

文件说明:

  • scrapy.cfg  项目的主配置信息,用来部署scrapy时使用,爬虫相关的配置信息在settings.py文件中。
  • items.py    设置数据存储模板,用于结构化数据,如:Django的Model
  • pipelines    数据处理行为,如:一般结构化的数据持久化
  • settings.py 配置文件,如:递归的层数、并发数,延迟下载等。强调:配置文件的选项必须大写否则视为无效,正确写法USER_AGENT='xxxx'
  • spiders      爬虫目录,如:创建文件,编写爬虫规则

注意:

1、一般创建爬虫文件时,以网站域名命名

2、默认只能在终端执行命令,为了更便捷操作:

  1. #在项目根目录下新建:entrypoint.py
  2. from scrapy.cmdline import execute
  3. execute(['scrapy', 'crawl', 'xiaohua'])

框架基础:spider类,选择器

5 牛刀小试

1.创建爬虫应用程序 

  1. cd project_name(进入项目目录)
  2. scrapy genspider 应用名称 爬取网页的起始url (例如:scrapy genspider qiubai www.qiushibaike.com

2 编写爬虫文件:在步骤2执行完毕后,会在项目的spiders中生成一个应用名的py爬虫文件,文件源码如下:

  1. # -*- coding: utf-8 -*-
  2. import scrapy
  3.  
  4. class QiubaiSpider(scrapy.Spider):
  5. name = 'qiubai' #应用名称
  6. #允许爬取的域名(如果遇到非该域名的url则爬取不到数据)
  7. allowed_domains = ['https://www.qiushibaike.com/']
  8. #起始爬取的url
  9. start_urls = ['https://www.qiushibaike.com/']
  10.  
  11. #访问起始URL并获取结果后的回调函数,该函数的response参数就是向起始的url发送请求后,获取的响应对象.该函数返回值必须为可迭代对象或者NUll
  12. def parse(self, response):
  13. print(response.text) #获取字符串类型的响应内容
  14. print(response.body)#获取字节类型的相应内容 

3 设置修改settings.py配置文件相关配置

  1. 修改内容及其结果如下:
  2. 19行:USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36' #伪装请求载体身份
  3.  
  4. 22行:ROBOTSTXT_OBEY = False #可以忽略或者不遵守robots协议

4 执行爬虫程序:

  1. scrapy crawl 应用名称

5 将糗百首页中段子的内容和标题进行爬取

  1. # -*- coding: utf-8 -*-
  2. import scrapy
  3.  
  4. class QiubaiSpider(scrapy.Spider):
  5. name = 'qiubai'
  6. allowed_domains = ['https://www.qiushibaike.com/']
  7. start_urls = ['https://www.qiushibaike.com/']
  8.  
  9. def parse(self, response):
  10. #xpath为response中的方法,可以将xpath表达式直接作用于该函数中
  11. odiv = response.xpath('//div[@id="content-left"]/div')
  12. content_list = [] #用于存储解析到的数据
  13. for div in odiv:
  14. #xpath函数返回的为列表,列表中存放的数据为Selector类型的数据。我们解析到的内容被封装在了Selector对象中,需要调用extract()函数将解析的内容从Selecor中取出。
  15. author = div.xpath('.//div[@class="author clearfix"]/a/h2/text()')[0].extract()
  16. content=div.xpath('.//div[@class="content"]/span/text()')[0].extract()
  17.  
  18. #将解析到的内容封装到字典中
  19. dic={
  20. '作者':author,
  21. '内容':content
  22. }
  23. #将数据存储到content_list这个列表中
  24. content_list.append(dic)
  25.  
  26. return content_list

执行爬虫程序:

  1. scrapy crawl 爬虫名称 :该种执行形式会显示执行的日志信息
  2. scrapy crawl 爬虫名称 --nolog:该种执行形式不会显示执行的日志信息

二 Spider类

Spiders是定义如何抓取某个站点(或一组站点)的类,包括如何执行爬行(即跟随链接)以及如何从其页面中提取结构化数据(即抓取项目)。换句话说,Spiders是您为特定站点(或者在某些情况下,一组站点)爬网和解析页面定义自定义行为的地方。 

  1. '''
  2. 1、 生成初始的Requests来爬取第一个URLS,并且标识一个回调函数
  3. 第一个请求定义在start_requests()方法内默认从start_urls列表中获得url地址来生成Request请求,
    默认的回调函数是parse方法。回调函数在下载完成返回response时自动触发
  4.  
  5. 2、 在回调函数中,解析response并且返回值
  6. 返回值可以4种:
  7. 包含解析数据的字典
  8. Item对象
  9. 新的Request对象(新的Requests也需要指定一个回调函数)
  10. 或者是可迭代对象(包含Items或Request)
  11.  
  12. 3、在回调函数中解析页面内容
  13. 通常使用Scrapy自带的Selectors,但很明显你也可以使用Beutifulsoup,lxml或其他你爱用啥用啥。
  14.  
  15. 4、最后,针对返回的Items对象将会被持久化到数据库
  16. 通过Item Pipeline组件存到数据库:https://docs.scrapy.org/en/latest/topics/item-pipeline.html#topics-item-pipeline)
  17. 或者导出到不同的文件(通过Feed exports:https://docs.scrapy.org/en/latest/topics/feed-exports.html#topics-feed-exports)
  18.  
  19. '''

三 选择器

  1. 为了解释如何使用选择器,我们将使用Scrapy shell(提供交互式测试)和Scrapy文档服务器中的示例页面,
  2.  
  3. 这是它的HTML代码:
  4.  
  5. <html>
  6. <head>
  7. <base href='http://example.com/' />
  8. <title>Example website</title>
  9. </head>
  10. <body>
  11. <div id='images'>
  12. <a href='image1.html'>Name: My image 1 <br /><img src='image1_thumb.jpg' /></a>
  13. <a href='image2.html'>Name: My image 2 <br /><img src='image2_thumb.jpg' /></a>
  14. <a href='image3.html'>Name: My image 3 <br /><img src='image3_thumb.jpg' /></a>
  15. <a href='image4.html'>Name: My image 4 <br /><img src='image4_thumb.jpg' /></a>
  16. <a href='image5.html'>Name: My image 5 <br /><img src='image5_thumb.jpg' /></a>
  17. </div>
  18. </body>
  19. </html>
  20.  
  21. 首先,让我们打开shell
  22.  
  23. 1 scrapy shell https://doc.scrapy.org/en/latest/_static/selectors-sample1.html
  24. 然后,在shell加载之后,您将获得响应作为response shell变量,并在response.selector属性中附加选择器。
  25.  
  26. 让我们构建一个XPath来选择title标签内的文本:
  27.  
  28. >>> response.selector.xpath('//title/text()')
  29. [<Selector (text) xpath=//title/text()>]
  30. 使用XPathCSS查询响应非常常见,响应包括两个便捷快捷方式:response.xpath()和response.css():
  31.  
  32. >>> response.xpath('//title/text()')
  33. [<Selector (text) xpath=//title/text()>]
  34. >>> response.css('title::text')
  35. [<Selector (text) xpath=//title/text()>]
  36. 正如你所看到的,.xpath()并且.css()方法返回一个 SelectorList实例,这是新的选择列表。此API可用于快速选择嵌套数据:
  37.  
  38. >>> response.css('img').xpath('@src').extract()
  39. [u'image1_thumb.jpg',
  40. u'image2_thumb.jpg',
  41. u'image3_thumb.jpg',
  42. u'image4_thumb.jpg',
  43. u'image5_thumb.jpg']
  44. 要实际提取文本数据,必须调用selector .extract() 方法,如下所示:
  45.  
  46. >>> response.xpath('//title/text()').extract()
  47. [u'Example website']
  48. 如果只想提取第一个匹配的元素,可以调用选择器 .extract_first()
  49.  
  50. >>> response.xpath('//div[@id="images"]/a/text()').extract_first()
  51. u'Name: My image 1 '
  52. 现在我们将获得基本URL和一些图像链接:
  53.  
  54. >>> response.xpath('//base/@href').extract()
  55. [u'http://example.com/']
  56.  
  57. >>> response.css('base::attr(href)').extract()
  58. [u'http://example.com/']
  59.  
  60. >>> response.xpath('//a[contains(@href, "image")]/@href').extract()
  61. [u'image1.html',
  62. u'image2.html',
  63. u'image3.html',
  64. u'image4.html',
  65. u'image5.html']
  66.  
  67. >>> response.css('a[href*=image]::attr(href)').extract()
  68. [u'image1.html',
  69. u'image2.html',
  70. u'image3.html',
  71. u'image4.html',
  72. u'image5.html']
  73.  
  74. >>> response.xpath('//a[contains(@href, "image")]/img/@src').extract()
  75. [u'image1_thumb.jpg',
  76. u'image2_thumb.jpg',
  77. u'image3_thumb.jpg',
  78. u'image4_thumb.jpg',
  79. u'image5_thumb.jpg']
  80.  
  81. >>> response.css('a[href*=image] img::attr(src)').extract()
  82. [u'image1_thumb.jpg',
  83. u'image2_thumb.jpg',
  84. u'image3_thumb.jpg',
  85. u'image4_thumb.jpg',
  86. u'image5_thumb.jpg']

四 DupeFilter(去重)

默认使用方式:

  1. DUPEFILTER_CLASS = 'scrapy.dupefilter.RFPDupeFilter'
  2. Request(...,dont_filter=False) ,如果dont_filter=True则告诉Scrapy这个URL不参与去重。

源码解析:

  1. from scrapy.core.scheduler import Scheduler
  2. Scheduler下的enqueue_request方法:self.df.request_seen(request)  

自定义去重规则:

  1. from scrapy.dupefilter import RFPDupeFilter,看源码,仿照BaseDupeFilter
  2.  
  3. #步骤一:在项目目录下自定义去重文件dup.py
  4. class UrlFilter(object):
  5. def __init__(self):
  6. self.visited = set() #或者放到数据库
  7.  
  8. @classmethod
  9. def from_settings(cls, settings):
  10. return cls()
  11.  
  12. def request_seen(self, request):
  13. if request.url in self.visited:
  14. return True
  15. self.visited.add(request.url)
  16.  
  17. def open(self): # can return deferred
  18. pass
  19.  
  20. def close(self, reason): # can return a deferred
  21. pass
  22.  
  23. def log(self, request, spider): # log that a request has been filtered
  24. pass

五 Item(项目)

抓取的主要目标是从非结构化源(通常是网页)中提取结构化数据。Scrapy蜘蛛可以像Python一样返回提取的数据。虽然方便和熟悉,但P很容易在字段名称中输入拼写错误或返回不一致的数据,尤其是在具有许多蜘蛛的较大项目中。

为了定义通用输出数据格式,Scrapy提供了Item类。 Item对象是用于收集抓取数据的简单容器。它们提供类似字典的 API,并具有用于声明其可用字段的方便语法。

1 声明项目

使用简单的类定义语法和Field 对象声明项。这是一个例子:

  1. import scrapy
  2.  
  3. class Product(scrapy.Item):
  4. name = scrapy.Field()
  5. price = scrapy.Field()
  6. stock = scrapy.Field()
  7. last_updated = scrapy.Field(serializer=str)

注意那些熟悉Django的人会注意到Scrapy Items被宣告类似于Django Models,除了Scrapy Items更简单,因为没有不同字段类型的概念。

2 项目字段

Field对象用于指定每个字段的元数据。例如,last_updated上面示例中说明的字段的序列化函数。

您可以为每个字段指定任何类型的元数据。Field对象接受的值没有限制。出于同样的原因,没有所有可用元数据键的参考列表。

Field对象中定义的每个键可以由不同的组件使用,只有那些组件知道它。您也可以根据Field自己的需要定义和使用项目中的任何其他键。

Field对象的主要目标是提供一种在一个地方定义所有字段元数据的方法。通常,行为取决于每个字段的那些组件使用某些字段键来配置该行为。

3 使用项目

以下是使用上面声明的Product项目对项目执行的常见任务的一些示例 。您会注意到API与dict API非常相似。

  1. 创建项目
  2. >>> product = Product(name='Desktop PC', price=1000)
  3. >>> print product
  4. Product(name='Desktop PC', price=1000)
  5. 获取字段值
  6. >>> product['name']
  7. Desktop PC
  8. >>> product.get('name')
  9. Desktop PC
  10.  
  11. >>> product['price']
  12. 1000
  13.  
  14. >>> product['last_updated']
  15. Traceback (most recent call last):
  16. ...
  17. KeyError: 'last_updated'
  18.  
  19. >>> product.get('last_updated', 'not set')
  20. not set
  21.  
  22. >>> product['lala'] # getting unknown field
  23. Traceback (most recent call last):
  24. ...
  25. KeyError: 'lala'
  26.  
  27. >>> product.get('lala', 'unknown field')
  28. 'unknown field'
  29.  
  30. >>> 'name' in product # is name field populated?
  31. True
  32.  
  33. >>> 'last_updated' in product # is last_updated populated?
  34. False
  35.  
  36. >>> 'last_updated' in product.fields # is last_updated a declared field?
  37. True
  38.  
  39. >>> 'lala' in product.fields # is lala a declared field?
  40. False
  41. 设定字段值
  42. >>> product['last_updated'] = 'today'
  43. >>> product['last_updated']
  44. today
  45.  
  46. >>> product['lala'] = 'test' # setting unknown field
  47. Traceback (most recent call last):
  48. ...
  49. KeyError: 'Product does not support field: lala'
  50. 访问所有填充值
  51. 要访问所有填充值,只需使用典型的dict API
  52.  
  53. >>> product.keys()
  54. ['price', 'name']
  55.  
  56. >>> product.items()
  57. [('price', 1000), ('name', 'Desktop PC')]
  58. 其他常见任务
  59. 复制项目:
  60.  
  61. >>> product2 = Product(product)
  62. >>> print product2
  63. Product(name='Desktop PC', price=1000)
  64.  
  65. >>> product3 = product2.copy()
  66. >>> print product3
  67. Product(name='Desktop PC', price=1000)
  68. 从项目创建dicts
  69.  
  70. >>> dict(product) # create a dict from all populated values
  71. {'price': 1000, 'name': 'Desktop PC'}
  72. dicts创建项目:
  73.  
  74. >>> Product({'name': 'Laptop PC', 'price': 1500})
  75. Product(price=1500, name='Laptop PC')
  76.  
  77. >>> Product({'name': 'Laptop PC', 'lala': 1500}) # warning: unknown field in dict
  78. Traceback (most recent call last):
  79. ...
  80. KeyError: 'Product does not support field: lala'

4 扩展项目

您可以通过声明原始Item的子类来扩展Items(以添加更多字段或更改某些字段的某些元数据)。

例如:

  1. class DiscountedProduct(Product):
  2. discount_percent = scrapy.Field(serializer=str)
  3. discount_expiration_date = scrapy.Field()

六 Item PipeLine

在一个项目被蜘蛛抓取之后,它被发送到项目管道,该项目管道通过顺序执行的几个组件处理它。

每个项目管道组件(有时简称为“项目管道”)是一个实现简单方法的Python类。他们收到一个项目并对其执行操作,同时决定该项目是否应该继续通过管道或被丢弃并且不再处理。

项目管道的典型用途是:

  • cleansing HTML data
  • validating scraped data (checking that the items contain certain fields)
  • checking for duplicates (and dropping them)
  • storing the scraped item in a database

1 编写自己的项目管道

  1. '''
  2. 每个项管道组件都是一个必须实现以下方法的Python类:
  3.  
  4. process_item(self,项目,蜘蛛)
  5. 为每个项目管道组件调用此方法。process_item()
  6.  
  7. 必须要么:返回带数据的dict,返回一个Item (或任何后代类)对象,返回Twisted Deferred或引发 DropItem异常。丢弃的项目不再由其他管道组件处理。
  8.  
  9. 此外,他们还可以实现以下方法:
  10.  
  11. open_spider(self,蜘蛛)
  12. 打开蜘蛛时会调用此方法。
  13.  
  14. close_spider(self,蜘蛛)
  15. 当蜘蛛关闭时调用此方法。
  16.  
  17. from_crawler(cls,crawler )
  18. 如果存在,则调用此类方法以从a创建管道实例Crawler。它必须返回管道的新实例。Crawler对象提供对所有Scrapy核心组件的访问,
  19. 如设置和信号; 它是管道访问它们并将其功能挂钩到Scrapy的一种方式。
  20. '''

2 项目管道示例

(1) 价格验证和丢弃物品没有价格

让我们看看下面的假设管道,它调整 price那些不包含增值税(price_excludes_vat属性)的项目的属性,并删除那些不包含价格的项目:

  1. from scrapy.exceptions import DropItem
  2.  
  3. class PricePipeline(object):
  4.  
  5. vat_factor = 1.15
  6.  
  7. def process_item(self, item, spider):
  8. if item['price']:
  9. if item['price_excludes_vat']:
  10. item['price'] = item['price'] * self.vat_factor
  11. return item
  12. else:
  13. raise DropItem("Missing price in %s" % item)

(2) 将项目写入JSON文件

以下管道将所有已删除的项目(来自所有蜘蛛)存储到一个items.jl文件中,每行包含一个以JSON格式序列化的项目:

  1. import json
  2.  
  3. class JsonWriterPipeline(object):
  4.  
  5. def open_spider(self, spider):
  6. self.file = open('items.jl', 'w')
  7.  
  8. def close_spider(self, spider):
  9. self.file.close()
  10.  
  11. def process_item(self, item, spider):
  12. line = json.dumps(dict(item)) + "\n"
  13. self.file.write(line)
  14. return item

注意JsonWriterPipeline的目的只是介绍如何编写项目管道。如果您确实要将所有已删除的项目存储到JSON文件中,则应使用Feed导出

(3) 将项目写入数据库

在这个例子中,我们将使用pymongo将项目写入MongoDB。MongoDB地址和数据库名称在Scrapy设置中指定; MongoDB集合以item类命名。

这个例子的要点是展示如何使用from_crawler() 方法以及如何正确地清理资源:

  1. import pymongo
  2.  
  3. class MongoPipeline(object):
  4.  
  5. collection_name = 'scrapy_items'
  6.  
  7. def __init__(self, mongo_uri, mongo_db):
  8. self.mongo_uri = mongo_uri
  9. self.mongo_db = mongo_db
  10.  
  11. @classmethod
  12. def from_crawler(cls, crawler):
  13. return cls(
  14. mongo_uri=crawler.settings.get('MONGO_URI'),
  15. mongo_db=crawler.settings.get('MONGO_DATABASE', 'items')
  16. )
  17.  
  18. def open_spider(self, spider):
  19. self.client = pymongo.MongoClient(self.mongo_uri)
  20. self.db = self.client[self.mongo_db]
  21.  
  22. def close_spider(self, spider):
  23. self.client.close()
  24.  
  25. def process_item(self, item, spider):
  26. self.db[self.collection_name].insert_one(dict(item))
  27. return item

(4) 重复过滤

一个过滤器,用于查找重复项目,并删除已处理的项目。假设我们的项目具有唯一ID,但我们的蜘蛛会返回具有相同ID的多个项目:

  1. from scrapy.exceptions import DropItem
  2.  
  3. class DuplicatesPipeline(object):
  4.  
  5. def __init__(self):
  6. self.ids_seen = set()
  7.  
  8. def process_item(self, item, spider):
  9. if item['id'] in self.ids_seen:
  10. raise DropItem("Duplicate item found: %s" % item)
  11. else:
  12. self.ids_seen.add(item['id'])
  13. return item

3 激活项目管道组件

要激活Item Pipeline组件,必须将其类添加到 ITEM_PIPELINES设置中,如下例所示:

  1. ITEM_PIPELINES = {
  2. 'myproject.pipelines.PricePipeline': 300,
  3. 'myproject.pipelines.JsonWriterPipeline': 800,
  4. }

您在此设置中为类分配的整数值决定了它们运行的​​顺序:项目从较低值到较高值类进行。习惯上在0-1000范围内定义这些数字。

七 下载中间件

  1. class MyDownMiddleware(object):
  2. def process_request(self, request, spider):
  3. """
  4. 请求需要被下载时,经过所有下载器中间件的process_request调用
  5. :param request:
  6. :param spider:
  7. :return:
  8. None,继续后续中间件去下载;
  9. Response对象,停止process_request的执行,开始执行process_response
  10. Request对象,停止中间件的执行,将Request重新调度器
  11. raise IgnoreRequest异常,停止process_request的执行,开始执行process_exception
  12. """
  13. pass
  14.  
  15. def process_response(self, request, response, spider):
  16. """
  17. spider处理完成,返回时调用
  18. :param response:
  19. :param result:
  20. :param spider:
  21. :return:
  22. Response 对象:转交给其他中间件process_response
  23. Request 对象:停止中间件,request会被重新调度下载
  24. raise IgnoreRequest 异常:调用Request.errback
  25. """
  26. print('response1')
  27. return response
  28.  
  29. def process_exception(self, request, exception, spider):
  30. """
  31. 当下载处理器(download handler)或 process_request() (下载中间件)抛出异常
  32. :param response:
  33. :param exception:
  34. :param spider:
  35. :return:
  36. None:继续交给后续中间件处理异常;
  37. Response对象:停止后续process_exception方法
  38. Request对象:停止中间件,request将会被重新调用下载
  39. """
  40. return None

八 基于scrapy-redis实现分布式爬虫

Scrapy-Redis则是一个基于Redis的Scrapy分布式组件。它利用Redis对用于爬取的请求(Requests)进行存储和调度(Schedule),并对爬取产生的项目(items)存储以供后续处理使用。scrapy-redi重写了scrapy一些比较关键的代码,将scrapy变成一个可以在多个主机上同时运行的分布式爬虫。

单机玩法:

按照正常流程就是大家都会进行重复的采集;我们都知道进程之间内存中的数据不可共享的,那么你在开启多个Scrapy的时候,它们相互之间并不知道对方采集了些什么那些没有没采集。那就大家伙儿自己玩自己的了。完全没没有效率的提升啊!

怎么解决呢?

这就是我们Scrapy-Redis解决的问题了,不能协作不就是因为请求和去重这两个不能共享吗?

那我把这两个独立出来好了。

将Scrapy中的调度器组件独立放到大家都能访问的地方不就OK啦!加上scrapy,Redis的后流程图就应该变成这样了

分布式玩法:

1 redis连接

  1. 配置scrapy使用redis提供的共享去重队列
  2.  
  3. # 在settings.py中配置链接Redis
  4. REDIS_HOST = 'localhost' # 主机名
  5. REDIS_PORT = 6379 # 端口
  6. REDIS_URL = 'redis://user:pass@hostname:9001' # 连接URL(优先于以上配置)
  7. REDIS_PARAMS = {} # Redis连接参数
  8. REDIS_PARAMS['redis_cls'] = 'myproject.RedisClient' # 指定连接Redis的Python模块
  9. REDIS_ENCODING = "utf-8" # redis编码类型
  10. # 默认配置:\python3.6\Lib\site-packages\scrapy_redis\defaults.py

2 dupefilter

  1. DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
  2. #使用scrapy-redis提供的去重功能,查看源码会发现是基于Redis的集合实现的
  3.  
  4. # 需要指定Redis中集合的key名,key=存放不重复Request字符串的集合
  5. DUPEFILTER_KEY = 'dupefilter:%(timestamp)s'
  6. #源码:dupefilter.py内一行代码key = defaults.DUPEFILTER_KEY % {'timestamp': int(time.time())}

3 Scheduler

  1. #1、源码:\python3.6\Lib\site-packages\scrapy_redis\scheduler.py
  2.  
  3. #2、settings.py配置
  4.  
  5. # Enables scheduling storing requests queue in redis.
  6. SCHEDULER = "scrapy_redis.scheduler.Scheduler"
  7.  
  8. # 调度器将不重复的任务用pickle序列化后放入共享任务队列,默认使用优先级队列(默认),其他:PriorityQueue(有序集合),FifoQueue(列表)、LifoQueue(列表)
  9. SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.PriorityQueue'
  10.  
  11. # 对保存到redis中的request对象进行序列化,默认使用pickle
  12. SCHEDULER_SERIALIZER = "scrapy_redis.picklecompat"
  13.  
  14. # 调度器中请求任务序列化后存放在redis中的key
  15. SCHEDULER_QUEUE_KEY = '%(spider)s:requests'
  16.  
  17. # 是否在关闭时候保留原来的调度器和去重记录,True=保留,False=清空
  18. SCHEDULER_PERSIST = True
  19.  
  20. # 是否在开始之前清空 调度器和去重记录,True=清空,False=不清空
  21. SCHEDULER_FLUSH_ON_START = False
  22.  
  23. # 去调度器中获取数据时,如果为空,最多等待时间(最后没数据,未获取到)。如果没有则立刻返回会造成空循环次数过多,cpu占用率飙升
  24. SCHEDULER_IDLE_BEFORE_CLOSE = 10
  25.  
  26. # 去重规则,在redis中保存时对应的key
  27. SCHEDULER_DUPEFILTER_KEY = '%(spider)s:dupefilter'
  28.  
  29. # 去重规则对应处理的类,将任务request_fingerprint(request)得到的字符串放入去重队列
  30. SCHEDULER_DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupeFilter'

3 RedisPipeline(持久化)

  1. ITEM_PIPELINES = { 'scrapy_redis.pipelines.RedisPipeline': 300, }
  2.  
  3. #将item持久化到redis时,指定key和序列化函数
  4.  
  5. REDIS_ITEMS_KEY = '%(spider)s:items'
  6. REDIS_ITEMS_SERIALIZER = 'json.dumps'

从Redis中获取起始URL

  1. scrapy程序爬取目标站点,一旦爬取完毕后就结束了,如果目标站点更新内容了,我们想重新爬取,那么只能再重新启动scrapy,非常麻烦
  2. scrapy-redis提供了一种供,让scrapyredis中获取起始url,如果没有scrapy则过一段时间再来取而不会关闭
  3. 这样我们就只需要写一个简单的脚本程序,定期往redis队列里放入一个起始url
  4.  
  5. #具体配置如下
  6.  
  7. #1、编写爬虫时,起始URL从redis的Key中获取
  8. REDIS_START_URLS_KEY = '%(name)s:start_urls'
  9.  
  10. #2、获取起始URL时,去集合中获取还是去列表中获取?True,集合;False,列表
  11. REDIS_START_URLS_AS_SET = False # 获取起始URL时,如果为True,则使用self.server.spop;如果为False,则使用self.server.lpop 

九 项目代码

下载项目代码

5、爬虫系列之scrapy框架的更多相关文章

  1. 爬虫系列之Scrapy框架

    一 scrapy框架简介 1 介绍 (1) 什么是Scrapy? Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架,非常出名,非常强悍.所谓的框架就是一个已经被集成了各种功能(高性能 ...

  2. 第三百三十五节,web爬虫讲解2—Scrapy框架爬虫—豆瓣登录与利用打码接口实现自动识别验证码

    第三百三十五节,web爬虫讲解2—Scrapy框架爬虫—豆瓣登录与利用打码接口实现自动识别验证码 打码接口文件 # -*- coding: cp936 -*- import sys import os ...

  3. 第三百三十四节,web爬虫讲解2—Scrapy框架爬虫—Scrapy爬取百度新闻,爬取Ajax动态生成的信息

    第三百三十四节,web爬虫讲解2—Scrapy框架爬虫—Scrapy爬取百度新闻,爬取Ajax动态生成的信息 crapy爬取百度新闻,爬取Ajax动态生成的信息,抓取百度新闻首页的新闻rul地址 有多 ...

  4. 第三百三十三节,web爬虫讲解2—Scrapy框架爬虫—Scrapy模拟浏览器登录—获取Scrapy框架Cookies

    第三百三十三节,web爬虫讲解2—Scrapy框架爬虫—Scrapy模拟浏览器登录 模拟浏览器登录 start_requests()方法,可以返回一个请求给爬虫的起始网站,这个返回的请求相当于star ...

  5. 第三百三十二节,web爬虫讲解2—Scrapy框架爬虫—Scrapy使用

    第三百三十二节,web爬虫讲解2—Scrapy框架爬虫—Scrapy使用 xpath表达式 //x 表示向下查找n层指定标签,如://div 表示查找所有div标签 /x 表示向下查找一层指定的标签 ...

  6. 第三百三十一节,web爬虫讲解2—Scrapy框架爬虫—Scrapy安装—Scrapy指令

    第三百三十一节,web爬虫讲解2—Scrapy框架爬虫—Scrapy安装—Scrapy指令 Scrapy框架安装 1.首先,终端执行命令升级pip: python -m pip install --u ...

  7. Python爬虫进阶之Scrapy框架安装配置

    Python爬虫进阶之Scrapy框架安装配置 初级的爬虫我们利用urllib和urllib2库以及正则表达式就可以完成了,不过还有更加强大的工具,爬虫框架Scrapy,这安装过程也是煞费苦心哪,在此 ...

  8. 分布式爬虫搭建系列 之三---scrapy框架初用

    第一,scrapy框架的安装 通过命令提示符进行安装(如果没有安装的话) pip install Scrapy 如果需要卸载的话使用命令为: pip uninstall Scrapy 第二,scrap ...

  9. 网络爬虫:使用Scrapy框架编写一个抓取书籍信息的爬虫服务

      上周学习了BeautifulSoup的基础知识并用它完成了一个网络爬虫( 使用Beautiful Soup编写一个爬虫 系列随笔汇总 ), BeautifulSoup是一个非常流行的Python网 ...

随机推荐

  1. EOS生产区块:解析插件producer_plugin

    producer_plugin是控制区块生产的关键插件. 关键字:producer_plugin,同步区块的处理,pending区块,生产区块,最后不可逆区块,生产循环,生产安排,水印轮次,计时器,确 ...

  2. 自己动手实现java数据结构(六)二叉搜索树

    1.二叉搜索树介绍 前面我们已经介绍过了向量和链表.有序向量可以以二分查找的方式高效的查找特定元素,而缺点是插入删除的效率较低(需要整体移动内部元素):链表的优点在于插入,删除元素时效率较高,但由于不 ...

  3. springboot:Java模板引擎Thymeleaf介绍

    Thymeleaf是一款用于渲染XML/XHTML/HTML5内容的模板引擎.类似JSP,Velocity,FreeMaker等,它也可以轻易的与Spring MVC等Web框架进行集成作为Web应用 ...

  4. .Net敏捷开发框架6.1.6.2版本,联系QQ:6539471

    演示地址:www.fishcmonkey.com .NET敏捷开发框架 6.1.6.2 版本发布 新增手机流程-我的流程(可查看流程进度和表单内容) 新增手机流程-待办任务(可查看流程进度和表单内容, ...

  5. oracle创建用户、创建表空间、授权、建表

    2.然后我就可以来创建用户了. create user zzg identified by zzg123; 3.创建好用户我们接着就可以修改用户的密码. alter user zzg identifi ...

  6. 菜鸟入门【ASP.NET Core】9:RoutingMiddleware介绍以及MVC引入

    前言 前面介绍了使用app.Map来配置路由,但是对于一般不是特别大的项目来说,不使用Map来进行路由配置. 配置路由 我们首先需要在Startup.cs文件中的ConfigureServices方法 ...

  7. oracle与mysql

    『创业团队最佳选择是Oracle+MongoDB,而不是MySQL』,当深蓝在QQ群里抛出这样的观点的时候,就像是在马蜂窝里丢了一串鞭炮一样热闹起来. 创业者甲: 开什么玩笑,Oracle要收钱的,太 ...

  8. elasticsearch6.7 05. Document APIs(3)GET API

    2.GET API get API 可以通过文档id从索引中获取json格式的文档,以下示例从twitter索引中获取type为_doc,id值为0为的JSON文档: GET twitter/_doc ...

  9. 【Spring】18、springMVC对异常处理的支持

    无论做什么项目,进行异常处理都是非常有必要的,而且你不能把一些只有程序员才能看懂的错误代码抛给用户去看,所以这时候进行统一的异常处理,展现一个比较友好的错误页面就显得很有必要了.跟其他MVC框架一样, ...

  10. java_查找里程

    题目内容: 下图为国内主要城市之间的公路里程: 你的程序要读入这样的一张表,然后,根据输入的两个城市的名称,给出这两个城市之间的里程. 注意:任何两个城市之间的里程都已经给出,不需要计算经第三地中转. ...