最近用 Cursor 配合 Playwright 做了一件挺有意思的事:让 AI 自己操作浏览器,登录头条号后台,填标题、写正文、点发布,一篇头条文章就这么自动发出去了。
这个组合比我预期中要顺——Cursor 负责把自然语言变成可执行的 Playwright 代码,Playwright 负责真正驱动 Chromium 内核去操作真实页面,AI 不再只停留在“生成文本”层面,而是能对 live 网页做出实际动作。这篇文章我会把这个“web 浏览器操作”的完整链路拆开讲清楚:从为什么选 Cursor + Playwright、环境搭建会踩哪些坑,到自动发布文章的完整代码和定位技巧,最后是几个实测遇到的典型问题和我自己的排查经验。如果你也玩 AI Agent、自动化测试,或者想让 AI 替你干点浏览器里的重复活,这篇应该能帮上不少忙。
1. 思路拆解:AI 要操作浏览器,首先得解决三个问题
想清楚“AI 自动发头条文章”到底是怎么跑通的,比直接抄代码更重要。因为核心不是“写一段脚本发文章”,而是“AI 如何理解网页、定位元素、执行点击输入”,这才是可以复制到其他场景的能力。
1.1 为什么是 Cursor 和 Playwright 这对组合
先说结论:Cursor 解决的是“怎么把需求变成代码”,Playwright 解决的是“代码怎么真实控制浏览器”。两者合在一起,才构成一个可落地的 AI Agent 雏形。
我试过纯人工写 Playwright 脚本,最痛苦的不是语法,而是页面选择器、加载时序、iframe 嵌套这些细节,改一版跑一版,非常耗时间。Cursor 的代码生成能力在这里发挥得特别好:我只需要用自然语言描述“打开头条创作平台,点击发布文章,填入标题和正文,最后发布”,它就能生成一段可运行的 Playwright 脚本,我再针对页面实际结构调整选择器就行。
Playwright 这边,它是微软开源的浏览器自动化框架,对标的是 Selenium。我选它而不是 Selenium,主要是几方面考虑:
- 内置自动等待机制。
fill、click这些动作会等待元素可操作,不用自己写一堆sleep和显式等待。 - 支持 Chromium、Firefox、WebKit 三套内核,而且安装很简单,
npx playwright install一条命令搞定。 - 支持
storageState保存登录态,这个对自动发布文章特别关键,后面细说。 - 有 trace viewer 和 codegen 录制工具,调试时能看到每一步到底发生了什么。
1.2 与传统 RPA 脚本的本质差别
以前做网页自动操作,通常叫 RPA(机器人流程自动化),思路是录制鼠标键盘动作,然后在固定页面上回放。但网页一改版、元素一变化,脚本就崩了,维护成本极高。
Cursor + Playwright 这条路不太一样。核心逻辑是“用代码定位元素、驱动浏览器”,而不是“模拟鼠标坐标点击”。定位靠的是选择器、文本、角色、iframe 关系,只要页面结构不是彻底重构,一般调整一下选择器就能继续用。
更关键的是,Cursor 介入后“编写脚本”的成本被大幅拉低了。以前写一个自动化脚本可能要半天,现在一轮对话就能出初稿,我只需要做审查和微调。这也让我意识到,以后的自动化测试和网页操作,核心能力可能不再是“手写 selector”,而是“如何用 Prompt 准确描述任务”和“如何快速判断 AI 生成的代码是否可靠”。
2. 环境准备与踩坑清单:装对工具链,后面才不折腾
这个项目的第一步就把不少人卡住了,尤其是 Playwright 安装和 Cursor 的初始配置。我把自己实测过的安装路径和错误处理记录在这里,照着做基本能避掉大部分坑。
2.1 Node.js 与 Playwright 的安装
我用的技术栈是 Node.js + Playwright,不是 Python。原因很简单:Playwright 在 JavaScript/TypeScript 生态里支持最完整,而且 Cursor 生成 JS 脚本的质量我自己体感更稳。
Node.js 安装不多说了,建议用 nvm 管理版本,我当时用的是 Node 18 LTS。安装 Playwright 两条命令:
npm init -y npm install playwright npx playwright install chromium第二条命令是下载 Chromium 浏览器内核,很多人漏了这一步,导致运行时直接报Executable doesn't exist。这里有个经验:如果只是做网页操作,不需要firefox和webkit都装,只装chromium就够,省时间也省磁盘空间。
如果你在 Cursor 里写代码,可以把 Playwright 的 API 描述直接发给它,比如“生成一段 Node.js 的 Playwright 脚本,打开百度搜索关键词并打印结果标题”,它生成的代码基本拿来就能跑。
2.2 npx playwright install 失败的三种典型原因
我在装 Playwright 时正好遇到安装失败的问题,这里把常见原因和解决办法列全:
| 报错现象 | 根本原因 | 解决办法 |
|---|---|---|
Host system is missing dependencies | Linux 系统缺运行浏览器所需的基础库 | 执行npx playwright install-deps chromium,让它自动补装系统依赖 |
Executable doesn't exist | 浏览器内核没下载或下载中断 | 重新执行npx playwright install chromium,或设置PLAYWRIGHT_DOWNLOAD_HOST换成可用镜像源 |
Error: Connection refused | 网络问题导致下载失败 | 检查网络,或手动下载浏览器包放到缓存目录 |
老话说得好,90% 的 Playwright 环境问题都是浏览器内核没装好,跟你的代码没关系。遇到问题第一步先去检查内核是否完整,别一上来就怀疑页面元素。
2.3 顺带把 Cursor 的中文设置和常用配置说清楚
从搜索热词来看,“cursor怎么设置中文”“cursor中文怎么设置”是大家非常关心的问题。其实 Cursor 本身是英文界面,你要的“中文”分两层:一是界面语言,二是 AI 回复语言。
界面汉化比较简单,直接在扩展市场搜索“Chinese”或“汉化”类插件安装即可。AI 回复用中文则更简单,只需要在对话里明确要求“请用中文回答”,或者建立一个全局规则,让 Cursor 在每次回答时都默认使用中文。你也可以在项目的.cursorrules文件里加上一句“Always respond in Chinese”,这样所有对话都会默认中文回复,不用每次重复叮嘱。
另外,Cursor 注册时手机号填写是个常见难题,因为部分地区手机号可能不在支持列表。我的建议是优先用邮箱注册,流程更顺畅。
3. 自动发布头条文章:从零到一的完整实现
现在到了最核心的部分:真正让 AI 操作浏览器把头条文章发布出去。这一节我按完整执行流程来写,从任务拆解到代码实现,包括中间遇到的 iframe 和动态元素问题。
3.1 任务拆解与执行流程
自动发布文章看起来是个单一动作,但实际拆分下来至少包含这些子任务:
- 打开头条号创作平台并确保已登录。
- 进入“发布文章”页面。
- 填写文章标题。
- 填写文章正文(头条编辑器通常嵌套在 iframe 中,这一步最容易踩坑)。
- 点击发布按钮。
- 校验是否发布成功。
用 Cursor 生成脚本时,我会把上面这些步骤直接写成自然语言清单发给它。这样做的好处是,AI 生成代码时会把每个步骤对应到 Playwright 的具体 API 上,代码结构清晰,后续调试也好定位问题。
下面是当时 Cursor 生成的核心脚本骨架,我加了一些注释和细节调整:
const { chromium } = require('playwright'); (async () => { const browser = await chromium.launch({ headless: false, // 有头模式,方便观察执行过程 }); const context = await browser.newContext({ storageState: 'state.json', // 复用已登录状态,省去每次扫码登录 viewport: { width: 1280, height: 800 }, }); const page = await context.newPage(); // 1. 打开头条号创作平台 await page.goto('https://mp.toutiao.com/', { waitUntil: 'domcontentloaded' }); // 2. 点击“发布文章”入口 const publishEntry = page.locator('text=发布文章').first(); await publishEntry.click(); await page.waitForLoadState('networkidle'); // 3. 定位标题输入框并填写 // 不同时期的头条后台控件会变化,这里用 placeholder 定位相对保险 const titleInput = page.locator('input[placeholder*="请输入标题"]'); await titleInput.fill('AI 自动发布的一篇测试文章'); // 4. 处理正文编辑器(头条编辑器通常渲染在 iframe 内) const editorFrame = page.frames.find((frame) => frame.url().includes('editor')); if (editorFrame) { const contentArea = editorFrame.locator('[contenteditable="true"]'); await contentArea.click(); await page.keyboard.type('这是用 Cursor + Playwright 自动发布的正文内容,验证 AI 操作浏览器的完整流程。'); } else { throw new Error('未找到编辑器 iframe,请检查页面结构'); } // 5. 点击“发布”按钮 await page.locator('button:has-text("发布")').first().click(); // 6. 等待发布成功提示 await page.waitForSelector('text=发布成功', { timeout: 15000 }); console.log('文章发布成功!'); await browser.close(); })();注意,这段代码是基于我当时实测的页面情况整理的。头条的后台页面结构会随版本变化,所以你复现时如果定位不到元素,优先用codegen现场抓取最新的选择器,而不是死磕固定写法。
3.2 登录态复用:不要每次登录都去扫码或输验证码
做自动发文章的人都要面临一个现实问题:头条后台的登录有扫码、手机验证码等机制,如果每次跑脚本都重新登录,流程会变得非常繁琐。
Playwright 的storageState功能完美解决了这个痛点。思路是:第一次手动登录一次,把登录后的状态(Cookie 和 localStorage)保存成 JSON 文件,后续再跑脚本时直接加载这个状态,不需要重新登录。
保存登录态的代码片段:
const { chromium } = require('playwright'); (async () => { const browser = await chromium.launch({ headless: false }); const context = await browser.newContext(); const page = await context.newPage(); // 手动登录过程 await page.goto('https://mp.toutiao.com/'); // 这里你手动扫码或输验证码 // 等待登录完成并进入后台 await page.waitForURL('**/profile/**', { timeout: 60000 }); // 保存状态 await context.storageState({ path: 'state.json' }); await browser.close(); })();后面每次执行自动发布脚本时,加载state.json就等同于“已登录”状态。但这里有个关键细节:登录态有有效期。如果脚本跑了一天突然报跳转到登录页,不用慌,重新用上面的代码保存一次登录态就行。我当时是设置成每次发文章前先检查一下当前 URL,如果发现被重定向到登录页,就自动触发一次重新登录的提醒。
3.3 定位编辑器:iframe 和动态元素处理
头条号的文章编辑器是我在这个项目里遇到的第一道坎:正文区域被包在 iframe 里,直接page.fill根本定位不到,因为 Playwright 的默认选择器只在主页面文档里查找。
解决 iframe 的标准操作是两步:
第一步,找到 iframe 对应的 frame 对象。可以直接按 URL 关键字匹配:page.frames.find((f) => f.url().includes('editor'));也可以用 frame 的 name 属性匹配,但只要做过后台开发的人都知道,前端 iframe 的 name 经常是随机字符串,所以我更推荐按 URL 匹配。
第二步,在 frame 对象内部使用locator。注意这时候你已经进入了 iframe 的独立文档环境,要找到正文输入框,通常的目标是contenteditable="true"的元素或富文本编辑器的可编辑区。
还有一类问题是动态元素。比如点击“发布文章”后,页面可能要等几秒才加载编辑器。Playwright 的waitForSelector就是干这个的,但我实际用下来,更推荐用locator.click()配合 Playwright 内置的自动等待机制,因为它会一直等到元素可操作再点击,比固定等待更稳健。
3.4 发布与结果校验
发布按钮的点击本身不难,难的是“如何确认发布真的成功了”。我在脚本里用了waitForSelector('text=发布成功'),这依赖发布成功的提示文案。如果页面没有这句提示,或者用的是弹窗提示(toast)一闪而过,那这个方法就会失效。
更可靠的做法是发布后直接去文章列表页面,校验新文章是否出现。可以这样写:
await page.goto('https://mp.toutiao.com/profile/'); // 检查新发布的文章标题是否出现在列表中 const articleItem = page.locator(`text=AI 自动发布的一篇测试文章`).first(); await articleItem.waitFor({ state: 'visible', timeout: 10000 }); console.log('文章已出现在列表页,发布确认成功');这个思路的本质是从“结果验证”而不是“过程提示”来判断是否成功,鲁棒性高很多。我在实际使用中甚至遇到过:页面提示“发布成功”,但文章列表里根本没内容的情况,后来排查是草稿状态没提交成功。只看过程提示容易被假象欺骗。
4. 常见问题与排查技巧实录
这个项目做完,我踩了不少坑。为了不让后来人重复走弯路,我把自己遇到的高频问题整理成了一张速查表,每个问题都附上我实际排查的思路和解法。
4.1 自动发布过程中的问题速查表
| 问题 | 可能原因 | 排查与解决 |
|---|---|---|
脚本运行时报TimeoutError: locator.click: Timeout 30000ms exceeded | 元素选择器不对,或页面加载过慢 | 先用page.locator(selector).count()确认元素是否存在;再用codegen现场录制一段操作,拿到最准确的选择器 |
| 定位不到正文编辑区 | 内容在 iframe 里,主页面找不到 | 按上文方法page.frames遍历查找包含 editor 关键字的 frame,再在 frame 内定位contenteditable元素 |
| 登录态失效,脚本自动跳转到登录页 | Cookie 过期或者被站点踢下线 | 用存储状态的脚本重新手动登录一次并覆盖state.json;不要尝试绕过登录验证,这是合规底线 |
| 文本输入内容丢失或乱码 | 编辑器有自动格式化逻辑,或依赖复杂的前端状态 | 输入后等 200ms,再触发一次page.keyboard.press('End'),保证内容被完整接收 |
| 点击发布后无反应 | 页面有二次确认弹窗 | 发布后轮询查找“确认发布”或“校验”类弹窗按钮,有则继续点击 |
上面这张表里的问题,前两个在开发初期出现概率极高,尤其是 iframe 那个,很多人上来就被拦住了。坚持一个原则:不要靠猜,要看真实页面结构。Playwright 里最简单暴力的办法就是codegen,你手动在浏览器里操作一遍,它把对应的选择器都给你录出来了。
4.2 三个独家实战心得,这些文档里不会写
第一个心得:不要把超时时间设得太短。我一开始把waitForSelector超时设在 5 秒,结果在网速波动或者页面缓存刷新的时候频繁失败,后来统一改成 15 秒,稳定性立刻上来了。Playwright 的自动等待本身比较智能,多给几秒不会拖慢整体速度,因为它是等到了就继续,不是傻等。
第二个心得:尽量用文本和语义定位,少用层级嵌套的 CSS 选择器。比如page.locator('text=发布文章')比#app > div > div > button稳健得多。页面结构稍微一调整,CSS 路径就废了,但文案匹配通常还能用。
第三个心得:先写一个小单测验证核心动作,再做完整流程。比如先只写“打开头条后台截图”,确认页面能正常打开;再添加“点击发布文章”,确认能进入编辑器;最后再拼接成完整流程。隔离调试比一口气跑全流程快十倍,因为出问题你知道是哪一步。
5. 这套玩法还能迁移到哪些场景
自动发布头条文章只是 Cursor + Playwright 能力的一个样板。真正让我兴奋的是,这套“AI 生成脚本 → 审查 → 执行 → 校验”的模式可以被复用到大量真实工作场景中。
5.1 除了发文章,还能自动做什么
举几个我目前验证过或正在测试的方向:
- 数据采集与展示:用 AI 写出采集脚本,打开指定网站、翻页、截取关键信息,最后落成 Excel 表格。比单纯用爬虫库更灵活,因为页面交互逻辑由 AI 生成初始版本,我再做细节适配。
- 后台管理系统的自动化巡检:每天定时打开后台,检查订单列表、异常告警、待办数量,异常时推送到钉钉或企业微信。
- 表单自动填写测试:无论是自己的系统还是第三方系统,用自然语言让 Cursor 生成一套浏览器自动操作用例,比起手写 UI 自动化测试用例省力很多。
- 多 AI 协作的浏览器 agent:把 Playwright 的操作结果回传给大模型,再由大模型决定下一步动作,这比“固定脚本”更接近真正的 AI Agent。
在这些场景里,核心还是同一件事:AI 生成代码、驱动浏览器、完成真实操作。所以你把自动发文章这一条链路跑通之后,其他任务基本就是换页面、换选择器、换数据来源的活。
5.2 风险提示和使用边界
工具是好的,用的时候得有边界意识。自动操作真实网站会涉及账号安全和使用条款的问题,我的建议是只在你有操作权限的账号和站点上做自动化,比如自己的头条号、自己的后台系统、自己有授权的测试环境。不要用自动脚本去批量发布垃圾内容、刷量、绕过验证码或攻击性的爬取,这是原则问题。
头条平台的用户协议中关于第三方工具自动操作有相应约束,所以在正式环境高频跑自动发布前,我强烈建议你确认一下你的使用场景是否在允许范围内。我自己目前的使用策略是低频、少量、真实内容验证,而不是走量。工具的价值在于提升效率,而不是制造麻烦。
6. 实战收尾:再分享一点真实体会
这一路折腾下来,最大的感受是 AI 自动化操作浏览器这件事,已经从“玩具阶段”走到了“工具阶段”。Cursor 和 Playwright 的配合正在把过去写脚本这件事的门槛不断拉低,你不需要记住所有 API 细节,但你需要理解基本的网页结构、元素定位逻辑、iframe 概念,这样才能判断 AI 给出的代码到底靠不靠谱。
如果你的电脑上还没有 Playwright 环境,我的建议是先别急着跑完整案例,花十分钟把环境配好,打开codegen对着头条后台点几下,看看它能生成什么代码。这个小实验会帮你在短时间内建立对这套体系的整体感觉,后面再玩 Cursor + Playwright 就会顺手很多。
我最后再分享一个小技巧:在 Cursor 的对话里描述任务时,尽量细化到“打开什么页面、点击什么文字、输入什么内容、期望看到什么结果”,而不是只说“自动发一篇文章”。Prompt 里信息粒度越细,AI 生成的代码越接近可用状态。这个习惯我用了很久,实测对提高生成代码的准确率帮助非常明显。