crawlSpider,分布式爬虫,增量式爬虫

一 . crawlSpider

  1. 上次了一种爬取全站数据是基于Scrapy框架中的Spider的递归爬取进行实现(Requests模块递归回调parse方法).

  2. 如今在讲介绍一种比较好用的方法:基于CrawlSpider的自动爬取进行实现(更加的简洁高效).

  crawlSpider的简介

  CrawlSpider实际上是Spider的一个子类,除了继承到Spider的特性和功能外,还派生除了其本身独有的更增强大的特性和功能。
其中最显著的功能就是”LinkExtractors连接提取器“。Spider是全部爬虫的基类,其设计原则只是为了爬取start_url列表中网页,
而从爬取到的网页中提取出的url进行继续的爬取工做使用CrawlSpider更合适。

  scrawlSpider的使用

1.建立scrapy工程:scrapy startproject projectName 2.建立爬虫文件:scrapy genspider -t crawl spiderName www.xxx.com  --此指令对比之前的指令多了 "-t crawl",表示建立的爬虫文件是基于CrawlSpider这个类的,而再也不是Spider这个基类。

  看一下生成的爬虫文件

 1 # -*- coding: utf-8 -*-
 2 import scrapy  3 from scrapy.linkextractors import LinkExtractor  4 from scrapy.spiders import CrawlSpider, Rule  5 
 6 
 7 class SuperSpiderSpider(CrawlSpider):  8     name = 'super_spider'
 9     allowed_domains = ['www.xxx.com'] 10     start_urls = ['http://www.xxx.com/'] 11 
12     rules = ( 13         Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True), 14  ) 15 
16     def parse_item(self, response): 17         item = {} 18         #item['domain_id'] = response.xpath('//input[@id="sid"]/@value').get()
19         #item['name'] = response.xpath('//div[@id="name"]').get()
20         #item['description'] = response.xpath('//div[@id="description"]').get()
21         return item

  -- 2,3,4行: 导入CrawlSpider相关模块css

  -- 7行: 表示该爬虫程序是基于CrawlSpider类的html

  -- 12,13,14行: 表示提取link规则正则表达式

  -- 16行: 解析方法redis

    CrawlSpider类和Spider类的最大不一样是CrawlSpider多了一个rules属性,其做用是定义”提取动做“。
在rules中能够包含一个或多个Rule对象,在Rule对象中包含了LinkExtractor对象。

  LinkExtrator:连接提取器

LinkExtractor(         allow=r'Items/'# 知足括号中“正则表达式”的值会被提取,若是为空,则所有匹配。
          deny=xxx,  # 知足正则表达式的则不会被提取。
          restrict_xpaths=xxx, # 知足xpath表达式的值会被提取
          restrict_css=xxx, # 知足css表达式的值会被提取
          deny_domains=xxx, # 不会被提取的连接的domains。     ) # 做用:提取response中符合规则的连接。

  Rule : 规则解析器。根据连接提取器中提取到的连接,根据指定规则提取解析器连接网页中的内容.

 Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True)  -- 参数介绍:     参数1:指定连接提取器     参数2:指定规则解析器解析数据的规则(回调函数)     参数3:是否将连接提取器继续做用到连接提取器提取出的连接网页中。当callback为None,参数3的默认值为true。

  rules=( ):指定不一样规则解析器。一个Rule对象表示一种提取规则。

   CrawlSpider总体爬取流程:

a)爬虫文件首先根据起始url,获取该url的网页内容

b)连接提取器会根据指定提取规则将步骤a中网页内容中的连接进行提取

c)规则解析器会根据指定解析规则将连接提取器中提取到的连接中的网页内容根据指定的规则进行解析

d)将解析数据封装到item中,而后提交给管道进行持久化存储

  话很少说, 上代码

# 爬取糗事百科糗图板块的全部页码数据

# -*- coding: utf-8 -*-
import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule class CrawldemoSpider(CrawlSpider): name = 'qiubai'
    #allowed_domains = ['www.qiushibaike.com']
    start_urls = ['https://www.qiushibaike.com/pic/'] #链接提取器:会去起始url响应回来的页面中提取指定的url
    link = LinkExtractor(allow=r'/pic/page/\d+\?') #s=为随机数
    link1 = LinkExtractor(allow=r'/pic/$')#爬取第一页
    #rules元组中存放的是不一样的规则解析器(封装好了某种解析规则)
    rules = ( #规则解析器:能够将链接提取器提取到的全部链接表示的页面进行指定规则(回调函数)的解析
        Rule(link, callback='parse_item', follow=True), Rule(link1, callback='parse_item', follow=True), ) def parse_item(self, response): print(response) 

  上面是牛刀小试,下边是一个完整的流程

  爬虫文件

