第 25 期 | 网络代理与爬虫 MCP (WebFetch / Playwright)
副标题/大纲:越过本地磁盘:让 Claude Code 在外网上收集参考资料后注入代码项目。
🎯 学习目标
- 理解网络代理在爬虫中的重要性、工作原理及其在 Claude Code 中的应用场景。
- 掌握 Claude Code 中通过
WebFetch工具进行轻量级、静态网页内容抓取的方法。 - 学习如何利用
Playwright工具进行复杂的动态网页交互、数据提取与自动化操作。 - 了解 Claude Code 如何整合这些外部数据收集能力,实现从互联网获取信息并注入到代码项目中的高级工作流。
📖 核心概念讲解
25.1 网络代理与爬虫伦理:数据获取的基石与边界
在进行大规模网络数据抓取时,直接使用本地 IP 地址往往会遇到诸多限制,例如:
- IP 封禁: 目标网站可能会识别并封禁来自同一 IP 地址的频繁访问。
- 频率限制: 网站对单个 IP 的访问频率设有阈值,超出即会触发验证码或临时封禁。
- 地理限制: 部分内容或服务仅对特定地区开放。
网络代理服务器(Proxy Server)正是为了解决这些问题而生。它充当客户端(我们的爬虫)和目标服务器之间的中间人,所有的请求都通过代理服务器转发。
+--------------------+ +-----------------------+ +--------------------+
| Claude Code / | | | | |
| 你的爬虫程序 |----->| 代理服务器 (Proxy) |----->| 目标网站服务器 |
| (Client) | | | | (Target Server) |
+--------------------+ +-----------------------+ +--------------------+
^ ^
| |
+-- 客户端看到的是代理IP --------+-- 目标服务器看到的是代理IP
代理的类型:
- HTTP/HTTPS 代理: 最常见,用于网页浏览。
- SOCKS 代理: 更底层,支持 TCP/UDP,可用于非 HTTP 协议的流量。
- 透明代理: 目标网站知道你使用了代理且知道你的真实 IP。
- 匿名代理: 目标网站知道你使用了代理,但不知道你的真实 IP。
- 高匿名代理: 目标网站甚至不知道你使用了代理,认为你是直接访问。
爬虫伦理与法律边界: 尽管代理提供了便利,但进行网络爬取时必须严格遵守伦理和法律规定:
robots.txt协议: 任何负责任的爬虫都应首先检查目标网站的robots.txt文件,并遵守其中定义的抓取规则(哪些路径不允许抓取、哪些用户代理不允许抓取等)。- 抓取频率: 模拟人类访问行为,避免在短时间内对服务器造成过大压力,否则可能被视为拒绝服务攻击。
- 数据用途: 明确收集数据的合法用途,不得用于侵犯隐私、版权或商业秘密。
- 登录与认证: 未经授权不得绕过登录机制,除非是抓取公开数据。
- 条款与条件: 遵守网站的服务条款。
Claude Code 在执行网络抓取任务时,会内置对 robots.txt 的初步检查,并提供配置选项来控制抓取频率和代理使用,以帮助用户在合规的前提下高效工作。
25.2 WebFetch:Claude Code 的轻量级网络抓取器
WebFetch 是 Claude Code 内部封装的一个轻量级、高效的网络请求工具,它旨在简化对静态网页内容的获取。你可以将其理解为 curl 或 Python requests 库的高级抽象,专门为 Claude 的任务执行环境优化。
核心特性:
- 简洁的 API: 通过简单的 JSON 或 YAML 配置即可发起 HTTP/HTTPS 请求。
- 支持代理: 可轻松集成代理服务器,实现匿名访问或 IP 轮换。
- 请求头定制: 允许自定义 User-Agent、Referer 等 HTTP 请求头,模拟不同客户端行为。
- 超时控制: 防止请求无限期等待。
- 自动重试: 在网络不稳定的情况下,可配置自动重试机制。
典型应用场景:
- 抓取博客文章、新闻页面等静态 HTML 内容。
- 获取 API 接口返回的 JSON 或 XML 数据。
- 检测网站的可访问性或特定内容的更新。
WebFetch 工具定义示例 (假设 Claude Code 的工具配置是 YAML 格式):
tools:
- name: WebFetch
description: "用于通过 HTTP/HTTPS 请求获取网页内容或API数据。"
parameters:
type: object
properties:
url:
type: string
description: "目标URL。"
method:
type: string
enum: ["GET", "POST", "PUT", "DELETE"]
default: "GET"
description: "HTTP 请求方法。"
headers:
type: object
description: "自定义HTTP请求头,键值对形式。"
data:
type: string
description: "POST/PUT请求体数据,可以是JSON字符串或表单数据。"
proxy:
type: string
description: "代理服务器地址,格式如 'http://user:pass@host:port' 或 'socks5://host:port'。"
timeout:
type: integer
default: 30
description: "请求超时时间(秒)。"
follow_redirects:
type: boolean
default: true
description: "是否跟随HTTP重定向。"
required: ["url"]
25.3 Playwright:应对动态网页的瑞士军刀
当目标网页大量依赖 JavaScript 进行内容渲染(如单页应用 SPA)或需要复杂的用户交互(如点击按钮、填写表单、滚动页面加载更多内容)时,仅仅使用 WebFetch 这样的 HTTP 客户端是无法满足需求的。这时,我们就需要一个无头浏览器(Headless Browser)。
Playwright 是 Microsoft 开发的一个强大的无头浏览器自动化库,它支持 Chromium (Chrome)、Firefox 和 WebKit (Safari) 三大浏览器引擎,并提供了统一的 API。
核心特性:
- 多浏览器支持: 在真实浏览器环境中运行测试或爬虫。
- 无头模式: 可以在后台运行浏览器,无需显示图形界面,节省资源。
- 丰富的交互 API:
- 页面导航 (
page.goto()) - 元素选择 (
page.locator(),page.$()) - 点击 (
element.click()) - 输入文本 (
element.fill()) - 键盘/鼠标操作
- 等待元素出现/网络请求完成 (
page.waitForSelector(),page.waitForLoadState()) - 截图 (
page.screenshot())、生成 PDF (page.pdf())
- 页面导航 (
- 网络拦截: 可以拦截、修改或阻止网络请求,例如加载代理、注入脚本、阻止图片加载等。
- 上下文隔离: 每个
BrowserContext都是独立的,拥有自己的缓存、cookie 和本地存储,互不影响。
Playwright 在 Claude Code 中的集成:
Claude Code 可以通过一个专门的 Playwright 工具或 browser_automation 工具来调用 Playwright 的能力。用户通过提供一个描述任务的自然语言指令,或者一个预定义的 Playwright 脚本,让 Claude 来执行。
Playwright 工具定义示例 (假设 Claude Code 的工具配置):
tools:
- name: Playwright
description: "用于在无头浏览器中执行复杂网页交互和动态内容抓取。"
parameters:
type: object
properties:
script:
type: string
description: "要执行的Playwright Python脚本代码字符串。"
url:
type: string
description: "可选:初始导航URL,如果脚本未指定。"
browser:
type: string
enum: ["chromium", "firefox", "webkit"]
default: "chromium"
description: "使用的浏览器类型。"
headless:
type: boolean
default: true
description: "是否在无头模式下运行浏览器。"
proxy:
type: string
description: "代理服务器地址,格式如 'http://user:pass@host:port'。"
args:
type: array
items:
type: string
description: "传递给浏览器启动的额外参数,例如 '--disable-gpu'。"
context_options:
type: object
description: "Playwright BrowserContext 选项,如 user_agent, locale 等。"
required: ["script"]
通过 Playwright,Claude Code 能够模拟真实用户行为,绕过 JavaScript 渲染障碍,从而获取到最接近用户浏览器所见的内容,极大地扩展了 Claude 的信息收集能力。
💻 模拟 Claude TUI 交互
场景 1:使用 WebFetch 快速抓取特定技术博客的最新文章标题
我需要获取
https://www.example-tech-blog.com/网站上关于 "AI" 标签的最新五篇文章标题和链接。
╭─ Claude ──────────────────────────────╮
│ 好的,我将使用 WebFetch 工具尝试抓取该博客的 "AI" 标签页,│
│ 然后解析 HTML 内容以提取标题和链接。如果该页面是动态渲染的,│
│ 我可能需要切换到 Playwright。请稍等... │
│ │
│ 工具调用: WebFetch │
│ json │ │ { │ │ "url": "https://www.example-tech-blog.com/tags/AI", │ │ "method": "GET", │ │ "headers": { │ │ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"│ │ } │ │ } │ │ │
│ │
│ 工具输出: WebFetch (部分 HTML 内容) │
│ html │ │ <!DOCTYPE html> │ │ <html lang="zh"> │ │ <head>...</head> │ │ <body> │ │ <div id="posts-list"> │ │ <article class="post-item"> │ │ <h2><a href="/article/ai-future-trends">AI 未来趋势分析</a></h2>│ │ <p>...</p> │ │ </article> │ │ <article class="post-item"> │ │ <h2><a href="/article/llm-development">大型语言模型开发实践</a></h2>│ │ <p>...</p> │ │ </article> │ │ <!-- 更多文章 --> │ │ </div> │ │ </body> │ │ </html> │ │ │
│ │
│ 已成功抓取并解析内容。以下是关于 "AI" 的最新五篇文章: │
│ │
│ 1. AI 未来趋势分析 │
│ 链接: https://www.example-tech-blog.com/article/ai-future-trends│
│ 2. 大型语言模型开发实践 │
│ 链接: https://www.example-tech-blog.com/article/llm-development│
│ 3. 深度学习在图像识别中的应用 │
│ 链接: https://www.example-tech-blog.com/article/deep-learning-image-recognition│
│ 4. 强化学习入门指南 │
│ 链接: https://www.example-tech-blog.com/article/reinforcement-learning-guide│
│ 5. AI 伦理与治理的挑战 │
│ 链接: https://www.example-tech-blog.com/article/ai-ethics-challenges│
│ │
│ 您希望我将这些信息注入到您的项目文档中,还是用于生成代码? │
╰───────────────────────────────────────╯
场景 2:使用 Playwright 登录并抓取需要认证的用户仪表盘数据 (假设已获授权)
我需要从
https://my-dashboard.secure-app.com/上的个人仪表盘抓取我的项目列表。我的用户名是myuser,密码是mypassword。请使用代理http://proxy.example.com:8080。
╭─ Claude ──────────────────────────────╮
│ 好的,我将使用 Playwright 模拟登录并抓取您的仪表盘数据。 │
│ 请注意,在实际操作中,泄露密码是危险的。通常我会建议您使用 │
│ 环境变量或安全凭据管理系统来传递敏感信息。本次操作我会直接使用 │
│ 您提供的凭据和代理。 │
│ │
│ 工具调用: Playwright │
│ json │ │ { │ │ "browser": "chromium", │ │ "headless": true, │ │ "proxy": "http://proxy.example.com:8080", │ │ "script": """ │ │import asyncio │ │from playwright.async_api import Page, expect, BrowserContext, request as playwright_request, TimeoutError # 导入 TimeoutError │ │ │ │async def run(page: Page, context: BrowserContext): │ │ try: │ │ print("Navigating to login page...") │ │ await page.goto("https://my-dashboard.secure-app.com/login", wait_until="domcontentloaded")│ │ await page.wait_for_selector('input[name="username"]', timeout=10000) # 等待用户名输入框出现 │ │ │ │ print("Filling credentials...") │ │ await page.fill('input[name="username"]', "myuser") │ │ await page.fill('input[name="password"]', "mypassword")│ │ │ │ print("Clicking login button...") │ │ await page.click('button[type="submit"]') │ │ │ │ print("Waiting for dashboard to load...") │ │ await page.wait_for_url("https://my-dashboard.secure-app.com/dashboard", timeout=30000) # 等待跳转到仪表盘页面 │ │ await page.wait_for_load_state("networkidle") │ │ │ │ print("Extracting project list...") │ │ project_elements = await page.locator('.project-item').all()│ │ projects = [] │ │ for el in project_elements: │ │ title = await el.locator('.project-title').text_content()│ │ status = await el.locator('.project-status').text_content()│ │ projects.append({"title": title.strip(), "status": status.strip()})│ │ │ │ print(f"Extracted {len(projects)} projects.") │ │ return {"projects": projects} │ │ except TimeoutError: │ │ print("Playwright operation timed out.") │ │ return {"error": "Playwright operation timed out. Check selectors or network."}│ │ except Exception as e: │ │ print(f"An error occurred: {e}") │ │ return {"error": str(e)} │ │ │ │""" │ │ } │ │ │
│ │
│ 工具输出: Playwright (JSON 结果) │
│ json │ │ { │ │ "projects": [ │ │ {"title": "Website Redesign Project", "status": "进行中"},│ │ {"title": "Mobile App v2.0", "status": "已完成"}, │ │ {"title": "Backend API Optimization", "status": "待开始"} │ │ ] │ │ } │ │ │
│ │
│ 成功从您的仪表盘获取了项目列表: │
│ │
│ - Website Redesign Project: 进行中 │
│ - Mobile App v2.0: 已完成 │
│ - Backend API Optimization: 待开始 │
│ │
│ 您希望我根据这些项目信息做些什么?例如,生成项目状态报告,或者为 │
│ "进行中" 的项目创建 Jira 任务? │
╰───────────────────────────────────────╯
💻 代码演示
以下是一些在 Claude Code 环境中,WebFetch 和 Playwright 工具可能被调用的实际配置或脚本示例。
1. WebFetch 调用示例 (获取 JSON API 数据,带代理和自定义 User-Agent)
假设你需要从一个公共 API 获取数据,并希望通过代理隐藏真实 IP,同时模拟浏览器请求。
{
"tool_name": "WebFetch",
"parameters": {
"url": "https://api.public-data-source.com/v1/latest-news?category=technology",
"method": "GET",
"headers": {
"Accept": "application/json",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
"X-Requested-With": "XMLHttpRequest"
},
"proxy": "http://user:[email protected]:8888",
"timeout": 45
}
}
2. Playwright 脚本示例 (模拟用户登录、搜索并截图)
这个 Python 脚本展示了如何使用 Playwright 启动一个浏览器,导航到登录页,输入凭据,执行搜索,并截取搜索结果页面的图片。Claude Code 将执行这个脚本并返回结果。
# playwright_script.py (此脚本将作为字符串传递给 Claude 的 Playwright 工具)
import asyncio
from playwright.async_api import Page, expect, BrowserContext, request as playwright_request, TimeoutError
async def run(page: Page, context: BrowserContext):
"""
此函数将在Playwright的Page和BrowserContext中执行。
"""
try:
# 1. 导航到登录页面
print("Navigating to login page...")
await page.goto("https://www.my-internal-tool.com/login", wait_until="domcontentloaded")
await page.wait_for_selector('input[name="username"]', timeout=15000)
# 2. 填写登录凭据
print("Filling credentials...")
await page.fill('input[name="username"]', "my_secure_username")
await page.fill('input[name="password"]', "my_secure_password_from_env") # 假设密码从环境变量或安全存储中获取
# 3. 点击登录按钮
print("Clicking login button...")
await page.click('button[type="submit"]')
# 4. 等待跳转到内部仪表盘或主页
print("Waiting for dashboard to load...")
await page.wait_for_url("https://www.my-internal-tool.com/dashboard", timeout=30000)
await page.wait_for_load_state("networkidle") # 等待所有网络请求完成
# 5. 执行搜索操作
print("Performing search...")
search_input = page.locator('input[placeholder="Search projects..."]')
await search_input.fill("critical bug")
await search_input.press("Enter") # 模拟按下回车键
# 6. 等待搜索结果加载
print("Waiting for search results...")
await page.wait_for_selector('.search-results-list', timeout=20000)
await page.wait_for_load_state("networkidle")
# 7. 截图保存结果
screenshot_path = "search_results.png"
await page.screenshot(path=screenshot_path, full_page=True)
print(f"Screenshot saved to {screenshot_path}")
# 8. 提取搜索结果的标题和链接
result_elements = await page.locator('.search-results-list .result-item h3 a').all()
results = []
for el in result_elements:
title = await el.text_content()
href = await el.get_attribute('href')
results.append({"title": title.strip(), "link": href})
return {"status": "success", "screenshot": screenshot_path, "search_results": results}
except TimeoutError:
print("Playwright operation timed out.")
return {"status": "error", "message": "Playwright operation timed out. Check selectors or network issues."}
except Exception as e:
print(f"An unexpected error occurred: {e}")
return {"status": "error", "message": str(e)}
# 在 Claude Code 的 tool_name: Playwright 中,这个脚本会作为 "script" 参数的字符串值传入。
# Claude 会在后台执行这个脚本,并将 run 函数的返回值作为工具输出。
3. Claude Code 调用 Playwright 工具的 JSON 配置
当用户通过自然语言指令请求 Claude 执行复杂网页操作时,Claude 可能会生成如下的 Playwright 工具调用:
{
"tool_name": "Playwright",
"parameters": {
"browser": "chromium",
"headless": true,
"proxy": "http://myproxy.com:8080",
"script": "import asyncio\nfrom playwright.async_api import Page, expect, BrowserContext, request as playwright_request, TimeoutError\n\nasync def run(page: Page, context: BrowserContext):\n try:\n print(\"Navigating to login page...\")\n await page.goto(\"https://www.my-internal-tool.com/login\", wait_until=\"domcontentloaded\")\n await page.wait_for_selector('input[name=\"username\"]', timeout=15000)\n\n print(\"Filling credentials...\")\n await page.fill('input[name=\"username\"]', \"my_secure_username\")\n await page.fill('input[name=\"password\"]', \"my_secure_password_from_env\")\n\n print(\"Clicking login button...\")\n await page.click('button[type=\"submit\"]')\n\n print(\"Waiting for dashboard to load...\")\n await page.wait_for_url(\"https://www.my-internal-tool.com/dashboard\", timeout=30000)\n await page.wait_for_load_state(\"networkidle\")\n\n print(\"Performing search...\")\n search_input = page.locator('input[placeholder=\"Search projects...\"]')\n await search_input.fill(\"critical bug\")\n await search_input.press(\"Enter\")\n\n print(\"Waiting for search results...\")\n await page.wait_for_selector('.search-results-list', timeout=20000)\n await page.wait_for_load_state(\"networkidle\")\n\n screenshot_path = \"search_results.png\"\n await page.screenshot(path=screenshot_path, full_page=True)\n print(f\"Screenshot saved to {screenshot_path}\")\n\n result_elements = await page.locator('.search-results-list .result-item h3 a').all()\n results = []\n for el in result_elements:\n title = await el.text_content()\n href = await el.get_attribute('href')\n results.append({\"title\": title.strip(), \"link\": href})\n\n return {\"status\": \"success\", \"screenshot\": screenshot_path, \"search_results\": results}\n\n except TimeoutError:\n print(\"Playwright operation timed out.\")\n return {\"status\": \"error\", \"message\": \"Playwright operation timed out. Check selectors or network issues.\"}\n except Exception as e:\n print(f\"An unexpected error occurred: {e}\")\n return {\"status\": \"error\", \"message\": str(e)}\n"
}
}
🔧 涉及的 Tools / Commands
| 工具名称 | 用途 | 说明