mcpField
F
Fitter

Fitter

本地服务
github.com浏览器自动化1339 周前更新

关于 Fitter

Fitter 是一个将网站或 API 转换为结构化 JSON 的工具,数据获取与提取通过声明式的 JSON/YAML 配置完成。其 MCP 服务 Fitter MCP 让 Claude Code、Claude Desktop 等客户端可以编写、校验并在本地运行这些配置。配置为纯数据,便于审查、保存和复用。

主要功能

  1. 01以 JSON/YAML 声明式描述数据抓取与提取
  2. 02通过 MCP 工具校验并执行配置
  3. 03支持 HTTP、无头浏览器、文件和静态值来源
  4. 04支持 stdio 与可远程部署的 HTTP 传输
  5. 05提供 Prometheus 指标与 Docker 镜像

适用场景

  1. 01让模型根据自然语言需求生成并运行抓取配置
  2. 02将已验证的配置转为定时任务或服务配置
  3. 03抓取没有 API 的页面并用 JSON API 补充字段

MCP 相关

Fitter MCP 是 MCP 服务,向 MCP 客户端暴露 fitter_run、fitter_validate_config 和 fitter_inspect_url 等工具,并提供配置参考资源。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

暂未收录安装方式

我们还没有获取到这个服务的安装包或远程地址,请按项目 README 中的说明安装。

代码仓库
安装包
mcpbfitter-mcp-darwin-arm64.mcpb
mcpbfitter-mcp-darwin-amd64.mcpb
mcpbfitter-mcp-linux-arm64.mcpb
mcpbfitter-mcp-linux-amd64.mcpb
mcpbfitter-mcp-windows-amd64.mcpb
ocighcr.io/pxyup/fitter-mcp:v1.8.3
ocighcr.io/pxyup/fitter-mcp:v1.8.3-playwright

工具

还没有获取到工具列表

本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。

详细介绍

Fitter — 面向 AI Agent 的 Web 数据

MCP Toplist

Release License: MIT Go Reference Sponsor

Fitter 可将任何网站或 API 以声明式方式转换为结构化 JSON。 只需一个 JSON/YAML 配置,就能描述数据所在位置(HTTP 请求、无头浏览器、文件、静态值)以及要提取的内容(JSON 路径、CSS 选择器、XPath)。无需编写代码,也不必维护脆弱的抓取脚本。

🚀 在浏览器中试用 — 通过 WebAssembly 编译的真实引擎:包含实时示例、可视化配置构建器,无需安装。

由于配置是纯数据,LLM 可以编写配置。内置的 MCP 服务可让 Claude Code、Claude Desktop 或任何 MCP 客户端按需在你的机器上编写并运行抓取工作流:

“获取 HackerNews 排名前 5 的故事及其标题和分数” → 模型编写 fitter 配置、验证配置、在本地运行,并返回整洁的 JSON。

一个引擎,五种使用方式:

🤖 Fitter MCPMCP 服务,可让 Claude Code、Claude Desktop 和任何 MCP 客户端使用 fitter
🧠 Fitter AgentAI 驱动的命令行工具:自然语言 → 配置 → 执行结果
🖥 Fitter CLI在本地运行配置,用于测试、调试或日常使用
📦 Fitter Lib将引擎嵌入你自己的 Go 程序
⚙️ Fitter长时间运行的服务模式,支持调度和通知

为什么 AI Agent 适合使用 fitter?

  • 声明式且可审计 — Agent 生成的配置可供你阅读、保存和重新运行,而不是用完即弃的代码
  • 本地优先 — 所有数据获取都在你的机器上进行;无需第三方抓取 API、密钥,也没有按请求计费
  • 功能齐全 — 单个静态二进制文件中包含 HTTP 客户端、无头浏览器(Playwright/Chromium/Docker)、JSON/HTML/XML/XPath/PDF 解析、分页、缓存引用和主机速率限制
  • 可复用 — Agent 今天编写的内容,明天就能用作 cron 任务或服务配置

fitter 演示 — 从声明式配置到结构化 JSON

如何使用 Fitter_MCP

Fitter MCP 是一个 Model Context Protocol 服务(stdio 传输),可让任何 MCP 客户端(Claude Code、Claude Desktop、IDE 助手、自定义 Agent)运行 Fitter 配置并获取结构化 JSON。

快速开始(Claude Desktop — 一键安装)

从发布页面下载 fitter-mcp-<os>-<arch>.mcpb 并打开,Claude Desktop 会自动安装该服务。

快速开始(Claude Code)
# 1. get the binary: download fitter_mcp_<version>-<os>-<arch> from the release page
#    https://github.com/PxyUp/fitter/releases — or build it from source:
go build -o fitter_mcp ./cmd/mcp

# 2. register it once, available in every project
claude mcp add fitter -s user -- "$(pwd)/fitter_mcp"

然后直接询问:

使用 fitter 获取 HackerNews 排名前 5 的故事及其标题和分数

模型会调用 fitter_config_reference 编写配置,可选择使用 fitter_validate_config 检查配置,然后通过 fitter_run 执行 — 所有数据获取都在你的机器上本地进行。如需使用现成的工作流,请尝试 examples/config_morning_briefing.json:

使用 fitter 运行 examples/config_morning_briefing.json 并给我简报

在 Claude Desktop 中注册
{
  "mcpServers": {
    "fitter": {
      "command": "/path/to/fitter_mcp"
    }
  }
}
浏览器支持(Playwright)

.mcpb 捆绑包和原生二进制文件不包含浏览器:HTTP、静态和文件连接器开箱即用,但浏览器配置(playwright 连接器)需要 Playwright 的浏览器。可以通过以下几种方式获取:

  • 首次使用时(原生二进制文件 / .mcpb): 在 playwright 连接器中设置 "install": true — fitter 会在首次使用时下载与其内置 playwright-go 版本匹配的驱动程序和浏览器(仅需一次,之后会缓存),因此无需单独安装。
  • 提前安装(原生环境,可选): 若要避免首次运行时下载,请提前安装浏览器,并使用 fitter 构建时所依据的相同 playwright-go 版本(查看 go.mod,当前为 v0.6100.0):
    go run github.com/mxschmitt/playwright-go/cmd/[email protected] install
    # Linux: append --with-deps to also install the required OS libraries
    
    版本必须与 go.mod 完全一致 — playwright-go 无法使用不匹配的驱动程序运行。然后运行配置时无需设置 "install": true。
  • Docker: 使用 ghcr.io/pxyup/fitter-mcp:playwright 镜像,其中已预装 Chromium、Firefox 和 WebKit(无需设置 "install": true)。
工具
工具描述
fitter_run运行以内联方式传入的 Fitter 配置(JSON 或 YAML 字符串),并将提取的数据作为 JSON 返回。接受可选的 input 值,可在配置中通过 {{{FromInput=.}}} / {{{FromInput=json.path}}} 使用
fitter_run_file与 fitter_run 相同,但从本地 .json/.yaml 文件读取配置
fitter_run_url与 fitter_run 相同,但从 HTTP(S) URL 下载配置,例如原始 GitHub 链接
fitter_inspect_url获取 URL 并返回精简的结构大纲和候选选择器/路径(JSON 使用 gjson 路径;HTML 使用重复元素/列表行选择器),让模型能一次性编写出配置,而不是猜测选择器并得到 null。可检测客户端渲染的 SPA,并可在无头浏览器中对其进行 render。只读 — 不会提取数据
fitter_validate_config不执行配置,只验证配置(结构、response_type、连接器数据源、模型)。适用于迭代调整配置
fitter_config_reference返回完整配置格式的精简参考(连接器、解析器、模型/字段架构、占位符、通知器、引用、限制),并附带可运行示例,让模型无需外部文档即可编写配置

支持资源的客户端也可通过 MCP 资源 fitter://config-reference 获取参考资料。

配置格式与 Fitter_CLI 完全相同:顶层对象包含 item(必填)、limits 和 references。通知器 也可用(结果还会额外推送到 http/telegram/redis/file/console);trigger_config 和 http_server 仅用于服务模式,在 MCP 调用中会被忽略。

远程 / 托管模式(可流式传输的 HTTP)

默认情况下,fitter_mcp 使用 stdio 通信。传入 --http 即可改为提供可流式传输的 HTTP 传输 — 适用于团队共享服务、容器或任何远程部署:

# serve MCP at http://<host>:8080/mcp (health probe at /healthz)
FITTER_MCP_AUTH_TOKEN=my-secret fitter_mcp --http :8080

# register the remote endpoint in Claude Code
claude mcp add --transport http fitter http://localhost:8080/mcp --header "Authorization: Bearer my-secret"
  • --http <addr>(环境变量 FITTER_MCP_HTTP_ADDR)— 监听地址;为空时使用 stdio 模式
  • FITTER_MCP_AUTH_TOKEN — 设置后,每个 /mcp 请求都必须发送 Authorization: Bearer <token>;未设置时,端点不需要身份验证,因此请绑定到 localhost 或将其置于代理之后
  • --stateless(环境变量 FITTER_MCP_STATELESS=true)— 不保留会话状态,因此副本可置于负载均衡器之后,无需会话粘滞
  • --metrics-addr <addr>(环境变量 FITTER_MCP_METRICS_ADDR)— 在单独的地址上提供 Prometheus 指标(例如 127.0.0.1:9091);stdio 模式下也可用;为空时禁用。提供按方法统计的 MCP 请求数、按工具和结果统计的工具调用时长,以及按主机、方法和状态统计的出站 HTTP 请求时长。该端点不需要身份验证 — 请将其绑定到 localhost 或私有接口