# -*- coding: utf-8 -*-
import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule from qiubaiBycrawl.items import QiubaibycrawlItem import re class QiubaitestSpider(CrawlSpider): name = 'qiubaiTest'
    #起始url
    start_urls = ['http://www.qiushibaike.com/'] #定义连接提取器,且指定其提取规则
    page_link = LinkExtractor(allow=r'/8hr/page/\d+/') rules = ( #定义规则解析器,且指定解析规则经过callback回调函数
        Rule(page_link, callback='parse_item', follow=True), ) #自定义规则解析器的解析规则函数
    def parse_item(self, response): div_list = response.xpath('//div[@id="content-left"]/div') for div in div_list: #定义item
            item = QiubaibycrawlItem() #根据xpath表达式提取糗百中段子的做者
            item['author'] = div.xpath('./div/a[2]/h2/text()').extract_first().strip('\n') #根据xpath表达式提取糗百中段子的内容
            item['content'] = div.xpath('.//div[@class="content"]/span/text()').extract_first().strip('\n') yield item #将item提交至管道

  items.py

# -*- coding: utf-8 -*-

# Define here the models for your scraped items # # See documentation in: # https://doc.scrapy.org/en/latest/topics/items.html

import scrapy class QiubaibycrawlItem(scrapy.Item): # define the fields for your item here like:
    # name = scrapy.Field()
    author = scrapy.Field() #做者
    content = scrapy.Field() #内容

  pipelines.py

# -*- coding: utf-8 -*-

# Define your item pipelines here # # Don't forget to add your pipeline to the ITEM_PIPELINES setting # See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html

class QiubaibycrawlPipeline(object): def __init__(self): self.fp = None def open_spider(self,spider): print('开始爬虫') self.fp = open('./data.txt','w') def process_item(self, item, spider): #将爬虫文件提交的item写入文件进行持久化存储
        self.fp.write(item['author']+':'+item['content']+'\n') return item def close_spider(self,spider): print('结束爬虫') self.fp.close()

二 . 分布式爬虫

  首先咱们先考虑一个问题: scrapy框架是否能够本身实现分布式?

不能够。缘由有二。

  其一:由于多台机器上部署的scrapy会各自拥有各自的调度器,这样就使得多台机器没法分配start_urls列表中的url。(多台机器没法共享同一个调度器)

  其二:多台机器爬取到的数据没法经过同一个管道对数据进行统一的数据持久出存储。(多台机器没法共享同一个管道)

  基于scrapy-redis组件的分布式爬虫

scrapy-redis能够解决上述两个问题 scrapy-redis组件中为咱们封装好了能够被多台机器共享的调度器和管道,咱们能够直接使用并实现分布式数据爬取。
实现方式:
1.基于该组件的RedisSpider类 2.基于该组件的RedisCrawlSpider类

  分布式实现流程

1.下载scrapy-redis组件:pip install scrapy-redis 2. redis配置文件的配置: - 注释该行:bind 127.0.0.1,表示可让其余ip访问redis - 将yes该为no:protected-mode no,表示可让其余ip操做redis

  修改爬虫文件中的相关代码:

# 先导入包: from scrapy_redis.spiders import RedisCrawlSpider
- 将爬虫类的父类修改为基于RedisSpider或者RedisCrawlSpider。   注意:若是原始爬虫文件是基于Spider的,则应该将父类修改为RedisSpider,   若是原始爬虫文件是基于CrawlSpider的,则应该将其父类修改为RedisCrawlSpider。 - 注释或者删除start_urls列表,且加入redis_key属性,属性值为scrpy-redis组件中调度器队列的名称

  

  在配置文件中进行相关配置,开启使用scrapy-redis组件中封装好的管道

ITEM_PIPELINES = { 'scrapy_redis.pipelines.RedisPipeline': 400   # 直接复制粘贴就行
}

  在配置文件中进行相关配置,开启使用scrapy-redis组件中封装好的调度器

# 使用scrapy-redis组件的去重队列
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# 使用scrapy-redis组件本身的调度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 是否容许暂停
SCHEDULER_PERSIST = True

  在配置文件中进行爬虫程序连接redis的配置

