十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

BrowserSkill:让Agent接管你已登录的浏览器,绕过登录墙

BrowserSkill:让Agent接管你已登录的浏览器,绕过登录墙 搞Agent开发这几年我一直觉得浏览器是整个生态里最被低估的入口。你让Agent去查资料、填表单、抓数据绕来绕去总绕不开一个事浏览器上下文。大多数Agent默认开一个全新的浏览器实例干净是干净但用户已经登录的SaaS后台、付费订阅页面、验证码状态这些带登录态的东西它全都没有。于是就会出现很尴尬的场景——Agent明明很强却被一道登录墙拦在外面抓了半天抓回来一堆空页面。BrowserSkill这个方案很讨巧别再造一个浏览器环境让Agent直接接进你已经登录的浏览器里。把登录态、Cookie、本地存储、浏览器指纹这些现成的东西全部复用起来Agent真正变成“坐在你电脑前操作浏览器的人”而不是一个被隔离在沙箱里的机器人。这事听起来简单做起来牵扯到Chrome DevTools Protocol、调试端口、会话上下文接管、权限边界设计还涉及到你在真实浏览器里那点“活人痕迹”怎么安全地交给Agent使用。这篇文章我把完整的技术链路、实操步骤和踩过的坑都写清楚不管你是打算给项目加这个能力还是纯粹想研究Agent与浏览器交互的边界应该都能用得上。1. 为什么Agent需要“直接用已登录的浏览器”1.1 登录态才是Agent能看见真实网络的关键很多人把网页抓取理解成“发个HTTP请求拿HTML”这在十年前还行现在基本走不通。主流网站普遍做了登录墙、动态渲染、接口签名、设备指纹风控。你自己浏览器里打开是完整的业务数据换成一个新浏览器环境不登录、没Cookie、没有历史行为服务器看到的完全是一个陌生访客给你的页面模板都可能是另一套。BrowserSkill解决的就是这个最基础的问题Agent不是以“匿名游客”身份访问网页而是以“你”的身份带着你已经完成的登录认证去操作。我举个例子就明白了。假设你要写一个Agent帮你汇总某个数据分析平台里昨天所有报表这个平台必须登录才能看。传统方案里Agent得先走一遍登录流程遇到短信验证码、扫码验证、二次认证基本就卡死。就算你给Agent准备好账号密码很多平台还有设备风控在无头浏览器里登录直接触发异常检测账号都可能被锁。但BrowserSkill直接复用它当前浏览器里的登录态跳过整个认证环节打开就是你已经登录好的页面Agent只需要专注做数据提取和分析。这类场景在真实项目里特别多查自己的云服务器后台、读某个付费社群的帖子、整理邮箱里的账单、更新后台系统配置全是“必须登录才能操作”的需求。你要是给Agent准备一套完整的登录能力工程量巨大不说安全和风控风险也高复用已登录浏览器会话几乎零成本地把问题解决掉了。1.2 独立浏览器实例的三大硬伤早期Agent与网页交互的标准做法是Launched模式——启动一个全新的浏览器实例让Agent从头开始操作。这个方案在技术社区里很流行但它有几个硬伤做实际项目的人体会特别深。第一是风控识别问题。独立浏览器实例的浏览器指纹、Canvas特征、WebGL参数、时区语言、字体列表都跟真实用户不一致而且没有历史Cookie和LocalStorage行为特征也偏“机器人”。现在稍微有点规模的网站都有风控系统识别这种“干净环境”简直不要太容易。轻则弹出验证码让你过重则直接拒绝访问或者给假数据。你在本地明明看到的是正常页面Agent抓回来的却是风控页面整个自动化流程直接崩掉。第二是会话不连续。很多业务操作需要多步骤、跨页面的状态保持。传统独立浏览器实例里Agent刷新一下、跳转一下某些依赖Session的状态就丢了。尤其是OAuth流程、支付回调、文件上传回调这类多跳转场景会话丢失率非常高。你复用它本来的浏览器这些状态天然就是完好的。第三是资源开销。新开一个浏览器实例意味着新的一套进程、渲染引擎、缓存体系内存占用轻松几百兆。你要是跑多个Agent机器直接卡死。复用已登录浏览器Agent只是作为一个连接者挂进现有进程额外开销小得多。所以从风控、会话连续性和资源效率三个维度看“直接接管已登录浏览器”都是更合适的选择。对比维度独立浏览器实例BrowserSkill 复用已登录浏览器登录态无需额外处理认证完整保留直接可用浏览器指纹全新指纹易触发风控真实指纹接近正常用户会话连续性跨步骤易丢状态原生保持内存和CPU开销高独立进程渲染引擎低复用现有进程适用场景无登录要求的公开页面登录后操作、个人数据场景2. BrowserSkill的核心思路与技术选型2.1 借力CDP而不是改造浏览器BrowserSkill能实现的关键在于Chrome DevTools Protocol也就是CDP。它是Chrome、Edge等Chromium内核浏览器原生提供的调试协议允许外部程序通过WebSocket连接浏览器实例然后发送指令做各种操作打开新标签页、切换标签、执行JavaScript、读取DOM、获取Cookie、模拟鼠标键盘、拦截网络请求。常见的Selenium、Playwright、Puppeteer底层其实都是套了一层CDP封装。BrowserSkill的思路非常朴素——既然CDP能控制浏览器那我就不该自建浏览器而是直接通过CDP连接用户已经打开的浏览器实例。只需要在启动浏览器时开一个远程调试端口再用Agent程序连上去就能拿到用户当前的标签页列表、执行页面操作、读取数据。这整个链路里浏览器还是那个浏览器页面还是那些页面Agent只是一个“远程操作者”。为什么选CDP而不是别的方案两个原因。第一CDP是浏览器原生能力稳定性有保证不需要在页面里注入外部脚本也不依赖任何第三方扩展第二CDP能做的事情足够“底层”从读取、执行到点击、滚动、文件上传、网络监听全覆盖Agent需要的能力它基本都提供了。相比之下浏览器扩展方案虽然也能实现类似能力但扩展的API权限模型和页面操作能力都比CDP受限得多尤其是跨域读取、本地存储访问、网络请求操控这些高级操作扩展根本做不了。我觉得BrowserSkill比较聪明的地方在于它没有尝试“重新发明轮子”。它把Agent的自动化能力和真实浏览器的原生能力解耦开Agent负责思考决策浏览器负责执行和呈现中间用CDP做桥接。这样Agent不需要关心浏览器怎么渲染页面、怎么管理Cookie只需要关心“我要在这个页面做什么”剩下的交给BrowserSkill。2.2 技术组件的选择与组合BrowserSkill的实现通常不是单靠CDP裸协议而是包含两层一个是浏览器侧的能力暴露层另一个是Agent侧的指令封装层。浏览器侧常见做法是Chrome扩展加一个后台脚本负责向Agent暴露当前标签页、Cookie、登录状态等信息并向CDP转发操作指令Agent侧则是一个客户端库把CDP的原始指令封装成人性化的API比如open_url、type_text、click_element、read_dom。扩展加CDP的组合优势在于权限控制更精细。扩展能明确告诉用户这个扩展需要哪些权限而CDP的调试连接也能精确到单个标签页维度。Agent要读取某张页面只需要连接对应的标签页而不是整个浏览器全局的能力。理论上直接通过命令行启动带远程调试端口的浏览器也能实现类似效果但扩展层的授权机制和用户可见性更好用户能清楚知道Agent正在操作哪个页面。实际项目中还有一套更轻量的接法就是通过Playwright或Puppeteer的connectOverCDP方法直接连接已经在运行的浏览器实例。这种方法不需要自研扩展只需要拿到调试端口地址就能把现有的、带登录态的浏览器页面“接管”过来。在做原型和内部工具时这个方案最快如果要面向普通用户发布再做一层扩展封装把端口分配、权限提示、状态展示都处理好。2.3 关键链路从“用户浏览器”到“Agent可操作”整个BrowserSkill的调用链路可以拆成四步暴露、发现、连接、操作。暴露阶段用户在启动浏览器时开启远程调试端口或者在扩展中开放“允许远程连接”开关。这是Agent能够接触浏览器的前提。发现阶段Agent程序通过固定端口或者扩展注册的通道找到正在运行的浏览器实例拿到WebSocket调试地址。连接阶段Agent用这个地址建立CDP连接枚举当前打开的标签页选定目标页面。操作阶段Agent发送指令执行具体的页面操作比如读取文本、填写表单、点击按钮然后收集结果返回给上层Agent大模型。链路说起来简单但每一步都有细节。暴露这一步最要命因为浏览器默认是不开远程调试端口的用户得手动在启动命令里加--remote-debugging-port9222或者通过扩展一键开启。发现阶段要处理“多个浏览器实例”、“端口被占用”、“浏览器版本兼容性”这些问题。连接阶段要处理WebSocket握手失败、页面目标变化等情况。操作阶段则要注意选择器失效、页面异步加载、弹窗遮挡这些日常Web自动化会遇到的问题。我在实际项目里跑通这条链路时最大的感受是“复用登录态”这个红利太明显了。Agent连上去看到的页面就已经是登录状态完全不用处理认证逻辑。一个原本需要写几百行登录适配代码的流程现在直接省掉了。3. 实操从零接入BrowserSkill并跑通一次真实操作3.1 环境准备用调试端口“打开”你的浏览器BrowserSkill想运行第一步必须是让浏览器开启远程调试能力。这一步有个常见的坑你必须在浏览器完全退出后用带参数的命令行重新启动否则调试端口不会生效。Windows、macOS、Linux的命令略有差异。Windows下常见的做法是打开PowerShell或CMD执行# 关闭所有Chrome进程后用独立用户目录启动调试模式 chrome.exe --remote-debugging-port9222 --user-data-dirC:\tmp\browser-agent-profilemacOS下执行# 先退出Chrome再通过open命令带参数启动 open -na Google Chrome --args --remote-debugging-port9222 --user-data-dir/tmp/browser-agent-profileLinux下执行google-chrome --remote-debugging-port9222 --user-data-dir/tmp/browser-agent-profile 为什么要加--user-data-dir因为如果你不指定一个全新的用户数据目录Chrome可能会把参数转发给一个已经运行的实例而后台那个实例并没有开启调试端口结果就是端口连不上。用一个独立的user-data-dir可以保证启动的是一个全新的、带调试端口的浏览器进程。启动之后在浏览器里正常登录你需要的网站把这套登录态留着。然后验证一下CDP服务是否启动成功——在另一个终端执行命令应该能拿到一个包含webSocketDebuggerUrl字段的JSONcurl http://localhost:9222/json/version能看到浏览器版本、WebSocket地址这些信息就算成功一半了。3.2 代码接入用connectOverCDP接管现有浏览器会话环境准备好之后Agent侧就可以开始“接管”了。这里我用Python加Playwright来演示是因为Playwright的connectOverCDP方法几乎是为此场景量身定做的它不会新起浏览器而是连接到一个已经运行的浏览器实例上拿到现有浏览器的所有上下文包括登录态。先安装Playwrightpip install playwright然后写一段最小的连接代码import asyncio from playwright.async_api import async_playwright async def main(): async with async_playwright() as p: # 关键点connect_over_cdp而不是launch browser await p.chromium.connect_over_cdp(http://localhost:9222) # 获取浏览器所有context默认取第一个 context browser.contexts[0] # 获取正在使用的页面 pages context.pages print(当前打开的标签页数量:, len(pages)) if pages: page pages[0] print(当前页面标题:, await page.title()) print(当前页面URL:, page.url) # 在已登录页面里读取一个用户头像或用户名的DOM验证登录态是否生效 # 以GitHub为例 if github.com in page.url: username await page.text_content(.AppHeader-user .avatar-user) print(当前登录用户头像元素存在登录态有效) await browser.close() asyncio.run(main())这段代码做了三件事通过CDP连接到已经启动的浏览器获取浏览器已有的上下文和页面读取页面内容验证访问的是登录态下的真实页面。如果你是在一个已经登录的GitHub页面上跑这段代码应能正常打印出用户信息而不是被重定向到登录页。connectOverCDP和launch的本质区别在于launch是启动一个全新的浏览器实例所有上下文都是“空的”connectOverCDP是附着到现有实例上能直接访问用户创建的上下文。这就是为什么Agent能“用你已登录的浏览器”的核心原因。3.3 执行一次完整操作读取、填写、提交接入验证通过之后就可以做更实际的操作了。我演示一个常见场景——Agent自动在某个已登录的后台页面查找并填写内容。假设目标是某个内部系统页面里有一个搜索框我们要输入关键词并触发搜索。直接在Playwright里继续写import asyncio from playwright.async_api import async_playwright async def auto_search(): async with async_playwright() as p: browser await p.chromium.connect_over_cdp(http://localhost:9222) context browser.contexts[0] # 找到目标标签页如果没有就新建一个 page None for pg in context.pages: if your-dashboard.com in pg.url: page pg break if page is None: page await context.new_page() await page.goto(https://your-dashboard.com) # 等待页面加载完成 await page.wait_for_load_state(networkidle) # 在搜索框输入内容 search_input page.locator(input[typesearch]) await search_input.fill(2025年第一季度报表) await search_input.press(Enter) # 等待结果区域加载 await page.wait_for_selector(.result-list) # 提取结果列表第一项的文本 first_result await page.text_content(.result-list .item:first-child) print(搜索结果第一项:, first_result) await browser.close() asyncio.run(auto_search())这个流程里search_input的选择器、wait_for_selector等都需要根据真实页面调整。这里我写的是通用逻辑实际开发中建议用page.locator先调试选择器最好直接把Playwright的交互式调试跑起来试出稳定选择器再固化到代码里。有一个细节值得注意context.pages拿到的标签页列表是实时变化的。如果用户手动关闭了某个标签页这个列表也会更新。所以每次操作前重新枚举一下页面比缓存一个page对象更稳妥。3.4 参数计算与注意事项端口、选择器、超时如果说实操里最影响成功率的三件事我拎出来单讲调试端口冲突、元素选择器不稳定、超时设置不合理。调试端口冲突很常见。默认端口9222如果被占用可以换一个比如9223、9333。换了之后connectOverCDP的URL也得改。启动多个调试实例时端口和user-data-dir都要一一对应别复用同一个用户目录否则浏览器实例互相干扰。元素选择器不稳定是Web自动化永远的话题。页面改版、按钮文案变化、动态渲染都会影响选择器。我的经验是优先用稳定属性比如id、name、data-testid其次用相对层级定位尽量不用纯文本匹配因为改动频率太高。超时设置其实是控制“Agent会不会卡死”的关键。BrowserSkill操作真实浏览器时页面可能因为网络慢、懒加载、弹窗等导致操作等待时间变化很大。建议所有wait操作设置合理的timeout比如# 等待页面网络空闲最多等10秒超时提前抛异常 await page.wait_for_load_state(networkidle, timeout10000) # 等待按钮可见最多等10秒 await page.wait_for_selector(#submit-btn, timeout10000)超时时间不是越长越好太长会让失败反馈延迟太短会让弱网环境下的正常操作频繁失败。10秒左右是个平衡点遇到特殊页面再单独调整。注意操作已登录浏览器时千万不要在代码里硬编码读取用户的Cookie并打印到日志中。登录态是敏感数据日志泄漏和明文存储都是大忌。后续所有拿到Cookie做本地持久化的做法都要经过严格的授权评估。4. 安全边界与授权机制的工程化设计4.1 权限最小化Agent不能什么都能做BrowserSkill让Agent“拿到真浏览器”的同时也意味着Agent获得了很大的能力——它能读你登录态的页面、操作你的账号、发送请求。能力越大权限边界越要设计清楚。我的原则是“权限最小化”Agent默认只能读不能写写入操作必须显式授权。具体到实现里可以给操作分几个等级只读操作读取页面标题、URL、DOM文本、截图为默认级别表单填写和按钮点击等写入操作为中级执行前需要用户确认高危操作发邮件、转账、删数据、提交订单为高级必须二次授权。BrowserSkill在设计授权时要做到“用户知道Agent正在做什么”而不是让Agent在后台静默执行一切。4.2 授权确认机制用一个“确认队列”兜底无人值守是Agent的理想状态但涉及真实浏览器和自己账号时完全无人值守是非常危险的。我建议加一个“确认队列”机制Agent执行到指定节点时先把意图发给用户侧比如直接推送消息或在扩展弹窗里展示“Agent即将在xxx页面点击xxx按钮是否允许”用户点确认后Agent才能继续。这里比较重要的是不能阻塞Agent的整个流程。可以让Agent先把所有需要授权的操作排成一个队列用户批量确认也可以只对“写入”和“高危”操作弹确认框读操作不打扰用户。实际工程里弹窗太多用户会烦弹窗太少风险高需要拿捏好尺度。4.3 登录态数据的安全保管BrowserSkill能工作的前提是复用用户浏览器的登录态但这个登录态不该被Agent程序随意读取和保存。真实项目中Cookie、Token、Session这类会话凭证是非常敏感的数据。任何时候都不要把Cookie写到普通日志、数据库明文存储、或上传到第三方服务。一种比较稳妥的做法是整个操作过程中Agent始终通过CDP连接在浏览器环境内操作不显式读取Cookie。只有当页面流程本身需要验证身份时才依赖浏览器自动携带的认证信息。这样Agent拿到的只是“操作结果”而不是“身份凭证”。如果一定要持久化会话至少要加密存储并使用系统级的密钥管理机制比如操作系统的钥匙串。还有一点容易被忽略BrowserSkill的调试端口本身也是暴露面。启动调试端口后同一台机器上的任何本地进程都可以连上来控制浏览器这等同于放了一个远程控制后门。所以调试端口只应在需要时开启用完立刻关闭如果部署在服务器上还要考虑通过防火墙限制端口访问来源。4.4 容易被忽略的行为边界浏览器指纹、设备信息、操作节奏这些都是线上平台风控系统关注的维度。BrowserSkill复用真实浏览器登录态确实比独立浏览器实例“自然得多”但它不代表可以为所欲为。高频操作、异常时间点操作、多账号短时间切换这些行为即使复用了真实浏览器也可能被风控系统盯上。我在测试里就翻过车让Agent在几分钟内反复刷新一个页面若干次刷新频率远超正常用户操作结果页面直接跳出了验证码更尴尬的是这个验证码弹在Agent控制的页面里Agent识别了半天也没搞明白为什么会出现一个奇怪的验证页面。所以不要滥用BrowserSkill还是那句话模拟正常用户行为节奏而不是比拼操作速度。页面加载后先等一会儿再点击跳转前后留出合理的间隔这些都写在Agent的prompt或者调度层里。5. 常见问题与排查技巧实录5.1 速查表从表象到根因做BrowserSkill这类项目最常见的问题其实高度集中在几个环节。我把它们整理成一张速查表方便后面直接对照。现象可能原因排查方向connectOverCDP连接失败浏览器调试端口未开启或端口被占用检查启动命令是否带--remote-debugging-portcurl http://localhost:9222/json/version验证连接成功但获取不到页面连接到了错误的浏览器上下文确认user-data-dir和浏览器实例一一对应换用browser.contexts枚举上下文页面是登录墙/空白页新开的标签页没有复用已有登录态避免用全新context尽量复用现有context和pages元素定位不到页面异步渲染或选择器过时加wait_for_selector使用相对稳定选择器先页面截图确认当前状态操作执行成功但页面无变化目标选错、按钮被弹窗遮挡、点击被拦截检查页面是否有弹窗或遮罩层用is_visible判断目标元素页面频繁跳出验证码操作频率过高、行为特征异常放慢操作节奏避免高频刷新和重复点击减少多账号交替浏览器崩溃或闪退调试模式与扩展冲突、浏览器版本问题查看浏览器日志尝试新开user-data-dir升级浏览器版本5.2 调试工具与经验技巧排查这类问题最有效的工具我推荐三个。第一个是Chrome自带的chrome://inspect页面。在浏览器里打开这个地址能看到所有已经启用调试的页面目标点击链接可以直接打开对应的DevTools面板比在Agent代码里打日志直观得多。第二个是CDP的原始指令调试。有时候用Playwright封装的API排查问题会绕圈子直接通过WebSocket向CDP发送原始指令比如Page.enable、Page.captureScreenshot、Runtime.evaluate能看清浏览器到底处于什么状态。写一小段Node或Python脚本直接连WebSocket调试排查思路会清晰很多。第三个是页面截图。在Agent代码执行到关键节点前后各截一张图能快速判断是页面渲染问题、选择器问题还是操作顺序问题。Playwright里从已有的page对象截图很简单await page.screenshot(pathdebug_step_1.png, full_pageTrue)5.3 实战中我踩过的坑和最终解法做BrowserSkill接入时我印象最深的一个坑是“浏览器启动参数被吞”。Windows上我写了一个片段让程序调用chrome.exe带远程调试参数结果chrome.exe启动后没加参数直接弹了正常窗口端口也连不上。原因就是系统里已经有Chrome进程在跑了新启动的Chrome把请求转发给了老进程参数全部作废。解决办法就是文章前面说的必须用一个独立的user-data-dir强制启动新进程而且要等老进程完全退出再启动新进程。另一个坑是“上下文选择错误”。connectOverCDP连上浏览器后browser.contexts可能不止一个。有些扩展、WebApp会创建自己的上下文如果Agent默认选了第一个可能正好选中了扩展的后台页面而不是用户正常浏览的那个上下文结果读出来的DOM完全不对。后来我改成用context.pages里的URL去匹配目标页面才彻底解决。还有一个让我印象深刻的教训就是“隐藏的登录态并不是万能的”。我在跑一个自动化场景时发现Agent能打开登录后的页面但点击某个按钮时会跳出一个二次认证要求输入手机验证码。这个验证流程登录态解决不了它属于“操作级认证”而不是“会话级认证”。遇到这种场景我没有试图自动破解或绕过验证而是把控制权交回给用户让用户手动完成验证Agent在验证完成后继续执行。这个判断很重要也是合规边界的体现自动化工具帮助用户简化操作但脆弱的认证环节还是应该留给人来做。6. 我在实际项目里的几点体会BrowserSkill这套思路做下来我最深的体会是“复用”比“重建”靠谱得多。登录态是用户在漫长使用过程中积累下来的信任资产Agent直接继承这份资产省掉的不只是登录流程还有一堆跟风控斗智斗勇的糟心事。真实的浏览器环境里有用户的历史操作、浏览习惯、设备特征这些东西是任何模拟手段都很难复制的。第二个体会是要尊重用户的“在场感”。Agent操作一个登录态的浏览器账号本质上是在替用户行使账号权利。哪怕技术上能完全无人值守我也建议在关键操作节点保留用户的确认权。这不仅是安全考虑也是在帮Agent建立可信度。用户看着Agent一步步做心里踏实后续才敢把更复杂的任务交给它。第三个体会是浏览器侧的调试能力和安全能力要一起设计不能先做一个能跑通的Demo再补安全。调试端口一开Agent能做的事情边界就模糊了在没有授权和隔离的情况下等于给Agent发了一张“万能通行证”。权限最小化、确认机制、会话不落盘、用完即关调试端口这些不是后期优化项而是第一版就应该做进去的基线能力。如果你也想在项目里跑一个BrowserSkill这样的能力我的建议是从一个最小的闭环开始先手动启动带调试端口的浏览器用connectOverCDP连上去读一个已登录页面的信息走通之后再加操作和授权。别一上来就铺一个大而全的框架复杂的东西翻车了不好排查。等最核心的路径稳定了再逐步扩展场景和边界控制你会发现这条路比想象中的顺很多。
返回列表