服务器会在收到 SIGINT/SIGTERM 时平稳关闭。

Docker

每次发布都会提供精简的多架构镜像(linux/amd64 + linux/arm64):

# hosted HTTP mode
docker run --rm -p 8080:8080 \
  -e FITTER_MCP_HTTP_ADDR=:8080 \
  -e FITTER_MCP_AUTH_TOKEN=my-secret \
  ghcr.io/pxyup/fitter-mcp:latest

# or stdio mode, spawned by the MCP client
claude mcp add fitter -s user -- docker run --rm -i ghcr.io/pxyup/fitter-mcp:latest

精简镜像仅包含 fitter 二进制文件和 CA 证书:服务/静态/文件连接器可用,浏览器连接器(chromium/docker/playwright)不可用。

对于基于浏览器的配置,请使用 playwright 变体,其中包含 Playwright、Chromium、Firefox 和 WebKit(与构建 fitter 时所用的 playwright-go 版本匹配,因此配置中无需设置 "install": true):

docker run --rm -i ghcr.io/pxyup/fitter-mcp:playwright        # stdio mode
# per-release tag: ghcr.io/pxyup/fitter-mcp:vX.Y.Z-playwright

该镜像基于 Dockerfile.mcp-playwright 构建;使用 --build-arg PLAYWRIGHT_BROWSERS=chromium 构建可获得更小的仅含 Chromium 的镜像。

Docker 中的 OAuth2 账号

两个镜像都包含 fitter_cli,因此可以在容器中执行一次性 OAuth2 登录。将令牌存储在挂载到 /tokens 的卷中(镜像中已预先创建且可写),并与 MCP 服务共享:

# one-time login, device flow: no ports needed — open the printed url on any device
docker run --rm -it -v fitter-tokens:/tokens --entrypoint fitter_cli \
  ghcr.io/pxyup/fitter-mcp:latest \
  auth --provider github --client-id <ID> --client-secret <SECRET> --token-file /tokens/github.json

# or browser flow (device flow not enabled for the app): publish the callback port and
# bind on 0.0.0.0 so the published port reaches the listener; the browser still visits 127.0.0.1
docker run --rm -it -p 8988:8988 -e FITTER_AUTH_LISTEN=0.0.0.0 \
  -v fitter-tokens:/tokens --entrypoint fitter_cli ghcr.io/pxyup/fitter-mcp:latest \
  auth --provider github --client-id <ID> --client-secret <SECRET> --token-file /tokens/github.json

# then run the MCP server with the same volume; configs reference "token_file": "/tokens/github.json"
# stdio mode (spawned by the MCP client, no port):
docker run --rm -i -v fitter-tokens:/tokens ghcr.io/pxyup/fitter-mcp:latest
# hosted HTTP mode (MCP endpoint on 8080, like the run examples above):
docker run --rm -p 8080:8080 -v fitter-tokens:/tokens \
  -e FITTER_MCP_HTTP_ADDR=:8080 \
  -e FITTER_MCP_AUTH_TOKEN=my-secret \
  ghcr.io/pxyup/fitter-mcp:latest

注意:8988 仅用于一次性浏览器流程登录;MCP 服务本身在 stdio 模式下无需端口,在托管 HTTP 模式下仅需 8080。

Docker 中已登录的浏览器会话

浏览器会话需要 playwright 镜像(精简镜像不含浏览器)。一次性有头登录需要显示设备,因此请在主机上运行,然后将会话目录绑定挂载到容器中(镜像已预先创建可写的 /sessions):

# on the host: log in once, save the session
fitter_cli browser-login --url https://example.com/login --storage-state ~/.fitter/sessions/example.json

# run the MCP server with the sessions dir mounted; configs reference "storage_state_file": "/sessions/example.json"
docker run --rm -i -v ~/.fitter/sessions:/sessions ghcr.io/pxyup/fitter-mcp:playwright

请使用绑定挂载(而非具名卷):容器每次运行后都会写回更新后的 Cookie,因此主机上的副本始终是最新的,可随时通过 browser-login 延长会话。

服务必须能够写入该卷:每次刷新时都会写回轮换后的刷新令牌。

环境变量
  1. FITTER_PLUGINS - string[""] - 插件文件夹的路径,与 Fitter/Fitter_CLI 的 --plugins 标志相同
  2. FITTER_MCP_HTTP_ADDR - string[""] - 远程模式的监听地址,与 --http 相同
  3. FITTER_MCP_AUTH_TOKEN - string[""] - 用于保护 HTTP 端点的持有者令牌
  4. FITTER_MCP_STATELESS - bool[false] - 无状态 HTTP 传输,与 --stateless 相同
  5. FITTER_MCP_METRICS_ADDR - string[""] - Prometheus 指标监听地址,与 --metrics-addr 相同

示例

展示主要模式的完整且经过测试的配置。所有配置都可通过 Fitter_MCP(fitter_run_file)、Fitter_CLI 或库直接运行,无需修改——更多示例见 examples/。

抓取没有 API 的页面,并从有 API 的页面补充数据

GitHub 趋势没有官方 API——抓取 HTML 获取仓库标识(html_attribute 会读取 href),然后使用 {PL} 将每个标识传入 GitHub REST API:

examples/config_github_trending.json

{
  "item": {
    "connector_config": {
      "response_type": "HTML",
      "url": "https://github.com/trending",
      "server_config": { "method": "GET", "headers": { "User-Agent": "Mozilla/5.0 (fitter demo)" } }
    },
    "model": {
      "array_config": {
        "root_path": "article.Box-row h2 a",
        "length_limit": 5,
        "item_config": {
          "field": {
            "type": "string",
            "html_attribute": "href",
            "generated": { "model": {
              "type": "object",
              "connector_config": {
                "response_type": "json",
                "url": "https://api.github.com/repos{PL}",
                "server_config": { "method": "GET", "headers": { "User-Agent": "fitter-demo" } },
                "null_on_error": true
              },
              "model": { "object_config": { "fields": {
                "repo": { "base_field": { "type": "string", "path": "full_name" } },
                "stars": { "base_field": { "type": "int", "path": "stargazers_count" } },
                "language": { "base_field": { "type": "string", "path": "language" } }
              } } }
            } }
          }
        }
      }
    }
  },
  "limits": { "host_request_limiter": { "api.github.com": 2 } }
}
[{"repo": "block/buzz", "stars": 6214, "language": "Rust"}, {"repo": "koala73/worldmonitor", "stars": 71179, "language": "TypeScript"}]
使用表达式按 JSON 字段联接

当数组项是对象时,联接键位于对象内部——使用 {{{FromExp=...}}} 将其取出(通过作用于当前项的 fRes 使用 expr-lang)。搜索图书 → 作者详情,搜索查询通过 input 提供:

examples/config_book_authors.json

"url": "https://openlibrary.org/authors/{{{FromExp=fromJSON(fRes).author_key[0]}}}.json"
./fitter_cli --path=examples/config_book_authors.json --input=dune
[{"title": "Dune", "year": 1965, "author": {"name": "Frank Herbert", "born": "8 October 1920", "died": "11 February 1986"}}]
将结果写入本地文件

file_storage 生成字段会将字段转换为写入操作:将排名前 5 的加密货币追加到 CSV,每个项目占一行。裸 {{{json.path}}} 占位符会读取当前项目;{HUMAN_INDEX} 会标记从 1 开始的排名(项目会并行处理,因此追加顺序取决于完成顺序——请按排名列排序):

examples/config_crypto_csv.json

"file_storage": {
  "content": "{HUMAN_INDEX},{{{name}}},{{{current_price}}},{{{price_change_percentage_24h}}}\n",
  "file_name": "coins.csv",
  "path": "/tmp/fitter-report",
  "append": true
}
$ sort -n /tmp/fitter-report/coins.csv
1,Bitcoin,64778,-2.3
2,Ethereum,1881.01,-3.4
3,Tether,0.999265,0
从 PDF 中提取文本

response_type: "pdf" 会将获取到的任何 PDF 转换为 JSON 文档——{"text": "...", "pages": ["..."], "total_pages": N}——因此常规 JSON 路径(text、pages.0)和表达式都可用于处理。以下示例展示 Bitcoin 白皮书的页数以及经过裁剪的简介:

examples/config_pdf.json

{
  "item": {
    "connector_config": {
      "response_type": "pdf",
      "url": "https://bitcoin.org/bitcoin.pdf",
      "server_config": { "method": "GET" }
    },
    "model": {
      "object_config": {
        "fields": {
          "total_pages": { "base_field": { "type": "int", "path": "total_pages" } },
          "intro": {
            "base_field": {
              "type": "string",
              "path": "pages.0",
              "generated": {
                "calculated": {
                  "type": "string",
                  "expression": "trim(fRes[:100]) + \"...\""
                }
              }
            }
          }
        }
      }
    }
  }
}
{"intro": "Bitcoin: A Peer-to-Peer Electronic Cash SystemSatoshi [email protected]...", "total_pages": 9}

