Files
Sansheng-Intelligence/scripts/build_site.py
T
Jack f1ce583fd1 feat: 发布三笙AI情报局独立版公司研发基线
同步家纺情报 v2 数据、59 个严格权威源、趋势图库、移动端交互、Google/Yandex 翻译门控及静态 SEO/GEO 构建。

补充面向 C# 研发的 README,明确当前无 LLM、多模态与生图模型,记录翻译请求参数、缓存与失败策略、数据处理边界、前端职责和无 GitHub Pages 部署流程。

新增按日期倒序维护的 CHANGELOG,记录影响、验证与代码/数据/dist 回滚方法。

验证:11 个 Python 契约测试、13 个 Node 前端测试通过;静态构建生成 1096 个 Sitemap URL。
2026-08-14 19:26:39 +08:00

129 lines
5.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""构建可直接部署或由本地 HTTP 服务打开的独立静态站。"""
from __future__ import annotations
import argparse
import json
import os
import shutil
import subprocess
import sys
from pathlib import Path
from seo_config import BRAND_NAME, ENTITY_DEFINITION, KEYWORD_CLUSTERS, SECTION_INTENTS
ROOT = Path(__file__).resolve().parents[1]
PUBLISH_DATA = ("textile-intelligence.json", "textile-trends.json")
DEFAULT_SITE_URL = "http://127.0.0.1:8765"
def replace_directory(source: Path, target: Path) -> None:
previous = target.with_name(f".{target.name}-previous")
if previous.exists():
shutil.rmtree(previous)
if target.exists():
target.replace(previous)
try:
source.replace(target)
except Exception:
if previous.exists() and not target.exists():
previous.replace(target)
raise
if previous.exists():
shutil.rmtree(previous)
def write_llms_file(target: Path, payload: dict, site_url: str) -> None:
summary = payload.get("summary") or {}
lines = [
f"# {BRAND_NAME}",
"",
f"> {ENTITY_DEFINITION}",
"",
f"- 站点:{site_url}/",
f"- 权威源:{payload.get('source_success', 0)}/{payload.get('source_target', 0)}",
f"- 备货机会:{summary.get('calendar_featured', 0)}",
f"- 展会:{summary.get('exhibitions', 0)}",
f"- 政策:{summary.get('policies', 0)}",
f"- 平台:{summary.get('platforms', 0)}",
f"- 跨境活动:{summary.get('activities', 0)}",
f"- 核心词:{'、'.join((*KEYWORD_CLUSTERS['core'], *KEYWORD_CLUSTERS['task']))}",
"",
"## 可索引栏目",
"",
f"- [备货日历]({site_url}/textile/calendar/)",
f"- [展会通]({site_url}/textile/exhibitions/)",
f"- [政策预警]({site_url}/textile/policies/)",
f"- [趋势雷达]({site_url}/textile/standards/)",
f"- [平台渠道]({site_url}/textile/platforms/)",
f"- [跨境活动]({site_url}/textile/activities/)",
"",
"## 栏目回答意图",
"",
*[f"- {section}:{intent}" for section, intent in SECTION_INTENTS.items()],
"",
"官方事实、机器翻译与本站经营分析分区展示;机器翻译仅作阅读辅助,不替代报名、付款或合规提交。",
]
(target / "llms.txt").write_text("\n".join(lines) + "\n", encoding="utf-8")
def build(data_dir: Path, output_dir: Path, site_url: str) -> None:
data_dir = data_dir.resolve()
output_dir = output_dir.resolve()
staging = output_dir.with_name(f".{output_dir.name}-build")
if staging.exists():
shutil.rmtree(staging)
shutil.copytree(ROOT / "public", staging)
target_data = staging / "data"
target_data.mkdir(parents=True)
for name in PUBLISH_DATA:
source = data_dir / name
if not source.is_file():
raise FileNotFoundError(f"缺少发布数据:{source}")
shutil.copy2(source, target_data / name)
index = staging / "index.html"
html = index.read_text(encoding="utf-8")
html = html.replace('<link rel="canonical" href="./" />', f'<link rel="canonical" href="{site_url}/" />')
html = html.replace('<link rel="alternate" hreflang="zh-CN" href="./" />', f'<link rel="alternate" hreflang="zh-CN" href="{site_url}/" />')
html = html.replace('<link rel="alternate" hreflang="x-default" href="./" />', f'<link rel="alternate" hreflang="x-default" href="{site_url}/" />')
html = html.replace('<meta property="og:image" content="./assets/logo.svg" />', f'<meta property="og:image" content="{site_url}/assets/logo.svg" />')
index.write_text(html, encoding="utf-8", newline="\n")
subprocess.run([
sys.executable,
str(ROOT / "scripts" / "build_seo.py"),
"--data", str(target_data / "textile-intelligence.json"),
"--root", str(staging),
"--site-url", site_url,
], check=True, cwd=ROOT)
payload = json.loads((target_data / "textile-intelligence.json").read_text(encoding="utf-8"))
write_llms_file(staging, payload, site_url)
required = ("index.html", "robots.txt", "sitemap.xml", "llms.txt")
missing = [name for name in required if not (staging / name).is_file()]
if missing:
raise RuntimeError(f"构建产物不完整:{missing}")
replace_directory(staging, output_dir)
def main() -> int:
parser = argparse.ArgumentParser(description="构建三笙AI情报局独立静态站")
parser.add_argument("--data-dir", default=str(ROOT / "data"))
parser.add_argument("--output-dir", default=str(ROOT / "dist"))
parser.add_argument("--site-url", default=os.environ.get("SANSHENG_SITE_URL", DEFAULT_SITE_URL))
args = parser.parse_args()
site_url = args.site_url.rstrip("/")
try:
build(Path(args.data_dir), Path(args.output_dir), site_url)
except Exception as exc:
print(f"[构建失败] {exc}", file=sys.stderr)
return 1
print(f"[构建完成] {Path(args.output_dir).resolve()}")
return 0
if __name__ == "__main__":
raise SystemExit(main())