增量抓取实战:Redis 指纹校验、带宽优化

📂 所属阶段:第四阶段 — 实战演练(项目开发篇)


1. 什么是增量爬虫?

在讲代码前,咱们先把增量爬虫和「普通全量爬虫」的区别理清楚——
全量爬虫每次启动,都会把所有目标页面重新抓一遍,不管内容变没变;
增量爬虫则只抓「首次出现」或「内容已经更新」的页面

新手可能觉得无所谓,但一旦项目上线(比如监控竞品价格、实时热点聚合),全量爬取的代价会非常扎眼:

  • 浪费服务器带宽和计算资源
  • 爬取速度慢,更新频率跟不上
  • 极容易触发目标网站的反爬机制

所以今天用 Scrapy + Redis 实现一套最基础、通用性最强的 URL 指纹式增量爬虫


2. 实战代码:URL 指纹校验版增量爬虫

2.1 前置准备

先安装依赖:

pip install scrapy redis

确保本地或远程 Redis 服务已启动(默认端口 6379,演示时可不设密码,生产环境务必开启认证)。

2.2 完整代码解析

import scrapy
import redis
import hashlib
from scrapy.http import Request   # 补全原示例中未显式导入的 Request

class IncrementalSpider(scrapy.Spider):
    name = "incremental_demo"
    allowed_domains = ["quotes.toscrape.com"]
    start_urls = ["https://quotes.toscrape.com/"]

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # 连接 Redis(生产环境建议将配置统一放在 settings.py 里)
        self.redis_client = redis.Redis(
            host="127.0.0.1",
            port=6379,
            db=0,
            decode_responses=True   # 返回字符串,省去手动 .decode()
        )

    def parse(self, response):
        for quote_card in response.css("div.quote"):
            detail_url = quote_card.css("span small a::attr(href)").get()
            full_detail_url = response.urljoin(detail_url)

            # 生成 URL 指纹(MD5 快速够用,极严格场景可换 SHA256)
            url_fingerprint = hashlib.md5(
                full_detail_url.encode("utf-8")
            ).hexdigest()

            redis_key = f"crawled_urls:{url_fingerprint}"

            # 如果不见 → 新 URL → 发起请求并记录指纹
            if not self.redis_client.exists(redis_key):
                yield Request(
                    url=full_detail_url,
                    callback=self.parse_quote_detail,
                    meta={"author": quote_card.css("span small.author::text").get()}
                )
                # 设置指纹,并让键 7 天后自动过期,防止 Redis 内存溢出
                self.redis_client.set(redis_key, "1", ex=604800)  # 604800s = 7天

        # 处理分页
        next_page = response.css("li.next a::attr(href)").get()
        if next_page:
            yield response.follow(next_page, callback=self.parse)

    def parse_quote_detail(self, response):
        yield {
            "author": response.meta["author"],
            "author_birth_date": response.css("span.author-born-date::text").get(),
            "author_birth_place": response.css("span.author-born-location::text").get(),
            "quote": response.css("div.quote span.text::text").get().strip('“”'),
            "tags": response.css("div.quote div.tags a.tag::text").getall()
        }

2.3 启动爬虫测试

在终端进入 Scrapy 项目根目录,运行:

scrapy crawl incremental_demo -o quotes.json
  • 第一次执行:爬遍全站,生成包含作者信息的 quotes.json
  • 第二次执行:终端中只会出现首页和翻页的请求,没有任何详情页请求 —— 增量抓取成功生效!

3. 扩展优化方向

3.1 不只是 URL 指纹

有些网站 URL 不变但内容会动态更新(新闻首页、电商商品页等),光靠 URL 指纹就不灵了。这时可以考虑:

  • 响应内容哈希:对整个页面或关键字段做哈希(注意过滤掉广告、时间戳等随机部分)
  • HTTP 响应头:利用 Last-ModifiedETag 判断页面是否修改
  • 关键信息比对:配合 MySQL、Elasticsearch 保存价格、库存等业务字段,只抓有变动的数据

3.2 生产环境 Redis 建议

  • 单独分配一个数据库(如 db=1)给爬虫指纹,避免混用
  • 必设认证密码,必要时使用 SSL/TLS
  • 集群化部署时使用 Redis Cluster 替代单机
  • 内存紧张时开启 maxmemory-policy allkeys-lru,自动淘汰冷数据

4. 小结

增量抓取的核心逻辑就一句话:先判断“要不要爬”,再决定“爬不爬”

本文演示的 URL 指纹版增量爬虫,虽基础但适用范围极广——尤其适合「静态列表页不断推新详情页」的场景:

  • 监控新闻站点新文章
  • 抓取论坛新帖
  • 同步电商平台新商品

💡 记住:在生产级爬虫项目中,增量抓取是性价比最高的优化手段之一,没有之一。


🔗 扩展阅读