CrawlSpider(爬取多页面数据)
scrapy startproject baike #建立 baike 的项目 cd baike scrapy genspider -t crawl duanzi www.xxx.com #建立爬虫文件 duanzi scrapy crawl suanzi #执行程序,要找对duanzi.py的路径 这里这个执行跟其余的执行有点不一样
CrawlSpider的介绍
引入php
提问:若是想要经过爬虫程序去爬取”糗百“全站数据新闻数据的话,有几种实现方法?css
方法一:基于Scrapy框架中的Spider的递归爬取进行实现(Request模块递归回调parse方法)。html
方法二:基于CrawlSpider的自动爬取进行实现(更加简洁和高效)。java
今日概要python
- CrawlSpider简介
- CrawlSpider使用
- 基于CrawlSpider爬虫文件的建立
- 连接提取器
- 规则解析器
今日详情正则表达式
一.简介redis
CrawlSpider实际上是Spider的一个子类,除了继承到Spider的特性和功能外,还派生除了其本身独有的更增强大的特性和功能。其中最显著的功能就是”LinkExtractors连接提取器“。Spider是全部爬虫的基类,其设计原则只是为了爬取start_url列表中网页,而从爬取到的网页中提取出的url进行继续的爬取工做使用CrawlSpider更合适。数据库
二.使用服务器
1.建立scrapy工程:scrapy startproject projectName
2.建立爬虫文件:scrapy genspider -t crawl spiderName www.xxx.com
--此指令对比之前的指令多了 "-t crawl",表示建立的爬虫文件是基于CrawlSpider这个类的,而再也不是Spider这个基类。
3.观察生成的爬虫文件
- 2,3行:导入CrawlSpider相关模块
- 7行:表示该爬虫程序是基于CrawlSpider类的
- 12,13,14行:表示为提取Link规则
- 16行:解析方法
CrawlSpider类和Spider类的最大不一样是CrawlSpider多了一个rules属性,其做用是定义”提取动做“。在rules中能够包含一个或多个Rule对象,在Rule对象中包含了LinkExtractor对象。
3.1 LinkExtractor:顾名思义,连接提取器。
LinkExtractor(
allow=r'Items/',# 知足括号中“正则表达式”的值会被提取,若是为空,则所有匹配。
deny=xxx, # 知足正则表达式的则不会被提取。
restrict_xpaths=xxx, # 知足xpath表达式的值会被提取
restrict_css=xxx, # 知足css表达式的值会被提取
deny_domains=xxx, # 不会被提取的连接的domains。
)
- 做用:提取response中符合规则的连接。
3.2 Rule : 规则解析器。根据连接提取器中提取到的连接,根据指定规则提取解析器连接网页中的内容。
Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True)
- 参数介绍:
参数1:指定连接提取器
参数2:指定规则解析器解析数据的规则(回调函数)
参数3:是否将连接提取器继续做用到连接提取器提取出的连接网页中。当callback为None,参数3的默认值为true。
3.3 rules=( ):指定不一样规则解析器。一个Rule对象表示一种提取规则。
3.4 CrawlSpider总体爬取流程:
a)爬虫文件首先根据起始url,获取该url的网页内容
b)连接提取器会根据指定提取规则将步骤a中网页内容中的连接进行提取
c)规则解析器会根据指定解析规则将连接提取器中提取到的连接中的网页内容根据指定的规则进行解析
d)将解析数据封装到item中,而后提交给管道进行持久化存储
需求:爬取趣事百科中全部的段子(包含1-35页)
# qiubai.py
import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule class ChoutiSpider(CrawlSpider): name = 'qiubai' # 执行的时候根据这个name名来执行该文件,不是该文件的名称,不过默认生成的该名称和文件名一致 # allowed_domains = ['www.xxx.com'] start_urls = ['https://www.qiushibaike.com/pic/'] # 起始url # rules规则解析器:将连接提取器提取到的链接所对应的页面数据进行指定形式的解析,正则表达式 # follow =True ,让链接提取器继续做用到连接提取器提取到的链接所对应的页面中,False只获取起始url页面中链接 rules = ( Rule(LinkExtractor(allow=r'/pic/page/\d+\?s=\d+'), callback='parse_item', follow=True), #正则匹配的是第几页按钮中的,a标签中的href所对应的链接 Rule(LinkExtractor(allow=r"/pic/$"),callback='parse_item',follow=True) ) def parse_item(self, response): print('==>>>>',response) #能够打印出解析出来的页面的链接 # response.xapth() #这里直接用来解析想要的数据便可
# settings.py
USER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36' ROBOTSTXT_OBEY = False
总结:
一、该crawlSpider适用于解析多页面的网页,好比含有第一页,第二页... 等等,页面数据同样,采用的解析方法也是同样的,
二、当页码中所对应的链接url,不一致时,能够写两条Rule规则来匹配
三、follow=True,让链接提取器继续做用到连接提取器提取到的链接所对应的页面中
基于scrapy-redis分布式爬虫
文件的建立和执行的命令:
scrapy startproject redis_chouti #建立分布式项目redis_chouti scrapy genspider -t crawl chouti www.xxx.com #建立爬虫文件chouti scrapy runspider ./redis_chouti/spiders/chouti.py #执行程序,文件名,执行以后会夯住,等待给定的起始url
#redis-cli客户端中执行:给一个起始的url lpush chouti http://dig.chouti.com #chouti指的是redis_key调度器,不是爬虫的文件名称
1、redis分布式部署

