docs: 联网搜索服务架构方案全套(plan-final/design-arch/选型决策/整合导览/MCP文档/部署预设/联调手册) bench: 5 方案 + 代理 + 站点矩阵本机实测工程(无密钥) 部署目标:primary mgr1 先行测试(待批准后执行)
144 lines
4.6 KiB
Python
Executable file
144 lines
4.6 KiB
Python
Executable file
#!/usr/bin/env python3
|
||
"""Assert one SearXNG T1 JSON body. stdout: JSON verdict."""
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
import re
|
||
import sys
|
||
from urllib.parse import urlparse
|
||
|
||
POLICY = re.compile(
|
||
r"(gov\.cn|customs\.gov\.cn|chinatax\.gov\.cn|mofcom\.gov\.cn|"
|
||
r"yidaiyilu\.gov\.cn|news\.cn|people\.com\.cn|xinhuanet\.com)$",
|
||
re.I,
|
||
)
|
||
T2_PREF = re.compile(r"(gov\.cn|news\.cn|people\.com\.cn)$", re.I)
|
||
|
||
|
||
def first_cjk4(text: str) -> str:
|
||
chars = re.findall(r"[\u4e00-\u9fff]", text or "")
|
||
return "".join(chars[:4])
|
||
|
||
|
||
def eval_body(raw: str) -> dict:
|
||
out = {
|
||
"assert_ok": False,
|
||
"fail_class": "",
|
||
"note": "",
|
||
"n_results": 0,
|
||
"n_rich": 0,
|
||
"engines": [],
|
||
"engine_counts": {},
|
||
"unresponsive_engines": [],
|
||
"policy_hosts": [],
|
||
"hosts": [],
|
||
"t2_url": "",
|
||
"t2_title": "",
|
||
"t2_title_cjk4": "",
|
||
"sample_titles": [],
|
||
}
|
||
try:
|
||
data = json.loads(raw)
|
||
except json.JSONDecodeError:
|
||
out["fail_class"] = "json_403"
|
||
out["note"] = "body_not_json"
|
||
return out
|
||
if isinstance(data, dict) and data.get("error"):
|
||
out["fail_class"] = "json_403"
|
||
out["note"] = str(data.get("error"))[:200]
|
||
return out
|
||
results = data.get("results") if isinstance(data, dict) else None
|
||
if not isinstance(results, list):
|
||
out["fail_class"] = "json_403"
|
||
out["note"] = "no_results_array"
|
||
return out
|
||
out["n_results"] = len(results)
|
||
engines = []
|
||
counts = {}
|
||
hosts = []
|
||
policy = []
|
||
rich = 0
|
||
t2_url = ""
|
||
t2_title = ""
|
||
for i, item in enumerate(results):
|
||
if not isinstance(item, dict):
|
||
continue
|
||
title = (item.get("title") or "").strip()
|
||
url = (item.get("url") or "").strip()
|
||
content = (item.get("content") or "").strip()
|
||
eng = item.get("engine") or ""
|
||
if isinstance(item.get("engines"), list) and not eng:
|
||
eng = item["engines"][0] if item["engines"] else ""
|
||
if eng:
|
||
engines.append(str(eng))
|
||
counts[str(eng)] = counts.get(str(eng), 0) + 1
|
||
host = urlparse(url).hostname or ""
|
||
if host:
|
||
hosts.append(host.lower())
|
||
if POLICY.search(host.lower()):
|
||
policy.append(host.lower())
|
||
if title and url.startswith("http") and len(content) >= 8:
|
||
rich += 1
|
||
if i < 3:
|
||
out["sample_titles"].append({"title": title[:120], "url": url[:200], "engine": eng, "host": host})
|
||
if not t2_url and url.startswith("http") and T2_PREF.search(host.lower()):
|
||
t2_url = url
|
||
t2_title = title
|
||
out["n_rich"] = rich
|
||
out["engines"] = sorted(set(engines))
|
||
out["engine_counts"] = counts
|
||
out["hosts"] = hosts[:20]
|
||
out["policy_hosts"] = sorted(set(policy))
|
||
out["t2_url"] = t2_url
|
||
out["t2_title"] = t2_title
|
||
out["t2_title_cjk4"] = first_cjk4(t2_title)
|
||
unresp = data.get("unresponsive_engines") or []
|
||
out["unresponsive_engines"] = unresp
|
||
notes = []
|
||
if unresp:
|
||
notes.append(f"unresponsive={unresp}")
|
||
|
||
ok = True
|
||
if out["n_results"] < 5:
|
||
ok = False
|
||
out["fail_class"] = "engine_outage"
|
||
notes.append("results<5")
|
||
if rich < 5:
|
||
# first 10 rich count — we counted all; restrict to first 10
|
||
rich10 = 0
|
||
for item in results[:10]:
|
||
if not isinstance(item, dict):
|
||
continue
|
||
title = (item.get("title") or "").strip()
|
||
url = (item.get("url") or "").strip()
|
||
content = (item.get("content") or "").strip()
|
||
if title and url.startswith("http") and len(content) >= 8:
|
||
rich10 += 1
|
||
out["n_rich"] = rich10
|
||
if rich10 < 5:
|
||
ok = False
|
||
out["fail_class"] = out["fail_class"] or "thin_snippets"
|
||
notes.append("rich<5_in_top10")
|
||
if len(out["engines"]) < 2:
|
||
# not a hard fail per select.md: 单引擎全灭记 engine_outage,不直接判方案失败
|
||
# but plan T1 assert 4 says engines >= 2
|
||
ok = False
|
||
out["fail_class"] = out["fail_class"] or "engine_outage"
|
||
notes.append("engines<2")
|
||
if not out["policy_hosts"]:
|
||
ok = False
|
||
notes.append("no_policy_host")
|
||
# plan: assert_ok=false but keep host list
|
||
out["assert_ok"] = ok
|
||
out["note"] = "; ".join(notes)
|
||
return out
|
||
|
||
|
||
def main() -> None:
|
||
path = sys.argv[1]
|
||
raw = open(path, "r", encoding="utf-8", errors="replace").read()
|
||
print(json.dumps(eval_body(raw), ensure_ascii=False))
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|