REDIS_HOST = 'redis服务的ip地址' REDIS_PORT = 6379

  

        1. 开启redis服务器:redis-server 配置文件数据库

        2. 开启redis客户端:redis-cli服务器

        3. 运行爬虫文件:scrapy runspider SpiderFile框架

        4. 向调度器队列中扔入一个起始url(在redis客户端中操做):lpush redis_key属性值 起始urldom

  

三 . 增量式爬虫

   什么是增量是爬虫?

说白了就是你爬完一个网站的数据后,他又更新了新的数据,而你不须要从新全爬一边,只须要把更新的数据爬下来就能够啦,这就是增量式爬虫!

  如何进行增量式爬取工做呢?

第一种方法:在发送请求以前判断这个URL是否是以前爬取过
第二种方法:在解析内容后判断这部份内容是否是以前爬取过
第三种方法:写入存储数据库时判断内容是否是已经在数据库中存在

  实现上述方法的核心其实就是去重

第一种方法适合不断有新网页出现的网站,好比小说的新章节,天天最新的新闻等等
第二种方法适合内容更新的网站
第三种方法是最大程度上去重

  去重方法

1.将爬取过程当中产生的url进行存储,存储在redis的set中。当下次进行数据爬取时, 首先对即将要发起的请求对应的url在存储的url的set中作判断,若是存在则不进行请求,不然才进行请求。
2.对爬取到的网页内容进行惟一标识的制定,而后将该惟一表示存储至redis的set中。 当下次爬取到网页数据的时候,在进行持久化存储以前,首先能够先判断该数据的惟一标识在redis的set中是否存在,在决定是否进行持久化存储。

  案例1: 爬取4567tv网站中全部的电影详情数据。(基于url是否重复)

import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule from redis import Redis from incrementPro.items import IncrementproItem class MovieSpider(CrawlSpider): name = 'movie'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['http://www.4567tv.tv/frim/index7-11.html'] rules = ( Rule(LinkExtractor(allow=r'/frim/index7-\d+\.html'), callback='parse_item', follow=True), ) #建立redis连接对象
    conn = Redis(host='127.0.0.1',port=6379) def parse_item(self, response): li_list = response.xpath('//li[@class="p1 m1"]') for li in li_list: #获取详情页的url
            detail_url = 'http://www.4567tv.tv'+li.xpath('./a/@href').extract_first() #将详情页的url存入redis的set中
            ex = self.conn.sadd('urls',detail_url) if ex == 1: print('该url没有被爬取过,能够进行数据的爬取') yield scrapy.Request(url=detail_url,callback=self.parst_detail) else: print('数据尚未更新,暂无新数据可爬取!') #解析详情页中的电影名称和类型,进行持久化存储
    def parst_detail(self,response): item = IncrementproItem() item['name'] = response.xpath('//dt[@class="name"]/text()').extract_first() item['kind'] = response.xpath('//div[@class="ct-c"]/dl/dt[4]//text()').extract() item['kind'] = ''.join(item['kind']) yield item

  pipelines.py

# -*- coding: utf-8 -*-

from redis import Redis class IncrementproPipeline(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 = { 'name':item['name'], 'kind':item['kind'] } print(dic) self.conn.lpush('movieData',dic) return item

  案例2: 爬取糗事百科中的段子和做者数据。(基于内容的惟一标识)

# -*- coding: utf-8 -*-
import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule from incrementByDataPro.items import IncrementbydataproItem 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), Rule(LinkExtractor(allow=r'/text/$'), callback='parse_item', follow=True), ) #建立redis连接对象
    conn = Redis(host='127.0.0.1',port=6379) def parse_item(self, response): div_list = response.xpath('//div[@id="content-left"]/div') for div in div_list: item = IncrementbydataproItem() item['author'] = div.xpath('./div[1]/a[2]/h2/text() | ./div[1]/span[2]/h2/text()').extract_first() item['content'] = div.xpath('.//div[@class="content"]/span/text()').extract_first() #将解析到的数据值生成一个惟一的标识进行redis存储
            source = item['author']+item['content'] source_id = hashlib.sha256(source.encode()).hexdigest() #将解析内容的惟一表示存储到redis的data_id中
            ex = self.conn.sadd('data_id',source_id) if ex == 1: print('该条数据没有爬取过,能够爬取......') yield item else: print('该条数据已经爬取过了,不须要再次爬取了!!!')

  pipelines.py

# -*- coding: utf-8 -*-

from redis import Redis class IncrementbydataproPipeline(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'] } # print(dic)
        self.conn.lpush('qiubaiData', dic) return item
相关文章
相关标签/搜索