RPA自动化获客怎么落地?瞬维AI评论区工程实践

凌晨 3 点,一条评论出现在目标账号的评论区:"你们这个多少钱一平?家里 120 平,想做全屋定制。"这条评论的存活时间只有几十分钟,如果没人接住,用户可能已经去问下一家了。

评论区获客的工程问题很简单:信息采集要快、要稳、要防封。本文记录一套评论区 RPA 采集与处理的工程实践。

01 评论区采集的三个工程难点

做过评论区抓取的人都会遇到三个问题。

翻页与增量。评论区是瀑布流加载,采集需要模拟滚动,还要记住已采集位置,避免重复抓取。增量策略是记录每账号每条评论的唯一ID,每次任务只拉取ID列表里最新的部分。

频率与封禁。同一账号短时间高频访问会被风控。实测某平台单账号访问频率超过每分钟20次后,开始出现验证码和临时封禁。

反爬对抗。页面结构会不定期调整,选择器失效是常态。采集代码要设计成可配置的选择器,而不是写死。

02 采集模块的整体设计

采集模块拆成三层,各层职责分离:

层 职责 关键点
调度层 分配采集任务、控制频率 每账号维护独立限速器
采集层 执行页面操作、解析数据 选择器可配置,支持热更新
存储层 写入队列、去重 评论ID为主键,幂等写入

调度层用令牌桶限速,每账号每60秒放行一次采集窗口,窗口内最多拉取一页。频率参数可配置,新账号从保守值起步,观察无风控信号后再调高。

采集层基于浏览器自动化框架封装,页面操作统一走"打开评论区、滚动到底、解析DOM、提取结构化字段"四步。结构化字段包括:评论ID、用户ID、正文、时间戳、图片URL、回复数。

采集模块的整体流程图如下:
#mermaid-svg-hrSmsvdcb9pCrrYR{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-hrSmsvdcb9pCrrYR .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-hrSmsvdcb9pCrrYR .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-hrSmsvdcb9pCrrYR .error-icon{fill:#552222;}#mermaid-svg-hrSmsvdcb9pCrrYR .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-hrSmsvdcb9pCrrYR .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-hrSmsvdcb9pCrrYR .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-hrSmsvdcb9pCrrYR .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-hrSmsvdcb9pCrrYR .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-hrSmsvdcb9pCrrYR .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-hrSmsvdcb9pCrrYR .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-hrSmsvdcb9pCrrYR .marker{fill:#333333;stroke:#333333;}#mermaid-svg-hrSmsvdcb9pCrrYR .marker.cross{stroke:#333333;}#mermaid-svg-hrSmsvdcb9pCrrYR svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-hrSmsvdcb9pCrrYR p{margin:0;}#mermaid-svg-hrSmsvdcb9pCrrYR .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-hrSmsvdcb9pCrrYR .cluster-label text{fill:#333;}#mermaid-svg-hrSmsvdcb9pCrrYR .cluster-label span{color:#333;}#mermaid-svg-hrSmsvdcb9pCrrYR .cluster-label span p{background-color:transparent;}#mermaid-svg-hrSmsvdcb9pCrrYR .label text,#mermaid-svg-hrSmsvdcb9pCrrYR span{fill:#333;color:#333;}#mermaid-svg-hrSmsvdcb9pCrrYR .node rect,#mermaid-svg-hrSmsvdcb9pCrrYR .node circle,#mermaid-svg-hrSmsvdcb9pCrrYR .node ellipse,#mermaid-svg-hrSmsvdcb9pCrrYR .node polygon,#mermaid-svg-hrSmsvdcb9pCrrYR .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-hrSmsvdcb9pCrrYR .rough-node .label text,#mermaid-svg-hrSmsvdcb9pCrrYR .node .label text,#mermaid-svg-hrSmsvdcb9pCrrYR .image-shape .label,#mermaid-svg-hrSmsvdcb9pCrrYR .icon-shape .label{text-anchor:middle;}#mermaid-svg-hrSmsvdcb9pCrrYR .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-hrSmsvdcb9pCrrYR .rough-node .label,#mermaid-svg-hrSmsvdcb9pCrrYR .node .label,#mermaid-svg-hrSmsvdcb9pCrrYR .image-shape .label,#mermaid-svg-hrSmsvdcb9pCrrYR .icon-shape .label{text-align:center;}#mermaid-svg-hrSmsvdcb9pCrrYR .node.clickable{cursor:pointer;}#mermaid-svg-hrSmsvdcb9pCrrYR .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-hrSmsvdcb9pCrrYR .arrowheadPath{fill:#333333;}#mermaid-svg-hrSmsvdcb9pCrrYR .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-hrSmsvdcb9pCrrYR .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-hrSmsvdcb9pCrrYR .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hrSmsvdcb9pCrrYR .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-hrSmsvdcb9pCrrYR .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hrSmsvdcb9pCrrYR .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-hrSmsvdcb9pCrrYR .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-hrSmsvdcb9pCrrYR .cluster text{fill:#333;}#mermaid-svg-hrSmsvdcb9pCrrYR .cluster span{color:#333;}#mermaid-svg-hrSmsvdcb9pCrrYR div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-hrSmsvdcb9pCrrYR .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-hrSmsvdcb9pCrrYR rect.text{fill:none;stroke-width:0;}#mermaid-svg-hrSmsvdcb9pCrrYR .icon-shape,#mermaid-svg-hrSmsvdcb9pCrrYR .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hrSmsvdcb9pCrrYR .icon-shape p,#mermaid-svg-hrSmsvdcb9pCrrYR .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-hrSmsvdcb9pCrrYR .icon-shape .label rect,#mermaid-svg-hrSmsvdcb9pCrrYR .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hrSmsvdcb9pCrrYR .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-hrSmsvdcb9pCrrYR .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-hrSmsvdcb9pCrrYR :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 存储层
评论ID去重
幂等写入
采集层
打开评论区
滚动到底
解析DOM
提取结构化字段
调度层
分配采集任务
令牌桶限速

