AI Agent 代理配置:browser-use、Crawl4AI、MCP

运行在云服务器上的智能体从数据中心 IP 出口。本文给出 browser-use、Crawl4AI 和 MCP 服务器当前可用的代理配置,附粘性与轮换的决策表,以及需要避开的成本陷阱。

Proxy Setup for AI Agents: browser-use, Crawl4AI and an MCP Server
本文目录

运行在云服务器上的 AI 智能体会从数据中心 IP 出口。网站首先看到的就是这个 IP,于是智能体拿到的是错误国家的结果,撞上并非自己造成的频率限制,或者陷入无法脱身的 CAPTCHA 循环。好的 AI Agent 代理配置在浏览器配置里解决出口问题,而不是在提示词里。多步骤任务从头到尾保持同一个粘性 IP,并行抓取则轮换。下面给出 browser-use、Crawl4AI 和 MCP 服务器的可用配置,以及成本控制方法和最常见的故障模式。

为什么 AI Agent 的代理配置比脚本更重要

智能体需要代理的理由和爬虫一样,有三个:地理位置、频率限制和 IP 信誉。但智能体的需求更迫切,因为它们无人值守,并且自行决定是否重试。脚本被封就停了;智能体被封会再试一次、换个说法、打开新标签页再试,每次尝试都要消耗带宽和 LLM token。

  • 地理位置。价格、搜索结果、库存和法律声明都因国家而异。在美国东部的服务器上为德国客户比价的智能体,采集到的是错误数据。在代理用户名中指定国家即可解决,无需改动智能体逻辑。原理见地理定位代理的工作原理。
  • 云出口信誉。AWS、GCP、Azure 和 Hetzner 的 IP 段是公开的,IP 信誉服务会把它们评为托管(hosting),而非普通用户接入。许多网站一看到这些 IP 段就发起挑战或限速,不管请求本身是什么样子。评分机制见我们的 IP 信誉深度解析。
  • 共享频率限制。位于同一个 NAT 网关后的多个智能体共享同一个公网 IP。单看每一个都很克制,合在一起就像一个激进的客户端。

代理解决不了的问题:住宅 IP 并不会让自动化浏览器看起来像人。行为检测和指纹检测依然有效,我们的姊妹篇网站如何检测 AI 浏览器智能体说明了具体做法。代理的用途是获取正确的、地域准确的数据,并公平地分散负载,而不是用来绕过已经明确拒绝自动化访问的网站。

粘性会话还是轮换:按任务决定,而不是按项目

经验法则:任务有状态就固定 IP,没有状态就轮换。登录 → 浏览 → 加入购物车 → 结账这样的流程必须从头到尾保持同一个出口 IP,因为许多网站会把会话 Cookie、CSRF token 或风险评分绑定到开启会话的 IP 上。流程中途 IP 一变,你往往会被登出或重新挑战。相互独立的页面抓取正好相反:把它们分散到不同 IP 上,可以让每个 IP 都低于单 IP 限额。

使用 backconnect 网关时,只需修改用户名即可切换模式。USERNAME-sid-abc123-ttl-10m 只要你一直复用这个会话 ID,就保持同一个出口 IP。普通的 USERNAME 会轮换。USERNAME-country-US 固定国家。会话时长的详细说明见粘性会话与轮换会话指南。

智能体任务IP 模式原因
登录后在账户内操作粘性,每个账户一个会话 ID会话 Cookie 和风险评分通常与登录时的 IP 绑定。会话中途换 IP 看起来像账户被盗。
多页表单或结账测试整个流程使用粘性CSRF token 和购物车状态得以保留。失败的步骤可以从同一个 IP 重试。
研究型智能体阅读 50 个互不相关的页面轮换没有共享状态。分散请求可避免在任一域名上触发单 IP 限速。
为 RAG 入库做批量爬取轮换,或少量粘性槽位轮换分散负载。粘性槽位让 JS 密集型网站在几个页面之间保留 Cookie。
地域对比(同一 URL,多个国家)按国家定位,国家内轮换你要的是每个市场一个答案,而不是每个 IP 一个。
每 N 分钟监控一个页面轮换同一 IP 反复访问会形成模式。轮换让每个 IP 的负载保持在低位。

browser-use:当前 API 中的代理配置

