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

1 # -*- coding: utf-8 -*- 2 import scrapy 3 from scrapy.linkextractors import LinkExtractor 4 from scrapy.spiders import CrawlSpider, Rule 5 from redis import Redis 6 from moviePro.items import MovieproItem 7 8 9 class MovieSpider(CrawlSpider): 10 name = 'movie' 11 # allowed_domains = ['www.xxx.com'] 12 start_urls = ['http://www.4567tv.tv/frim/index7-11.html'] 13 14 rules = ( 15 Rule(LinkExtractor(allow=r'/frim/index7-\d+\.html'), callback='parse_item', follow=True), 16 ) 17 # 创建redis链接对象 18 conn = Redis(host="127.0.0.1",port=6380,db=1) 19 def parse_item(self, response): 20 li_list = response.xpath('//div[@class="index-area clearfix"]/ul/li') 21 for li in li_list: 22 # 获取详情页面的url 23 detail_url = 'http://www.4567tv.tv' + li.xpath('./a/@href').extract_first() 24 #将详情页面的url存入redis 25 ex = self.conn.sadd("urls",detail_url) 26 # 在向redis中存取的时候判断当前的url是否存在,如果不存在返回1,存在返回0 27 if ex==1: 28 print("该url还未被爬取,可以进行数据爬取") 29 # 获取详情页面 30 yield scrapy.Request(url=detail_url,callback=self.detail_data) 31 else: 32 print("该url已被爬取,暂无更新数据") 33 34 # 解析详情页中的电影名称和类型,进行持久化存储 35 def detail_data(self,response): 36 # 解析出所需的数据 37 name = response.xpath('//dt[@class="name"]/text()').extract_first() 38 kind = response.xpath('//div[@class="ct-c"]/dl/dt[4]/a/text()').extract_first() 39 # 创建一个item对象,并将解析到数据进行储存到该对象当中 40 item = MovieproItem() 41 item["name"] = name 42 item["kind"] = kind 43 item["kind"] = "".join(item["kind"]) 44 # 将item对象进行持久化储存 45 yield item

import scrapy class MovieproItem(scrapy.Item): # define the fields for your item here like: name = scrapy.Field() kind = scrapy.Field()
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, }
示例二: 在解析内容后判断这部分内容是不是之前爬取过
基于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("该条数据已被爬取,请勿再次爬取") 爬虫文件
items。py
import scrapy class QiubaiproItem(scrapy.Item): # define the fields for your item here like: author = scrapy.Field() content = scrapy.Field()
pipelines 管道文件
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
setting 配置文件
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, }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· AI Agent开发,如何调用三方的API Function,是通过提示词来发起调用的吗