第 3 课

把 Scrapling 交给 AI 智能体:MCP stdio 与 HTTP

按官方文档把 scrapling-mcp 配进 Claude Desktop、Claude Code、Cursor,用 Python MCP SDK 2.x 写客户端走 stdio 列出 16 个工具,按顺序调用 make_request、browser_open、browser_fetch、browser_actions、brows

图文20 分钟Scrapling 官方文档 ↗

课程目录第 3 / 3 课

学习位置仅保存在当前浏览器,有效期 180 天。

01 / 图文教材

图文教材

本课对照的官方页面:MCP 服务器指南、MCP 工具参考。命令与输出在 Linux x86_64 云主机上实测(Scrapling 0.5,随 scrapling[ai] 安装的 Python MCP SDK 2.3.0),输出为节选。

前两课都是你自己写代码抓取。这一课把 Scrapling 作为 MCP 服务器交给 AI 应用(Claude Desktop、Claude Code、Cursor 等),让智能体自己决定用 HTTP 请求还是浏览器、什么时候点按钮和填表。测试机上没有桌面 AI 应用,所以第 2~4 步用一个 Python 脚本扮演 AI 客户端,把智能体会做的调用逐个跑一遍。

第 1 步:把 scrapling-mcp 配进 AI 应用(未实测)

本步按官方文档整理,测试机没有这些桌面应用。

上一课装的 scrapling[all] 已经包含 MCP 依赖;只想用 MCP 的话,装 scrapling[ai] 再运行 scrapling install 即可。scrapling-mcp 是 0.4.13 起提供的快捷命令,等同于 scrapling mcp。

先查到它的完整路径(Windows 用 where scrapling-mcp):

which scrapling-mcp

对照结果(测试机,实测):

/home/box/scrapling-lab/.venv/bin/scrapling-mcp

Claude Desktop:菜单 → Settings → Developer → Edit Config,写入下面的配置,把路径换成你自己的,然后完全退出并重启 Claude Desktop:

{
  "mcpServers": {
    "ScraplingServer": {
      "command": "/home/box/scrapling-lab/.venv/bin/scrapling-mcp"
    }
  }
}

配置文件位置:

系统文件
macOS~/Library/Application Support/Claude/claude_desktop_config.json
Windows%APPDATA%\Claude\claude_desktop_config.json

Claude Code 用一条命令添加:

claude mcp add ScraplingServer \
  "/home/box/scrapling-lab/.venv/bin/scrapling-mcp"

官方说明同样的配置也适用于 Cursor、Windsurf 等支持 MCP 的应用。用 Docker 的话,command 写 docker,args 写 ["run", "-i", "--rm", "pyd4vinci/scrapling", "mcp"]。

上面 which 的结果里,路径 /home/box/scrapling-lab 是本课的练习目录,你的会不一样。

第 2 步:用 Python 脚本连上服务器,列出 16 个工具

scrapling-mcp 默认走 stdio:AI 应用把它当子进程启动,通过标准输入输出通信。下面的脚本做同样的事。新建 list_tools.py:

import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

server = StdioServerParameters(command="scrapling-mcp")

async def main():
    async with stdio_client(server) as (read, write):
        async with ClientSession(read, write) as s:
            info = await s.initialize()
            print(info.server_info.name, info.server_info.version)
            tools = (await s.list_tools()).tools
            print(len(tools), "tools")
            for t in tools:
                print("-", t.name)

asyncio.run(main())
python list_tools.py

对照结果(工具名合并成多行显示):

Scrapling 0.5
16 tools
- browser_open  - browser_save_state  - browser_load_state
- open_request_session  - close_session  - list_sessions
- make_request  - browser_fetch_once  - browser_fetch
- session_make_request  - browser_extract  - browser_actions
- browser_evaluate  - browser_network_requests
- browser_network_request  - browser_screenshot

实际输出是每行一个工具名,共 16 行。注意 MCP SDK 2.x 的属性名是下划线写法(server_info、input_schema);网上很多 1.x 示例写成 serverInfo、inputSchema,在 2.3.0 上会报 AttributeError。

按官方文档,16 个工具分成这几组:

用途工具
一次性抓取make_request(HTTP)、browser_fetch_once(浏览器)
会话管理browser_open、open_request_session、close_session、list_sessions
通过会话抓取browser_fetch、session_make_request
读当前页面browser_extract、browser_screenshot、browser_evaluate
页面操作browser_actions(点击、填表、按键、等待)
网络记录browser_network_requests、browser_network_request
登录状态browser_save_state、browser_load_state