browser-use 在 Browser(BrowserSession 的别名)上接受 proxy=ProxySettings(...) 参数,包含 server、username、password 和 bypass 字段。凭据交给浏览器,永远不会进入 LLM 上下文。在底层,browser-use 用 --proxy-server 启动 Chromium,并通过 CDP(Fetch.authRequired)应答代理的认证质询,因此你无需把凭据写进 URL。注意导入路径:ProxySettings 位于 browser_use.browser,而不在包的根目录(参见浏览器参数参考和 profile.py)。

import asyncio
import os
import uuid

from browser_use import Agent, Browser, ChatOpenAI
from browser_use.browser import ProxySettings

PROXY_USER = os.environ["PROXYHAT_USER"]
PROXY_PASS = os.environ["PROXYHAT_PASS"]


async def run_checkout_test() -> None:
    # 整个流程使用同一个粘性会话 ID,让登录 -> 购物车 -> 结账保持同一出口 IP。
    session_id = uuid.uuid4().hex[:12]

    browser = Browser(
        headless=True,
        proxy=ProxySettings(
            server="http://gate.proxyhat.com:8080",
            username=f"{PROXY_USER}-sid-{session_id}-ttl-10m",
            password=PROXY_PASS,
            bypass="localhost,127.0.0.1",
        ),
        # 把智能体限制在被测网站内;同时也限制了 sensitive_data 可以输入到哪里。
        allowed_domains=["https://staging.example-shop.com"],
        # Chromium 开关:完全不下载图片。
        args=["--blink-settings=imagesEnabled=false"],
    )

    agent = Agent(
        task="Log in with x_user / x_pass, add the cheapest USB-C cable to the cart, "
             "stop at the payment page and report the order total.",
        llm=ChatOpenAI(model=os.environ["OPENAI_MODEL"]),
        browser=browser,
        # 模型只看到占位符 x_user / x_pass,看不到真实值。
        sensitive_data={
            "https://staging.example-shop.com": {
                "x_user": os.environ["SHOP_USER"],
                "x_pass": os.environ["SHOP_PASS"],
            }
        },
        use_vision=False,   # 不在每一步截图:更省 token
        max_failures=2,     # 默认为 5;每次失败都意味着又一轮页面加载
    )
    history = await agent.run(max_steps=25)   # 默认为 500
    print(history.final_result())


asyncio.run(run_checkout_test())

关于这种写法的说明:

  • 带认证的代理请使用 HTTP 端口。Chromium 的网络栈文档写明 SOCKSv5 不支持任何认证方式,因此带凭据的 socks5://…:1080 在任何基于 Chromium 的智能体中都会失败。请使用 http://gate.proxyhat.com:8080。
  • 代理凭据不属于 sensitive_data。sensitive_data 用于智能体需要在页面中输入的值。代理凭据放在 ProxySettings 里,模型永远看不到。不要为了“让智能体知道”而把代理 URL 粘贴进 task 字符串。
  • allowed_domains 比看上去更重要。如果传入了 sensitive_data 却没有锁定域名,browser-use 会记录警告,因为遭受提示词注入的页面可能诱使智能体把机密输入到别处。
  • 云模式是另一种出口。Browser(use_cloud=True) 会在 Browser Use Cloud 上运行浏览器,使用其自带的代理。你的 ProxySettings 只作用于你自己启动的本地浏览器或 cdp_url 浏览器。
  • 默认扩展能省流量。enable_default_extensions 默认开启,会加载 uBlock Origin Lite 和一个 Cookie 横幅处理器,因此大多数广告和追踪器根本不会计入你的流量。

Crawl4AI:逐次爬取的代理、轮换与粘性会话

在 Crawl4AI 中,代理在 CrawlerRunConfig 上按每次爬取设置,可以用 proxy_config,也可以通过 proxy_rotation_strategy。代理文档将旧的 BrowserConfig proxy 参数标为已弃用,并建议按请求配置代理。若需粘性行为,proxy_session_id 会把策略中的同一个代理固定给所有共享该 ID 的爬取。

backconnect 网关只有一个主机,所以“一组代理”其实是一组会话用户名。每个会话 ID 是一个槽位,拥有自己的出口 IP,轮询策略把 URL 分散到各个槽位:

import asyncio
import os
import uuid

from crawl4ai import (AsyncWebCrawler, BrowserConfig, CacheMode,
                      CrawlerRunConfig, ProxyConfig, RoundRobinProxyStrategy)

