run_creation_search.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. #!/usr/bin/env python3
  2. """真实运行 430 query 搜索/详情/媒体/分类。
  3. 示例:
  4. PYTHONPATH=. CK_ENV_FILE=.env python scripts/run_creation_search.py \
  5. --query "公共安全 视频 灵感 怎么做" --search-limit 10 --display-limit 5
  6. """
  7. from __future__ import annotations
  8. import argparse
  9. import concurrent.futures as cf
  10. import datetime as dt
  11. import os
  12. import sys
  13. import time
  14. from pathlib import Path
  15. ROOT = Path(__file__).resolve().parent.parent
  16. if str(ROOT) not in sys.path:
  17. sys.path.insert(0, str(ROOT))
  18. from acquisition import store
  19. from acquisition.creation_search import PLATFORMS, load_creation_queries, run_platform_query
  20. from core.config import Settings
  21. def _run_id() -> str:
  22. return "creation-search-" + dt.datetime.now().strftime("%Y%m%d-%H%M%S")
  23. def _parse_platforms(value: str | None) -> list[str]:
  24. if not value:
  25. return list(PLATFORMS)
  26. out = [p.strip() for p in value.split(",") if p.strip()]
  27. bad = [p for p in out if p not in PLATFORMS]
  28. if bad:
  29. raise SystemExit(f"unsupported platform(s): {', '.join(bad)}")
  30. return out
  31. def run_platform_workers(*, run_id: str, queries: list[str], platforms: list[str],
  32. settings: Settings, search_limit: int, display_limit: int,
  33. classify: bool, skip_done: bool,
  34. db_path=store.DB_PATH) -> int:
  35. """Run long-lived platform workers. Return failed platform-job count."""
  36. def _platform_worker(platform: str) -> tuple[str, int, int]:
  37. c = store.connect(db_path)
  38. ok = fail = 0
  39. current_q = ""
  40. try:
  41. for i, q in enumerate(queries, start=1):
  42. current_q = q
  43. if skip_done and store.creation_job_is_done(
  44. c, run_id, q, platform, display_limit=display_limit
  45. ):
  46. ok += 1
  47. print(f"[{platform}] {i}/{len(queries)} skip done {q}", flush=True)
  48. continue
  49. print(f"[{platform}] {i}/{len(queries)} {q}", flush=True)
  50. res = run_platform_query(
  51. c,
  52. run_id=run_id,
  53. query=q,
  54. platform=platform,
  55. settings=settings,
  56. search_limit=search_limit,
  57. display_limit=display_limit,
  58. classify=classify,
  59. )
  60. if res["status"] in ("done", "partial"):
  61. ok += 1
  62. else:
  63. fail += 1
  64. print(
  65. f"[{platform}] {res['status']} display={res['display_count']} "
  66. f"{res.get('error') or ''}",
  67. flush=True,
  68. )
  69. except Exception as exc:
  70. fail += 1
  71. msg = f"worker异常: {str(exc)[:160]}"
  72. if current_q:
  73. store.update_creation_job(
  74. c, run_id, current_q, platform, status="failed",
  75. error=msg, ts=int(time.time()),
  76. )
  77. print(f"[{platform}] {msg}", flush=True)
  78. finally:
  79. c.close()
  80. return platform, ok, fail
  81. failed = 0
  82. with cf.ThreadPoolExecutor(max_workers=len(platforms)) as ex:
  83. futs = [ex.submit(_platform_worker, p) for p in platforms]
  84. for fut in cf.as_completed(futs):
  85. platform, ok, fail = fut.result()
  86. failed += fail
  87. print(f"[{platform}] finished ok={ok} failed={fail}")
  88. return failed
  89. def main() -> None:
  90. ap = argparse.ArgumentParser()
  91. ap.add_argument("--run-id", default=_run_id())
  92. ap.add_argument("--query", help="只跑这一条 query;不传则读取 creation_demo.json")
  93. ap.add_argument("--limit-queries", type=int, default=0, help="从 query 文件取前 N 条;0=不截断")
  94. ap.add_argument("--platform", help="只跑指定平台;多个用逗号分隔")
  95. ap.add_argument("--search-limit", type=int, default=10)
  96. ap.add_argument("--display-limit", type=int, default=5)
  97. ap.add_argument("--query-file", default=str(Path("data/queries/creation_demo.json")))
  98. ap.add_argument("--no-classify", action="store_true", help="只采集不调用 AI 判断")
  99. ap.add_argument("--resume", action="store_true", help="保留同 run_id 已有结果,继续未完成 job")
  100. ap.add_argument("--skip-done", action="store_true", help="跳过已 done 且 display_count>=display_limit 的 job")
  101. args = ap.parse_args()
  102. settings = Settings.from_env(os.getenv("CK_ENV_FILE", ".env"))
  103. queries = [args.query.strip()] if args.query else load_creation_queries(args.query_file)
  104. if args.limit_queries and args.limit_queries > 0:
  105. queries = queries[:args.limit_queries]
  106. platforms = _parse_platforms(args.platform)
  107. if not queries:
  108. raise SystemExit("no queries to run")
  109. conn = store.connect()
  110. if args.resume and store.creation_run_exists(conn, args.run_id):
  111. print(f"resume run_id={args.run_id}")
  112. else:
  113. store.create_creation_run(
  114. conn,
  115. args.run_id,
  116. total_queries=len(queries),
  117. note=f"platforms={','.join(platforms)} search_limit={args.search_limit} display_limit={args.display_limit}",
  118. ts=int(time.time()),
  119. )
  120. for q in queries:
  121. for p in platforms:
  122. store.ensure_creation_job(conn, args.run_id, q, p, ts=int(time.time()))
  123. conn.close()
  124. print(f"run_id={args.run_id} queries={len(queries)} platforms={','.join(platforms)}")
  125. print("平台接口限速:同平台搜索/详情共享 10-12s 间隔;OSS/本地下载/AI 判断不占平台闸。")
  126. failed = run_platform_workers(
  127. run_id=args.run_id,
  128. queries=queries,
  129. platforms=platforms,
  130. settings=settings,
  131. search_limit=args.search_limit,
  132. display_limit=args.display_limit,
  133. classify=not args.no_classify,
  134. skip_done=args.skip_done or args.resume,
  135. )
  136. c = store.connect()
  137. store.finish_creation_run(c, args.run_id, status="finished" if failed == 0 else "partial", ts=int(time.time()))
  138. c.close()
  139. print(f"done run_id={args.run_id} failed_platform_jobs={failed}")
  140. if __name__ == "__main__":
  141. main()