CrawlSpider
:全站数据爬虫的方式,它是一个类,属于Spider的子类
如果不使用CrawlSpider
,那么就相当于基于spider
,手动发送请求,太不方便
基于CrawlSpider
可以很方便地进行全站数据爬取
基本步骤:
scrapy startproject ProjectName
scrapy genspider -t crawl xxx www.xxx.com
使用CrawlSpider
和spider
产生的爬虫文件除了继承类不一样外还有一个rules
的规则解析器
rules = ( Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True), )
在rules
规则解析器内有一个链接提取器LinkExtractor(allow=r'Items/')
,callback
是规则解析器指定的解析方法,follow
是指爬取页面内可见部分页面还是全部
页面内可见部分页面如下:
链接提取器作用:根据指定的规则allow=r'Items/'
进行指定的链接的提取
规则解析器作用:把链接提取器提取到的链接进行指定规则callback='parse_item'
的解析操作
follow
作用:True
可以把 链接提取器 继续作用到 链接提取器提取到的链接
所对应的 页面
中,False
爬取页面内可见部分页面
使用CrawlSpider
生成爬虫文件时,在规则解析器rules
里面添加正则表达式进而发起请求,如果要一个请求内需要再次发起请求,就需要在rules
中添加链接请求并指定对应的解析方法
注意
:xpath
中最好不要出现tbody
标签
import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule from sunPro.items import SunproItem,DetailItem class SunSpider(CrawlSpider): name = 'sun'#爬虫文件名 # allowed_domains = ['www.xxx.com']#允许的url start_urls = ['http://dk.test.com/mail/?ac=list&tid=1']#url列表 #规则解析器 rules = ( #LinkExtractor(allow=r'Items/')连接提取器,就是用来提取连接,根据指定规则(allow=r'Items/')进行指定连接的提取 Rule(LinkExtractor(allow=r'ac=list&tid=1&order=1&page=\d+'), callback='parse_item', follow=False), #获取详情信息 Rule(LinkExtractor(allow=r'ct=index&ac=detail&id=\d+'), callback='parse_detail', follow=False), ) def parse_item(self, response): tr_list=response.xpath('/html/body/table[2]//tr/td/table//tr[3]/td/table//tr/td[1]/table//tr/td/table//tr[1]/td/div/table//tr[@bgcolor="#FFFFFF"]') # print(tr_list) for tr in tr_list: news_num = tr.xpath('./td[1]/text()').extract_first() news_title = tr.xpath('./td[2]/a/text()').extract_first() print(news_num,news_title) """ item=SunproItem() item['news_title']=news_title item['news_num']=news_num yield item """ def parse_detail(self,response): news_id=response.xpath('/html/body/table[2]//tr/td/table//tr[3]/td/table//tr/td[1]/table//tr[1]/td/table//tr/td/table//tr[2]/td/table//tr[1]/td[1]/span/text()').extract_first() news_content=response.xpath('/html/body/table[2]//tr/td/table//tr[3]/td/table//tr/td[1]/table//tr[1]/td/table//tr/td/table//tr[3]/td/table//tr[1]/td/table//tr[2]/td//text()').extract() news_content=''.join(news_content) item=DetailItem() item['news_id']=news_id item['news_content']=news_content yield item
由于不能发送请求时传参因此,需要两个item类文件
import scrapy class SunproItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() news_title=scrapy.Field() news_num=scrapy.Field() class DetailItem(scrapy.Item): news_id=scrapy.Field() news_content=scrapy.Field()
分布式爬虫:需要搭建一个分布式的集群,让其对一组资源进行分布联合爬取,主要是为了提升爬取数据效率
安装一个scrapy-redis
的组件:pip install scrapy-redis
,由于原生的scrapy
不可以失效分布式爬虫,必须让scrapy
结合scrapy-redis
组件一起实现分布式爬虫
那么为什么原生scrapy
不可以实现分布式?
但是scrapy-redis
组件可以提供共享的管道和调度器
基本使用步骤:
CrawlSpider
的爬虫文件,修改爬虫文件导包:from scrapy_redis.spiders import RedisCrawlSpider
把start_urls
和allowed_domains
注释掉
添加一个新属性:redis_key='sun'
作为可以被共享的调度器队列名称
编写数据解析相关操作
把当前父类修改为RedisCrawlSpider
settings.py
,不要开启项目自带的pipelines
不然还是走的原来的管道,需要指定共享的管道RedisPipeline
,还要指定调度器指定管道 ITEM_PIPELINES = { #'sunPro.pipelines.SunproPipeline': 300, 'scrapy_redis.pipelines.RedisPipeline':400 } 指定调度器 #增加一个去重容器类的配置,作用使用redis的set集合来存储请求的指纹数据,从而实现请求去重的持久化 DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupeFilter' #使用scrapy-redis组件自己的调度器 SCHEDULER='scrapy_redis.scheduler.Scheduler' #配置调度器是否需要持久化,也就是当爬虫结束了,要不要清空reids中请求队列 #如果服务器宕机了,重启后从爬取的位置继续爬取 SCHEDULER_PERSIST = True 指定redis地址和端口 REDIS_HOST='127.0.0.1' REDIS_PORT='6379'
把redis.windows-server.conf
文件修改把bind 127.0.0.1
给注释掉,由于要把爬到的数据库储存到不同地方,因此不要绑定本地
关闭保护模式protected-mode yes
修改为protected-mode no
,如果开启了保护模式,那么其他客户端只能读取redis而不能写入
分布式爬虫启动和scrapy工程不同,需要定位到爬虫文件.py
目录内,执行scrapy runspider xxx.py
工程启动后在redis客户端中向redis添加调度队列:lpush sun www.xxx.com
(由于之前写过redis_key='sun'的共享调度属性)
增量式爬虫:检测网站数据更新的情况,只会爬取网站最新出来的数据
还是基于CrawlSpider
获取其他页码链接处理的,每次爬取时,都会对已经爬取的数据进行比较,若爬取过了,就不再爬取
主要通过redis来判断是否已经存储过
from redis import Redis from sunPro.items import SunproItem class SunSpider(CrawlSpider): name = 'sun' # allowed_domains = ['www.xxx.com'] start_urls = ['http://www.xxx.com/'] rules = ( Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True), ) #创建redis对象 conn = Redis(host='127.0.0.1',port=6379) def parse_item(self, response): li_list=response.xpath('xxxxx'); for li in li_list: # 获取详情url detail_url=li.xpath('xxxxxxxxxx').extract_first() ex=self.conn.sadd('urls',detail_url) if ex==1: print('该url没有爬取过,可以进行数据爬取') yield scrapy.Request(url=detail_url,callback=self.parse_detail) else: print('数据没有更新,暂无新数据可爬取') def parse_detail(self,response): item = SunproItem() item['name']=response.xpath('xxxxxxxxxxxxx').extract()
在管道文件中获取redis
class SunproPipeline: conn=None # 开启爬虫时执行,只执行一次 def open_spider(self,spider): self.conn=spider.conn #理提取的数据(保存数据) def process_item(self, item, spider): dict={ 'name':item['name'] } self.conn.lpush('test',dict) return item # 关闭爬虫时执行,只执行一次。 (如果爬虫中间发生异常导致崩溃,close_spider可能也不会执行) def close_spider(self, spider): # 可以关闭数据库等 pass