采集任务从「一个脚本」到「编排的工作流」

单关键词采集是一个脚本,但真实业务往往是多步骤的:搜索 → 详情 → 清洗 → 落库 → 报表,步骤之间有依赖,中间会失败,还得重试。这种「多步骤 + 依赖 + 失败」的组合,就需要编排(orchestration)。这篇记录采集任务从单脚本走向编排的实践,以及什么时候才需要编排。

什么时候需要编排

不是所有采集都需要编排。判断信号:

  1. 有依赖链:B 步骤依赖 A 步骤的结果(比如地图详情依赖搜索返回的 feature_id)
  2. 步骤会失败:中途失败要重试,且失败要在正确的步骤重试
  3. 多步骤跨时间:部分步骤是异步/定时,要等上游
  4. 要可视化进度:想知道「跑到第几步了、哪步失败」

单脚本顺序跑 + try/except 能撑到某个复杂度,越过就乱。编排的价值是把「步骤、依赖、失败、进度」显式化。

编排的三种形态(从简到繁)

形态一:脚本内步骤函数(起步)

def pipeline(keyword, feature_id=None):
    # 步骤 1:搜索
    search_data = fetch_search(keyword)
    # 步骤 2:详情(依赖搜索)
    if feature_id:
        detail_data = fetch_detail(feature_id)
    # 步骤 3:清洗
    records = clean(search_data, detail_data)
    # 步骤 4:落库
    store(records)
    return len(records)

简单,但失败处理、重试、进度都要自己写。适合步骤少、够用的阶段。

形态二:步骤表 + 状态机(团队/生产)

把「步骤」显式化,每步有状态,失败可定位、可重试:

STEPS = ["search", "detail", "clean", "store"]

class PipelineTask:
    def __init__(self, keyword):
        self.steps = {s: "pending" for s in STEPS}
        self.keyword = keyword

    def run_step(self, step, context):
        if step == "search":
            context["search"] = fetch_search(self.keyword)
        elif step == "detail":
            context["detail"] = fetch_detail(context["search"][0]["feature_id"])
        # ...
        self.steps[step] = "done"

    def run(self):
        context = {}
        for step in STEPS:
            try:
                self.run_step(step, context)
            except Exception as e:
                self.steps[step] = "failed"
                raise RetryFromStep(step, context)   # 从失败步骤重试

优点:失败定位到具体步骤 + 从失败步骤续跑,不用整条重跑(幂等篇的状态机思路,用在步骤级)。

形态三:现成编排工具(DAG)

步骤多、依赖复杂时,用现成工具(Airflow、Prefect、Dagster)或轻量的任务队列。适合「步骤非常多 + 要调度 + 要可视化的团队」。

建议:独立开发/小团队,形态一或二就够;形态三是「步骤真正复杂到脚本撑不住」时才上。

编排要解决的核心问题

1. 失败从正确步骤重试

单脚本失败重跑 = 从头跑(浪费前面步骤)。编排 = 从失败步骤续跑(省时省费)。地图采集尤其明显:搜索已经成功了,详情失败,重跑不该重新搜索。

# 伪代码:续跑
def resume(task):
    for step in task.steps:
        if task.steps[step] != "done":
            task.run_step(step, task.context)

2. 步骤产物持久化

步骤结果存在 context(内存)会丢;跨时间编排要把中间产物存下来(搜索的 feature_id 落库,详情失败时不用重搜)。

3. 依赖与数据流

显式声明「详情依赖搜索的 feature_id」,编排层按依赖顺序跑。别在代码里靠「碰巧的顺序」。

踩坑记录

坑 1:失败重跑整条。 详情失败重跑整条 pipeline,搜索步骤白白重发(浪费 credits)。改成「从失败步骤续跑」。

坑 2:中间产物不持久。 搜索的 feature_id 只在内存,进程重启就丢,得重搜。中间产物落库。

坑 3:步骤顺序靠代码顺序。 加步骤时改错顺序,依赖乱。显式声明依赖。

坑 4:没记录步骤状态。 出问题不知道「跑到哪步挂的」。步骤状态表 + 日志(带 request_id)。

工程清单沉淀

  1. 步骤显式化:每步有状态(pending/done/failed)
  2. 失败从失败步骤续跑,别整条重跑
  3. 中间产物持久化(如 feature_id 落库)
  4. 依赖显式声明
  5. 步骤状态 + 日志可定位进度
  6. 复杂度没到,别上重编排工具

采集编排的本质,是把「多步骤 + 依赖 + 失败」从「纠缠在代码里」变成「显式可管理」。步骤状态化 + 失败续跑 + 产物持久,这三件做好了,多步骤采集就是「可控的工作流」而不是「一个长脚本」。

接口的分步特性(地图搜索→详情的两段式依赖)在 SerpBase 官方文档 里就是典型例子,做编排时对照。你的采集任务用到编排了吗?评论区聊聊从脚本到工作流的经历。

posted @ 2026-09-18 12:21  蜘蛛人  阅读(21)  评论(0)    收藏  举报