🔥本期内容已收录至专栏《Python爬虫实战》,持续完善知识体系与项目实战,建议先订阅收藏,后续查阅更方便~持续更新中!!

全文目录:
-
-
- [🌟 开篇语](#🌟 开篇语)
- [📚 上期回顾](#📚 上期回顾)
- [🎯 本篇目标](#🎯 本篇目标)
- [💡 增量采集是什么?](#💡 增量采集是什么?)
- [🔑 两种常见增量策略](#🔑 两种常见增量策略)
-
- [策略 1:基于时间戳(last_time)](#策略 1:基于时间戳(last_time))
- [策略 2:基于 ID(last_id)](#策略 2:基于 ID(last_id))
- [🛠️ 代码实战:基于时间戳的增量采集](#🛠️ 代码实战:基于时间戳的增量采集)
- [⚠️ 新手常见坑](#⚠️ 新手常见坑)
-
- [坑 1:时区问题](#坑 1:时区问题)
- [坑 2:边界数据重复](#坑 2:边界数据重复)
- [坑 3:时间格式不一致](#坑 3:时间格式不一致)
- [📊 增量效果验收](#📊 增量效果验收)
- [🚀 进阶优化](#🚀 进阶优化)
-
- [优化 1:增量 + 去重双保险](#优化 1:增量 + 去重双保险)
- [优化 2:增量失败回退](#优化 2:增量失败回退)
- [📝 小结](#📝 小结)
- [🎯 下期预告](#🎯 下期预告)
- [🌟 文末](#🌟 文末)
-
- [📌 专栏持续更新中|建议收藏 + 订阅](#📌 专栏持续更新中|建议收藏 + 订阅)
- [✅ 互动征集](#✅ 互动征集)
-
🌟 开篇语
哈喽,各位小伙伴们你们好呀~我是【喵手】。
运营社区: C站 / 掘金 / 腾讯云 / 阿里云 / 华为云 / 51CTO
欢迎大家常来逛逛,一起学习,一起进步~🌟
我长期专注 Python 爬虫工程化实战 ,主理专栏 👉 《Python爬虫实战》:从采集策略 到反爬对抗 ,从数据清洗 到分布式调度 ,持续输出可复用的方法论与可落地案例。内容主打一个"能跑、能用、能扩展 ",让数据价值真正做到------抓得到、洗得净、用得上。
📌 专栏食用指南(建议收藏)
- ✅ 入门基础:环境搭建 / 请求与解析 / 数据落库
- ✅ 进阶提升:登录鉴权 / 动态渲染 / 反爬对抗
- ✅ 工程实战:异步并发 / 分布式调度 / 监控与容错
- ✅ 项目落地:数据治理 / 可视化分析 / 场景化应用
📣 专栏推广时间 :如果你想系统学爬虫,而不是碎片化东拼西凑,欢迎订阅/关注专栏《Python爬虫实战》
订阅后更新会优先推送,按目录学习更高效~
📚 上期回顾
上一期《Python爬虫零基础入门【第五章:数据保存与入库·第4节】原始数据留存:HTML/截图/源文件如何管理(可追溯)!》内容中,我们搞定了原始数据留存,学会了用规范的目录结构管理 HTML、截图和源文件。现在你的爬虫不仅能采集数据,还能把"犯罪现场"完整保存下来------这在排查问题时简直就是救命稻草。
但新问题来了:每次运行都把整站从头爬一遍,太浪费了吧?网站可能一天只更新十几条内容,你却要重复抓取几千条旧数据。这不仅耗时耗流量,还容易被服务器盯上。
今天,我们就来解决这个痛点------只抓新的,跳过旧的。😎
🎯 本篇目标
看完这篇,你能做到:
- 理解增量采集的核心思想(用时间戳或 ID 做边界)
- 设计一个实用的增量策略(last_time 或 last_id)
- 处理边界问题(漏采、重复、时区)
- 写出可验收的增量代码(跑两次,第二次只抓新增)
验收标准很简单:第一次跑全量,第二次只抓新增的 3 条数据。
💡 增量采集是什么?
举个生活例子:
你每天早上刷新闻 App,它不会把昨天、前天的新闻再推一遍,而是只显示今天的新内容。这就是增量思维。
爬虫也一样:
- 全量采集:从第 1 页爬到第 100 页,不管是不是已经抓过
- 增量采集:记住上次爬到哪里,下次从那个位置开始,遇到旧数据就停
核心问题是:怎么知道哪些是"旧数据"?
🔑 两种常见增量策略
策略 1:基于时间戳(last_time)
适用场景:数据有明确的发布时间或更新时间。
原理:
json
如果 文章发布时间 <= 上次采集的最大时间:
说明这条数据已经采集过,跳过
优点:
- 逻辑直观,好理解
- 不依赖数据库 ID
缺点:
- 依赖数据源的时间字段(有些网站不给时间)
- 时区问题要小心处理
策略 2:基于 ID(last_id)
适用场景:数据有递增的唯一 ID(如文章 ID、订单号)。
原理:
json
如果 当前ID <= 上次采集的最大ID:
说明这条数据已经采集过,跳过
优点:
- 稳定可靠,不受时间影响
- 适合大部分数据库场景
缺点:
- ID 必须严格递增(有些系统 ID 是随机的)
- 需要数据源提供 ID 字段
🛠️ 代码实战:基于时间戳的增量采集
第一步:记录上次采集的时间
我们用一个简单的文本文件存储上次的最大时间:
python
import json
from datetime import datetime
from pathlib import Path
class IncrementalState:
"""增量状态管理器"""
def __init__(self, state_file="state.json"):
self.state_file = Path(state_file)
self.state = self._load_state()
def _load_state(self):
"""加载上次的状态"""
if self.state_file.exists():
with open(self.state_file, 'r', encoding='utf-8') as f:
return json.load(f)
return {"last_time": None, "last_id": None}
def get_last_time(self):
"""获取上次采集的最大时间"""
return self.state.get("last_time")
def update_last_time(self, new_time):
"""更新最大时间"""
# 确保是 ISO 格式字符串
if isinstance(new_time, datetime):
new_time = new_time.isoformat()
self.state["last_time"] = new_time
self._save_state()
def _save_state(self):
"""保存状态到文件"""
with open(self.state_file, 'w', encoding='utf-8') as f:
json.dump(self.state, f, ensure_ascii=False, indent=2)
第二步:判断是否需要跳过
python
from datetime import datetime
def should_skip(item_time, last_time):
"""
判断这条数据是否应该跳过
Args:
item_time: 当前数据的时间(字符串或 datetime)
last_time: 上次采集的最大时间(字符串)
Returns:
True 表示跳过,False 表示继续采集
"""
if last_time is None:
# 第一次运行,全量采集
return False
# 统一转成 datetime 对象比较
if isinstance(item_time, str):
item_time = datetime.fromisoformat(item_time)
if isinstance(last_time, str):
last_time = datetime.fromisoformat(last_time)
# 如果当前数据的时间 <= 上次最大时间,说明已采集过
return item_time <= last_time
第三步:完整的增量爬虫示例
python
import requests
from bs4 import BeautifulSoup
from datetime import datetime
import time
class IncrementalSpider:
"""支持增量采集的爬虫"""
def __init__(self, base_url):
self.base_url = base_url
self.state = IncrementalState()
self.new_items = [] # 本次新采集的数据
def parse_item_time(self, time_str):
"""解析时间字符串(根据实际格式调整)"""
# 示例:'2026-01-23 10:30:00'
return datetime.strptime(time_str.strip(), '%Y-%m-%d %H:%M:%S')
def crawl_list_page(self, page=1):
"""采集列表页"""
url = f"{self.base_url}/list?page={page}"
resp = requests.get(url, timeout=10)
resp.raise_for_status()
soup = BeautifulSoup(resp.text, 'html.parser')
items_time = self.state.get_last_time()
max_time = None # 记录本次采集的最大时间
for item in items:
title = item.select_one('.title').text.strip()
time_str = item.select_one('.publish-time').text
item_time = self.parse_item_time(time_str)
# 核心判断:是否跳过
if should_skip(item_time, last_time):
print(f"⏭️ 跳过旧数据:{title} ({item_time})")
return False # 遇到旧数据,停止翻页
# 新数据,采集并记录
print(f"✅ 采集新数据:{title} ({item_time})")
self.new_items.append({
'title': title,
'time': item_time.isoformat()
})
# 更新最大时间
if max_time is None or item_time > max_time:
max_time = item_time
# 更新状态
if max_time:
self.state.update_last_time(max_time)
return True # 继续翻页
def run(self):
"""运行增量采集"""
print(f"📌 上次采集时间:{self.state.get_last_time()}")
page = 1
while True:
print(f"\n🔍 采集第 {page} 页...")
should_continue = self.crawl_list_page(page)
if not should_continue:
print("🛑 遇到旧数据,停止采集")
break
page += 1
time.sleep(1) # 礼貌延迟
if page > 10: # 安全边界
print("⚠️ 达到最大页数限制")
break
print(f"\n📊 本次新增数据:{len(self.new_items)} 条")
return self.new_items
# 使用示例
if __name__ == '__main__':
spider = IncrementalSpider('https://example.com')
items = spider.run()
⚠️ 新手常见坑
坑 1:时区问题
现象:明明是新数据,却被误判为旧数据跳过。
原因:服务器时间是 UTC,你本地是北京时间(+8),直接比较会出错。
解决:
python
from datetime import timezone, timedelta
# 统一转成 UTC
def to_utc(dt):
if dt.tzinfo is None:
# 假设无时区信息的是本地时间
dt = dt.replace(tzinfo=timezone(timedelta(hours=8)))
return dt.astimezone(timezone.utc)
# 比较时先转换
item_time_utc = to_utc(item_time)
last_time_utc = to_utc(last_time)
return item_time_utc <= last_time_utc
坑 2:边界数据重复
场景:上次采集到 2026-01-23 10:00:00,这次有新数据也是这个时间。
问题:时间相同,会被跳过(漏采)。
解决方案 A(推荐):
python
# 改成 < 而不是 <=
return item_time < last_time
解决方案 B(更严谨):
python
# 记录 last_time 和 last_id 双重边界
if item_time == last_time and item_id <= last_id:
return True # 跳过
坑 3:时间格式不一致
现象:有的页面是 2026-01-23,有的是 Jan 23, 2026。
解决:
python
from dateutil import parser
def safe_parse_time(time_str):
"""容错的时间解析"""
try:
return parser.parse(time_str)
except Exception as e:
print(f"⚠️ 时间解析失败:{time_str},错误:{e}")
return None
📊 增量效果验收
第一次运行(全量):
json
📌 上次采集时间:None
🔍 采集第 1 页...
✅ 采集新数据:文章A (2026-01-23 10:00:00)
✅ 采集新数据:文章B (2026-01-23 09:30:00)
✅ 采集新数据:文章C (2026-01-23 09:00:00)
📊 本次新增数据:3 条
state.json 内容:
json
{
"last_time": "2026-01-23T10:00:00"
}
第二次运行(增量):
假设网站新增了 2 条数据。
json
📌 上次采集时间:2026-01-23T10:00:00
🔍 采集第 1 页...
✅ 采集新数据:文章D (2026-01-23 11:00:00)
✅ 采集新数据:文章E (2026-01-23 10:30:00)
⏭️ 跳过旧数据:文章A (2026-01-23 10:00:00)
🛑 遇到旧数据,停止采集
📊 本次新增数据:2 条
state.json 更新:
json
{
"last_time": "2026-01-23T11:00:00"
}
🚀 进阶优化
优化 1:增量 + 去重双保险
虽然有增量判断,但某些场景下(如数据更新)可能需要双重校验:
python
from hashlib import md5
def get_dedup_key(item):
"""生成去重键"""
raw = f"{item['title']}_{item['time']}"
return md5(raw.encode()).hexdigest()
# 配合集合去重
seen_keys = set()
if get_dedup_key(item) in seen_keys:
print("⚠️ 重复数据,跳过")
continue
seen_keys.add(get_dedup_key(item))
优化 2:增量失败回退
如果本次采集中途失败,不应该更新 last_time(否则会漏数据)。
python
def run(self):
temp_max_time = None
try:
# 采集逻辑...
temp_max_time = max([item['time'] for item in self.new_items])
except Exception as e:
print(f"❌ 采集失败:{e}")
print("🔄 不更新 last_time,下次重新采集")
return
# 只有成功时才更新
if temp_max_time:
self.state.update_last_time(temp_max_time)
📝 小结
今天我们学会了增量采集的核心思想:
- 用时间或 ID 做边界,跳过旧数据
- 状态持久化,记住上次采集到哪里
- 处理边界问题(时区、重复、失败回退)
增量采集不仅能提升效率,还能降低被封的风险。但记住:宁可多抓几条,也不要漏掉新数据 。遇到边界情况时,让 < 替代 <= 就能减少很多麻烦。
🎯 下期预告
增量解决了"只抓新的",但还有个问题没解决:万一中途断电怎么办?
下一篇《断点续爬:失败队列、重放、任务状态》,我们会设计一个任务状态表,让爬虫具备"断点续传"能力------不管在第几页挂掉,重启后都能无缝继续。💪
记得动手验收:让你的爬虫跑两次,第二次只抓 3 条新增数据!加油!
🌟 文末
好啦~以上就是本期 《Python爬虫实战》的全部内容啦!如果你在实践过程中遇到任何疑问,欢迎在评论区留言交流,我看到都会尽量回复~咱们下期见!
小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦~
三连就是对我写作道路上最好的鼓励与支持! ❤️🔥
📌 专栏持续更新中|建议收藏 + 订阅
专栏 👉 《Python爬虫实战》,我会按照"入门 → 进阶 → 工程化 → 项目落地"的路线持续更新,争取让每一篇都做到:
✅ 讲得清楚(原理)|✅ 跑得起来(代码)|✅ 用得上(场景)|✅ 扛得住(工程化)
📣 想系统提升的小伙伴:强烈建议先订阅专栏,再按目录顺序学习,效率会高很多~

✅ 互动征集
想让我把【某站点/某反爬/某验证码/某分布式方案】写成专栏实战?
评论区留言告诉我你的需求,我会优先安排更新 ✅
⭐️ 若喜欢我,就请关注我叭~(更新不迷路)
⭐️ 若对你有用,就请点赞支持一下叭~(给我一点点动力)
⭐️ 若有疑问,就请评论留言告诉我叭~(我会补坑 & 更新迭代)
免责声明:本文仅用于学习与技术研究,请在合法合规、遵守站点规则与 Robots 协议的前提下使用相关技术。严禁将技术用于任何非法用途或侵害他人权益的行为。