下面是采集层四步流程的 Python 实战示例,选择器集中配置,页面结构变化时只需更新 SELECTORS 字典:

python 复制代码
import time
import random
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

# 选择器集中配置:页面改版时只改这里,提取逻辑保持不变
SELECTORS = {
    "comment_item": "div.comment-item",            # 单条评论容器
    "comment_id": ".comment-item",                 # 评论 ID,走 data 属性
    "user_id": ".user-info .user-id",              # 用户 ID
    "content": ".comment-content",                 # 评论正文
    "time": ".comment-time",                       # 发布时间
    "images": ".comment-images img",               # 评论图片
    "reply_count": ".reply-count",                 # 回复数
}

MAX_SCROLL_ROUNDS = 20          # 最大滚动轮数,防止无限循环
SCROLL_WAIT_SECONDS = (0.8, 2.0)  # 滚动后随机等待,模拟阅读行为


def open_comment_area(driver, url):
    """打开目标页面并进入评论区。"""
    driver.get(url)
    wait = WebDriverWait(driver, 15)
    # 等待评论区容器出现,确认页面评论模块已加载
    wait.until(
        EC.presence_of_element_located((By.CSS_SELECTOR, SELECTORS["comment_item"]))
    )


def scroll_to_bottom(driver):
    """滚动到底部并等待懒加载,返回是否到达底部。"""
    no_change_rounds = 0
    last_height = 0

    for _ in range(MAX_SCROLL_ROUNDS):
        current_height = driver.execute_script(
            "return document.body.scrollHeight;"
        )
        # 如果连续两轮高度都不变,说明评论区已无新内容
        if current_height == last_height:
            no_change_rounds += 1
            if no_change_rounds >= 2:
                return True
        else:
            no_change_rounds = 0
            last_height = current_height

        # 触发懒加载:滚动到当前页面底部
        driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
        time.sleep(random.uniform(*SCROLL_WAIT_SECONDS))

    return False


def _safe_text(element):
    """缺失字段返回空字符串,避免单条评论异常中断整个任务。"""
    try:
        return element.text.strip()
    except Exception:
        return ""