USER = os.environ["PROXYHAT_USER"]
PASS = os.environ["PROXYHAT_PASS"]


def slot() -> ProxyConfig:
    # 每个槽位一个粘性会话,因此每个槽位保持一个出口 IP。
    sid = uuid.uuid4().hex[:10]
    return ProxyConfig(
        server="http://gate.proxyhat.com:8080",
        username=f"{USER}-sid-{sid}-ttl-10m",
        password=PASS,
    )


strategy = RoundRobinProxyStrategy([slot() for _ in range(8)])

browser_cfg = BrowserConfig(
    headless=True,
    text_mode=True,   # 尝试禁用图片及其他重量级内容
    avoid_ads=True,   # 屏蔽常见的广告/追踪域名
)

bulk_cfg = CrawlerRunConfig(
    proxy_rotation_strategy=strategy,   # 每个 URL 用下一个槽位
    cache_mode=CacheMode.BYPASS,
    check_robots_txt=True,
    max_retries=1,
)


async def main(urls: list[str]) -> None:
    async with AsyncWebCrawler(config=browser_cfg) as crawler:
        results = await crawler.arun_many(urls, config=bulk_cfg)
        for r in results:
            print(r.url, r.success, len(r.markdown or ""))

        # 有状态的流程:每一步使用相同的代理槽位,以及相同的浏览器页面。
        step_cfg = bulk_cfg.clone(proxy_session_id="acct-42", session_id="acct-42")
        await crawler.arun("https://example.com/login", config=step_cfg)
        await crawler.arun("https://example.com/account/orders", config=step_cfg)


asyncio.run(main(["https://example.com/a", "https://example.com/b"]))

有两个设置容易混淆。session_id 保持同一个浏览器页面,跨越多次 arun() 调用,proxy_session_id 则保持同一个代理。有状态的流程两者都需要。如果想按地域轮换而不是使用粘性槽位,就用单个 ProxyConfig,用户名设为 USERNAME-country-US。

一个重试放大的陷阱:proxy_config 也接受一个代理列表作为备用,而 async_webcrawler.py 中的重试循环在每次尝试时都会遍历整个列表。因此一个被封的 URL 最多可能被抓取 (max_retries + 1) × len(list) 次。备用代理有 10 个、max_retries=3 时,一个注定加载不了的 URL 就会产生 40 次完整页面加载。保持列表简短,并有意识地设置 max_retries。

为 MCP 助手提供走代理的网络访问

有两种方案:运行微软的 Playwright MCP 服务器并在配置中设置代理,或者写一个经代理抓取并返回文本的小型 MCP 工具。助手需要点击和输入时用第一种;只需阅读页面时用第二种,因为每次调用的成本低得多。

方案一:带认证代理的 Playwright MCP

@playwright/mcp 提供 --proxy-server 和 --proxy-bypass 参数(README)。对于需要凭据的代理,请改用 JSON 配置文件。其中的 browser.launchOptions 会直接传给 Playwright,而 Playwright 的 proxy 选项有独立的 username 和 password 字段:

{
  "browser": {
    "browserName": "chromium",
    "isolated": true,
    "launchOptions": {
      "headless": true,
      "proxy": {
        "server": "http://gate.proxyhat.com:8080",
        "username": "USERNAME-sid-mcp01-ttl-10m",
        "password": "PASSWORD"
      }
    }
  },
  "capabilities": ["core"]
}
npx @playwright/mcp@latest --config ./playwright-mcp.json --image-responses omit

不要把这个文件放进代码仓库,并确保只有运行服务器的用户可以读取。这里的会话 ID 意味着整个助手会话共享一个 IP,适合“登录后执行某操作”的场景。想换新 IP 时,用新的 ID 重启即可。--image-responses omit 阻止截图回传给模型。"isolated": true(即 --isolated 参数)把配置文件保存在内存中,避免 Cookie 在任务之间泄漏。README 还指出,--allowed-origins 和 --blocked-origins 不是安全边界。把它们当作卫生措施,而不是防护。

方案二:用官方 MCP Python SDK 写一个最小的代理抓取工具

Python SDK 的 v2 把 FastMCP 重命名为 MCPServer(迁移指南)。为 v1 编写、使用 from mcp.server.fastmcp import FastMCP 的代码需要改用新的导入。下面是一个只读工具,它经网关抓取页面,限制响应大小,并且绝不在输出中包含代理 URL:

import os
import re

