docs: 联网搜索服务架构方案全套(plan-final/design-arch/选型决策/整合导览/MCP文档/部署预设/联调手册) bench: 5 方案 + 代理 + 站点矩阵本机实测工程(无密钥) 部署目标:primary mgr1 先行测试(待批准后执行)
19 KiB
S3b / S3c 本机 Docker 实测计划
- 对应筛选:
.dsh/artifacts/run-20260901-browser-arch/select.md - 主机假设:darwin arm64 / Docker Desktop 29.4.0(与 S3a 相同)
- 代理:先
export PROXY_SUB_URL(只从.dsh/artifacts/proxy-subscriptions.local.mdSUB-1 注入),再bash bench/proxy/up.sh。国外组复用http://127.0.0.1:17890。脚本与 JSON 禁止出现订阅 URL / uuid / password。 - 同时运行 候选容器 ≤3。
bench-proxy-mihomo算基建,不占 3 席。 - 每方案第一次发业务请求前:idle 采样 5 分钟。
0. 目录骨架(S3b 建,本文件只约定)
bench/
lib/
sample_stats.sh # docker stats 循环
curl_time.sh # 单次计时
summarize.py # 读 raw → results.json
searxng-cn/ compose.yml settings.yml request.sh results.json
trafilatura-http/ compose.yml Dockerfile app.py request.sh results.json
searxng-global/ compose.yml settings.yml request.sh results.json
lightpanda/ compose.yml request.sh results.json
chrome-headless-shell/ compose.yml request.sh results.json
raw/ # 各批次 stats-*.tsv 与 curl-*.jsonl(可选)
results.json 顶层必须能被评分卡直接引用,字段见 §4。
1. 统一采集规范
1.1 容器命名
| 方案 | 容器名 | 宿主机端口 |
|---|---|---|
| searxng-cn | bench-searxng-cn |
18880 → 8080 |
| trafilatura-http | bench-trafilatura |
18881 → 8080 |
| searxng-global | bench-searxng-global |
18882 → 8080 |
| lightpanda | bench-lightpanda |
19222 → 9222 |
| chrome-headless-shell | bench-headless-shell |
19223 → 9222 |
| 代理(基建) | bench-proxy-mihomo |
17890 / 19090(已有) |
1.2 docker stats
bench/lib/sample_stats.sh 约定(S3b 实现时按此抄):
# 用法: sample_stats.sh <label> <interval_s> <duration_s> <container...>
# 例: bash bench/lib/sample_stats.sh searxng-cn-idle 5 300 bench-searxng-cn
label="$1"; interval="$2"; duration="$3"; shift 3
out="bench/raw/${label}.tsv"
echo -e "ts_iso\tname\tmem_bytes\tmem_limit_bytes\tcpu_pct" > "$out"
end=$(( $(date +%s) + duration ))
while [ "$(date +%s)" -lt "$end" ]; do
ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
docker stats --no-stream --format '{{.Name}}\t{{.MemUsage}}\t{{.CPUPerc}}' "$@" \
| while IFS=$'\t' read -r name mem cpu; do
# MemUsage 形如 "85.2MiB / 256MiB" —— summarize.py 解析
printf '%s\t%s\t%s\t%s\n' "$ts" "$name" "$mem" "$cpu" >> "$out"
done
sleep "$interval"
done
- idle:
interval=5duration=300(5 分钟),期间禁止发业务请求。 - 单会话:发请求前 10s 起采到请求结束后 10s,
interval=1。 - T5 burst:从 t=0 到全部结束 + 15s,
interval=1。 - 指标:
idle_mb= 5 min 中位;session_delta_mb= 单会话峰值 − idle 中位;burst_peak_mb= burst 窗口最大。
辅助(可选,更准 RSS):
docker exec "$c" sh -c 'cat /sys/fs/cgroup/memory.current 2>/dev/null || cat /sys/fs/cgroup/memory/memory.usage_in_bytes'
cgroup 读不到就只信 docker stats,在 results.json 标 rss_source=docker_stats。
1.3 curl 计时
# bench/lib/curl_time.sh
# 环境: URL METHOD DATA PROXY(可选)
extra=()
[ -n "${PROXY:-}" ] && extra+=(-x "$PROXY")
code_file=$(mktemp)
body_file=$(mktemp)
metrics=$(curl -sS "${extra[@]}" -X "${METHOD:-GET}" \
-o "$body_file" \
-w '%{http_code} %{time_namelookup} %{time_connect} %{time_starttransfer} %{time_total} %{size_download}' \
--connect-timeout 10 --max-time "${MAX_TIME:-30}" \
-A 'onesvm-bench/s3b' \
${DATA:+-H 'Content-Type: application/json' -d "$DATA"} \
"$URL")
# 输出一行 JSON 到 stdout,body 路径给断言脚本
写入 jsonl 的字段:ts, scheme, template, http_code, t_namelookup, t_connect, ttfb, t_total, bytes, assert_ok, note。
p50 / p95:对该模板全部 t_total 排序后取分位(线性插值即可)。不要用 Excel 手估。
1.4 镜像 pin 与 digest
每个 compose.yml 写死 select.md 的 tag,启动后:
docker inspect --format '{{.Name}} {{.Image}} {{index .RepoDigests 0}}' "$c"
写入 results.json.image。拉不到 Hub 时用 docker load(见 select.md §5),不要改 pin 成 latest。
1.5 清理
每批次结束:
docker rm -f bench-searxng-cn bench-trafilatura bench-searxng-global bench-lightpanda bench-headless-shell || true
# 代理只在当天国外批次全部结束时 down
# bash bench/proxy/down.sh
2. 五套需求模板
T1 国内关键词搜索 — 方案 searxng-cn
请求(可执行):
curl -sS -G 'http://127.0.0.1:18880/search' \
--data-urlencode 'q=跨境电商 出口退税 政策 2026' \
--data-urlencode 'format=json' \
--data-urlencode 'language=zh-CN' \
--data-urlencode 'categories=general'
断言(脚本硬判):
- HTTP 200。
results为数组且length >= 5。- 前 10 条中,至少 5 条同时具备非空
title、非空url(http开头)、非空content(strip 后 ≥ 8 字符)。 results[*].engine去重后 ≥ 2(允许百度+必应,或搜狗+360)。若unresponsive_engines含某引擎,记入note,不单独失败。- 至少 1 条
urlhost 匹配gov.cn|customs.gov.cn|chinatax.gov.cn|mofcom.gov.cn|yidaiyilu.gov.cn|news.cn|people.com.cn|xinhuanet.com(政策向)。不满足则assert_ok=false但把实际 host 列表留下,供人工改阈值。
失败分类: json_403(忘开 formats)/ engine_outage / thin_snippets / timeout。
质量抽检(人工 2 分钟): 打开第 1 条 URL,标题是否与结果 title 同主题。
T2 国内页面正文提取 — 方案 trafilatura-http
URL 选择顺序:
- 优先用 T1 结果里第一条 host ∈
gov.cn|news.cn|people.com.cn且 GET 200 的文章 URL。 - 回退固定页(公开、长期稳定、有导航壳):
https://www.gov.cn/zhengce/2021-03/13/content_5592681.htm
(《国民经济和社会发展第十四个五年规划和2035年远景目标纲要》。若 2026 年该 URL 变更,改用 T1 第一条 gov.cn。)
请求:
# 约定包装 API(S3b 实现 app.py 时遵守)
curl -sS -X POST 'http://127.0.0.1:18881/v1/read' \
-H 'Content-Type: application/json' \
-d '{"url":"https://www.gov.cn/zhengce/2021-03/13/content_5592681.htm","formats":["markdown"],"max_chars":20000}'
包装器内部:trafilatura.fetch_url + trafilatura.extract(..., output_format="markdown", include_comments=False, include_tables=True, favor_precision=True)。
断言:
- HTTP 200,
ok=true,markdown长度 ≥ 800。 title非空。- 无导航/广告残渣(规则):
markdown不得匹配下列任一条(忽略大小写):网站地图|设为首页|加入收藏|政务微信|客户端下载|无障碍浏览- 连续导航口吻:
首页\s*[>|/]\s*政策且出现 ≥ 3 次面包屑分隔
- 正文在: 须同时命中
十四五与2035(回退 URL);若改用 T1 URL,改为命中该条 T1title的前 4 个汉字。 - 不得把整页当 HTML 倾倒:
markdown中<div/<script出现次数 = 0。 - 若
truncated=true,char_count必须 =max_chars。
失败分类: fetch_fail / empty_extract / nav_residue / html_dump。
T3 国外关键词搜索 — 方案 searxng-global(经代理)
前置: bench-proxy-mihomo 已 up;SearXNG 容器 HTTP_PROXY=http://host.docker.internal:17890(或 settings outgoing.proxies)。
请求:
curl -sS -G 'http://127.0.0.1:18882/search' \
--data-urlencode 'q=best bluetooth earbuds 2026 reddit' \
--data-urlencode 'format=json' \
--data-urlencode 'language=en-US' \
--data-urlencode 'categories=general'
断言:
- HTTP 200,
results.length >= 5。 results[*].engine去重 ≥ 2(多源聚合)。results[*].urlhost 去重 ≥ 3。- 至少 1 条 URL host 为
reddit.com或redd.it,或title|content含reddit(小写)。
说明:S3a 对https://www.reddit.com/裸 GET 为 403,本模板走元搜索,不直接打 reddit 首页。 - 记录
unresponsive_engines。Google 单独 403/验证码 →note=google_captcha,只要其余引擎撑住断言 1–4 仍算通过。
失败分类: proxy_down(先 curl -x http://127.0.0.1:17890 https://www.google.com/generate_204 期望 204)/ single_engine / no_reddit_ref。
T4 国外 JS 渲染页提取 — lightpanda 主测,chrome-headless-shell 对照
页面(按序,记录每一档):
| 档 | URL | 目的 |
|---|---|---|
| T4-ctrl | https://quotes.toscrape.com/js/ |
无 WAF 的 JS 对照:至少 3 条 quote 文本 |
| T4-shop | https://www.allbirds.com/products/mens-wool-runners |
Shopify 公开商品:标题 + 价格字段 |
| T4-amz | https://www.amazon.com/dp/B08N5WRWNW |
Amazon 公开商品页(Echo Dot 常见 ASIN)。预期反爬,必须记。 |
不要用 Medium 登录墙当主断言。若 Allbirds 404,改 https://www.allbirds.com/ 首页抽首个商品价。
驱动约定(两方案同一脚本,只换 CDP 端点):
- Lightpanda:
http://127.0.0.1:19222(CDP)。 - shell:
http://127.0.0.1:19223。 - 实现可用 Playwright
connectOverCDP或 chromedp;不要再在客户端拉起本地 Chrome。 - 导航超时 25s;
waitUntil=networkidle最多 8s,超时则退domcontentloaded+ 固定 sleep 2s。 - 代理:容器启动已带
--proxy-server/HTTP_PROXY,客户端不要套两层。
抽取:
title = document.title
text = document.body.innerText.slice(0, 8000)
# T4-shop / T4-amz:
price = meta[itemprop=price] || [class*="price"] 首个匹配的 innerText
jsonld = script[type="application/ld+json"] 里 Offers.price(有则优先)
断言:
| 档 | 通过条件 |
|---|---|
| T4-ctrl | text 含 quote 或作者名;或 ≥3 个 .quote 节点。失败 = 内核 JS 基本不可用。 |
| T4-shop | title 含 Wool Runner 或 Allbirds;price 匹配 \$[0-9] 或 jsonld 有数字价。 |
| T4-amz | 能解析 title 且不像验证码页;price 可选。若 title/text 匹配 `(?i)robot |
反爬记录(强制字段): http_status_in_page, blocked, challenge_vendor(cloudflare|datadome|akamai|amazon|unknown|none), final_url。
评分:T4-ctrl 失败重创质量分;T4-amz 拦截只进 note,不把成功率打到 0。
T5 60 会话 burst
语义: 60 = 同时涌入的请求数(队列深度),不是 60 个 Chromium。浏览器方案 CONCURRENT=1(shell)或 Lightpanda 单进程多客户端;超槽位必须排队或 429,禁止把 60 个 Chrome 拉起来。
| 方案 | 涌入内容 | 并发模型 |
|---|---|---|
| searxng-cn | 60× T1 同 query | 直接打 SearXNG,无自研队列 |
| trafilatura-http | 60× T2 同 URL | 包装器内 semaphore=8(避免 60 条 TCP 打爆目标站) |
| searxng-global | 60× T3 同 query | 同 CN |
| lightpanda | 60× T4-ctrl | 客户端最多 4 个并行 CDP 导航,其余等(模拟网关 QUEUED) |
| chrome-headless-shell | 60× T4-ctrl | 并行度 1,其余 FIFO;超时 30s/个则记 fail 继续 |
发射:
# 60 个后台 curl 同一秒涌入(搜索类)
seq 60 | xargs -P 60 -I{} bash bench/lib/curl_time.sh
Trafilatura / 浏览器不要用 -P 60 打目标站;用本机队列脚本:60 个任务入队,worker 池 = 上表并行度。
记录:
- 排队:首个完成 ttfb、最后完成
t_total、是否出现 429/503。 - 成功率:断言通过 / 60。
- p50 / p95:60 个
t_total。 burst_peak_mb、是否 OOM(docker inspect .State.OOMKilled)。- 浏览器方案若未实现网关,用脚本侧 FIFO 代替,
results.json.queue_impl=client_fifo,并在 note 写「生产应是 SQLite WAL + CONCURRENT」。
断言(方案级):
- 容器未退出、
OOMKilled=false。 - 搜索类成功率 ≥ 85%;读取/JS ≥ 70%(T4-ctrl)。
- 峰值内存:搜索/trafilatura < 400 MB;lightpanda < 256 MB cgroup;shell < 400 MB cgroup。超限记
mem_over,综合评分按评分卡 0 档。
3. 批次安排(本机 ≤3 候选容器)
每方案:up → idle 5 min → 单模板 5 次(取中位)→ 该方案 T5 → down。不要跨方案复用未清的浏览器进程。
| 批次 | 候选容器 | 基建 | 做的模板 | 预计墙钟 |
|---|---|---|---|---|
| A 国内 | searxng-cn + trafilatura-http |
无 | T1(cn)→ T2(用 T1 URL)→ T5 cn → T5 trafilatura | 40–50 min |
| B 国外搜索+轻渲染 | searxng-global + lightpanda |
bench-proxy-mihomo |
T3 → T4 三档(panda)→ T5 global → T5 panda | 50–70 min |
| C 保真对照 | chrome-headless-shell + lightpanda(已有数据可不再 idle 5 min,若刚 down 过则重做 idle) |
同一代理 | T4 三档(shell)→ T5 shell | 40–60 min |
| D(可选)合成冒烟 | searxng-cn + searxng-global + trafilatura-http |
代理 | 各 1 次 T1/T2/T3,只看三容器 RSS 之和 | 15 min |
批次 A 与 B 不可与 C 重叠。D 不得加入 shell。
代理: 批次 B 开始时 up.sh,C 结束或当天收工时 down.sh。切换节点(可选):
curl -sS -X PUT "http://127.0.0.1:19090/proxies/%F0%9F%9A%80%E8%8A%82%E7%82%B9%E9%80%89%E6%8B%A9" \
-H 'Content-Type: application/json' \
-d '{"name":"🇺🇸美国圣何塞02 | 三网推荐"}'
节点名以 bench/proxy/probe-results.json 为准。
4. 各方案 compose / 请求要点
4.1 bench/searxng-cn/compose.yml
services:
searxng:
image: docker.io/searxng/searxng:2026.8.29-d226b78bc
container_name: bench-searxng-cn
ports: ["18880:8080"]
volumes:
- ./settings.yml:/etc/searxng/settings.yml:ro
environment:
- SEARXNG_BASE_URL=http://localhost:18880/
mem_limit: 192m
restart: "no"
request.sh t1|t5。settings 见 select.md §1.1。
4.2 bench/trafilatura-http/
Dockerfile:
FROM python:3.12-slim-bookworm
RUN pip install --no-cache-dir trafilatura==2.2.0
WORKDIR /app
COPY app.py .
EXPOSE 8080
CMD ["python", "-u", "app.py"]
app.py:stdlib http.server 即可,避免再拉 Flask。只实现 POST /v1/read。mem_limit: 128m。
国外复用时加:
environment:
HTTP_PROXY: http://host.docker.internal:17890
HTTPS_PROXY: http://host.docker.internal:17890
NO_PROXY: 127.0.0.1,localhost
本轮 T2 不要开代理。
4.3 bench/searxng-global/compose.yml
同 4.1,端口 18882,容器名 bench-searxng-global,extra_hosts: ["host.docker.internal:host-gateway"],环境或 settings 走 17890。mem_limit: 192m。
4.4 bench/lightpanda/compose.yml
services:
lightpanda:
image: docker.io/lightpanda/browser:0.3.7
container_name: bench-lightpanda
command: ["lightpanda", "serve", "--host", "0.0.0.0", "--port", "9222"]
ports: ["19222:9222"]
extra_hosts: ["host.docker.internal:host-gateway"]
environment:
HTTP_PROXY: http://host.docker.internal:17890
HTTPS_PROXY: http://host.docker.internal:17890
mem_limit: 256m
request.sh:Node 或 Python Playwright connectOverCDP('http://127.0.0.1:19222')。
4.5 bench/chrome-headless-shell/compose.yml
services:
shell:
image: docker.io/chromedp/headless-shell:151.0.7922.109
container_name: bench-headless-shell
ports: ["19223:9222"]
extra_hosts: ["host.docker.internal:host-gateway"]
command:
- --no-sandbox
- --remote-debugging-address=0.0.0.0
- --remote-debugging-port=9222
- --proxy-server=http://host.docker.internal:17890
shm_size: "128m"
mem_limit: 400m
镜像 ENTRYPOINT 已是 headless-shell 时,command 只追加 flag。S3b 若启动即退,把 docker logs 贴进 results.json.note(未验证 Desktop 对 151.x arm64)。
5. results.json schema
每个方案一份:
{
"scheme": "searxng-cn",
"group": "domestic",
"image": {"ref": "docker.io/searxng/searxng:2026.8.29-d226b78bc", "digest": "sha256:…"},
"probed_at": "2026-09-01T00:00:00+08:00",
"rss_source": "docker_stats",
"idle_mb_p50": null,
"session_delta_mb": null,
"burst_peak_mb": null,
"templates": {
"T1": {
"n": 5,
"success": 0,
"http_codes": [],
"p50_s": null,
"p95_s": null,
"assert_ok": false,
"note": ""
}
},
"t5": {
"n": 60,
"parallelism": 60,
"queue_impl": "none",
"success": 0,
"p50_s": null,
"p95_s": null,
"oom": false,
"http_429": 0,
"http_503": 0
},
"t4_waf": [],
"scorecard": {
"mem": null, "quality": null, "stability": null, "latency": null, "ops": null, "total": null
}
}
T4 的 t4_waf 元素:{tier, url, blocked, vendor, http_status_in_page}。
填完后把数字抄回 select.md §4.7。本文件不改 select 以外的架构文档。
6. S3b / S3c 执行顺序(可当 checklist)
S3b(国内,可无代理)
docker pull或load:searxng/searxng:2026.8.29-d226b78bc、python:3.12-slim-bookworm。- 批次 A:建
searxng-cn+trafilatura-http目录与 compose。 - 只起
bench-searxng-cn→ idle 5 min → T1 ×5 → T5。 - 保持 cn 或 down 后起
bench-trafilatura(同时 ≤2)→ idle 5 min → T2 ×5(URL 来自 T1)→ T5。 - 写两份
results.json。两容器同时存在时再采 30s,看 RSS 之和(P0 冒烟)。
S3c(国外,必须代理)
bash bench/proxy/up.sh;curl -x http://127.0.0.1:17890 -o /dev/null -w '%{http_code}\n' https://www.google.com/generate_204期望 204。- pull/load:
lightpanda/browser:0.3.7、chromedp/headless-shell:151.0.7922.109。 - 批次 B:global idle → T3 ×5 → T5;panda idle → T4 三档各 3 次 → T5-ctrl。
- 批次 C:down panda(或留着对照但 不要 同时 T5)→ shell idle → T4 三档 → T5-ctrl。
down.sh。写results.json+ 回填评分卡。
密钥扫描(收工):rg -n 'uuid:|password:|PROXY_SUB_URL=http' bench 应无节点凭据。
7. 本机注意
- Apple Silicon 必须拉 arm64 digest。
docker image inspect --format '{{.Architecture}}'若为amd64会走 qemu,内存与延迟作废,重拉--platform linux/arm64。 host.docker.internal在 Docker Desktop 可用;若不通,改172.17.0.1或host-gateway,记入 note。- 目标站限流:T2/T4 单 URL 5 次 + T5 会打同一 host。Trafilatura T5 并行度 ≤8;Amazon T4 每方案最多 3 次,禁止 T5 打 Amazon。
- 合规:只打公开页;不登录、不绕验证码、不采集个人数据。拦截页当
blocked,不当成功正文。