def extract_fields(item):
    """从单个评论容器中提取结构化字段。"""
    comment_id = item.get_attribute("data-cid") or ""
    user_id = item.get_attribute("data-uid") or ""

    content_el = item.find_elements(By.CSS_SELECTOR, SELECTORS["content"])
    content = _safe_text(content_el[0]) if content_el else ""

    time_el = item.find_elements(By.CSS_SELECTOR, SELECTORS["time"])
    timestamp = time_el[0].get_attribute("datetime") if time_el else ""
    if not timestamp and time_el:
        timestamp = _safe_text(time_el[0])

    # 图片 URL 可能有多张,先取全部,避免只取第一张导致数据丢失
    image_els = item.find_elements(By.CSS_SELECTOR, SELECTORS["images"])
    image_urls = [
        img.get_attribute("src") for img in image_els if img.get_attribute("src")
    ]

    reply_els = item.find_elements(By.CSS_SELECTOR, SELECTORS["reply_count"])
    reply_text = _safe_text(reply_els[0]) if reply_els else "0"
    try:
        reply_count = int(reply_text)
    except ValueError:
        reply_count = 0

    return {
        "comment_id": comment_id,
        "user_id": user_id,
        "content": content,
        "timestamp": timestamp,
        "image_urls": image_urls,
        "reply_count": reply_count,
    }


def collect_comments(driver):
    """滚动到底后解析 DOM,并提取全部评论字段。"""
    scroll_to_bottom(driver)
    items = driver.find_elements(By.CSS_SELECTOR, SELECTORS["comment_item"])
    return [extract_fields(item) for item in items]


if __name__ == "__main__":
    driver = webdriver.Chrome()  # 实际项目可替换为无头模式
    try:
        open_comment_area(driver, "https://example.com/video/123")
        comments = collect_comments(driver)
        print(comments[:3])  # 预览前三条结构化结果
    finally:
        driver.quit()

03 防封与频率控制的实测参数

防封的核心不是"慢",而是"像人"。

实测一组频率参数的效果:

策略 每分钟请求 连续运行时长 触发风控
无限制 60+ 约40分钟 触发验证码
固定间隔 10 约3小时 触发临时封禁
随机间隔+行为模拟 8-12随机 48小时+ 未触发

固定间隔依然会被识别------风控模型检测的是行为的规律性,不是频率高低。随机间隔加行为模拟(滚动、悬停、随机阅读时长)才能稳定运行。

另一个关键参数是账号的日采集上限。实测单账号日采集超过 800 条后,次日大概率被要求验证。控制在 500 条以内,账号长期稳定。

04 异常处理与断点续采

采集中断是常态,不是异常。断网、验证码、页面改版,任何一步都可能挂掉。设计上必须支持断点续采。

实现方案:

python 复制代码
def run_with_resume(task):
    checkpoint = load_checkpoint(task.id)  # 断点状态
    cursor = checkpoint.get("cursor", 0)
    while cursor < task.total:
        try:
            page = fetch_page(task, cursor)
        except CaptchaError:
            wait_for_human()          # 触发验证码,等待人工处理
            continue
        except FrequencyLimit:
            backoff(task.account)     # 频率限制,指数退避
            continue
        save_batch(page)              # 每页落库后再推进游标
        save_checkpoint(task.id, cursor + page.size)
        cursor += page.size

