20.scrapy框架之增量式爬虫

一、增量式爬虫

1.什么是增量式爬虫???

  —  通俗的来说,爬取网站中更新的数据,不管是产生新页面,还是原本的页面更新,这种变化都被称为增量, 而爬取过程则被称为增量爬取

2.回顾一下爬虫的工作流程

  1. 指定URL,发送URL请求,获取页面数据

  2. 获得响应对象

  3. 解析对象的内容

  4. 储存内容

3. 实现增量式爬虫的方案:

  1.在发送请求之前,判断url之前是否爬取过

    a.将即将进行爬取的数据对应的url存储到redis的set中.

  2.根据爬取到的数据进行重复过滤,然后在进行持久化存储(在解析内容后判断这部分内容是不是之前爬取过)

       b.将爬取到的数据给其生成一个唯一的标识(可以将该标识作为mysql的列.可以将该标识存储到redis的set中)

  3.写入存储介质时判断内容是不是已经在介质中存在

4. 实现增量式爬取:4567

  不难发现,其实增量爬取的核心是去重, 至于去重的操作在哪个步骤起作用,只能说各有利弊

  前两种思路需要根据实际情况取一个(也可能都用)。第一种思路适合不断有新页面出现的网站,比如说小说的新章节,每天的最新新闻等等;第二种思路则适合页面内容会更新的网站。第三个思路是相当于是最后的一道防线。这样做可以最大程度上达到去重的目的。

最简单的去重方式自然是将所有访问过的URL和其对应的内容保存下来,然后过一段时间重新爬取一次并进行比较,然后决定是否需要覆盖。这显然是不实际的,因为会消耗很多资源。目前比较实际的做法就是给URL或者其内容(取决于这个网站采用哪种更新方式)上一个标识,这个标识有个比较好听的名字,叫数据指纹

  这里很容易想到的一种数据指纹就是哈希值,根据哈希函数的特性,我们可以为任意内容生成一个独一无二的定长字符串,之后只要比较这个哈希值就行了。哈希值是一个很伟大的发明,几乎在任何地方都有它的影子,它利用数学特性,计算机只要经过简单的计算就可以得到唯一的特征值,这个计算过程的开销基本可以忽略不计,当然这是题外话了。

  不过即使用了哈希值,你仍需要一个地方存储所有的哈希值,并且要能做到方便的取用。如果你的存储介质是数据库,一般的数据库系统都能提供索引,如果把哈希值作为唯一索引呢,这应该是可行的。有些数据库也提供查询后再插入的操作,不过本质上应该也是索引。和哈希值类似的还有MD5校验码,殊途同归。

  除了自建指纹,其实在发送请求时还有一些技巧,比如说304状态码,Last-modified字段,文件大小和MD5签名。很好理解,就不细说了。

  综上所述,在数据量不大的时候,几百个或者就几千个的时候,简单自己写个小函数或者利用集合的特性去重就行了。如果数据量够大,数据指纹的价值就体现出来了,另外,如果要对数据做持久化(简单说就是去重操作不会被事故影响,比如说断电),就需要用到Redis数据库

5. 具体实现

  示例一:在发送请求之前,判断url之前是否爬取过

        基于scrape框架中的CrawlSpider类进行爬取,4567电影网中电影下面的爱情片对应数据

      http://www.4567tv.tv/frim/index7-11.html

   
# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from redis import Redis
from moviePro.items import MovieproItem


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=6380,db=1)
    def parse_item(self, response):
        li_list = response.xpath('//div[@class="index-area clearfix"]/ul/li')
        for li in li_list:
            # 获取详情页面的url
            detail_url = 'http://www.4567tv.tv' + li.xpath('./a/@href').extract_first()
            #将详情页面的url存入redis
            ex = self.conn.sadd("urls",detail_url)
            # 在向redis中存取的时候判断当前的url是否存在,如果不存在返回1,存在返回0
            if ex==1:
                print("该url还未被爬取,可以进行数据爬取")
                # 获取详情页面
                yield scrapy.Request(url=detail_url,callback=self.detail_data)
            else:
                print("该url已被爬取,暂无更新数据")

    # 解析详情页中的电影名称和类型,进行持久化存储
    def detail_data(self,response):
        # 解析出所需的数据
        name = response.xpath('//dt[@class="name"]/text()').extract_first()
        kind = response.xpath('//div[@class="ct-c"]/dl/dt[4]/a/text()').extract_first()
        # 创建一个item对象,并将解析到数据进行储存到该对象当中
        item = MovieproItem()
        item["name"] = name
        item["kind"] = kind
        item["kind"] = "".join(item["kind"])
        # 将item对象进行持久化储存
        yield item
爬虫文件
   
import scrapy

class MovieproItem(scrapy.Item):
    # define the fields for your item here like:
    name = scrapy.Field()
    kind = scrapy.Field()
items.py
    pipelines.py 管道文件 
   
BOT_NAME = 'moviePro'

SPIDER_MODULES = ['moviePro.spiders']
NEWSPIDER_MODULE = 'moviePro.spiders'
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'

ROBOTSTXT_OBEY = False

# 开启管道文件
ITEM_PIPELINES = {
   'moviePro.pipelines.MovieproPipeline': 300,
}
setting.py 配置文件

  示例二: 在解析内容后判断这部分内容是不是之前爬取过

     基于scrape框架中的CrawlSpider类进行爬取,糗事百科,文字对应新闻的名称,和内容

     https://www.qiushibaike.com/text/

    
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from qiubaiPro.items import QiubaiproItem
import hashlib
from redis import Redis
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=6380,db=2)

    def parse_item(self, response):
        print(response)
        div_list = response.xpath('//div[@id="content-left"]/div')
        for div in div_list:
            author = div.xpath('./div/a[2]/h2/text() | ./div[1]/span[2]/h2/text()').extract_first()
            content = div.xpath('.//div[@class="content"]/span/text()').extract_first()
            #
            item = QiubaiproItem()
            item["author"] = author
            item["content"] = content

            # 将解析到的数据值生成一个唯一的标识进行redis存储
            source = item["author"] + item["content"]
            # 使用md5进行加密
            md5_obj = hashlib.md5()
            md5_obj.update(source.encode("utf-8"))
            source_id= md5_obj.hexdigest()

            # 将解析内容的唯一表示存储到redis的data_id中
            ex = self.conn.sadd("data_id",source_id)

            if ex == 1:
                print("数据还没有被爬取,可以进行爬取!!!")
                yield item
            else:
                print("该条数据已被爬取,请勿再次爬取")
爬虫文件 
   
import scrapy

class QiubaiproItem(scrapy.Item):
    # define the fields for your item here like:
    author = scrapy.Field()
    content = scrapy.Field()
items.py
   
from redis import Redis
import json
class QiubaiproPipeline(object):
    conn =None

    def open_spider(self,spider):
        self.conn = Redis(host="127.0.0.1",port=6380,db=2)
    def process_item(self, item, spider):
        dic = {
            "author":item["author"],
            "content":item["content"]
        }
        print(dic)
        self.conn.lpush("qiubaiData",json.dumps(dic))
        return item
piipelines.py 管道文件
    
BOT_NAME = 'qiubaiPro'

SPIDER_MODULES = ['qiubaiPro.spiders']
NEWSPIDER_MODULE = 'qiubaiPro.spiders'
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'

ROBOTSTXT_OBEY = False

# 开启管道文件
ITEM_PIPELINES = {
   'qiubaiPro.pipelines.QiubaiproPipeline': 300,
}
setting.py 配置文件

猜你喜欢

转载自www.cnblogs.com/mwhylj/p/10279554.html