一次性工具抓完就关闭自己的浏览器或连接;会话工具保留 cookie 和设置,适合连续多步操作。官方还说明,浏览器类工具会自动屏蔽约 3500 个广告和跟踪域名,并清理隐藏元素、零宽字符、HTML 注释等可能藏提示词注入的内容。

第 3 步:跑一遍智能体的调用:抓列表、开浏览器、登录

下面的脚本按顺序调用 6 个工具:先用 make_request 只取作者名;再开一个浏览器会话,打开练习站登录页,用 browser_actions 填用户名密码并点登录,用 browser_extract 读页头确认已登录,最后关闭会话。练习站的登录页接受任意用户名和密码。新建 agent_flow.py:

import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

server = StdioServerParameters(command="scrapling-mcp")


def show(title, result, limit=300):
    text = "".join(c.text for c in result.content if c.type == "text")
    print(f"== {title} (isError={result.is_error})")
    print(text[:limit])


async def main():
    async with stdio_client(server) as (read, write):
        async with ClientSession(read, write) as s:
            await s.initialize()

            # 1. 一次性 HTTP 请求,只取作者名
            r = await s.call_tool("make_request", {
                "url": "https://quotes.toscrape.com/",
                "css_selector": ".quote .author",
                "extraction_type": "text",
            })
            show("make_request", r)

            # 2. 打开浏览器会话,登录后读取页面
            r = await s.call_tool("browser_open", {})
            show("browser_open", r)
            sid = r.structured_content["session_id"]

            r = await s.call_tool("browser_fetch", {
                "session_id": sid,
                "url": "https://quotes.toscrape.com/login",
                "extraction_type": "markdown",
            })
            show("browser_fetch", r, 200)

            r = await s.call_tool("browser_actions", {
                "session_id": sid,
                "actions": [
                    {"type": "textbox", "target": "#username",
                     "value": "kun"},
                    {"type": "textbox", "target": "#password",
                     "value": "demo"},
                    {"type": "click", "target": "input[type=submit]"},
                    {"type": "wait_load", "state": "load"},
                ],
            })
            show("browser_actions", r)

            r = await s.call_tool("browser_extract", {
                "session_id": sid,
                "extraction_type": "text",
                "target": ".header-box",
            })
            show("browser_extract", r)

            r = await s.call_tool("close_session", {"session_id": sid})
            show("close_session", r)


asyncio.run(main())
python agent_flow.py

对照结果(已去掉 INFO 日志行,作者列表与 Markdown 有删节,测试机全程约 4.5 秒):

