☰
AI Agent实战:浏览器自动化+大模型决策,4.3小时自动发稿腾讯云社区
2026/10/8 4:56:23 网站建设 项目流程

1. 项目缘起与整体设计思路

1.1 为什么想让 AI 自己去发一篇稿

这个项目的起点其实特别朴素:我手头有一堆需要分发到不同技术社区的内容,每次手动复制粘贴、调整格式、上传封面、选分类标签,一套流程走下来少说二十分钟,多的时候一个小时都打不住。重复劳动做多了人就容易烦躁,烦躁就会出错,出错就得返工,返工又浪费时间——这个循环我实在受够了。

于是我就想,能不能让 AI 自己把这件事干了?不是那种"帮我生成一段文字我自己去贴"的半自动,而是从打开浏览器、登录账号、进入编辑器、粘贴正文、设置封面、选择分类、点击发布,整条链路全部交给 AI 去跑。我只需要在最后确认一下结果就行。

这个想法听起来有点疯狂,但拆开来看其实并不复杂。核心就是三件事:浏览器自动化操作、AI 对页面状态的判断与决策、异常情况的处理与重试。把这三件事串起来,就是一个能自己发稿的 AI Agent。

我给它起了个名字叫 WorkBuddy,定位就是一个"能帮你干脏活累活的 AI 工作搭子"。这次实战的目标很明确:让 WorkBuddy 自己去腾讯云社区发一篇技术稿,全程记录操作次数、耗时、踩到的坑,做一次完整的复盘。

1.2 整体架构是怎么搭的

整个方案的架构我反复调整过好几版,最终定下来的结构是这样的:

  • 控制层:一个 Python 脚本作为主控,负责调度整个流程,维护状态机,记录日志。
  • 浏览器层:用 Playwright 驱动 Chromium,负责所有页面操作。选 Playwright 而不是 Selenium,主要是因为它对现代前端框架的兼容性更好,自动等待机制更省心,而且支持拦截网络请求,方便我做调试。
  • 决策层:接入大模型 API,把当前页面的 DOM 摘要和截图传给模型,让模型判断"现在处于哪个阶段""下一步该点哪里""有没有异常弹窗"。
  • 状态层:用一个 JSON 文件记录当前进度,比如"已登录""已进入编辑器""正文已粘贴""封面已上传",这样即使中途崩了也能从断点恢复,不用从头再来。

为什么要把决策层单独拆出来?因为纯靠选择器硬编码的自动化脚本太脆了。腾讯云社区的页面结构时不时会微调,今天能用的 CSS 选择器明天可能就失效了。让 AI 来看页面、做判断,虽然慢一点,但鲁棒性高很多。实测下来,同样的流程,纯选择器方案在页面改版后基本全废,而 AI 决策方案只需要调整提示词就能继续跑。

1.3 预期目标与验收标准

我给这次实战定了几个硬指标:

指标项目标值实际值
总操作次数控制在 300 次以内227 次
总耗时6 小时以内4.3 小时
人工干预次数不超过 3 次2 次
最终发布成功是是
踩坑数量记录完整9 个

227 次操作是什么概念?平均下来每分钟大概 0.88 次操作。这个节奏其实不算快,因为每次操作之后都要等页面响应、等 AI 判断、等网络请求完成。但胜在稳定,没有出现那种"疯狂点击导致页面卡死"的情况。

4.3 小时的耗时里,真正花在"操作"上的时间大概只占三分之一,剩下三分之二都花在等待和重试上。这个比例后面我会详细拆解,因为搞清楚时间花在哪里,才能知道优化空间在哪里。

2. 核心细节解析与实操要点

2.1 浏览器自动化的选型与配置

浏览器自动化这块,我前后试过三种方案:

第一种是直接用 requests 库发 HTTP 请求模拟表单提交。这个方案最快,但问题是腾讯云社区的发布接口有签名校验和 CSRF token,逆向成本太高,而且一旦接口变了就得重新逆向,维护成本爆炸。我试了两个小时就放弃了。