import httpx
from mcp.server import MCPServer

mcp = MCPServer("proxied-fetch")

_USER = os.environ["PROXYHAT_USER"]
_PASS = os.environ["PROXYHAT_PASS"]
_MAX_BYTES = 1_500_000
_COUNTRY = re.compile(r"^[A-Za-z]{2}$")
_SESSION = re.compile(r"^[A-Za-z0-9]{1,24}$")


def _proxy(country: str | None, session: str | None) -> str:
    user = _USER
    # 校验模型提供的值:它们最终会进入代理用户名。
    if session:
        if not _SESSION.match(session):
            raise ValueError("session must be 1-24 alphanumerics")
        user = f"{_USER}-sid-{session}-ttl-10m"
    elif country:
        if not _COUNTRY.match(country):
            raise ValueError("country must be a 2-letter code")
        user = f"{_USER}-country-{country.upper()}"
    return f"http://{user}:{_PASS}@gate.proxyhat.com:8080"


@mcp.tool()
async def fetch_page(url: str, country: str | None = None, session: str | None = None) -> str:
    """Fetch a public http(s) page through a residential proxy and return its text.

    country: optional 2-letter exit country. session: optional ID to reuse one exit IP.
    """
    if not url.startswith(("http://", "https://")):
        return "error: only http(s) URLs are allowed"
    try:
        async with httpx.AsyncClient(proxy=_proxy(country, session), timeout=30,
                                     follow_redirects=True) as client:
            async with client.stream("GET", url) as resp:
                body = b""
                async for chunk in resp.aiter_bytes():
                    body += chunk
                    if len(body) > _MAX_BYTES:
                        break
        text = body.decode(resp.encoding or "utf-8", errors="replace")
        return f"HTTP {resp.status_code} {resp.url}\n\n{text[:200_000]}"
    except ValueError as e:
        return f"error: {e}"
    except httpx.HTTPError as e:
        # 只返回异常类型:某些异常消息可能回显代理 URL。
        return f"error: fetch failed ({type(e).__name__})"


if __name__ == "__main__":
    mcp.run(transport="stdio")

在你的 MCP 客户端中把它注册为 stdio 服务器。通过客户端的 env 配置块传入 PROXYHAT_USER 和 PROXYHAT_PASS,绝不要通过工具参数传递。以 Claude Code 为例,命令是 claude mcp add --env PROXYHAT_USER=… --env PROXYHAT_PASS=… --transport stdio proxied-fetch -- python server.py。--transport 要放在最后一个 --env 和服务器名称之间,否则 CLI 会把名称当作另一组环境变量。正则校验物有所值:遭受提示词注入的页面可能让模型用奇怪的参数调用工具,不加校验的输入会让它把任意文本写进你的代理用户名。需要 JavaScript 的页面请使用方案一;如果你要自己构建浏览器层,可以参考我们的 Playwright 轮换代理指南。

成本控制:智能体消耗带宽的方式与脚本不同

住宅流量按 GB 计费,而智能体加载完整页面,每一步往往加载好几次。以下控制措施效果最显著,大致按重要性排序:

  1. 模型不读的内容就不要加载。图片、视频、字体和广告脚本占了现代网页的大部分体积。在 Crawl4AI 中使用 text_mode=True 和 avoid_ads=True,在 browser-use 中使用 imagesEnabled=false 开关或 uBlock;页面是服务端渲染时,用抓取工具代替浏览器。
  2. 在两个地方限制重试。框架重试(max_failures、max_retries)和智能体层面的重新规划会相互放大。遇到 403 后“换个思路”的智能体,往往只是重新加载同一个被封的页面。把 max_steps 设得足够低,让卡住的运行以低成本结束。
  3. 有状态流程中复用上下文。新的浏览器上下文意味着新的 Cookie、新的挑战,以及重新下载每一个已缓存的资源。在同一个任务内,保持上下文(以及粘性 IP)存活。
  4. 遇到封锁信号就停止。把 CAPTCHA 页面、机器人管理厂商返回的 403 或 429 视为终止状态,上报给人工处理。不要让智能体靠重试硬闯。退避的理由见我们的 CAPTCHA 处理指南。
  5. 按任务计量,而不是按月。记录每次智能体运行的字节数。突然跳升通常意味着出现了循环,而不是任务变大了。

值得提前设计应对的故障模式