- 为何原生的scrapy不能实现分布式? - 调度器不能被共享 - 管道没法被共享 - scrapy-redis组件的做用是什么? - 提供了能够被共享的调度器和管道 - 分布式爬虫实现流程 1.环境安装:pip install scrapy-redis 2.建立工程 3.建立爬虫文件:RedisCrawlSpider RedisSpider - scrapy genspider -t crawl xxx www.xxx.com 4.对爬虫文件中的相关属性进行修改: - 导报:from scrapy_redis.spiders import RedisCrawlSpider - 将当前爬虫文件的父类设置成RedisCrawlSpider - 将起始url列表替换成redis_key = 'xxx'(调度器队列的名称) 5.在配置文件中进行配置: - 使用组件中封装好的能够被共享的管道类: ITEM_PIPELINES = { 'scrapy_redis.pipelines.RedisPipeline': 400 } - 配置调度器(使用组件中封装好的能够被共享的调度器) # 增长了一个去重容器类的配置, 做用使用Redis的set集合来存储请求的指纹数据, 从而实现请求去重的持久化 DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" # 使用scrapy-redis组件本身的调度器 SCHEDULER = "scrapy_redis.scheduler.Scheduler" # 配置调度器是否要持久化, 也就是当爬虫结束了, 要不要清空Redis中请求队列和去重指纹的set。若是是True, 就表示要持久化存储, 就不清空数据, 不然清空数据 SCHEDULER_PERSIST = True - 指定存储数据的redis: REDIS_HOST = 'redis服务的ip地址' REDIS_PORT = 6379 - 配置redis数据库的配置文件 - 取消保护模式:protected-mode no - bind绑定: #bind 127.0.0.1 - 启动redis 6.执行分布式程序 scrapy runspider xxx.py 7.向调度器队列中仍入一个起始url: 在redis-cli中执行:
1.scrapy框架是否能够本身实现分布式?
- 不能够。缘由有二。
其一:由于多台机器上部署的scrapy会各自拥有各自的调度器,这样就使得多台机器没法分配start_urls列表中的url。(多台机器没法共享同一个调度器)
其二:多台机器爬取到的数据没法经过同一个管道对数据进行统一的数据持久出存储。(多台机器没法共享同一个管道)
2.基于scrapy-redis组件的分布式爬虫
- scrapy-redis组件中为咱们封装好了能够被多台机器共享的调度器和管道,咱们能够直接使用并实现分布式数据爬取。
- 实现方式:
1.基于该组件的RedisSpider类
2.基于该组件的RedisCrawlSpider类
3.分布式实现流程:上述两种不一样方式的分布式实现流程是统一的
- 3.1 下载scrapy-redis组件:pip install scrapy-redis
- 3.2 redis配置文件的配置:
3.3 修改爬虫文件中的相关代码:
- 将爬虫类的父类修改为基于RedisSpider或者RedisCrawlSpider。注意:若是原始爬虫文件是基于Spider的,则应该将父类修改为RedisSpider,若是原始爬虫文件是基于CrawlSpider的,则应该将其父类修改为RedisCrawlSpider。
- 注释或者删除start_urls列表,切加入redis_key属性,属性值为scrpy-redis组件中调度器队列的名称
3.4 在settings.py配置文件中进行相关配置,开启使用scrapy-redis组件中封装好的管道
3.5 在settings.py配置文件中进行相关配置,开启使用scrapy-redis组件中封装好的调度器
3.6 在settings.py配置文件中进行爬虫程序连接redis的配置:
3.7 开启redis服务器:redis-server 配置文件
3.8 开启redis客户端:redis-cli
3.9 运行爬虫文件:scrapy runspider SpiderFile.py
3.10 向调度器队列中扔入一个起始url(在redis客户端中操做):lpush redis_key属性值 起始url
需求:分布式爬取抽屉网中的标题(存储到redis中)
爬虫文件 chouti.py
# -*- coding: utf-8 -*- import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule from scrapy_redis.spiders import RedisCrawlSpider from fenbushi_choutiPro.items import FenbushiChoutiproItem class ChoutiSpider(RedisCrawlSpider): name = 'chouti' # allowed_domains = ['www.xxx.com'] # start_urls = ['http://www.xxx.com/'] redis_key = "chouti" #调度器队列的名称 rules = ( Rule(LinkExtractor(allow=r'/all/hot/recent/\d+'), callback='parse_item', follow=True), ) def parse_item(self, response): div_list = response.xpath('//div[@class="news-content"]') for div in div_list: author = div.xpath('./div[2]/a[4]/b/text()').extract_first() title = div.xpath('./div[1]/a/text()').extract_first() item = FenbushiChoutiproItem() item["author"] = author item["title"] = title print('===>>',item["author"]) yield item
- items.py