信息收集方式

  1. Server - 解析某些 API 或 HTTP 请求的响应(使用 http.Client)
  2. Browser - 使用 chromium + docker + playwright/cypress 模拟真实浏览器并获取 DOM 信息
  3. Static - 将静态字符串解析为数据

可解析的格式

  1. JSON - 解析 JSON 以获取特定信息
  2. XML - 解析 XML 树以获取特定信息
  3. HTML - 解析 DOM 树以获取特定信息
  4. XPath - 使用 XPath 解析 DOM 树以获取特定信息
  5. PDF - 从 PDF 文档中提取文本;内容以 JSON 形式提供:{"text": "...", "pages": ["..."], "total_pages": N},因此可使用 text 或 pages.0 等常规 JSON 路径

作为库使用

go get github.com/PxyUp/fitter
package main

import (
	"fmt"
	"github.com/PxyUp/fitter/lib"
	"github.com/PxyUp/fitter/pkg/config"
	"log"
	"net/http"
)

func main() {
	res, err := lib.Parse(&config.Item{
		ConnectorConfig: &config.ConnectorConfig{
			ResponseType:  config.Json,
			Url:           "https://random-data-api.com/api/appliance/random_appliance",
			ServerConfig: &config.ServerConnectorConfig{
				Method: http.MethodGet,
			},
		},
		Model: &config.Model{
			ObjectConfig: &config.ObjectConfig{
				Fields: map[string]*config.Field{
					"my_id": {
						BaseField: &config.BaseField{
							Type: config.Int,
							Path: "id",
						},
					},
					"generated_id": {
						BaseField: &config.BaseField{
							Generated: &config.GeneratedFieldConfig{
								UUID: &config.UUIDGeneratedFieldConfig{},
							},
						},
					},
					"generated_array": {
						ArrayConfig: &config.ArrayConfig{
							RootPath: "@this|@keys",
							ItemConfig: &config.ObjectConfig{
								Field: &config.BaseField{
									Type: config.String,
								},
							},
						},
					},
				},
			},
		},
	}, nil, nil, nil, nil)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(res.ToJson())
}


输出:

{
  "generated_array": ["id","uid","brand","equipment"],
  "my_id": 6000,
  "generated_id": "26b08b73-2f2e-444d-bcf2-dac77ac3130e"
}

使用 lib.ParseCtx(ctx, ...) 传入 context.Context:取消该上下文会中止正在进行的获取操作(HTTP 请求、无头浏览器、docker 容器),并在整个调用过程中应用截止时间。lib.Parse 等同于 lib.ParseCtx(context.Background(), ...)。

如何使用 Fitter

从发布页面下载最新版本

或在本地使用:

go run cmd/fitter/main.go --path=./examples/config_api.json
参数
  1. --path - string[""] - Fitter 配置文件的路径
  2. --url - string[""] - Fitter 配置的 URL
  3. --verbose - bool[false] - 启用日志记录
  4. --plugins - string[""] - Fitter 插件的路径
  5. --log-level - enum["info", "error", "debug", "fatal"] - 设置日志级别(仅在启用 verbose 时生效)

如何使用 Fitter_CLI

从发布页面下载最新版本

或在本地使用:

go run cmd/cli/main.go --path=./examples/cli/config_cli.json
参数
  1. --path - string[""] - Fitter_CLI 配置文件的路径
  2. --url - string[""] - Fitter_CLI 配置的 URL
  3. --copy - bool[false] - 将信息复制到剪贴板
  4. --pretty - bool[true] - 使结果更易读(也会影响复制结果)
  5. --verbose - bool[false] - 启用日志记录
  6. --omit-error-pretty - bool[false] - 如果 pretty 无效,则提供纯值
  7. --plugins - string[""] - Fitter 插件的路径
  8. --log-level - enum["info", "error", "debug", "fatal"] - 设置日志级别(仅在启用 verbose 时生效)
  9. --input - string[""] - 指定用于格式化的输入值。示例:--input=\""124"\" --input=124 --input='{"test": 5}'
./fitter_cli_${VERSION} --path=./examples/cli/config_cli.json --copy=true
fitter_cli auth — 连接 OAuth2 账户

一次性交互式登录,用于存储一个(刷新)令牌,供 oauth2 connector config 使用:

# device flow (default when the provider supports it): no callback, works headless
./fitter_cli_${VERSION} auth --provider github --client-id <ID> --client-secret <SECRET> --token-file ~/.fitter/tokens/github.json

# custom provider without preset
./fitter_cli_${VERSION} auth --auth-url https://.../authorize --token-url https://.../token --client-id <ID> --token-file ./token.json