第二种是 Selenium。经典方案,资料多,但坑也多。最大的问题是它的等待机制不够智能,经常出现"元素还没渲染出来就去点"的情况,导致大量 NoSuchElementException。而且 Selenium 对 Shadow DOM 的支持一直不太好,腾讯云社区的部分组件用了 Shadow DOM,操作起来很别扭。

第三种就是最终采用的 Playwright。选它的理由很实在:

  • 自动等待:Playwright 的click()方法自带等待机制,会等元素可交互了再点,省去了大量sleep()。
  • 网络拦截:可以监听所有 XHR 请求,方便判断"发布请求有没有发出去""返回了什么"。
  • 多浏览器支持:虽然这次只用 Chromium,但以后要扩展到 Firefox 或 WebKit 也不用改代码。
  • 截图和录屏:内置的截图功能对调试太重要了,每次操作前后各截一张,出问题了一看便知。

安装配置很简单:

pip install playwright playwright install chromium

然后在代码里这样初始化:

from playwright.sync_api import sync_playwright with sync_playwright() as p: browser = p.chromium.launch( headless=False, # 调试阶段开有头模式,方便观察 args=['--disable-blink-features=AutomationControlled'] ) context = browser.new_context( viewport={'width': 1920, 'height': 1080}, user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' ) page = context.new_page()

注意:--disable-blink-features=AutomationControlled这个参数很重要,不加的话很多网站会检测到你是自动化工具,直接给你弹验证码或者限制操作。

2.2 AI 决策层的提示词设计

AI 决策层是整个方案里最"玄学"的部分,提示词写得好不好,直接决定了 AI 能不能准确判断页面状态。我前后改了十几版提示词,最终稳定下来的版本大概是这样的结构:

你是一个浏览器操作助手。当前页面的 DOM 摘要如下: {dom_summary} 当前页面的截图已附上。 请判断: 1. 当前处于哪个阶段?(登录页/首页/编辑器/发布确认页/其他) 2. 下一步应该执行什么操作?(点击某个元素/输入文字/滚动页面/等待/报告异常) 3. 如果点击,目标元素的描述是什么? 请以 JSON 格式返回: { "stage": "阶段名称", "action": "操作类型", "target": "目标元素描述", "reason": "判断理由" }

这里有几个关键设计:

DOM 摘要而不是完整 DOM。完整 DOM 动辄几万行,传给模型既慢又贵,而且模型容易被无关信息干扰。我的做法是只提取可见元素、按钮、输入框、链接,并且只保留文本内容和关键属性(id、class、role),把 DOM 压缩到 2000 字以内。

截图辅助判断。有些状态光看 DOM 看不出来,比如"页面是不是在加载中""有没有弹窗遮挡",这时候截图就派上用场了。我用的是多模态模型,能同时理解文本和图像。

强制 JSON 输出。这样解析起来方便,不会出现模型返回一段自然语言然后我还要用正则去提取的情况。

reason 字段。这个字段看起来多余,但实际调试时特别有用。AI 判断错了,我看一眼 reason 就知道它为什么错,是 DOM 摘要没提取好,还是提示词有歧义。

2.3 状态机与断点恢复机制

整个发布流程我拆成了 12 个状态:

  1. INIT:初始化,打开浏览器
  2. LOGIN_CHECK:检查是否已登录
  3. LOGIN:执行登录操作
  4. NAV_TO_EDITOR:导航到编辑器页面
  5. TITLE_INPUT:输入标题
  6. CONTENT_INPUT:输入正文
  7. COVER_UPLOAD:上传封面
  8. TAG_SELECT:选择分类标签
  9. PREVIEW_CHECK:预览检查
  10. PUBLISH_CLICK:点击发布
  11. PUBLISH_CONFIRM:确认发布
  12. DONE:完成

每个状态执行完就写一次 JSON 文件:

import json def save_state(state, extra=None): data = { 'current_state': state, 'timestamp': time.time(), 'extra': extra or {} } with open('workbuddy_state.json', 'w') as f: json.dump(data, f, ensure_ascii=False, indent=2)

这样如果脚本在第 7 步崩了,重启之后直接从第 7 步继续,不用重新登录、重新输入标题正文。实测下来,这个机制至少帮我省了 1.5 小时。

实操心得:状态文件一定要加时间戳,并且设置过期时间。我有一次隔了一天再跑,状态文件还停留在"已登录",但实际上 cookie 早就失效了,结果脚本一直在那儿点发布按钮,点了二十多次都没反应。后来加了判断:如果状态文件超过 2 小时,强制从LOGIN_CHECK重新开始。

3. 实操过程与核心环节实现

3.1 登录环节:最耗时但也最关键的 40 分钟

登录是整个流程里最麻烦的一步,没有之一。腾讯云社区的登录支持多种方式:账号密码、手机验证码、扫码登录。我一开始想用账号密码,但发现它有滑块验证,Playwright 模拟的鼠标轨迹太"完美"了,一眼就被识别出来。

后来改用扫码登录。思路是这样的:脚本打开登录页,切换到扫码模式,然后把二维码截图保存到本地,我用手机扫一下,脚本检测到登录成功后继续。

# 切换到扫码登录 page.click('text=扫码登录') page.wait_for_selector('.qrcode-img', timeout=10000) # 截图二维码 qrcode_element = page.query_selector('.qrcode-img') qrcode_element.screenshot(path='qrcode.png') print('请扫描 qrcode.png 完成登录') # 轮询检测登录状态 for i in range(60): # 最多等 2 分钟 if page.query_selector('.user-avatar'): print('登录成功') break time.sleep(2) else: raise Exception('登录超时')

这个环节花了 40 分钟,主要时间都耗在调试上。踩的坑包括:二维码元素被遮挡截不到、登录成功后页面跳转导致选择器失效、轮询间隔太短导致请求过多被限流。

注意:轮询检测登录状态时,间隔不要低于 2 秒。我一开始设的 0.5 秒,结果触发了风控,账号被临时限制登录 10 分钟。这个坑后面还会提到。

3.2 正文输入:从"逐字打字"到"一次性粘贴"

正文输入这块我走了弯路。最开始用的是 Playwright 的type()方法,模拟真人逐字输入:

page.type('.editor-content', article_content, delay=50)

delay=50表示每个字符间隔 50 毫秒。一篇 3000 字的文章,光输入就要 150 秒,而且中间只要有一个字符输入失败,整段就乱了。更麻烦的是,腾讯云的编辑器是富文本编辑器,type()方法对它的兼容性很差,经常出现文字跑到错误的位置、格式丢失的情况。

后来改成用剪贴板方案:

# 把内容写入剪贴板 import pyperclip pyperclip.copy(article_content) # 聚焦编辑器,然后粘贴 page.click('.editor-content') page.keyboard.press('Control+V')

这个方案快得多,3000 字瞬间粘贴完成。但新的问题来了:富文本编辑器对粘贴的内容会做格式清洗,我原本的 Markdown 格式全丢了,变成了一坨纯文本。

最终的解决方案是:先用剪贴板粘贴纯文本,然后通过编辑器的 API 或者模拟快捷键来重新应用格式。腾讯云的编辑器支持 Markdown 快捷输入,比如输入##然后空格就会变成二级标题。我写了一个小函数,把 Markdown 内容拆成块,逐块处理:

def paste_markdown(page, markdown_text): blocks = parse_markdown_blocks(markdown_text) for block in blocks: if block['type'] == 'heading': page.keyboard.type('#' * block['level'] + ' ') page.keyboard.type(block['content']) page.keyboard.press('Enter') elif block['type'] == 'paragraph': page.keyboard.type(block['content']) page.keyboard.press('Enter') elif block['type'] == 'code': page.keyboard.type('```') page.keyboard.press('Enter') page.keyboard.type(block['content']) page.keyboard.press('Enter') page.keyboard.type('```') page.keyboard.press('Enter')

这个环节花了大概 50 分钟,其中 30 分钟都在调试格式问题。

3.3 封面与标签:看似简单实则暗坑无数

封面和标签这两个环节,我原本以为是最简单的,结果加起来花了将近 1 小时。

封面问题:腾讯云社区的封面上传支持拖拽和点击两种方式。我一开始用拖拽,Playwright 的拖拽 API 对文件拖拽支持不太好,试了好几次都失败。后来改用点击上传:

# 点击上传按钮 with page.expect_file_chooser() as fc_info: page.click('.cover-upload-btn') file_chooser = fc_info.value file_chooser.set_files('cover.png')

这里有个坑:expect_file_chooser必须和click配合使用,而且 click 必须触发文件选择框。如果点击之后弹的是自定义的上传弹窗而不是系统文件选择框,这个方法就失效了。腾讯云社区恰好就是自定义弹窗,所以我又得换方案:

# 直接设置 input[type=file] 的值 page.set_input_files('input[type=file]', 'cover.png')

这个方案的前提是页面上有input[type=file]元素。腾讯云社区的上传组件底层确实用了这个,只是被隐藏了。用set_input_files可以直接绕过 UI 操作,稳定得多。

标签问题:标签选择看起来就是点几个按钮的事,但腾讯云的标签是动态加载的,而且有数量限制(最多选 5 个)。我一开始没注意限制,脚本疯狂点击,结果第 6 个标签点下去之后,前面选的标签全被清空了。后来加了计数逻辑:

selected_tags = [] for tag in target_tags: if len(selected_tags) >= 5: break page.click(f'text={tag}') selected_tags.append(tag) time.sleep(0.5) # 等标签选中状态更新

实操心得:标签选择一定要加延时。腾讯云的标签选中状态是异步更新的,点太快会出现"点了但没选上"的情况。我实测下来,0.5 秒的间隔比较稳妥,低于 0.3 秒就容易出问题。

3.4 发布确认:最后一步的惊魂 20 分钟

点击发布按钮之后,腾讯云社区会弹一个确认框,让你确认标题、分类、原创声明等信息。这个确认框的"确认发布"按钮位置是固定的,但问题是——它有时候会加载很慢。

我第一次跑的时候,脚本点击发布按钮之后立刻去找确认按钮,结果没找到,报错退出。第二次加了 3 秒等待,还是偶尔失败。第三次改成轮询等待:

page.click('.publish-btn') # 轮询等待确认框出现 for i in range(30): confirm_btn = page.query_selector('.confirm-publish-btn') if confirm_btn and confirm_btn.is_visible(): confirm_btn.click() break time.sleep(1) else: raise Exception('确认框未出现')

这个环节花了 20 分钟,主要是在处理各种边界情况:确认框没出现、确认框出现了但按钮不可点、点击之后页面没反应、点击之后报错说"内容不能为空"(实际上是正文加载延迟导致的)。

最终发布成功的那一刻,我看了一下计时器:4 小时 18 分钟。加上前面准备和调试的时间,总共 4.3 小时。

4. 九个坑的完整记录与排查技巧

4.1 坑一:登录轮询触发风控

现象:轮询检测登录状态时,间隔设得太短(0.5 秒),跑了大概 2 分钟之后,页面突然跳转到"操作过于频繁,请稍后再试"。

排查:一开始以为是 IP 被 ban 了,换了网络环境还是不行。后来看请求日志,发现 2 分钟内发了 240 次检测请求,平均每秒 2 次。这个频率对任何网站来说都算异常。

解决:轮询间隔改成 2 秒,并且加了随机抖动(1.8 到 2.2 秒之间随机)。另外,检测逻辑从"每次都查 DOM"改成"先查 cookie 再查 DOM",减少不必要的页面查询。

避坑技巧:任何轮询操作,间隔不要低于 1 秒。如果必须高频检测,用 WebSocket 或者长轮询,不要用短轮询硬怼。

4.2 坑二:富文本编辑器粘贴格式丢失

现象:用剪贴板粘贴 Markdown 内容后,所有格式(标题、加粗、代码块)全部丢失,变成纯文本。

排查:一开始以为是剪贴板的问题,换了pyperclip和clipboard两个库,结果一样。后来发现是腾讯云编辑器的粘贴处理逻辑:它默认只接受纯文本,富文本格式需要走特定的粘贴事件。

解决:改成逐块输入 + Markdown 快捷语法触发。虽然慢一点,但格式准确率从 0% 提升到了 95% 以上。

4.3 坑三:封面上传的两种失败模式

现象:用拖拽上传失败,用expect_file_chooser也失败。

排查:拖拽失败是因为 Playwright 的拖拽 API 对文件拖拽支持不完善;expect_file_chooser失败是因为腾讯云用的是自定义上传弹窗,不是系统文件选择框。

解决:直接用set_input_files操作隐藏的input[type=file]元素。这个方案最稳定,但前提是你能找到那个 input 元素。找的方法是在页面上搜索input[type=file],或者用开发者工具看上传组件的实现。

4.4 坑四:标签选择数量超限导致清空

现象:选了 6 个标签之后,前面选的 5 个全没了。

排查:看腾讯云的提示文案,发现最多只能选 5 个。脚本没有做数量控制,点第 6 个的时候触发了"重置"逻辑。

解决:加计数逻辑,超过 5 个就停止。另外,每次选择之后加 0.5 秒延时,确保选中状态更新完成。

4.5 坑五:确认框加载延迟导致点击失败

现象:点击发布按钮后,脚本立刻去找确认按钮,找不到就报错。

排查:确认框是异步加载的,有时候快有时候慢,取决于网络状况。

解决:改成轮询等待,最多等 30 秒。同时加了"如果确认框没出现,截图保存现场"的逻辑,方便事后排查。

4.6 坑六:状态文件过期导致无效操作

现象:隔天再跑脚本,状态文件显示"已登录",但实际上 cookie 已失效,脚本一直在点发布按钮,点了二十多次。

排查:状态文件没有过期机制,脚本无条件信任文件里的状态。

解决:状态文件加时间戳,超过 2 小时强制从LOGIN_CHECK重新开始。另外,每次操作前先检查关键元素是否存在,不存在就触发状态回退。

4.7 坑七:DOM 摘要提取不完整导致 AI 误判

现象:AI 把"发布确认页"误判为"编辑器页",导致重复输入内容。

排查:DOM 摘要提取时,只提取了可见元素,但确认页的某些关键元素被判定为"不可见"(因为它们在折叠区域里)。

解决:调整提取逻辑,把display:none但visibility:visible的元素也纳入提取范围。同时,在提示词里加了"如果页面同时存在编辑器和确认框元素,优先判断为确认页"的规则。

4.8 坑八:网络请求超时导致操作中断

现象:上传封面时,请求卡住,脚本等了 60 秒后超时退出。

排查:封面图片有 2MB,上传时网络波动导致超时。

解决:给所有网络操作加了重试机制,最多重试 3 次,每次间隔 5 秒。另外,把封面图片压缩到 500KB 以内,减少上传时间。

4.9 坑九:发布成功但页面没跳转

现象:点击确认发布后,接口返回成功,但页面没有跳转到文章详情页,脚本一直等在那儿。

排查:腾讯云社区发布成功后是局部刷新,不是整页跳转。脚本在等 URL 变化,但 URL 根本没变。

解决:改成检测页面上的"发布成功"提示元素,而不是等 URL 变化。同时,加了"如果 30 秒内没检测到成功提示,但接口返回了成功,也判定为成功"的兜底逻辑。

4.10 常见问题速查表

问题现象可能原因排查方法解决方案
登录轮询被限制轮询频率过高看请求日志频率间隔改 2 秒 + 随机抖动
粘贴格式丢失编辑器只接受纯文本手动粘贴测试逐块输入 + Markdown 语法
封面上传失败拖拽 API 不兼容看上传组件实现用 set_input_files
标签被清空超过数量限制看页面提示文案加计数逻辑
确认框找不到异步加载延迟截图看现场轮询等待 + 超时截图
状态文件过期无过期机制看时间戳加 2 小时过期判断
AI 误判页面DOM 摘要不完整看 AI 返回的 reason调整提取逻辑 + 提示词
网络请求超时图片太大/网络波动看请求耗时重试机制 + 压缩图片
发布成功没跳转局部刷新看接口返回检测提示元素而非 URL

5. 效率分析与优化空间

5.1 227 次操作的时间分布

我把 227 次操作按类型做了统计:

操作类型次数占比总耗时平均耗时
页面导航187.9%12 分钟40 秒
元素点击8939.2%45 分钟30 秒
文本输入3415.0%38 分钟67 秒
等待/轮询5222.9%95 分钟110 秒
截图/日志219.3%8 分钟23 秒
异常重试135.7%20 分钟92 秒

从这张表可以看出来,等待和轮询占了将近一半的时间(95 分钟 / 258 分钟 ≈ 37%),加上异常重试的 20 分钟,超过 44% 的时间花在"非操作"上。这是自动化流程的常态,但也说明优化空间很大。

5.2 三个可落地的优化方向

方向一:减少不必要的等待。目前很多等待是固定延时(比如time.sleep(2)),改成条件等待(等某个元素出现就继续)可以省不少时间。我粗略估算,如果全部改成条件等待,等待时间能从 95 分钟压缩到 60 分钟左右。

方向二:并行化。封面压缩、标签准备、正文格式化这些操作可以在浏览器操作之前就并行完成,不用串行等待。这部分能省大概 10 分钟。

方向三:缓存登录态。每次跑都重新登录太浪费时间。可以把 cookie 持久化到本地,下次直接加载,跳过登录环节。这个能省 40 分钟。

三个方向加起来,理论上能把总耗时从 4.3 小时压缩到 2.5 小时左右。当然,实际优化效果还得看具体场景。

5.3 成本核算

这次实战的成本主要包括三块:

  • API 调用费用:AI 决策层调用了大概 180 次模型,每次平均消耗 1500 token(输入+输出),按当前价格算下来不到 5 块钱。
  • 时间成本:4.3 小时,如果按人工时薪折算,这是最大的一块。
  • 调试成本:前期调试花了大概 6 小时,这部分是一次性投入,后续复用不用再花。

所以如果只跑一次,这个方案其实不划算。但如果要发 10 篇、20 篇,边际成本就降下来了。我的判断是:发布量超过 5 篇,这个方案就开始有优势了。

6. 后续扩展与个人体会

这套方案跑通之后,我陆续把它扩展到了其他场景:自动回复社区评论、自动同步文章到多个平台、自动生成周报并提交。核心逻辑都是一样的:浏览器自动化 + AI 决策 + 状态机 + 异常重试。

有几个个人体会比较深:

第一,不要追求一步到位。我一开始想做一个"全自动、零干预"的方案,结果处处碰壁。后来改成"半自动、关键节点人工确认",反而跑得更顺。比如登录环节,扫码登录就是人工介入,但只花 10 秒,比硬怼滑块验证划算得多。

第二,日志和截图比什么都重要。自动化脚本出问题是常态,关键是出问题之后能不能快速定位。我现在的习惯是:每个关键操作前后各截一张图,日志里记录操作类型、目标元素、耗时、结果。出问题了一看日志和截图,基本能定位到具体哪一步。

第三,AI 决策层不是万能的。AI 擅长处理"模糊判断",比如"这个页面是什么状态""这个按钮能不能点",但不擅长处理"精确操作",比如"点击坐标 (523, 287) 的位置"。所以我的方案是:AI 负责判断,Playwright 负责执行,各司其职。

第四,状态机是稳定性的基石。没有状态机的自动化脚本,就像没有存档的游戏,一崩就得从头来。有了状态机,崩了也能从断点恢复,心理压力小很多,调试效率也高很多。

最后分享一个小技巧:如果你也在做类似的浏览器自动化项目,建议先用有头模式(headless=False)跑通全流程,确认没问题了再切无头模式。有头模式下你能看到页面在干什么,调试效率比看日志高十倍。我这次全程用的有头模式,虽然占屏幕,但省下来的调试时间远远超过那点不便。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询