从「能跑就行」到「敢改就敢改」:采集脚本重构实录
接手过一个采集脚本:单文件 700 多行,一个函数从头写到尾,请求逻辑复制了四遍(对应四个端点),Key 硬编码在第 12 行,改一个字段要通读全文。每次需求变更我都手心冒汗。后来用一个周末重构,现在改起来轻松多了。这篇记录重构的顺序和手法。
先认「坏味道」
对照自查,中了三条以上就该排重构了:
- 一个函数干完所有事:请求 + 清洗 + 入库 + 报表
- 硬编码:Key、关键词列表、市场参数、文件路径
- 复制粘贴:几个端点的请求逻辑几乎一样,改一处漏一处
- 错误吞掉(
except: pass)或一处报错全批中断 - 没有测试,改完靠「跑一遍看看」
重构的第一原则
先保行为不变,再改善结构。 重构和加功能分开做——混在一起,出 bug 就分不清是谁引入的。顺序固定:先重构(行为不变)→ 验证一致 → 再改功能。
第一步:写「保命测试」
重构最大的风险是改坏了不知道。先给现有代码写特征测试(characterization test):拿真实响应当 fixture,把当前输出固化为期望值:
def test_normalize_matches_fixture():
raw = json.load(open("fixtures/search_python.json"))
expected = json.load(open("fixtures/normalized_python.json"))
assert normalize(raw) == expected
跑通「现状」,就有了安全网:重构后测试还绿,说明行为没变。
第二步:参数外提
# before
# api_key = "sk_xxx" # 写死
# keywords = ["a", "b"] # 写死
# after
API_KEY = os.environ["SERPBASE_API_KEY"]
cfg = yaml.safe_load(open("config.yaml"))
Key 外提到环境变量不只是整洁,还是必须的安全修复——硬编码的 Key 随时可能跟着 git 提交泄露。
第三步:消灭复制粘贴(表驱动)
四个端点的请求逻辑几乎一样,抽成一张表:
ENDPOINTS = {
"search": "https://api.serpbase.dev/google/search",
"images": "https://api.serpbase.dev/google/images",
"news": "https://api.serpbase.dev/google/news",
"videos": "https://api.serpbase.dev/google/videos",
}
def fetch(endpoint: str, params: dict) -> dict:
resp = session.post(ENDPOINTS[endpoint], json=params, timeout=30)
resp.raise_for_status()
return resp.json()
端点增加一个,改一行;超时策略调整,改一处,所有端点受益。
第四步:按步骤拆函数
巨型函数按职责拆开:fetch → normalize → validate → store。每个函数只做一件事,能单独测试,也能单独替换。
第五步:错误处理分级
把「全批中断」改成「单条隔离」:
for kw in keywords:
try:
process(kw)
except Exception as e:
log.error("failed kw=%s: %s", kw, e)
failed.append(kw) # 记录失败项,继续跑
一条失败不再拖垮整批,失败的收尾统一处理。
第六步:新旧影子比对
重构完成后,用同一批输入跑新旧两版,逐条 diff:
assert old_normalize(raw) == new_normalize(raw)
不一致的地方逐个确认,是重构引入的差异就修,是原有 bug 就记下来单独处理。
踩坑记录
坑 1:重构和改功能混在一个提交。 上线后出问题,回溯分不清来源。分成两次提交、两个 PR。
坑 2:没写保命测试就动手。 重构完只有「跑一遍好像没问题」的自信,线上炸了才发现。先写特征测试,再动结构。
坑 3:过度抽象。 有人抽了个「配置 DSL」,结果没人看得懂,比原脚本还难维护。抽象到「消除重复」即可,别造框架。
坑 4:憋一次性大重构。 攒了两周的改动一起上线,风险和收益一起失控。小步重构,每一步都可运行、可回滚。
工程清单
- 先写特征测试(真实响应做 fixture)
- 重构与改功能分开提交
- 参数外提,Key 走环境变量
- 复制粘贴抽成表驱动
- 按职责拆函数,错误分级隔离
- 新旧影子比对,不一致逐个确认
重构的价值不是「代码好看」,是「下次改需求不心虚」。能跑就行的代码是欠债,重构是还债——早还早轻松。
接口的六个端点共用一套响应结构(统一信封),SerpBase 官方文档 里每个端点都列了完整字段,重构时把请求层抽成表驱动会特别顺。你们的采集脚本重构过吗?评论区聊聊最痛的坏味道。接手过一个采集脚本:单文件 700 多行,一个函数从头写到尾,请求逻辑复制了四遍(对应四个端点),Key 硬编码在第 12 行,改一个字段要通读全文。每次需求变更我都手心冒汗。后来用一个周末重构,现在改起来轻松多了。这篇记录重构的顺序和手法。
先认「坏味道」
对照自查,中了三条以上就该排重构了:
- 一个函数干完所有事:请求 + 清洗 + 入库 + 报表
- 硬编码:Key、关键词列表、市场参数、文件路径
- 复制粘贴:几个端点的请求逻辑几乎一样,改一处漏一处
- 错误吞掉(
except: pass)或一处报错全批中断 - 没有测试,改完靠「跑一遍看看」
重构的第一原则
先保行为不变,再改善结构。 重构和加功能分开做——混在一起,出 bug 就分不清是谁引入的。顺序固定:先重构(行为不变)→ 验证一致 → 再改功能。
第一步:写「保命测试」
重构最大的风险是改坏了不知道。先给现有代码写特征测试(characterization test):拿真实响应当 fixture,把当前输出固化为期望值:
def test_normalize_matches_fixture():
raw = json.load(open("fixtures/search_python.json"))
expected = json.load(open("fixtures/normalized_python.json"))
assert normalize(raw) == expected
跑通「现状」,就有了安全网:重构后测试还绿,说明行为没变。
第二步:参数外提
# before
# api_key = "sk_xxx" # 写死
# keywords = ["a", "b"] # 写死
# after
API_KEY = os.environ["SERPBASE_API_KEY"]
cfg = yaml.safe_load(open("config.yaml"))
Key 外提到环境变量不只是整洁,还是必须的安全修复——硬编码的 Key 随时可能跟着 git 提交泄露。
第三步:消灭复制粘贴(表驱动)
四个端点的请求逻辑几乎一样,抽成一张表:
ENDPOINTS = {
"search": "https://api.serpbase.dev/google/search",
"images": "https://api.serpbase.dev/google/images",
"news": "https://api.serpbase.dev/google/news",
"videos": "https://api.serpbase.dev/google/videos",
}
def fetch(endpoint: str, params: dict) -> dict:
resp = session.post(ENDPOINTS[endpoint], json=params, timeout=30)
resp.raise_for_status()
return resp.json()
端点增加一个,改一行;超时策略调整,改一处,所有端点受益。
第四步:按步骤拆函数
巨型函数按职责拆开:fetch → normalize → validate → store。每个函数只做一件事,能单独测试,也能单独替换。
第五步:错误处理分级
把「全批中断」改成「单条隔离」:
for kw in keywords:
try:
process(kw)
except Exception as e:
log.error("failed kw=%s: %s", kw, e)
failed.append(kw) # 记录失败项,继续跑
一条失败不再拖垮整批,失败的收尾统一处理。
第六步:新旧影子比对
重构完成后,用同一批输入跑新旧两版,逐条 diff:
assert old_normalize(raw) == new_normalize(raw)
不一致的地方逐个确认,是重构引入的差异就修,是原有 bug 就记下来单独处理。
踩坑记录
坑 1:重构和改功能混在一个提交。 上线后出问题,回溯分不清来源。分成两次提交、两个 PR。
坑 2:没写保命测试就动手。 重构完只有「跑一遍好像没问题」的自信,线上炸了才发现。先写特征测试,再动结构。
坑 3:过度抽象。 有人抽了个「配置 DSL」,结果没人看得懂,比原脚本还难维护。抽象到「消除重复」即可,别造框架。
坑 4:憋一次性大重构。 攒了两周的改动一起上线,风险和收益一起失控。小步重构,每一步都可运行、可回滚。
工程清单
- 先写特征测试(真实响应做 fixture)
- 重构与改功能分开提交
- 参数外提,Key 走环境变量
- 复制粘贴抽成表驱动
- 按职责拆函数,错误分级隔离
- 新旧影子比对,不一致逐个确认
重构的价值不是「代码好看」,是「下次改需求不心虚」。能跑就行的代码是欠债,重构是还债——早还早轻松。
接口的六个端点共用一套响应结构(统一信封),SerpBase 官方文档 里每个端点都列了完整字段,重构时把请求层抽成表驱动会特别顺。你们的采集脚本重构过吗?评论区聊聊最痛的坏味道。

浙公网安备 33010602011771号