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

资讯详情

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

LangChain+Playwright实战:构建能自主浏览网页的Agent

LangChain+Playwright实战:构建能自主浏览网页的Agent 用 LangChain Playwright 搭建一个能自己打开网页、读取信息、再做判断的 Agent实际难度比想象中低但坑也不少。我最近把这一套组合完整跑了一遍覆盖了环境准备、代码实现、开发测试和报错排查最大的感受是LangChain 负责把 Agent 的思维流程组织起来Playwright 负责把 Agent 的“手”接到浏览器上。两者配合好了很多以前需要手动写死脚本的网页任务都能交给 Agent 动态完成。如果你正准备做 Agent 开发或者想让大模型不只是在聊天框里回答问题而是能真正访问网页、拿页面内容回来分析这篇文章值得看完。下面讲的是我实际跑下来的过程不是理论所有配置和代码都会拆开说明。1. 为什么偏偏是 LangChain Playwright 这个组合1.1 LangChain 负责 Agent 的“大脑”和“调度”在 Agent 开发里LangChain 解决的不是“大模型能做什么”而是“大模型怎么把任务拆开、怎么调用外部能力、怎么根据结果继续执行”。它提供了工具抽象、Prompt 模板、Agent 执行器、记忆和输出解析。这里的核心概念很简单你把若干个函数包装成 Tool让大模型根据用户输入决定调用哪个工具然后拿到工具返回结果再决定下一步动作。这个“决定—调用—观察—再决定”的循环就是 Agent 的基本工作方式。很多人会把 LangChain 和 LangGraph 放在一起比较。站在开发测试的角度我的理解是LangChain 更偏基础工具集合和 Agent 快速实现LangGraph 更偏状态图适合需要精细控制流程分支、状态流转和人工介入的场景。刚开始搭 Agent用 LangChain 的 AgentExecutor 就能跑通不用一上来就上 LangGraph。1.2 Playwright 负责 Agent 的“手”Playwright 是一个浏览器自动化框架支持 Chromium、Firefox、WebKit能打开浏览器、点击按钮、输入文本、翻页、提取页面内容。它比 requests 这类简单 HTTP 库强在一点它用真实浏览器内核运行页面JavaScript 动态渲染出来的内容也能拿到。很多页面用 requests 抓下来只剩一堆脚本标签实际数据都在浏览器加载后才出现。Playwright 会等页面执行完动态请求再给你渲染后的 DOM所以它特别适合做 Agent 的信息获取通道。使用的时候要记住一条边界只操作自己有权限访问的公开页面、自己项目里的测试页面或者内部系统。不要用 Playwright 去做绕过访问控制、突破反爬机制的事情这是合规底线。1.3 这套组合和“写死脚本”有什么不一样传统自动化脚本通常流程固定先打开某 URL再等某个选择器再提取数据。页面结构一变脚本就崩。Agent 不是这样用户只需要描述目标比如“打开这个页面提取正文和标题”Agent 自己决定先调用哪个工具、用什么参数、要不要再打开一个链接做交叉验证。但也不能把 Agent 神化。它本质上是把“选择工具和推理步骤”自动化底层仍然需要你提供稳定、可被调用的工具函数。工具封装得越清晰Agent 跑得越稳工具本身就是一把抓Agent 再聪明也容易绕晕。2. 搭建之前先把环境和依赖确认清楚2.1 Python 环境、LangChain 安装和 Playwright 安装我建议直接用 Python 3.10 以上版本。太老的 Python 在安装 LangChain 相关依赖时容易出现兼容问题。安装命令不复杂pip install langchain langchain-openai playwright装完 Python 库之后还差一步容易漏掉安装浏览器内核。python -m playwright install chromium这一步不是可选项。没有浏览器内核Playwright 运行时会直接报找不到浏览器的错误。哪怕你在本地已经装了 ChromePlaywright 默认也认不到它使用的是自己下到缓存目录里的 Chromium 版本。在 Windows 上有一个很常见的报错playwright : 无法将“playwright”项识别为 cmdlet、函数、脚本文件或可运行程序。这个问题的根源通常是 Python 的可执行文件目录没有加入 PATH或者你确实没有安装 playwright 包。可以先执行pip show playwright确认包是否存在再直接用python -m playwright来调用绕开 PATH 问题。2.2 LLM 服务怎么接Agent 需要一个能支持工具调用Function Calling的大模型。OpenAI 的格式是事实上的标准很多兼容服务也支持你可以根据实际情况选模型。调试阶段建议用小模型比如 gpt-4o-mini 这类成本低输出简洁跑测试时不会因为回答太长刷屏。需要准备好 API Key并在代码里正确配置。如果你是本地开发Key 可以放在环境变量里避免硬编码到代码里export OPENAI_API_KEY你的key如果你使用的是私有化部署或者公司内网模型要确认它能支持 OpenAI 格式的 Function Calling 接口否则 Agent 的工具调用没法正常触发。2.3 硬件要求和目录结构本地跑这套组合并不需要太高配置但也不能太低。Chromium 在无头模式下运行会占用几百 MB 到 1GB 内存加上 LangChain、LLM 服务调用建议内存至少 8GB。如果后续要并发开多个浏览器上下文内存会成倍上涨这点要提前有预期。磁盘方面Chromium 内核加依赖包预留 1GB 以上比较稳。项目目录建议这样组织agent-project/ ├── tools/ # 自定义工具函数 ├── config.py # 模型、超时、路径等配置 ├── agent_runner.py # Agent 组装和运行入口 ├── logs/ # 运行日志 ├── tasks/ # 任务输入可以是 txt、json、csv └── outputs/ # 结果输出目录分开的最大好处是排错方便。任务卡住时你不需要翻代码找日志任务输入、输出、日志三块独立能快速定位问题出在哪个环节。2.4 网络和权限边界确保目标站点可以从你当前网络访问。如果站点需要登录你要在代码里处理登录态比如用 Playwright 的storage_state保存和复用 cookie。但这只适用于你有合法账号、有权限访问的站点。这里多说一句网页自动化不是不能做而是要守住边界。公开信息的获取和测试页面的自动化是正常开发场景未授权访问、绕过验证、破解反爬这些不属于技术教程该覆盖的范围也不应该写进开发流程里。3. 核心架构设计浏览器操作怎么变成 Agent 的工具3.1 不要把“打开页面”直接扔给 Agent最开始我踩过一个坑只给 Agent 一个“打开任意 URL”的空工具然后让它自由发挥。结果 Agent 经常把 URL 拼错、反复打开同一个页面、在一个问题上绕很多轮。更稳的做法是把常见浏览器能力拆成几个职责明确的工具。比如“打开页面并返回标题”“提取页面可见文本”“等待某个元素出现”“点击页面内按钮”。每个工具只做一件事输入参数和返回值都描述清楚Agent 才知道什么时候该调它。3.2 封装 Playwright 工具的通用思路封装 Playwright 工具时有几个关键点第一尽量复用同一个浏览器实例不要每个工具都启动一个新浏览器。启动 Chromium 的开销很大频繁启动会让整个流程变慢。第二每个工具函数要写好异常处理。页面加载失败、元素找不到、超时这些都是常态。工具函数应该把异常信息返回给 Agent而不是直接崩溃。Agent 拿到错误信息后有可能自己修正策略比如换个 URL或者换个方式重试。第三超时等待要写清楚。元素没出现时不要硬等固定秒数最好用locator.wait_for(timeout5000)这种带超时的等待。页面加载可以分层次比如先等 DOM 加载完成再根据业务需要等待关键元素。3.3 Prompt 设计把任务边界说清楚Agent 的 Prompt 不只是聊天开场白它是整套行为的规则。我的建议是在 system 消息里明确写这几条你是网页调研助手只能通过给定工具获取页面信息。工具返回的内容是唯一信息源不要自己编造页面内容。如果页面加载失败尝试换一种方式但最多重试一次。如果没有获取到有效内容直接返回“未获取到有效内容”不编造结果。这些规则看着简单实测定能减少很多“幻觉”问题。没有边界的大模型很容易在工具调用失败后随手编一段文字看起来像结果实际全是假的。3.4 执行循环是怎么串起来的Agent 的执行流程如图文字描述版用户输入目标。大模型分析任务决定调用哪个工具。系统执行工具函数拿到页面数据或错误信息。大模型根据观察结果判断任务完成就直接输出答案没完成就继续调用工具。重复直到模型认为任务完成或者达到最大迭代次数。在 LangChain 里AgentExecutor已经封装好了这个循环。你需要配置的只有模型、工具列表、Prompt 和几个关键参数比如最大迭代次数max_iterations。如果任务比较复杂建议把最大迭代次数限制在 5 到 10 次防止模型在死循环里出不来。4. 完整代码实现一个最简可运行的 Agent 样例4.1 代码结构和安装注意事项下面这段代码是结构示例不是某个仓库的完整源码复制。LangChain 的 API 在版本演进中变化比较快不同版本的函数导入位置可能不一样。运行前先确认你的langchain、langchain-openai、playwright版本再根据实际情况调整导入。4.2 定义浏览器工具import json from playwright.sync_api import sync_playwright # 全局浏览器实例避免每个工具都重新启动 playwright_obj None browser None def init_browser(headless: bool True): global playwright_obj, browser playwright_obj sync_playwright().start() browser playwright_obj.chromium.launch(headlessheadless) def open_page_and_get_text(url: str) - str: 打开指定网页返回页面标题和可见文本。 输入必须是完整的 URL例如 https://example.com。 if browser is None: return 错误浏览器未初始化请先调用 init_browser() try: page browser.new_page() page.goto(url, timeout15000, wait_untildomcontentloaded) title page.title() text page.locator(body).inner_text(timeout5000) page.close() return f标题: {title}\n正文前2000字:\n{text[:2000]} except Exception as e: return f页面打开失败: {str(e)}这个工具只做一件事输入 URL返回标题和正文前 2000 字。返回值设计成纯文本是因为大模型读文本比读结构化 HTML 更稳定也不会把大量无用标签混进上下文。4.3 创建 Agent 并配置模型from langchain.agents import AgentExecutor, create_openai_functions_agent from langchain.tools import Tool from langchain_openai import ChatOpenAI from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder tools [ Tool( nameopen_page_and_get_text, funcopen_page_and_get_text, description输入一个完整的网页URL返回该页面的标题和可见正文。适用于需要获取网页信息的场景。, ) ] llm ChatOpenAI(modelgpt-4o-mini, temperature0) prompt ChatPromptTemplate.from_messages([ (system, 你是网页调研助手。请调用可用工具获取网页信息不要自己编造页面内容。 目标完成后用中文总结结论和来源URL。如果工具返回错误尝试换一种方式但不要虚构结果。), (human, {input}), MessagesPlaceholder(variable_nameagent_scratchpad), ]) agent create_openai_functions_agent(llm, tools, prompt) agent_executor AgentExecutor( agentagent, toolstools, verboseTrue, max_iterations3, handle_parsing_errorsTrue, )temperature0是故意设置的。Agent 执行任务时不需要创意它需要稳定、可重复的输出。温度调高之后同一个页面它可能每次总结都不一样排错时很难判断是代码问题还是随机性问题。4.4 运行入口if __name__ __main__: init_browser(headlessTrue) try: result agent_executor.invoke({ input: 打开 https://example.com 并返回页面标题 }) print(result[output]) finally: if browser: browser.close() if playwright_obj: playwright_obj.stop()finally里的关闭逻辑很重要。Chromium 如果长期不释放即使程序退出后台也可能残留浏览器进程占用内存越来越大。我在测试时遇到过几次任务卡住最后发现是浏览器实例没关干净。运行成功后终端会输出类似这样的信息Agent 先调用open_page_and_get_text拿到标题然后生成最终答案。如果verboseTrue你能看到完整的工具调用过程这是排查 Agent 行为最直接的线索。5. 开发测试全过程先单条再批量再看边界5.1 第一轮测试先跑通一个 URL 的完整链路第一次测试不要设计复杂任务。就一个最核心的目标让 Agent 调用工具打开一个网页拿回标题再输出结果。我一般会选一个结构简单、加载快的公开页面。判断标准有四个Agent 没有自己编造标题而是确实通过工具拿到。工具调用次数不超过 2 次。最终输出包含标题和来源 URL。全程没有超时和脚本报错。如果这一步都跑不通问题大概率出在环境或模型配置而不是 Agent 逻辑。先不要急着改 Prompt先看依赖和网络。5.2 第二轮测试多步骤任务单条跑通后可以试一个需要两步以上操作的任务。例如打开一个列表页提取页面里的链接再打开前两个链接做内容摘要。这种任务能检验 Agent 的规划能力。但也很容易出问题最常见的是 Agent 反复打开同一个页面。明明第一步已经访问过列表页第二步可能又调一次工具访问同一个 URL浪费时间和 token。解决方案是在 Prompt 里加一句“如果已经打开过某个页面不要再重复打开”。另外一个办法是给工具加一个简单的缓存同一个 URL 在短时间内直接返回已缓存结果。这两种方式我都试过缓存更省资源但副作用是 Agent 拿到的可能是旧内容Prompt 约束虽然简单但效果已经足够。5.3 第三轮测试批量输入批量跑的时候不要天真地以为“能跑单条就一定能并行跑”。我建议按下面这个顺序做先用单线程循环跑 3 到 5 条任务记录每条任务的耗时和成功率。确认单条稳定后再开并发。并发数从 2 开始逐步往上加。每一条任务都要有独立的日志和输出保存。批量任务需要考虑三个额外问题失败重试一条任务失败了是整个流程暂停还是跳过继续我的建议是单条任务内部做一次重试重试失败就标记为失败不阻塞整个队列。输出命名多条任务的结果不能都写到同一个文件里。可以用任务 ID 或者 URL 的哈希作为文件名避免互相覆盖。资源和日志每条任务的 Agent 日志、工具调用记录、最终输出、耗时都要保存下来。以后排查的时候没有日志等于没有过程只看结果根本定位不了问题。一个简单的批量框架可以这样import json import time from concurrent.futures import ThreadPoolExecutor, as_completed def run_one_task(url): start time.time() try: result agent_executor.invoke({input: f打开 {url} 并返回页面标题和简介}) output result[output] status success except Exception as e: output str(e) status failed return { url: url, status: status, output: output, time_cost: round(time.time() - start, 2), } urls [ https://example.com, https://example.org, # 更多输入... ] results [] with ThreadPoolExecutor(max_workers2) as pool: futures [pool.submit(run_one_task, url) for url in urls] for f in as_completed(futures): results.append(f.result()) with open(outputs/result.jsonl, w, encodingutf-8) as f: for r in results: f.write(json.dumps(r, ensure_asciiFalse) \n)不要一开始就把max_workers调到很大。2 到 3 个并发已经能看出问题。如果并发上去后大量报超时或内存飙升先降下来优先保证稳定性。5.4 第四轮测试接口化批量脚本跑通后如果想让其他人通过 API 调用可以用 FastAPI 把这个 Agent 包成一个服务。接口接收 URL 列表返回结构化结果。接口化和脚本化最大的区别是脚本是跑完就结束接口是长期驻留。所以你需要考虑任务队列、超时控制、请求限流和并发管理。一个简单的设计是创建一个任务队列接口收到请求后先把任务放进队列立刻返回一个任务 ID后台 Worker 去处理。前端轮询任务状态。这样即使一个任务很慢也不会阻塞其他请求。但在你还没有把批量任务跑稳之前先不要急着接接口。接口化会把很多隐性问题放大比如线程安全问题、浏览器并发、内存泄漏、日志丢失。我的建议是脚本阶段至少连续跑 20 条任务确认成功率和稳定性再进入接口阶段。5.5 验收标准不同类型任务的验收标准不完全一样可以用下面这个表做参考阶段判断指标合格线单条任务工具调用是否正常、输出是否准确单条成功无幻觉编造多步骤任务Agent 能否按顺序完成多个工具调用能在 3 次迭代内完成批量任务成功率、平均耗时、失败重试连续 20 条成功率达到 90% 以上接口服务请求响应、并发稳定性、日志单请求不超时并发 3 稳定运行这个表不是绝对标准但我在做开发测试时通常会先按这个思路定一个基线再根据项目需要调整。6. 常见报错和排查链路6.1 playwright 命令找不到报错现象playwright : 无法将“playwright”项识别为 cmdlet、函数、脚本文件或可运行程序。排查顺序pip show playwright看包是否安装。如果没安装执行pip install playwright。如果安装了但命令还是找不到用python -m playwright代替命令。检查 Python 的 Scripts 目录是否在 PATH 中。这个问题本质是环境变量问题不是 Playwright 本身出了问题。换一个终端窗口有时也能解决因为新的窗口会重新加载 PATH。6.2 浏览器启动失败找不到 Chromium报错里通常会出现类似“Executable doesnt exist”的信息。原因是只安装了 Python 包没有安装浏览器内核。解决方式python -m playwright install chromium有时候你之前装过但浏览器内核被清理软件删了报错也会出现。重新执行安装命令即可。6.3 Agent 执行超时execution provider did not respond in time如果日志里出现类似“execution provider did not respond in time”的提示我的排查顺序是先判断是哪一层超时单独调用一次 LLM看模型响应是否正常。单独调用一次工具函数看工具本身是否卡在网络请求或页面加载上。查看模型的 max_tokens 设置是否因为输出太长导致超时。查看 Agent 的最大迭代次数是否因为反复调用工具导致整体耗时过大。这个错误的常见原因不是 Agent 逻辑而是外部依赖太慢模型服务响应慢、目标页面加载慢、本地网络不稳定。先把每一段反应时间测出来再决定是调大超时还是减少工具调用次数。6.4 页面元素找不到、点击没反应这类问题通常和 Agent 无关而是页面自动化本身的问题。原因一般是页面是动态渲染元素加载有延迟。选择器不稳定class 名是动态生成的。页面根本没有这个元素可能 URL 或状态不对。排查方式先手动打开页面确认元素是否存在。在代码里增加等待逻辑等待关键选择器出现。失败时保存页面截图人工确认页面实际状态。选择器方面我更喜欢用get_by_text、get_by_role这类语义化定位方式而不是复制一长串带哈希的 class 名。语义化定位在页面结构变化时失败概率要低很多。6.5 通用排查顺序现象先查什么再查什么命令找不到Python 依赖PATH 环境变量浏览器启动失败内核是否安装缓存目录权限Agent 输出为空LLM 服务输入格式工具调用失败函数异常处理页面结构和选择器批量任务卡死并发数资源占用和日志遇到报错不要第一反应改 Prompt。先看现象再看输入和日志最后才动代码和参数。很多所谓“Agent 不听话”的问题底层都是数据格式、权限、依赖版本或者网络环境的问题。7. 这个方案的真实边界别把 Agent 当“全自动工具”7.1 适合什么场景这套方案最适合的场景是目标明确、页面结构相对稳定、你有权限自动化访问的站点。我把日常用得上的场景分成三类信息收集和汇总让 Agent 打开多个页面提取关键信息再做对比和总结。页面巡检定时打开业务页面检查标题、价格、库存等关键信息是否正常。自动化测试辅助用 Agent 生成动态测试步骤辅助测试人员执行回归验证。这些场景的共同点是任务边界清楚失败成本可控。就算 Agent 某次判断错了也不会造成严重后果最多重跑一次。7.2 不适合什么场景反过来有些场景不建议硬用这套方案高压反爬环境、需要频繁验证码验证、登录流程极其复杂的站点不适合。不是因为 Agent 做不到而是这类页面的自动化本身就有合规和稳定性风险做得越多越容易失控。需要很低延迟、对每次操作路径有绝对控制要求的任务也不适合。Agent 的每一步都是大模型临时决定的输出存在随机性不可能像传统脚本那样精确到每一步必然执行什么。还有一类不适合涉及敏感数据、未授权信息获取、绕过访问控制的场景。这种不管技术可行性如何都不应该做。7.3 生产化之前要补什么如果你决定把一套 Agent 用到生产环境至少需要解决这几个问题日志和审计。Agent 每一步调用了什么工具、读了什么页面、做了什么判断都要留痕。不是管理员不信任而是出了问题要能回溯。资源回收。浏览器实例、Page 对象、临时文件用完必须释放。不做资源管理的 Agent 跑一天内存会吓到你。队列和限流。不要写一个无限并发的循环开跑。控制好并发数给每个任务设置超时时间。人工审核。Agent 的中间操作和最终结论在正式流程里建议有人审一遍。目前的大模型 Agent 还没有到“全自动无人值守”的火候把最终人工审核保留下来比任何参数调优都管用。成本控制。设置单次任务的 token 上限、工具调用次数上限。没有限制的 Agent可能在一次小任务里反复调用工具账单涨得比预期快。7.4 下一步进阶方向如果单 Agent 跑顺了准备做更复杂的流程可以考虑两条路径。一条是迁到 LangGraph把浏览器工具拆成多个节点通过状态图控制分支和人工介入。比如“先打开列表页再选择符合条件的详情页最后汇总生成报告”这种流程用 LangGraph 表达会更清晰。另一条是关注 MCP 这类协议。MCP 提供了一种统一的方式把浏览器能力、数据库、文件系统等外部资源暴露给模型。以后做 Agent 工具接入可能不再需要为每个框架单独写适配协议层能统一掉一部分工作。不管选哪条路我的建议都是先把当前这套简单组合用熟。先跑通最基础的信息获取场景再逐步加复杂度。工具越多、流程越长出问题的概率不是线性增长而是指数级增长。我个人更建议把单任务跑稳再考虑批量和接口。这个方案真正落地时最该盯住的不是它聊得有多智能而是每一步操作有没有记录、有没有超时、有没有把页面内容弄错。工具只是把流程自动化判断标准始终要放在输入是否清晰环境是否稳定结果是否可审计。
返回列表