大多数智能体代理事故都源于四个原因之一:CAPTCHA 循环、重试放大、会话悄悄换了 IP,或凭据泄漏进模型上下文。

  • CAPTCHA 循环。启用视觉的智能体会尝试“解”挑战,失败、重新加载、再试。请识别挑战页面(已知的 iframe 主机、“Just a moment…”这类标题)并结束运行。大规模应答挑战不是正当的数据管道该做的事,而且同一住宅 IP 上的反复失败会损害该 IP 的信誉,影响所有使用它的人。
  • 重试放大。前文已述:框架重试 × 代理备用列表 × 智能体重新规划。要关注每个 URL 的总尝试次数,而不仅仅是失败次数。
  • 会话悄然中断。粘性会话在 TTL 到期时结束,如果上游节点下线还可能提前结束;之后的请求就会从新的 IP 发出。如果流程中途的某个请求突然重定向到登录页,就假定 IP 已经变了。用新的会话 ID 重新开始流程,而不是让智能体即兴发挥。
  • 凭据进入 LLM 上下文。任何到达模型的内容都可能出现在日志、追踪、评测数据集里,或者被提示词注入的外传请求带走。把代理凭据放在由浏览器或 HTTP 客户端读取的环境变量中。不要放进任务字符串、系统提示词或工具输出,返回异常消息前要先清理。Playwright MCP 的 secrets 选项可以从工具响应中遮盖已知字符串,但它自己的 README 称这只是便利功能,而非安全特性。

在怪罪智能体之前,先单独测试代理:用代理检测工具测试你配置的用户名变体,确认出口国家和 IP 类型符合预期。关于智能体工作负载的 IP 类型的更全面对比,请参阅适用于 AI 智能体与 LLM 网页数据采集的最佳代理。

这套配置解决不了什么

代理改变的是流量从哪里来,而不是流量的行为方式。它不会让智能体通过行为类机器人检测,也不等于获得了访问那些在 robots.txt 或服务条款中屏蔽自动化的网站的许可。它同样不会让采集个人数据变得合法。如果网站提供 API、数据源或签名智能体计划,这些途径通常比任何代理配置都更便宜、更可靠。把住宅 IP 用在它擅长的地方:正确的地理位置、公平的负载分配,以及不继承云 IP 段的不良信誉。

常见问题

browser-use 怎么加代理?

向 Browser 传入 proxy=ProxySettings(server=..., username=..., password=...),ProxySettings 从 browser_use.browser 导入。browser-use 会用 --proxy-server 启动 Chromium,并通过 CDP 应答代理的认证质询,因此凭据永远不会进入 LLM。请使用 HTTP 代理端点,因为 Chromium 不支持 SOCKS5 认证。

AI 智能体应该用轮换代理还是粘性代理?

凡是有状态的操作,例如登录、多页表单或结账,都应使用粘性会话,因为许多网站会把 Cookie 和风险评分绑定到开启会话的 IP 上。研究型阅读或批量爬取这类相互独立的抓取则适合轮换,把请求分散开,可以让每个 IP 都低于单站限额。

Crawl4AI 如何轮换代理?

创建一组 ProxyConfig 对象,用 RoundRobinProxyStrategy 包装,然后作为 proxy_rotation_strategy 传给 CrawlerRunConfig。如果只有一个 backconnect 网关,就让每个 ProxyConfig 使用不同的会话用户名,这样每个都有自己的出口 IP。多步骤流程需要固定同一个代理时,加上 proxy_session_id。

能让 Claude 或其他 MCP 助手通过代理上网吗?

可以。运行 Playwright MCP,并在配置文件的 browser.launchOptions.proxy 中填写 server、username 和 password;或者用 Python SDK 的 MCPServer 类写一个小型 MCP 工具,通过 httpx 经代理抓取页面。两种方式都要把代理凭据放在环境变量或配置文件里,不要放进提示词或工具输出。

为什么 AI 智能体消耗这么多代理流量?

智能体加载完整页面,而且每一步往往加载好几次,重试还会层层叠加:框架重试、代理备用列表和智能体自身的重新规划相互放大。屏蔽图片、字体和广告,限制 max_steps 和重试次数,复用浏览器上下文,并在遇到 CAPTCHA 页面时停止,可以消除大部分浪费。

准备好开始了吗?

覆盖 195+ 国家的住宅、ISP 和移动代理。创建免费账户。

创建免费账户
← 返回博客