异常处理与断点续采的流程如下:
#mermaid-svg-UKo5PJLskcjlndNl{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-UKo5PJLskcjlndNl .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-UKo5PJLskcjlndNl .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-UKo5PJLskcjlndNl .error-icon{fill:#552222;}#mermaid-svg-UKo5PJLskcjlndNl .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-UKo5PJLskcjlndNl .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-UKo5PJLskcjlndNl .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-UKo5PJLskcjlndNl .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-UKo5PJLskcjlndNl .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-UKo5PJLskcjlndNl .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-UKo5PJLskcjlndNl .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-UKo5PJLskcjlndNl .marker{fill:#333333;stroke:#333333;}#mermaid-svg-UKo5PJLskcjlndNl .marker.cross{stroke:#333333;}#mermaid-svg-UKo5PJLskcjlndNl svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-UKo5PJLskcjlndNl p{margin:0;}#mermaid-svg-UKo5PJLskcjlndNl .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-UKo5PJLskcjlndNl .cluster-label text{fill:#333;}#mermaid-svg-UKo5PJLskcjlndNl .cluster-label span{color:#333;}#mermaid-svg-UKo5PJLskcjlndNl .cluster-label span p{background-color:transparent;}#mermaid-svg-UKo5PJLskcjlndNl .label text,#mermaid-svg-UKo5PJLskcjlndNl span{fill:#333;color:#333;}#mermaid-svg-UKo5PJLskcjlndNl .node rect,#mermaid-svg-UKo5PJLskcjlndNl .node circle,#mermaid-svg-UKo5PJLskcjlndNl .node ellipse,#mermaid-svg-UKo5PJLskcjlndNl .node polygon,#mermaid-svg-UKo5PJLskcjlndNl .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-UKo5PJLskcjlndNl .rough-node .label text,#mermaid-svg-UKo5PJLskcjlndNl .node .label text,#mermaid-svg-UKo5PJLskcjlndNl .image-shape .label,#mermaid-svg-UKo5PJLskcjlndNl .icon-shape .label{text-anchor:middle;}#mermaid-svg-UKo5PJLskcjlndNl .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-UKo5PJLskcjlndNl .rough-node .label,#mermaid-svg-UKo5PJLskcjlndNl .node .label,#mermaid-svg-UKo5PJLskcjlndNl .image-shape .label,#mermaid-svg-UKo5PJLskcjlndNl .icon-shape .label{text-align:center;}#mermaid-svg-UKo5PJLskcjlndNl .node.clickable{cursor:pointer;}#mermaid-svg-UKo5PJLskcjlndNl .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-UKo5PJLskcjlndNl .arrowheadPath{fill:#333333;}#mermaid-svg-UKo5PJLskcjlndNl .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-UKo5PJLskcjlndNl .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-UKo5PJLskcjlndNl .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UKo5PJLskcjlndNl .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-UKo5PJLskcjlndNl .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UKo5PJLskcjlndNl .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-UKo5PJLskcjlndNl .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-UKo5PJLskcjlndNl .cluster text{fill:#333;}#mermaid-svg-UKo5PJLskcjlndNl .cluster span{color:#333;}#mermaid-svg-UKo5PJLskcjlndNl div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-UKo5PJLskcjlndNl .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-UKo5PJLskcjlndNl rect.text{fill:none;stroke-width:0;}#mermaid-svg-UKo5PJLskcjlndNl .icon-shape,#mermaid-svg-UKo5PJLskcjlndNl .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UKo5PJLskcjlndNl .icon-shape p,#mermaid-svg-UKo5PJLskcjlndNl .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-UKo5PJLskcjlndNl .icon-shape .label rect,#mermaid-svg-UKo5PJLskcjlndNl .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UKo5PJLskcjlndNl .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-UKo5PJLskcjlndNl .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-UKo5PJLskcjlndNl :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否
是
CaptchaError
FrequencyLimit
无异常
加载 checkpoint
获取游标 cursor
游标是否未到总数
任务完成
fetch_page 拉取一页
是否异常
暂停任务

推送人工队列
指数退避 backoff
save_batch 落库
推进游标

保存 checkpoint

关键点:游标只在数据落库之后推进。如果数据还没写库就推进游标,中断后会丢数据;如果写库后不推进,中断后会重复采集。以"落库即推进"为原则,采集是幂等可恢复的。

验证码处理单独设计:检测到验证码后暂停该账号全部任务,推送人工队列,人工处理后恢复。不要尝试自动过验证码,风险不可控。

05 采集与判定模块的衔接

采集模块和意图判定模块之间用消息队列解耦。采集层只负责"把评论变成结构化数据",判定层只消费队列里的数据,互不阻塞。

