- 引言 在数据采集过程中,爬虫经常需要面对 重复数据 的问题。如果每次爬取都全量抓取,不仅浪费资源,还可能导致数据冗余。增量爬取(Incremental Crawling) 是一种高效策略,它仅抓取 新增或更新 的数据,而跳过已采集的旧数据。 本文将详细介绍 Python爬虫的增量爬取与历史数据比对 策略,涵盖以下内容:
- 增量爬取的核心思路
- 去重方案对比(数据库、文件、内存)
- 基于时间戳、哈希、数据库比对的实现方法
- 完整代码示例(Scrapy + MySQL 增量爬取)
- 增量爬取的核心思路 增量爬取的核心是 识别哪些数据是新的或已更新的,通常采用以下方式: ● 基于时间戳(Last-Modified / Update-Time) ● 基于内容哈希(MD5/SHA1) ● 基于数据库比对(MySQL/Redis/MongoDB) 2.1 基于时间戳的增量爬取 适用于数据源带有 发布时间(如新闻、博客) 的场景:
- 记录上次爬取的 最新时间戳
- 下次爬取时,只抓取 晚于该时间戳的数据 优点:简单高效,适用于结构化数据 缺点:依赖数据源的时间字段,不适用于无时间戳的网页 2.2 基于内容哈希的去重 适用于 内容可能更新但URL不变 的页面(如电商价格):
- 计算页面内容的 哈希值(如MD5)
- 比对哈希值,若变化则视为更新 优点:适用于动态内容 缺点:计算开销较大 2.3 基于数据库比对的增量爬取 适用于 大规模数据管理:
- 将已爬取的 URL 或关键字段 存入数据库(MySQL/Redis)
- 每次爬取前查询数据库,判断是否已存在 优点:支持分布式去重 缺点:需要额外存储
- 去重方案对比 方案 适用场景 优点 缺点 内存去重 单机小规模爬虫 速度快(set() ) 重启后数据丢失 文件存储 中小规模爬虫 简单(CSV/JSON) 性能较低 SQL数据库 结构化数据管理 支持复杂查询(MySQL) 需要数据库维护 NoSQL数据库 高并发分布式爬虫 高性能(Redis/MongoDB) 内存占用较高
- 增量爬取实现方法 4.1 基于时间戳的增量爬取(示例)
1from datetime import datetime 2 3class NewsSpider(scrapy.Spider): 4 name = "news_spider" 5 last_crawl_time = None # 上次爬取的最新时间 6 7 def start_requests(self): 8 # 从文件/DB加载上次爬取时间 9 self.last_crawl_time = self.load_last_crawl_time() 10 11 # 设置代理信息 12 proxy = "http://www.16yun.cn:5445" 13 proxy_auth = "16QMSOML:280651" 14 15 # 添加代理到请求中 16 yield scrapy.Request( 17 url="https://news.example.com/latest", 18 meta={ 19 'proxy': proxy, 20 'proxy_user_pass': proxy_auth 21 } 22 ) 23 24 def parse(self, response): 25 # 检查响应状态码,判断是否成功获取数据 26 if response.status != 200: 27 self.logger.error(f"Failed to fetch data from {response.url}. Status code: {response.status}") 28 self.logger.error("This might be due to network issues or an invalid URL. Please check the URL and try again.") 29 return 30 31 for article in response.css(".article"): 32 pub_time = datetime.strptime( 33 article.css(".time::text").get(), 34 "%Y-%m-%d %H:%M:%S" 35 ) 36 if self.last_crawl_time and pub_time <= self.last_crawl_time: 37 continue # 跳过旧文章 38 39 yield { 40 "title": article.css("h2::text").get(), 41 "time": pub_time, 42 } 43 44 # 更新最新爬取时间 45 self.save_last_crawl_time(datetime.now()) 46 47 def load_last_crawl_time(self): 48 try: 49 with open("last_crawl.txt", "r") as f: 50 return datetime.strptime(f.read(), "%Y-%m-%d %H:%M:%S") 51 except FileNotFoundError: 52 return None 53 54 def save_last_crawl_time(self, time): 55 with open("last_crawl.txt", "w") as f: 56 f.write(time.strftime("%Y-%m-%d %H:%M:%S"))
4.2 基于内容哈希的去重(示例)
1 2class ContentHashSpider(scrapy.Spider): 3 name = "hash_spider" 4 seen_hashes = set() # 存储已爬取的哈希 5 6 def parse(self, response): 7 content = response.css("body").get() 8 content_hash = hashlib.md5(content.encode()).hexdigest() 9 10 if content_hash in self.seen_hashes: 11 return # 跳过重复内容 12 13 self.seen_hashes.add(content_hash) 14 yield {"url": response.url, "content": content}
4.3 基于MySQL的增量爬取(完整示例) (1)MySQL 表结构
1 id INT AUTO_INCREMENT PRIMARY KEY, 2 url VARCHAR(255) UNIQUE, 3 content_hash CHAR(32), 4 last_updated TIMESTAMP 5);
(2)Scrapy 爬虫代码
1import hashlib 2from scrapy import Spider, Request 3 4class MySQLIncrementalSpider(Spider): 5 name = "mysql_incremental" 6 start_urls = ["https://example.com"] 7 8 def __init__(self): 9 self.conn = pymysql.connect( 10 host="localhost", 11 user="root", 12 password="123456", 13 db="crawler_db" 14 ) 15 self.cursor = self.conn.cursor() 16 17 def parse(self, response): 18 url = response.url 19 content = response.text 20 content_hash = hashlib.md5(content.encode()).hexdigest() 21 22 # 检查是否已爬取 23 self.cursor.execute( 24 "SELECT content_hash FROM crawled_data WHERE url=%s", 25 (url,) 26 ) 27 result = self.cursor.fetchone() 28 29 if result and result[0] == content_hash: 30 return # 内容未更新 31 32 # 插入或更新数据库 33 self.cursor.execute( 34 """INSERT INTO crawled_data (url, content_hash, last_updated) 35 VALUES (%s, %s, NOW()) 36 ON DUPLICATE KEY UPDATE 37 content_hash=%s, last_updated=NOW()""", 38 (url, content_hash, content_hash) 39 ) 40 self.conn.commit() 41 42 yield {"url": url, "content": content} 43 44 def close(self, reason): 45 self.cursor.close() 46 self.conn.close()
- 结论 策略 适用场景 推荐存储方案 时间戳比对 新闻、博客等带时间的数据 文件/MySQL 内容哈希 动态内容(如商品价格) Redis/内存 数据库去重 结构化数据管理 MySQL/MongoDB 最佳实践: ● 小型爬虫 → 内存去重(set()) ● 中型爬虫 → 文件存储(JSON/CSV) ● 大型分布式爬虫 → Redis + MySQL 通过合理选择增量爬取策略,可以显著提升爬虫效率,减少资源浪费。