import scrapy class FenbushiChoutiproItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() title = scrapy.Field() author = scrapy.Field()
- settings.py

USER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36' # Obey robots.txt rules ROBOTSTXT_OBEY = False
分布式的启动:
scrapy runspider chouti.py #找对路径 redis-server #记住要修改配置文件,具体修改见前面所说 redis-cli lpush chouti https://dig.chouti.com/ # 其中chouti指的是redis_key中内容
增量式爬虫
引言:
当咱们在浏览相关网页的时候会发现,某些网站定时会在原有网页数据的基础上更新一批数据,例如某电影网站会实时更新一批最近热门的电影。小说网站会根据做者创做的进度实时更新最新的章节数据等等。那么,相似的情景,当咱们在爬虫的过程当中遇到时,咱们是否是须要定时更新程序以便能爬取到网站中最近更新的数据呢?
一.增量式爬虫
- 概念:经过爬虫程序监测某网站数据更新的状况,以即可以爬取到该网站更新出的新数据。
- 如何进行增量式的爬取工做:
- 在发送请求以前判断这个URL是否是以前爬取过
- 在解析内容后判断这部份内容是否是以前爬取过
- 写入存储介质时判断内容是否是已经在介质中存在
- 分析:
不难发现,其实增量爬取的核心是去重, 至于去重的操做在哪一个步骤起做用,只能说各有利弊。在我看来,前两种思路须要根据实际状况取一个(也可能都用)。第一种思路适合不断有新页面出现的网站,好比说小说的新章节,天天的最新新闻等等;第二种思路则适合页面内容会更新的网站。第三个思路是至关因而最后的一道防线。这样作能够最大程度上达到去重的目的。
- 分析:
- 去重方法
- 将爬取过程当中产生的url进行存储,存储在redis的set中。当下次进行数据爬取时,首先对即将要发起的请求对应的url在存储的url的set中作判断,若是存在则不进行请求,不然才进行请求。
- 对爬取到的网页内容进行惟一标识的制定,而后将该惟一表示存储至redis的set中。当下次爬取到网页数据的时候,在进行持久化存储以前,首先能够先判断该数据的惟一标识在redis的set中是否存在,在决定是否进行持久化存储。
需求:爬取4567tv网站中全部的电影详情数据。(有更新的url时)
- 爬虫文件 movie.py
# -*- coding: utf-8 -*- import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule from redis import Redis from increment1_move_Pro.items import Increment1MoveProItem class MoveSpider(CrawlSpider): name = 'movie' # allowed_domains = ['www.xxx.com'] start_urls = ['https://www.4567tv.tv/index.php/vod/show/id/7.html'] #起始url # 解析出其余页码中的电影数据 rules = ( Rule(LinkExtractor(allow=r'/index.php/vod/show/id/7/page/\d+.html'), callback='parse_item', follow=True), ) def parse_item(self, response): li_list = response.xpath('//li[@class="col-md-6 col-sm-4 col-xs-3"]') conn = Redis(host="127.0.0.1",port=6379)
for li in li_list: #获取详情页的数据 detail_url = 'https://www.4567tv.tv' + li.xpath('./div/a/@href').extract_first() ex = conn.sadd("movie_url",detail_url) #redis中添加set集合,有去重做用,若是已经存在会返回0,没有存在会添加并返回1 if ex == 1: yield scrapy.Request(url=detail_url,callback=self.parse_detail) #为1,说明是新添加的,而后去解析数据便可 else: print("没有要更新的数据") def parse_detail(self,response): #解析数据 title = response.xpath('/html/body/div[1]/div/div/div/div[2]/h1/text()').extract_first() actor = response.xpath('/html/body/div[1]/div/div/div/div[2]/p[3]/span/text()').extract_first()
item = Increment1MoveProItem() item["title"] = title item["actor"] = actor yield item
- items.py
import scrapy class Increment1MoveProItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() title = scrapy.Field() actor = scrapy.Field()
- 管道文件 pipelines.py
from redis import Redis class Increment1MoveProPipeline(object): conn = None def open_spider(self,spider): #该方法只被调用一次 self.conn = Redis(host="127.0.0.1",port=6379) def process_item(self, item, spider): #能够被调用屡次 print("爬取的数据正在入库......") self.conn.lpush("movie_data",item.__dict__) # 将解析到的数据存入redis中 lpush是添加列表,能够直接使用item.__dict__存储 return item
- 配置文件 settings.py
使用管道要在settings中添加
ITEM_PIPELINES = { 'increment1_move_Pro.pipelines.Increment1MoveProPipeline': 300, } USER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36' # Obey robots.txt rules ROBOTSTXT_OBEY = False
需求:爬取糗事百科中的段子和做者数据。(有更新的内容,同一个url)
好比新闻和段子都是实时更新的,若是想要获取最新的内容,能够采用如下的方法
将内容进行哈希,存入redis中的集合,由于集合是不重复的,
若是redis中以前没有,会添加到redis中并返回1
若是以前就有了,那么会返回0,不作什么操做
- 爬虫文件:qiubai.py
# -*- coding: utf-8 -*- import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule from increment2_Pro.items import Increment2ProItem from redis import Redis import hashlib class QiubaiSpider(CrawlSpider): name = 'qiubai' # allowed_domains = ['www.xxx.com'] start_urls = ['https://www.qiushibaike.com/text/'] rules = ( Rule(LinkExtractor(allow=r'/text/page/\d+/'), callback='parse_item', follow=True), #获取其余页码 ) def parse_item(self, response): div_list = response.xpath('//div[@class="article block untagged mb15 typs_hot"]') conn = Redis(host='127.0.0.1',port=6379) for div in div_list: item = Increment2ProItem() item['content'] = div.xpath('.//div[@class="content"]/span//text()').extract() item['content'] = ''.join(item['content']) item['author'] = div.xpath('./div/a[2]/h2/text() | ./div[1]/span[2]/h2/text()').extract_first() source = item['author']+item['content'] #本身制定了一种形式的数据指纹 hashValue = hashlib.sha256(source.encode()).hexdigest() ex = conn.sadd('qiubai_hash',hashValue) if ex == 1: yield item else: print('没有更新数据可爬!!!')
- items.py
import scrapy class Increment2ProItem(scrapy.Item): # define the fields for your item here like: content = scrapy.Field() author = scrapy.Field()
- 管道文件:pipelines.py
from redis import Redis class Increment2ProPipeline(object): conn = None def open_spider(self,spider): self.conn = Redis(host='127.0.0.1',port=6379) def process_item(self, item, spider): dic = { 'author':item['author'], 'content':item['content'] } self.conn.lpush('qiubaiData',dic) # self.conn.lpush('qiubaiDta',item.__dict__) #也能够直接使用 __dict__属性 print('爬取到一条数据,正在入库......') return item
- 配置文件 settings.py
使用管道要在settings中添加
ITEM_PIPELINES = { 'increment1_move_Pro.pipelines.Increment1MoveProPipeline': 300, } USER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36' # Obey robots.txt rules ROBOTSTXT_OBEY = False
================