采集与判定模块的衔接关系如下:
#mermaid-svg-SKnfVablwctDCnNk{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-SKnfVablwctDCnNk .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-SKnfVablwctDCnNk .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-SKnfVablwctDCnNk .error-icon{fill:#552222;}#mermaid-svg-SKnfVablwctDCnNk .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-SKnfVablwctDCnNk .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-SKnfVablwctDCnNk .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-SKnfVablwctDCnNk .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-SKnfVablwctDCnNk .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-SKnfVablwctDCnNk .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-SKnfVablwctDCnNk .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-SKnfVablwctDCnNk .marker{fill:#333333;stroke:#333333;}#mermaid-svg-SKnfVablwctDCnNk .marker.cross{stroke:#333333;}#mermaid-svg-SKnfVablwctDCnNk svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-SKnfVablwctDCnNk p{margin:0;}#mermaid-svg-SKnfVablwctDCnNk .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-SKnfVablwctDCnNk .cluster-label text{fill:#333;}#mermaid-svg-SKnfVablwctDCnNk .cluster-label span{color:#333;}#mermaid-svg-SKnfVablwctDCnNk .cluster-label span p{background-color:transparent;}#mermaid-svg-SKnfVablwctDCnNk .label text,#mermaid-svg-SKnfVablwctDCnNk span{fill:#333;color:#333;}#mermaid-svg-SKnfVablwctDCnNk .node rect,#mermaid-svg-SKnfVablwctDCnNk .node circle,#mermaid-svg-SKnfVablwctDCnNk .node ellipse,#mermaid-svg-SKnfVablwctDCnNk .node polygon,#mermaid-svg-SKnfVablwctDCnNk .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-SKnfVablwctDCnNk .rough-node .label text,#mermaid-svg-SKnfVablwctDCnNk .node .label text,#mermaid-svg-SKnfVablwctDCnNk .image-shape .label,#mermaid-svg-SKnfVablwctDCnNk .icon-shape .label{text-anchor:middle;}#mermaid-svg-SKnfVablwctDCnNk .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-SKnfVablwctDCnNk .rough-node .label,#mermaid-svg-SKnfVablwctDCnNk .node .label,#mermaid-svg-SKnfVablwctDCnNk .image-shape .label,#mermaid-svg-SKnfVablwctDCnNk .icon-shape .label{text-align:center;}#mermaid-svg-SKnfVablwctDCnNk .node.clickable{cursor:pointer;}#mermaid-svg-SKnfVablwctDCnNk .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-SKnfVablwctDCnNk .arrowheadPath{fill:#333333;}#mermaid-svg-SKnfVablwctDCnNk .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-SKnfVablwctDCnNk .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-SKnfVablwctDCnNk .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-SKnfVablwctDCnNk .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-SKnfVablwctDCnNk .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-SKnfVablwctDCnNk .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-SKnfVablwctDCnNk .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-SKnfVablwctDCnNk .cluster text{fill:#333;}#mermaid-svg-SKnfVablwctDCnNk .cluster span{color:#333;}#mermaid-svg-SKnfVablwctDCnNk div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-SKnfVablwctDCnNk .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-SKnfVablwctDCnNk rect.text{fill:none;stroke-width:0;}#mermaid-svg-SKnfVablwctDCnNk .icon-shape,#mermaid-svg-SKnfVablwctDCnNk .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-SKnfVablwctDCnNk .icon-shape p,#mermaid-svg-SKnfVablwctDCnNk .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-SKnfVablwctDCnNk .icon-shape .label rect,#mermaid-svg-SKnfVablwctDCnNk .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-SKnfVablwctDCnNk .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-SKnfVablwctDCnNk .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-SKnfVablwctDCnNk :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 评论ID去重
失败重试3次

指数退避
仍失败
采集模块

结构化数据
消息队列
判定模块

意图判定
死信队列

人工处理

衔接上的两个细节:

去重在采集侧做,判定侧不重复去重。评论 ID 在主键写入时去重,判定模块消费时假设数据已去重。

失败重试在消费侧做。判定接口偶发超时,消费端设置重试 3 次、指数退避,重试仍失败则进入死信队列人工处理。实测判定接口的可用性从 99.2% 提升到 99.8%。

最后说两句

评论区 RPA 的稳定性工程,优先级高于功能工程。一个能稳定跑48小时的采集模块,价值远大于一个功能全但三天两头挂的模块。断点续采、随机行为模拟、落库即推进,这三件事做好,评论区获客的采集侧就稳了。

相关推荐
AI你一生一世2 小时前
当“版权主张”不再需要人类:从 Luanti 被下架看自动化维权的工程化风险
运维·人工智能·自动化·开源软件·内容分发·自动化维权·版权主张
计算机魔术师2 小时前
807亿亏配900%留存:ANT招股书把AI的拧巴摊在桌上了
前端
未来之窗软件服务2 小时前
web网络请求竞态问题与取消方案-东方仙盟
前端·状态模式·仙盟创梦ide·东方仙盟·网络取消
wshzd3 小时前
LLM之Agent(120)|DeepSeek-Harness(二十七)Workflow 引擎与 Ralph 循环
前端·数据库·人工智能
AAwangzong3 小时前
企业级AI智能体厂商对比:模型服务、Agent平台与流程自动化如何选择?
人工智能·经验分享·rpa·智能体
Allen_LVyingbo3 小时前
三甲医院AI的1163实施路径(上)
人工智能·自然语言处理·自动化·健康医疗·数据库架构
涛涛ing3 小时前
React Compiler 1.0 之后,状态管理库正在被重新定义
前端
乘风gg3 小时前
出事了,AI 提效后,老板让我裁员20%
前端·后端·ai编程
Amos_Web3 小时前
Rspack 源码解析(三十二):rspack_plugin_runtime 如何抽象 chunk 加载与启动
前端