== make_request (isError=False)
{
  "status": 200,
  "content": [
    "Albert Einstein",
    "J.K. Rowling",
    ...
    "Steve Martin",
    ""
  ],
  "url": "https://quotes.toscrape.com
== browser_open (isError=False)
{
  "session_id": "38d40da85588",
  "session_type": "stealthy"
}
== browser_fetch (isError=False)
{
  "status": 200,
  "content": [
    "[Quotes to Scrape](/)\n=====...\n\n[Login](/login)\n\nUsername ...
== browser_actions (isError=False)
Actions completed.
== browser_extract (isError=False)
Quotes to Scrape
Logout
== close_session (isError=False)
{
  "session_id": "38d40da85588",
  "message": "Session '38d40da85588' closed successfully."
}

解读:

  • make_request 按 css_selector 只返回 10 位作者名,列表末尾多一个空字符串 "",是实测原样输出,处理时过滤掉即可。
  • browser_open 只返回 session_id,后面的会话工具都靠它找到同一个浏览器标签页。session_id 每次随机生成。
  • browser_actions 一次调用里连做了填两个输入框、点击、等待加载四件事,省去智能体来回好几轮。动作类型有 textbox、click、wait_load、wait_element、press_key、combobox(下拉框)、checkbox 等,完整字段见工具参考。
  • 登录后页头从 Login 变成了 Logout,说明 cookie 保存在会话里。
  • 会话用完一定要 close_session,否则浏览器会一直开着。

第 4 步:改用 HTTP 模式,并加上访问令牌

AI 应用和 Scrapling 不在同一台机器时,用 Streamable HTTP 模式。默认监听 127.0.0.1:8000,地址是 http://127.0.0.1:8000/mcp。0.5 版要求 HTTP 模式必须设令牌,直接 --http 会拒绝启动:

scrapling-mcp --http

对照结果(节选,退出码 2,长句已折行):

Error: Refusing to serve the MCP server over HTTP without
authentication because anyone who can reach 127.0.0.1:8000 would be
able to use every tool, including fetching arbitrary URLs from this
machine. Pass `--auth-token` (or set the SCRAPLING_MCP_AUTH_TOKEN
environment variable) to require a bearer token, or `--no-auth` to
serve it unauthenticated anyway.

官方建议用环境变量传令牌,避免留在命令历史和进程列表里。在一个终端里启动服务:

export SCRAPLING_MCP_AUTH_TOKEN="$(openssl rand -hex 32)"
echo "$SCRAPLING_MCP_AUTH_TOKEN"
scrapling-mcp --http

对照结果(节选):

INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)

开第二个终端,先确认不带令牌的请求会被拒绝:

curl -s -o /dev/null -w '%{http_code}\n' -X POST \
  http://127.0.0.1:8000/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

对照结果:

401

再用带令牌的 Python 客户端连接。在第二个终端里也 export 同一个令牌(把第一个终端 echo 出来的值粘进去),然后新建 http_client.py:

import asyncio
import os

from mcp import ClientSession
from mcp.client.streamable_http import (
    create_mcp_http_client,
    streamable_http_client,
)

URL = "http://127.0.0.1:8000/mcp"
TOKEN = os.environ["SCRAPLING_MCP_AUTH_TOKEN"]


async def main():
    headers = {"Authorization": f"Bearer {TOKEN}"}
    async with create_mcp_http_client(headers=headers) as http:
        transport = streamable_http_client(URL, http_client=http)
        async with transport as (read, write, *_):
            async with ClientSession(read, write) as s:
                await s.initialize()
                tools = (await s.list_tools()).tools
                print(len(tools), "tools over HTTP")

asyncio.run(main())
export SCRAPLING_MCP_AUTH_TOKEN="第一个终端里显示的令牌"
python http_client.py

对照结果:

16 tools over HTTP

令牌写错时,测试机上客户端报 MCPError: Server returned an error response。

AI 应用里按官方格式填 url 和请求头(未实测):

{
  "mcpServers": {
    "ScraplingServer": {
      "url": "https://your-server.example.com/mcp",
      "headers": {
        "Authorization": "Bearer <your-token>"
      }
    }
  }
}

要让别的机器访问,加 --host '0.0.0.0';对公网开放时,官方建议放在提供 HTTPS 的反向代理后面,并用 --allowed-host '你的域名:8000' 防 DNS 重绑定攻击。不要把 --no-auth 和 --host '0.0.0.0' 一起用,那等于任何人都能借你的机器抓任意网址。

第 5 步:给智能体下指令的写法(未实测)

本步的提示词来自官方文档示例,测试机没有在 Claude 等 AI 应用里实际对话。

官方的经验是:直接告诉 AI 用哪个工具,结果更稳定,也更省钱。比如同样是抓正文,第二种写法更好:

Scrape the main content from https://example.com and convert it
to markdown format.

Use regular requests to scrape the main content from
https://example.com and convert it to markdown format.

连续访问多个页面时,让它复用会话并在结束时关闭:

Open a stealthy browser session, then use it to scrape the main
details from the first 5 product pages on https://shop.example.com.
Close the session when you're done.

知道选择器时直接写进提示词,可以先缩小内容再交给模型,减少 token。中文提问也可以,比如「用普通 HTTP 请求,按 CSS 选择器 .quote .author 取出 https://quotes.toscrape.com/ 的作者名」。

使用边界:只抓取你有权访问的公开页面,遵守网站条款和 robots.txt;官方文档里绕过 Cloudflare 等防护的能力,不代表可以无视对方网站的规则。

小结

  • 装 scrapling[ai],在 AI 应用里把 command 指向 scrapling-mcp 的完整路径,就能让智能体调用 16 个抓取工具。
  • 一次性抓取用 make_request 或 browser_fetch_once;多步操作先 browser_open,再用 browser_fetch、browser_actions、browser_extract,最后 close_session。
  • 用 Python MCP SDK 2.x 写客户端时,属性名是 server_info、input_schema、structured_content 这类下划线写法。
  • HTTP 模式必须设令牌,不带令牌返回 401;对外开放要加 HTTPS 和 --allowed-host。
  • 给 AI 下指令时点明工具、选择器和「用完关闭会话」,结果更稳定。