参数:

  1. --provider - 预设的已知端点:github|google|microsoft|gitlab|spotify
  2. --client-id / --client-secret - OAuth2 应用凭据(某些设备授权流程无需密钥)
  3. --token-file - 接收的令牌的存储位置(权限为 0600);在 oauth2.token_file 中引用相同路径
  4. --flow - auto(如果可用则使用设备授权,否则使用浏览器)、device(访问网址并输入代码)或 browser(使用 PKCE 的 localhost 回调,默认端口为 8988 — 将 http://127.0.0.1:8988/callback 注册为应用回调 URL)
  5. --scopes - 以逗号分隔的作用域
  6. --auth-url/--token-url/--device-auth-url/--auth-style - 未预设提供方的端点覆盖项
  7. --port - int[8988] - 浏览器授权流程的回调端口(环境变量 FITTER_AUTH_PORT);使用默认值时,需在提供方注册的回调 URL 为 http://127.0.0.1:8988/callback
  8. --listen - 浏览器授权流程的绑定地址,默认为 127.0.0.1;在容器内设为 0.0.0.0,使已发布的端口能够连接到监听器(环境变量 FITTER_AUTH_LISTEN)
  9. --redirect-url - 在提供方注册的回调 URL,当其与监听地址不同时使用,例如 Docker 端口映射(环境变量 FITTER_AUTH_REDIRECT_URL)
  10. --no-browser - 仅打印授权 URL

在 Docker 中运行:请参阅 Docker 中的 OAuth2 账户。

登录后,命令会打印可直接使用的 oauth2 配置块。连接器会自动刷新访问令牌,并将轮换后的刷新令牌写回令牌文件,因此只需登录一次。

fitter_cli browser-login — 复用真实登录会话

对于没有 API/OAuth 的网站:在真实的(有头)浏览器窗口中手动登录一次——支持任何身份验证方案,包括密码、2FA、SSO 和验证码——然后保存会话,以便通过 storage_state_file 进行无头抓取:

./fitter_cli_${VERSION} browser-login --url https://example.com/login --storage-state ~/.fitter/sessions/example.json
# a browser window opens; log in, then press Enter in the terminal to save the session

参数:

  1. --url - 要打开的登录页面(必填)
  2. --storage-state - 会话的保存位置(cookies + localStorage,权限为 0600);在 playwright.storage_state_file 中引用相同路径(必填)
  3. --browser - 枚举值 ["Chromium", "FireFox", "WebKit"],默认值为 "Chromium";使用与抓取配置相同的值——网站可能会将会话绑定到浏览器指纹
  4. --install - bool[false] - 先安装 playwright 浏览器
  5. --indexeddb - bool[false] - 在快照中包含 IndexedDB(Firebase Auth 等)

重新运行命令时会先加载现有状态,因此你可以延长或刷新会话,而无需从头登录。抓取连接器也会在每次运行后写回刷新后的 cookies,只要定期使用,会话就能保持有效。需要显示器:在 Docker 中,请在主机上运行此命令并挂载文件——参见 Docker 中的浏览器会话。

示例:

  1. 服务器版本 HackerNews + Quotes + Guardian News - 使用 API + HTML + XPath 解析
  2. Chromium 版本 Guardian News + Quotes - 使用 HTML 解析 + 浏览器模拟
  3. Docker 版本 Docker version: Guardian News + Quotes - 使用 HTML 解析 + Docker 镜像中的浏览器
  4. Playwright 版本 Playwright version: Guardian News + Quotes - 使用 HTML 解析 + Playwright 框架中的浏览器
  5. Playwright 版本 Playwright version: England Cities + Weather - 使用 HTML + XPath 解析 + Playwright 框架中的浏览器
  6. JSON 版本 Generate pagination - 使用静态连接器生成分页数组
  7. 服务器版本 Get current time - 从 URL 获取时间并格式化

如何使用 Fitter_Agent

Fitter Agent 是一个由 AI 驱动的命令行工具,它使用 Claude 将自然语言请求转换为 Fitter 配置并自动执行。

从发布页面下载最新版本

或在本地运行:

export ANTHROPIC_API_KEY=<your-anthropic-api-key>
go run cmd/agent/main.go
参数
  1. --api-key - string[""] - Anthropic API 密钥。建议使用 ANTHROPIC_API_KEY 环境变量,以免密钥进入 shell 历史记录
  2. --model - string["claude-opus-4-8"] - 要使用的 Claude 模型
  3. --effort - enum["low", "medium", "high", "xhigh", "max"] - 推理力度,默认值为 "high"。降低可使配置生成更快、成本更低;面对更难的提取任务时可提高
  4. --verbose - bool[false] - 启用日志记录
  5. --log-level - enum["info", "error", "debug", "fatal"] - 设置日志级别
  6. --plugins - string[""] - Fitter 插件的路径
  7. --chromium-limit - uint[0] - 限制并发 Chromium 实例数
  8. --docker-limit - uint[0] - 限制并发 Docker 容器数
  9. --playwright-limit - uint[0] - 限制并发 Playwright 实例数
工作原理
┌─────────────────────────────────────────────────────────────────┐
│  1. User enters natural language request                       │
│     "Get top 5 HackerNews stories with titles and scores"      │
│                              ↓                                  │
│  2. Claude returns a config in a schema-constrained response   │
│                              ↓                                  │
│  3. Agent validates it; on failure the error is handed back    │
│     to Claude to repair (up to 3 attempts)                     │
│                              ↓                                  │
│  4. Agent displays config and asks for confirmation            │
│                              ↓                                  │
│  5. On confirmation, executes via lib.Parse()                  │
│                              ↓                                  │
│  6. Returns structured JSON result                             │
└─────────────────────────────────────────────────────────────────┘
优化配置

Agent 会保留对话,因此生成配置后,你只需说明要更改的内容,无需重述整个请求:

> Get top 3 HackerNews stories with titles and scores
refine> Only return 5 items and also include the article URL

使用 new 可忘记当前配置并开始新的会话。

交互式 REPL 命令
  • help - 显示帮助信息
  • new/reset - 忘记当前配置并重新开始
  • clear - 清屏
  • exit/quit/q - 退出 Agent
会话示例
$ export ANTHROPIC_API_KEY=sk-ant-...
$ ./fitter_agent

╔══════════════════════════════════════════════════════════════╗
║           Fitter Agent - AI-Powered Data Extraction           ║
╚══════════════════════════════════════════════════════════════╝

Describe what you want to extract. Follow-up messages refine the
previous config. Type 'help' for commands.

> Get top 3 HackerNews stories with titles and scores

┌─ Generated Fitter Config ───────────────────────────────────────
{
  "item": {
    "connector_config": {
      "response_type": "json",
      "url": "https://hacker-news.firebaseio.com/v0/topstories.json",
      "server_config": { "method": "GET" }
    },
    "model": {
      "array_config": {
        "root_path": "@this",
        "length_limit": 3,
        "item_config": {
          "fields": {
            "id": { "base_field": { "type": "int" } },
            "story": {
              "base_field": {
                "type": "int",
                "generated": {
                  "model": {
                    "type": "object",
                    "connector_config": {
                      "response_type": "json",
                      "url": "https://hacker-news.firebaseio.com/v0/item/{PL}.json",
                      "server_config": { "method": "GET" }
                    },
                    "model": {
                      "object_config": {
                        "fields": {
                          "title": { "base_field": { "type": "string", "path": "title" } },
                          "score": { "base_field": { "type": "int", "path": "score" } }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
└──────────────────────────────────────────────────────────────────

Execute this config? [y/n]: y

┌─ Result ────────────────────────────────────────────────────────
[
  {
    "id": 46740029,
    "story": { "title": "Show HN: Open-source project", "score": 161 }
  },
  {
    "id": 46737630,
    "story": { "title": "Interesting article", "score": 237 }
  },
  {
    "id": 46735644,
    "story": { "title": "New technology release", "score": 192 }
  }
]
└──────────────────────────────────────────────────────────────────

> exit
Goodbye!
请求示例
请求功能
Get Bitcoin price from CoinGecko API获取当前 BTC 价格
Scrape headlines from news.ycombinator.com with links使用 CSS 选择器抓取 HTML
Get top 5 stories from HackerNews with titles嵌套 API 调用
Fetch weather data from wttr.in for London简单 API 提取
Scrape product names and prices from example.com网页抓取
支持的功能

该 Agent 可以生成以下类型的配置:

  • JSON API - 使用 GET/POST 方法的 REST API
  • HTML 抓取 - 基于 CSS 选择器提取
  • XPath 抓取 - 基于 XPath 提取
  • 嵌套 API 调用 - 获取列表中每个项目的详细信息
  • 浏览器模拟 - 使用 Playwright 处理 JS 渲染的页面
  • 格式化字段 - 带占位符的 URL 模板
  • 数组限制 - 将结果限制为 N 个项目

配置

连接器

它是获取数据的方式

type ConnectorConfig struct {
    ResponseType ParserType `json:"response_type" yaml:"response_type"`
    Url          string     `json:"url" yaml:"url"`
    Attempts     uint32     `json:"attempts" yaml:"attempts"`
    
    NullOnError bool `yaml:"null_on_error" json:"null_on_error"`
    
    StaticConfig          *StaticConnectorConfig      `json:"static_config" yaml:"static_config"`
    IntSequenceConfig     *IntSequenceConnectorConfig `json:"int_sequence_config" yaml:"int_sequence_config"`
    ServerConfig          *ServerConnectorConfig      `json:"server_config" yaml:"server_config"`
    BrowserConfig         *BrowserConnectorConfig     `yaml:"browser_config" json:"browser_config"`
    PluginConnectorConfig *PluginConnectorConfig      `json:"plugin_connector_config" yaml:"plugin_connector_config"`
    ReferenceConfig       *ReferenceConnectorConfig   `yaml:"reference_config" json:"reference_config"`
    FileConfig            *FileConnectorConfig        `json:"file_config" yaml:"file_config"`
}
  • NullOnError[false] - 如果设为 true,则忽略所有错误
  • ResponseType - 枚举值["HTML", "json", "xpath", "XML", "pdf"] - 连接器返回的数据格式
  • Attempts - 连接器获取数据时尝试的次数
  • Url - 指定要请求的地址。注意:可以将父级值作为字符串注入 https://api.open-meteo.com/v1/forecast?latitude={{{latitude}}}&longitude={{{longitude}}}&hourly=temperature_2m&forecast_days=1

配置可以是以下类型之一:

示例:

{
  "response_type": "xpath",
  "attempts": 3,
  "url": "https://openweathermap.org/find?q={PL}",
  "browser_config": {
    "playwright": {
      "timeout": 30,
      "wait": 30,
      "install": false,
      "browser": "Chromium"
    }
  }
}
PluginConnectorConfig

连接器可以通过插件系统定义。使用该功能时,需要为 Fitter/Cli(插件所在位置)添加以下标志:

... --plugins=./examples/plugin

--plugins - 在指定文件夹中查找所有扩展名为 ".so" 的文件(不包括子目录)

type PluginConnectorConfig struct {
	Name   string          `json:"name" yaml:"name"`
	Config json.RawMessage `json:"config" yaml:"config"`
}
{
    "name": "connector",
    "config": {
      "name": "Elon"
    }
}
  • Name - 插件名称
  • Config - 插件的 json 配置
如何构建插件

构建插件

go build -buildmode=plugin -gcflags="all=-N -l" -o examples/plugin/connector.so examples/plugin/connector/connector.go

确保导出实现 pl.ConnectorPlugin 接口的 Plugin 变量

CLI 示例:

https://github.com/PxyUp/fitter/blob/master/examples/cli/config_plugin.json#L5

插件示例:

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/PxyUp/fitter/pkg/config"
	"github.com/PxyUp/fitter/pkg/logger"
	"github.com/PxyUp/fitter/pkg/builder"
	pl "github.com/PxyUp/fitter/pkg/plugins/plugin"
)

var (
	_ pl.ConnectorPlugin = &plugin{}

	Plugin plugin
)

type plugin struct {
	log  logger.Logger
	Name string `json:"name" yaml:"name"`
}

func (pl *plugin) Get(ctx context.Context, parsedValue builder.Interfacable, index *uint32, input builder.Interfacable) ([]byte, error) {
	return []byte(fmt.Sprintf(`{"name": "%s"}`, pl.Name)), nil
}

func (pl *plugin) SetConfig(cfg *config.PluginConnectorConfig, logger logger.Logger) {
	pl.log = logger

	if cfg.Config != nil {
		err := json.Unmarshal(cfg.Config, pl)
		if err != nil {
			pl.log.Errorw("cant unmarshal plugin configuration", "error", err.Error())
			return
		}
	}
}
ReferenceConnectorConfig

用于从引用中获取预取数据的连接器

type ReferenceConnectorConfig struct {
	Name string `yaml:"name" json:"name"`
}

示例

https://github.com/PxyUp/fitter/blob/master/examples/cli/config_ref.json#L66

IntSequenceConnectorConfig

改进版的静态连接器,可生成整数序列作为结果

type IntSequenceConnectorConfig struct {
	Start int `json:"start" yaml:"start"`
	End   int `json:"end" yaml:"end"`
	Step  int `json:"step" yaml:"step"`
}
  • Start[0] - 生成起点(包含)
  • End[0] - 生成终点(不包含在最终结果中,与任何语言中的 range 类似)
  • Step[1] - 序列间隔

示例

{
    "start": 0,
    "end": 2 
    // Generate [0, 1]
}

配置示例

FileConnectorConfig

从指定文件获取数据的连接器类型

type FileConnectorConfig struct {
    Path          string `yaml:"path" json:"path"`
    UseFormatting bool   `yaml:"use_formatting" json:"use_formatting"`
}
  • Path - 文件路径。支持格式化
  • UseFormatting[false] - 是否对文件内容使用格式化
StaticConnectorConfig

从指定字符串获取数据的连接器类型

type StaticConnectorConfig struct {
    Value string `json:"value" yaml:"value"`
    Raw   json.RawMessage `json:"raw" yaml:"raw"`
}
  • Value - 作为数据的静态字符串,可以是 html、json
  • Raw - 接受原始 json。示例。也支持格式化

示例:

https://github.com/PxyUp/fitter/blob/master/examples/cli/config_static_connector.json#L5

{
  "value": "[1,2,3,4,5]"
}
ServerConnectorConfig

使用 golang http.Client 获取数据的连接器类型(服务端请求,类似 curl)

type ServerConnectorConfig struct {
    Method        string            `json:"method" yaml:"method"`
    Headers       map[string]string `yaml:"headers" json:"headers"`
    Timeout       uint32            `yaml:"timeout" json:"timeout"`
    JsonRawBody   json.RawMessage   `json:"json_raw_body" yaml:"json_raw_body"`
    Body          string            `yaml:"body" json:"body"`
    ErrorOnStatus bool              `json:"error_on_status" yaml:"error_on_status"`
    
    Proxy  *ProxyConfig  `yaml:"proxy" json:"proxy"`
    OAuth2 *OAuth2Config `yaml:"oauth2" json:"oauth2"`
}
  • Method - 支持所有 HTTP 方法:GET、POST、PUT、DELETE、PATCH、OPTIONS、HEAD
  • Headers - 预定义请求过程中使用的标头可注入到键/值中
  • Timeout[sec] - 超时时间,默认 60 秒,也可使用指定值
  • Body - 请求正文,解析后的值可注入
  • JsonRawBody - JSON 格式的请求正文;值可注入
  • ErrorOnStatus - 可选,默认为 false。设为 true 时,HTTP 响应状态 >= 400 会被视为获取错误(通过 attempts / null_on_error 处理),而不是解析错误正文,因此可以区分获取失败和结果确实为空。保持为 false 则会继续按原有行为解析返回的任何正文。
  • Proxy - 为请求设置代理配置
  • OAuth2 - 自动获取/刷新访问令牌,并将其作为 Authorization 标头发送配置

默认情况下,请求会发送可识别的 User-Agent(fitter (+https://github.com/PxyUp/fitter));在 Headers 中设置自己的 User-Agent 即可覆盖它。

示例:

{
  "method": "GET",
  "proxy": {
    "server": "http://localhost:8080",
    "username": "pyx"
  }
}
OAuth2 配置

在请求前自动获取访问令牌,并将其作为 Authorization 标头注入(覆盖通过 headers 设置的标头)。令牌会缓存在内存中,并在过期前刷新;收到 401 响应时,会丢弃缓存的令牌,并使用新令牌重试一次请求。

type OAuth2Config struct {
    TokenUrl       string            `json:"token_url" yaml:"token_url"`
    GrantType      OAuth2GrantType   `json:"grant_type" yaml:"grant_type"`
    ClientId       string            `json:"client_id" yaml:"client_id"`
    ClientSecret   string            `json:"client_secret" yaml:"client_secret"`
    Scopes         []string          `json:"scopes" yaml:"scopes"`
    RefreshToken   string            `json:"refresh_token" yaml:"refresh_token"`
    EndpointParams map[string]string `json:"endpoint_params" yaml:"endpoint_params"`
    AuthStyle      string            `json:"auth_style" yaml:"auth_style"`
    TokenFile      string            `json:"token_file" yaml:"token_file"`
}
  • TokenUrl - 令牌端点 URL。也支持格式化
  • GrantType - 枚举值["client_credentials", "refresh_token"],默认为 "client_credentials"。对于用户已同意一次(Google、Microsoft 等)且你持有长期刷新令牌的 API,请使用 "refresh_token"
  • ClientId/ClientSecret - 客户端凭据。也支持格式化,例如 {{{FromEnv=CLIENT_SECRET}}}
  • Scopes - 请求的作用域
  • RefreshToken - "refresh_token" 授权类型必填。也支持格式化
  • EndpointParams - 令牌端点的额外参数(例如 Auth0 的 audience),仅适用于 "client_credentials" 授权类型
  • AuthStyle - 枚举值["", "header", "params"] - 客户端凭据传递到令牌端点的方式:基本认证标头或请求正文;留空表示自动检测
  • TokenFile - 可选的令牌持久化路径(支持 ~/),用于在多次运行之间保存令牌;存储的令牌优先于 RefreshToken,轮换后的刷新令牌会写回文件——使用 GitHub Apps 等采用一次性刷新令牌的提供方时必填。使用 fitter_cli auth 创建该文件

示例:

{
  "method": "GET",
  "oauth2": {
    "token_url": "https://oauth2.googleapis.com/token",
    "grant_type": "refresh_token",
    "client_id": "{{{FromEnv=GOOGLE_CLIENT_ID}}}",
    "client_secret": "{{{FromEnv=GOOGLE_CLIENT_SECRET}}}",
    "refresh_token": "{{{FromEnv=GOOGLE_REFRESH_TOKEN}}}"
  }
}
Proxy 配置
type ProxyConfig struct {
    // Proxy to be used for all requests. HTTP and SOCKS proxies are supported, for example
    // `http://myproxy.com:3128` or `socks5://myproxy.com:3128`. Short form `myproxy.com:3128`
    // is considered an HTTP proxy.
    Server string `json:"server" yaml:"server"`
    // Optional username to use if HTTP proxy requires authentication.
    Username string `json:"username" yaml:"username"`
    // Optional password to use if HTTP proxy requires authentication.
    Password string `json:"password" yaml:"password"`
}
  • Server - 代理服务的地址,需包含协议。也支持格式化
  • Username - 代理用户名(可为空)。也支持格式化
  • Password - 代理密码(可为空)。也支持格式化
{
  "server": "http://localhost:8080",
  "username": "pyx"
}
环境变量
  1. FITTER_HTTP_WORKER - int[1000] - 默认并发 HTTP 工作线程数
BrowserConnectorConfig

模拟通过浏览器获取数据的连接器类型

type BrowserConnectorConfig struct {
	Chromium   *ChromiumConfig   `json:"chromium" yaml:"chromium"`
	Docker     *DockerConfig     `json:"docker" yaml:"docker"`
	Playwright *PlaywrightConfig `json:"playwright" yaml:"playwright"`
}

配置可以是以下类型之一:

  • Chromium - 使用本地安装的 Chromium 获取数据
  • Docker - 使用 Docker 服务启动容器以获取数据
  • Playwright - 使用 Playwright 框架获取数据

示例:

{
    "docker": {
      "wait": 10000,
      "image": "docker.io/zenika/alpine-chrome:with-node",
      "entry_point": "chromium-browser",
      "purge": true
    }
}
Chromium

使用本地安装的 Chromium 获取数据

type ChromiumConfig struct {
	Path    string   `yaml:"path" json:"path"`
	Timeout uint32   `yaml:"timeout" json:"timeout"`
	Wait    uint32   `yaml:"wait" json:"wait"`
	Flags   []string `yaml:"flags" json:"flags"`
}
  • Path - Chromium 二进制文件的路径
  • Timeout[sec] - Chromium 的执行超时时间
  • Wait[msec] - 页面加载超时时间
  • Flags - Chromium 标志,默认值:"--headless"、"--proxy-auto-detect"、"--temp-profile"、"--incognito"、"--disable-logging"、"--disable-extensions"、"--no-sandbox"

示例:

{
  "path": "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
  "wait": 10000
}
Docker

使用 Docker 启动容器以获取数据

type DockerConfig struct {
	Image       string   `yaml:"image" json:"image"`
	EntryPoint  string   `json:"entry_point" yaml:"entry_point"`
	Timeout     uint32   `yaml:"timeout" json:"timeout"`
	Wait        uint32   `yaml:"wait" json:"wait"`
	Flags       []string `yaml:"flags" json:"flags"`
	Purge       bool     `json:"purge" yaml:"purge"`
	NoPull      bool     `yaml:"no_pull" json:"no_pull"`
	PullTimeout uint32   `yaml:"pull_timeout" json:"pull_timeout"`
}

Docker 默认镜像:docker.io/zenika/alpine-chrome

  • Image - Docker 注册表的镜像(提供注册表主机)
  • EntryPoint - 将在容器内运行的命令
  • Timeout[sec] - 运行容器的超时时间(不包括拉取镜像)
  • Wait[msec] - 页面加载超时时间(仅适用于基于 Chromium 的容器)
  • Flags - 运行容器的命令参数,基于 Chromium 时的默认值为:"--no-sandbox","--headless", "--proxy-auto-detect", "--temp-profile", "--incognito", "--disable-logging", "--disable-gpu"
  • Purge - 工作完成后是否删除容器(类似 docker rm)
  • NoPull - 阻止拉取镜像
  • PullTimeout - 设置拉取容器的超时时间
环境变量
  1. DOCKER_HOST - string - (EnvOverrideHost),用于设置 Docker 服务的 URL。
  2. DOCKER_API_VERSION - string - (EnvOverrideAPIVersion),用于设置要使用的 API 版本;留空表示使用最新版本。
  3. DOCKER_CERT_PATH - string - (EnvOverrideCertPath),用于指定加载 TLS 证书(ca.pem、cert.pem、key.pem)的目录。
  4. DOCKER_TLS_VERIFY - bool - (EnvTLSVerify),用于启用或禁用 TLS 验证(默认关闭)

示例:

{
  "wait": 10000,
  "image": "docker.io/zenika/alpine-chrome:with-node",
  "entry_point": "chromium-browser",
  "purge": true
}
Playwright

通过 Playwright 框架运行浏览器

type PlaywrightConfig struct {
    Browser       PlaywrightBrowser          `json:"browser" yaml:"browser"`
    Install       bool                       `yaml:"install" json:"install"`
    Timeout       uint32                     `yaml:"timeout" json:"timeout"`
    Wait          uint32                     `yaml:"wait" json:"wait"`
    TypeOfWait    *playwright.WaitUntilState `json:"type_of_wait" yaml:"type_of_wait"`
    PreRunScript  string                     `json:"pre_run_script" yaml:"pre_run_script"`
    PostRunScript string                     `json:"post_run_script" yaml:"post_run_script"`
    Stealth       bool                       `json:"stealth" yaml:"stealth"`
    
    StorageStateFile string `json:"storage_state_file" yaml:"storage_state_file"`
    IndexedDB        bool   `json:"indexed_db" yaml:"indexed_db"`
    
    Proxy *ProxyConfig `yaml:"proxy" json:"proxy"`
}
  • Browser - enum["Chromium", "FireFox", "WebKit"] - 要使用的浏览器
  • Install - 是否安装浏览器(首次使用时下载与内置 playwright-go 版本匹配的驱动和浏览器;使用预装了它们的 ghcr.io/pxyup/fitter-mcp:playwright 镜像时则不需要)
  • Timeout[sec] - 运行 Playwright 的超时时间
  • Wait[sec] - 页面加载超时时间
  • TypeOfWait - enum["load", "domcontentloaded", "networkidle", "commit"],要等待的页面状态,默认值为 "load"
  • PreRunScript[""] - 通过 AddInitScript 注入并在任何页面脚本运行前执行的脚本(在创建文档时、导航完成前)。适用于修补环境(覆盖 navigator、模拟 API)。无法访问已加载的 DOM。也支持占位符 {PL}
  • PostRunScript[""] - 页面加载后、读取页面内容前执行的脚本。适用于 DOM 交互(点击、滚动)。也支持占位符 {PL}
  • Stealth[false] - 添加用于尝试绕过机器人防护的脚本
  • StorageStateFile[""] - Playwright 存储状态 JSON(Cookie + localStorage)的路径(支持 ~/):导航前加载到浏览器上下文中,每次运行后写回,以保持刷新的会话有效。这样无头运行也能复用真实登录状态——使用 fitter_cli browser-login 创建一次文件。登录和抓取时使用相同的 browser:网站可能会将会话绑定到浏览器指纹。也支持格式化
  • IndexedDB[false] - 将 IndexedDB 包含在持久化存储状态中(一些 SPA,例如 Firebase Auth,会在其中保存令牌)
  • Proxy - 为请求配置代理配置

示例

{
  "timeout": 30,
  "wait": 30,
  "install": false,
  "browser": "Chromium"
}

模型

通过模型定义抓取结果

type Model struct {
    ObjectConfig *ObjectConfig `yaml:"object_config" json:"object_config"`
    ArrayConfig  *ArrayConfig  `json:"array_config" yaml:"array_config"`
    BaseField    *BaseField    `json:"base_field" yaml:"base_field"`
    IsArray      bool          `json:"is_array" yaml:"is_array"`
}

配置可以是以下之一:

示例:

{
  "object_config": {}
}
ObjectConfig

对象和字段的配置

type ObjectConfig struct {
    Fields      map[string]*Field `json:"fields" yaml:"fields"`
    Field       *BaseField        `json:"field" yaml:"field"`
    ArrayConfig *ArrayConfig      `json:"array_config" yaml:"array_config"`

    Condition string `json:"condition" yaml:"condition"`
}
  • Condition - 可选的 condition 表达式,在解析之前针对源节点进行求值;如果结果为 false,则从父对象中省略整个对象(完全不解析字段)

配置可以是以下任意一种:

  • Fields - 每个字段定义的映射;键为字段名称,值为配置
  • Field - 用于数组元素;字段将按 "string"、"int" 等基本类型进行反序列化(用于数组包含基本类型的情况)
  • ArrayConfig - 用于数组元素;将数组反序列化为数组

示例:

{
  "fields": {
    "title": {
      "base_field": {
        "type": "string",
        "path": "type"
      }
    }
  }
}
ArrayConfig

数组和字段的配置

type ArrayConfig struct {
    RootPath    string        `json:"root_path" yaml:"root_path"`
    Reverse     bool          `yaml:"reverse" json:"reverse"`
    
    ItemConfig  *ObjectConfig `json:"item_config" yaml:"item_config"`
    LengthLimit uint32        `json:"length_limit" yaml:"length_limit"`

    Condition     string `json:"condition" yaml:"condition"`
    ItemCondition string `json:"item_condition" yaml:"item_condition"`
    
    StaticConfig *StaticArrayConfig `json:"static_array"  yaml:"static_array"`
}
  • RootPath - 用于查找数组根元素的选择器;在 HTML 解析时也用于查找重复元素。数组大小为根元素下子元素的数量
  • Reverse - bool[false] - 指示是否需要反向迭代(从 n 到 1)
  • LengthLimit - 数组的固定长度(仅适用于生成的数组;不适用于静态数组)。注意:当源元素少于此限制时,数组会在末尾填充 null,以保持声明的大小(这是有意设计的)——省略 length_limit 可使数组长度与源长度完全一致
  • Condition - 可选的 condition 表达式,在解析之前针对源节点进行求值;如果结果为 false,则从父对象中省略整个数组
  • ItemCondition - 可选的 condition 表达式,对每个已构建的元素进行求值(fRes - 元素值,fSrc - 源元素,fIndex - 元素索引);解析结果为 false 的元素会从数组中移除——即声明式过滤。不适用于 static_array

配置可以是以下任意一种:

示例:

{
  "root_path": "#content dt.quote > a",
  "item_config": {
    "field": {
      "type": "string"
    }
  }
}
Field

字段通用配置

type Field struct {
	BaseField    *BaseField    `json:"base_field" yaml:"base_field"`
	ObjectConfig *ObjectConfig `json:"object_config" yaml:"object_config"`
	ArrayConfig  *ArrayConfig  `json:"array_config" yaml:"array_config"`

	FirstOf []*Field `json:"first_of" yaml:"first_of"`
}

配置可以是以下任意一种:

  • BaseField - 字段将按 "string"、"int" 等基本类型进行反序列化
  • ObjectConfig - 字段为嵌套对象时使用
  • ArrayConfig - 字段为数组时使用
  • FirstOf - 选择第一个解析结果非空的字段

示例:

{
  "base_field": {
    "type": "string",
    "path": "div.current-temp span.heading"
  }
}
BaseField

用于获取静态信息或生成新信息

type BaseField struct {
	Type FieldType `yaml:"type" json:"type"`
	Path string    `yaml:"path" json:"path"`

	HTMLAttribute string `json:"html_attribute" yaml:"html_attribute"`

	Condition string `json:"condition" yaml:"condition"`

	Generated *GeneratedFieldConfig `yaml:"generated" json:"generated"`

	FirstOf []*BaseField `json:"first_of" yaml:"first_of"`
}
  • FieldType - enum["null", "boolean", "string", "int", "int64", "float", "float64", "array", "object", "html", "raw_string"] - 用于解析的静态字段。重要:html 类型仅适用于返回 HTML 的连接器(在此情况下,HTMLAttribute 不起作用)。示例
  • Path - 用于解析的选择器(作为数组子元素时为相对路径)
  • HTMLAttribute - 额外值,仅在通过 goquery 进行 HTML 解析时生效。可在此指定要解析的属性。
  • Condition - 可选的 condition 表达式,对提取的值(fRes/fResJson/fResRaw、fIndex)进行求值;fSrc 为解析该字段时所依据的节点,包含同级节点。若结果为 false,则从父对象/数组中省略该字段,而不是生成 null。在 Generated 之前求值,因此条件为 false 时也会跳过生成操作(子请求、文件下载)

重要:默认情况下,"string" 类型会进行修剪并替换所有特殊字符;若需要纯字符串,请使用 "raw_string"

配置可以是以下任意一种,也可以为空:

  • Generated - 可使用自定义配置生成字段
  • FirstOf - 选择第一个解析结果非空的字段

示例

{
  "generated": {
    "uuid": {}
  }
}
{
  "type": "string",
  "path": "text()"
}
条件字段

每个字段都可以带有 condition,即一个 expr-lang 表达式(预定义值)。如果表达式的计算结果不为 true,该字段就会从输出中省略(键/项会消失),而不是设为 null。无效表达式也会导致字段被省略,并记录错误。

条件的计算时机:

  • BaseField.condition - 提取之后:fRes 是提取出的值,fSrc 是解析该字段的节点(包括其同级节点),因此 fSrc.on_sale == true 可以根据未提取的数据来决定是否包含某个字段。条件为 false 时,会完全跳过生成工作(不发起子请求,也不下载文件)
  • ObjectConfig.condition / ArrayConfig.condition - 解析之前:fRes/fSrc 是源节点(对于 json,是解析后的值;对于 html,是文本内容)
  • ArrayConfig.item_condition - 针对每个已构建的项进行计算:fRes 是该项,fSrc 是构建该项所用的源元素,fIndex 是该项的索引;条件为 false 的项会被丢弃,即声明式数组过滤。使用 fSrc 可根据源属性进行过滤,而无需将其添加到输出中

过滤数组项 - fSrc.in_stock 读取源元素(未提取到输出中),fRes.price 读取已构建的项:

{
  "array_config": {
    "root_path": "products",
    "item_condition": "fSrc.in_stock && fRes.price > 0",
    "item_config": {
      "fields": {
        "title": { "base_field": { "type": "string", "path": "title" } },
        "price": { "base_field": { "type": "float", "path": "price" } }
      }
    }
  }
}

仅当值通过检查时才包含键:

{
  "discount": {
    "base_field": {
      "type": "float",
      "path": "discount_pct",
      "condition": "fRes > 0"
    }
  }
}

特殊情况:

  • 在静态数组中,被省略的项仍会保留为 null(位置由定义固定,索引不会移动)
  • 如果省略了根模型配置,解析结果为 null
  • 在 first_of 中,条件为 false 的分支视为空,因此会尝试下一个分支

可运行示例:examples/config_conditions.json

GeneratedFieldConfig

提供在运行期间生成字段的功能

type GeneratedFieldConfig struct {
    UUID             *UUIDGeneratedFieldConfig   `yaml:"uuid" json:"uuid"`
    Static           *StaticGeneratedFieldConfig `yaml:"static" json:"static"`
    Formatted        *FormattedFieldConfig       `json:"formatted" yaml:"formatted"`
    Plugin           *PluginFieldConfig          `yaml:"plugin" json:"plugin"`
    Calculated       *CalculatedConfig           `yaml:"calculated" json:"calculated"`
    File             *FileFieldConfig            `yaml:"file" json:"file"`
    Model            *ModelField                 `yaml:"model" json:"model"`
    FileStorageField *FileStorageField           `json:"file_storage" yaml:"file_storage"`
}

配置可以是以下类型之一:

  • UUID - 生成随机 UUID V4
  • Static - 生成静态字段
  • Formatted - 格式化字段
  • Model - 由其他连接器和模型生成的模型
  • Plugin - 插件字段
  • Calculated - 计算字段
  • File - 文件字段(用于从服务下载文件)
  • FileStorage - 可保存到本地文件的文件字段

示例:

{
    "uuid": {}
}

https://github.com/PxyUp/fitter/blob/master/examples/cli/config_cli.json#L58

{
    "model": {
      "type": "array",
      "model": {
        "array_config": {
          "root_path": "#content dt.quote > a",
          "item_config": {
            "field": {
              "type": "string"
            }
          }
        }
      },
      "connector_config": {
        "response_type": "HTML",
        "url": "http://www.quotationspage.com/random.php",
        "attempts": 3,
        "browser_config": {
          "chromium": {
            "path": "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
            "wait": 10000
          }
        }
      }
    }
}
UUID

在运行期间生成随机 UUID V4,可用于生成唯一 ID

type UUIDGeneratedFieldConfig struct {
	Regexp string `yaml:"regexp" json:"regexp"`
}
  • Regexp - 提供一个匹配器,可用于获取生成的 UUID 的部分内容
Static

生成静态字段

type StaticGeneratedFieldConfig struct {
    Type  FieldType       `yaml:"type" json:"type"`
    Value string          `json:"value" yaml:"value"`
    Raw   json.RawMessage `json:"raw" yaml:"raw"`
}
  • Type - 枚举值["null", "boolean", "string", "int","int64","float","float64", "array", "object"] - 字段的类型
  • Value - 字段的字符串值
  • Raw - 字段的原始 json 值

示例

{
  "type": "int",
  "value": "65"
}
{
  "type": "array",
  "value": "[65,45]"
}
{
  "type": "array",
  "raw": [65,45]
}
Formatted Field Config

生成格式化字段,该字段会传递父级 base field 的值

type FormattedFieldConfig struct {
	Template string `yaml:"template" json:"template"`
}
  • Template - 包含占位符 {PL} 的模板,父级值将作为字符串注入其中

示例: https://github.com/PxyUp/fitter/blob/master/examples/cli/config_cli.json#L98

{
  "template": "https://news.ycombinator.com/item?id={PL}"
}
文件存储字段

该字段可用于将字段结果存储为本地文件

type FileStorageField struct {
    Content string          `json:"content" yaml:"content"`
    Raw     json.RawMessage `yaml:"raw" yaml:"raw"`
    
    FileName string `json:"file_name" yaml:"file_name"`
    Path     string `json:"path" yaml:"path"`
    Append   bool   `json:"append" yaml:"append"`
}
{
  "content": "{{{id}}}, {{{message}}}\n",
  "append": true,
  "file_name": "{{{id}}}.csv",
  "path": "/Users/pxyup/fitter/examples/cli/test/csv"
}
文件字段

该字段可用于从服务器下载文件到本地

type FileFieldConfig struct {
	Config *ServerConnectorConfig `yaml:"config" json:"config"`

	Url      string `yaml:"url" json:"url"`
	FileName string `json:"file_name" yaml:"file_name"`
	Path     string `json:"path" yaml:"path"`
}

该字段的结果将是一个表示本地文件路径的字符串

{
  "url": "https://images.shcdn.de/resized/w680/p/dekostoff-gobelinstoff-panel-oriental-cat-46-x-46_P19-KP_2.jpg",
  "path": "/Users/pxyup/fitter/bin",
  "config": {
    "method": "GET"
  }
}

包含传递的 URL(将父级值作为字符串注入)

{
  "url": "https://picsum.photos{PL}",
  "path": "/Users/pxyup/fitter/bin",
  "config": {
    "method": "GET"
  }
}

配置示例:

https://github.com/PxyUp/fitter/blob/master/examples/cli/config_image.json

https://github.com/PxyUp/fitter/blob/master/examples/cli/config_image_multiple.json

计算字段

该字段可根据表达式生成不同类型的值

type CalculatedConfig struct {
	Type       FieldType `yaml:"type" json:"type"`
	Expression string    `yaml:"expression" json:"expression"`
}
  • Type - 表达式的结果类型\
  • Expression - 用于计算的表达式(我们使用这个库来计算表达式)
预定义值

FNull - builder.Nullvalue 的别名

FNil - nil 的别名

isNull(value T) - 用于检查 value 是否为 FNull 的函数

fRes - 来自解析的基础字段的原始(带有正确类型)结果

fIndex - 在父级数组中的索引(仅当父级是数组字段时)

fResJson - 原始结果的 JSON 字符串表示形式

fResRaw - 字节格式的结果

fSrc - 仅用于 condition/item_condition 表达式:值所解析自的源节点(对于 JSON,是解析后的值,包含同级项;对于 HTML,是文本内容)。在计算、格式化和通知器表达式中不可用

FNewLine - 换行符分隔符

{
  "type": "bool",
  "expression": "fRes > 500"
}
插件字段

该字段可以是 fitter 的外部插件

更多信息

type PluginFieldConfig struct {
	Name string `json:"name" yaml:"name"`
	Config json.RawMessage `json:"config" yaml:"config"`
}
  • Name - 插件名称(不带扩展名,仅名称)
  • Config - 插件的 JSON 配置
模型字段

该字段类型可在运行时由新的模型和连接器生成

type ModelField struct {
	// Type of parsing
	ConnectorConfig *ConnectorConfig `yaml:"connector_config" json:"connector_config"`
	// Model of the response
	Model *Model `yaml:"model" json:"model"`

	Type FieldType `yaml:"type" json:"type"`
	Path string             `yaml:"path" json:"path"`

	Expression string    `yaml:"expression" json:"expression"`
}
  • ConnectorConfig - 要使用的连接器。重要:连接器中的 URL 可以将父级值作为字符串注入
  • Model - 底层模型的配置
  • Type - enum["null", "boolean", "string", "int", "int64", "float", "float64", "array", "object"] - 生成字段的类型
  • Path - 如果无法从生成的字段中提取某些信息,可以使用 JSON 选择器进行提取
  • Expression - 可用于对 Model 进行后处理的字符串(忽略 path 字段)

示例:

https://github.com/PxyUp/fitter/blob/master/examples/cli/config_cli.json#L60

{
  "type": "array",
  "model": {
    "array_config": {
      "root_path": "#content dt.quote > a",
      "item_config": {
        "field": {
          "type": "string"
        }
      }
    }
  }
}

https://github.com/PxyUp/fitter/blob/master/examples/cli/config_weather.json#L37

{
    "type": "string",
    "path": "temp.temp",
    "model": {
       "object_config": {
        "fields": {
          "temp": {
            "base_field": {
              "type": "string",
              "path": "//div[@id='forecast_list_ul']//td/b/a/@href",
              "generated": {
                "model": {
                  "type": "string",
                  "model": {
                    "object_config": {
                      "fields": {
                        "temp": {
                          "base_field": {
                            "type": "string",
                            "path": "div.current-temp span.heading"
                          }
                        }
                      }
                    }
                  },
                  "connector_config": {
                    "response_type": "HTML",
                    "attempts": 4,
                    "url": "https://openweathermap.org{PL}",
                    "browser_config": {
                      "playwright": {
                        "timeout": 30,
                        "wait": 30,
                        "install": false,
                        "browser": "FireFox",
                        "type_of_wait": "networkidle"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "connector_config": {
      "response_type": "xpath",
      "attempts": 3,
      "url": "https://openweathermap.org/find?q={PL}",
      "browser_config": {
        "playwright": {
          "timeout": 30,
          "wait": 30,
          "install": false,
          "browser": "Chromium"
        }
      }
    }
}
静态数组配置

提供静态(固定长度)数组生成

type StaticArrayConfig struct {
    Items map[uint32]*Field `yaml:"items" json:"items"`
    Length uint32            `yaml:"length" json:"length"`
}
  • Items - map[uint32]*Field - 键是数组中的索引,值是字段定义
  • Length - 如果设置(1+),可用于定义数组的自定义长度

示例:

{
  "0": {
    "base_field": {
      "type": "string",
      "path": "div.current-temp span.heading"
    }
  }
}
{
  "length": 4,
  "0": {
    "base_field": {
      "type": "string",
      "path": "div.current-temp span.heading"
    }
  }
}
{
  "length": 4,
  "2": {
    "base_field": {
      "type": "string",
      "path": "div.current-temp span.heading"
    }
  }
}
占位符列表
  1. {PL} - 用于注入值
  2. {INDEX} - 用于注入父数组中的索引
  3. {HUMAN_INDEX} - 用于以人类可读的方式注入父数组中的索引
  4. {{{json_path}}} - 从传播的“object”/“array”字段中获取信息
  5. {{{RefName=SomeName}}} - 按名称获取 引用 值。示例
  6. {{{RefName=SomeName json.path}}} - 按名称获取 引用 值,并通过 JSON 路径提取值。示例
  7. {{{FromEnv=ENV_KEY}}} - 从环境变量获取值
  8. {{{FromExp=fRes + 5 + fIndex}}} - 从表达式获取值。预定义值
  9. {{{FromInput=.}}} 或 {{{FromInput=json.path}}} - 从触发器或库的输入中获取值
  10. {{{FromFile=./test_file.log}}} - 按路径从文件中获取值。文件内容也可以包含占位符
  11. {{{FromURL=http://localhost:8081}}} - 从 URL 获取响应

示例:

{{{FromExp="{{{FromEnv=TEST_VAL}}}" + "hello"}}}
Current time is: {PL} with token from TokenRef={{{RefName=TokenRef}}} and TokenObjectRef={{{RefName=TokenObjectRef token}}}
Current time is: {PL} with token from TokenRef={{{RefName=TokenRef}}} and TokenObjectRef={{{RefName=TokenObjectRef token}}}
TokenRef={{{RefName=TokenRef}}} and TokenObjectRef={{{RefName=TokenObjectRef token}}} Object={{{value}}} {PL} Env={{{FromEnv=TEST_VAL}}} {INDEX} {HUMAN_INDEX}

引用

一种特殊映射,会在任何处理开始前预先获取,可用于连接器或占位符

可用于:

  1. 缓存 JWT 令牌并在标头中使用
  2. 缓存值
  3. 等等
引用
type Reference struct {
    *ModelField
    
    Expire *uint32 `yaml:"expire" json:"expire"`
}
  • ModelField - 嵌入式结构体,可使用相同的字段
  • Expire[sec] - 获取后引用过期的时长。未设置 => 永久缓存。设为 0 => 每次都重新获取。设为 n > 0 => 缓存 n 秒

用于 Fitter

type RefMap map[string]*Reference

type Config struct {
    // Other Config Fields

    Limits     *Limits `yaml:"limits" json:"limits"`
    References RefMap  `json:"references" yaml:"references"`
}

用于 Fitter Cli

type RefMap map[string]*Reference

type CliItem struct {
    // Other Config Fields

    Limits     *Limits `yaml:"limits" json:"limits"`
    References RefMap  `json:"references" yaml:"references"`
}

示例

https://github.com/PxyUp/fitter/blob/master/examples/cli/config_ref.json#L2

{
  "references": {
    "TokenRef": {
      "expire": 10,
      "connector_config": {
        "response_type": "json",
        "static_config": {
          "value": "\"plain token\""
        }
      },
      "model": {
        "base_field": {
          "type": "string"
        }
      }
    },
    "TokenObjectRef": {
      "connector_config": {
        "response_type": "json",
        "static_config": {
          "value": "{\"token\":\"token from object\"}"
        }
      },
      "model": {
        "object_config": {
          "fields": {
            "token": {
              "base_field": {
                "type": "string",
                "path": "token"
              }
            }
          }
        }
      }
    }
  }
}

示例

通知器

可选的单项配置 item.notifier_config,用于在处理后将解析结果推送到其他位置。结果仍会像往常一样返回(CLI/MCP 输出、服务日志);通知器会额外发送结果。适用于 Fitter(服务模式)、Fitter_CLI 和 Fitter_MCP。

type NotifierConfig struct {
    Expression      string `yaml:"expression" json:"expression"`
    Force           bool   `json:"force" yaml:"force"`
    SendArrayByItem bool   `yaml:"send_array_by_item" json:"send_array_by_item"`
    Template        string `yaml:"template" json:"template"`

    // exactly ONE destination:
    Console     *ConsoleConfig       `yaml:"console" json:"console"`
    TelegramBot *TelegramBotConfig   `yaml:"telegram_bot" json:"telegram_bot"`
    Http        *HttpConfig          `yaml:"http" json:"http"`
    Redis       *RedisNotifierConfig `json:"redis" yaml:"redis"`
    File        *FileStorageField    `json:"file" yaml:"file"`
}
  • Expression - 可选的 expr-lang 条件:仅当条件求值为 true 时才发送通知。解析结果可通过 fRes(解析后的值)、fResRaw(原始字节)、fResJson(JSON 字符串)获取,例如 len(fResRaw) > 0
  • Force - 即使解析以错误结束,也发送通知
  • SendArrayByItem - 如果结果是数组,则将每个元素作为单独的通知发送
  • Template - 可选模板,在发送前应用于结果,允许使用占位符
  • Destination - 只能是 console、telegram_bot、http、redis、file 中的一个

目标配置:

type HttpConfig struct {
    Url     string            `yaml:"url" json:"url"`
    Method  string            `json:"method" yaml:"method"`
    Headers map[string]string `yaml:"headers" json:"headers"`
    Timeout uint32            `yaml:"timeout" json:"timeout"`
}

type TelegramBotConfig struct {
    Token   string  `json:"token" yaml:"token"`
    UsersId []int64 `json:"users_id" yaml:"users_id"`
    Pretty  bool    `json:"pretty" yaml:"pretty"`
    OnlyMsg bool    `json:"only_msg" yaml:"only_msg"`
}

type RedisNotifierConfig struct {
    Addr     string `json:"addr" yaml:"addr"`
    Password string `json:"password" yaml:"password"`
    DB       int    `json:"db" yaml:"db"`
    Channel  string `json:"channel" yaml:"channel"`
}

type ConsoleConfig struct {
    OnlyResult bool `json:"only_result" yaml:"only_result"`
}

file 目标使用与 file 字段类型相同的 FileStorageField。

示例(examples/config_telegram.json):

{
  "item": {
    "connector_config": { "...": "..." },
    "model": { "...": "..." },
    "notifier_config": {
      "expression": "len(fResRaw) > 0",
      "telegram_bot": {
        "token": "{{{FromEnv=TG_TOKEN}}}",
        "users_id": [123456],
        "pretty": true
      }
    }
  }
}

限制

设置限制,以防止 DDOS 和内存占用过高

type Limits struct {
	HostRequestLimiter HostRequestLimiter `yaml:"host_request_limiter" json:"host_request_limiter"`
	ChromiumInstance   uint32             `yaml:"chromium_instance" json:"chromium_instance"`
	DockerContainers   uint32             `yaml:"docker_containers" json:"docker_containers"`
	PlaywrightInstance uint32             `yaml:"playwright_instance" json:"playwright_instance"`
}
  • HostRequestLimiter - map[string]int64 - 按主机名设置限制,键为主机,值为并行请求数(用于服务连接器)
  • ChromiumInstance - 并行 chromium 实例数
  • DockerContainers - 并行 docker 实例数
  • PlaywrightInstance - 并行 playwright 实例数

https://github.com/PxyUp/fitter/blob/master/examples/cli/config_cli.json#L2

{
  "limits": {
    "host_request_limiter": {
      "hacker-news.firebaseio.com": 5
    },
    "chromium_instance": 3,
    "docker_containers": 3,
    "playwright_instance": 3
  }
}

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。