Files
omni-parse/tools/probe_fresh.py
Vlad Gothbreach 42a864419b
Some checks failed
ci / test (3.10) (push) Has been cancelled
ci / test (3.11) (push) Has been cancelled
ci / test (3.12) (push) Has been cancelled
abuse: полный пересчёт индекса только по явному вызову (env не тормозит scan)
2026-10-27 01:21:01 +03:00

146 lines
6.3 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.

"""Живой прогон кандидатов из tools/fresh_candidates.py через штатные адаптеры omni.
python tools/probe_fresh.py # прогон + data/fresh_probe.json
python tools/probe_fresh.py --emit # + перегенерация omni/sources_fresh.py
python tools/probe_fresh.py --only tg_ --workers 4 --limit 5
Критерий живого: feeds.fetch(src, limit) вернул >= 1 запись.
Мёртвым добираем диагностику прямым net.get (код/блок/размер), чтобы причина
в отчёте была фактической, а не «0 items».
"""
import argparse
import json
import sys
import time
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))
sys.path.insert(0, str(ROOT / "tools"))
from omni import feeds, net # noqa: E402
from omni.sources import Source # noqa: E402
from fresh_candidates import CANDIDATES # noqa: E402
def _diag(src):
"""Почему пусто: код ответа, блокировка, размер тела."""
try:
r = net.get(src.url, timeout=20)
bits = ["http %s" % r.status]
if getattr(r, "blocked", False):
bits.append("blocked")
if getattr(r, "error", ""):
bits.append("err:%s" % str(r.error)[:120])
bits.append("%db" % len(r.text or ""))
return ", ".join(bits)
except Exception as e: # noqa: BLE001
return "exception:%s" % str(e)[:140]
def probe_one(src, limit):
t0 = time.time()
if src.adapter not in feeds.ADAPTERS:
return {"name": src.name, "category": src.category, "kind": src.kind, "adapter": src.adapter,
"url": src.url, "tags": list(src.tags), "ok": False, "n": 0,
"err": "no_adapter:%s" % src.adapter, "ms": 0, "samples": []}
try:
items = feeds.fetch(src, limit) or []
err = ""
except Exception as e: # noqa: BLE001
items, err = [], "raise:%s" % str(e)[:140]
ms = int((time.time() - t0) * 1000)
if not items and not err:
err = _diag(src)
return {"name": src.name, "category": src.category, "kind": src.kind, "adapter": src.adapter,
"url": src.url, "tags": list(src.tags), "ok": bool(items), "n": len(items),
"err": err, "ms": ms,
"samples": [{"title": i.title[:160], "url": i.url[:200]} for i in items[:3]]}
def emit(rows, dead, path):
"""omni/sources_fresh.py: только живые, без импортов из omni.sources."""
def q(s):
return '"%s"' % str(s).replace('"', '\\"')
lines = [
'"""Свежая волна источников omni: security/CVE/leaks/CN/RU/tg.',
"",
"Сгенерировано tools/probe_fresh.py по факту живого fetch (n>0).",
"Отклонённые кандидаты и причины — в хвостовом комментарии.",
"Импортов из omni.sources нет намеренно (циклический импорт).",
'"""',
"",
"",
"def fresh(S):",
' """S — конструктор Source; возвращает только проверенные сетью источники."""',
" return [",
]
for r in rows:
extra = ""
if r.get("extra"):
extra = ", extra=%s" % json.dumps(r["extra"], ensure_ascii=False, sort_keys=True)
limit = r.get("limit") or 15
lim = "" if limit == 15 else ", limit=%d" % limit
det = ", detail=True" if r.get("detail") else ""
tags = "(%s)" % ", ".join(q(t) for t in r.get("tags") or ())
lines.append(' S(%s, %s, %s, %s, %s, %s%s%s%s),' % (
q(r["name"]), q(r["category"]), q(r["kind"]), q(r["url"]), q(r["adapter"]),
tags, lim, det, extra))
lines += [" ]", "", "", "# --- отклонено прогоном (не отдают записи без ключа/логина) ---"]
for r in dead:
lines.append("# %-26s %-44s %s" % (r["name"], r["url"][:44], (r["err"] or "0 items")[:90]))
text = "\n".join(lines) + "\n"
path.write_text(text, encoding="utf-8")
return len(rows)
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--limit", type=int, default=5)
ap.add_argument("--workers", type=int, default=6)
ap.add_argument("--only", default="")
ap.add_argument("--out", default="data/fresh_probe.json")
ap.add_argument("--emit", action="store_true")
ap.add_argument("--fresh", action="store_true", help="прогонять только то, что уже в sources_fresh.py")
a = ap.parse_args()
cands = [c for c in CANDIDATES if not a.only or a.only in c["name"]]
srcs = []
for c in cands:
try:
srcs.append(Source(**c))
except Exception as e: # noqa: BLE001
print("skip %s: %s" % (c.get("name"), e))
t0 = time.time()
with ThreadPoolExecutor(max_workers=a.workers) as ex:
rows = list(ex.map(lambda s: probe_one(s, a.limit), srcs))
alive = [r for r in rows if r["ok"]]
dead = [r for r in rows if not r["ok"]]
cats = {}
for r in alive:
cats[r["category"]] = cats.get(r["category"], 0) + 1
out = {"ts": time.strftime("%Y-%m-%dT%H:%M:%S"), "limit": a.limit, "workers": a.workers,
"total": len(rows), "alive": len(alive), "dead": len(dead), "seconds": round(time.time() - t0, 1),
"by_category": dict(sorted(cats.items(), key=lambda kv: -kv[1])),
"results": sorted(rows, key=lambda r: (not r["ok"], r["category"], r["name"]))}
p = ROOT / a.out
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text(json.dumps(out, ensure_ascii=False, indent=1), encoding="utf-8")
print("всего %d | живых %d | мёртвых %d | %.1fs" % (len(rows), len(alive), len(dead), time.time() - t0))
print("по категориям:", out["by_category"])
for r in dead:
print(" dead %-24s %s" % (r["name"], r["err"][:110]))
if a.emit:
n = emit(alive, dead, ROOT / "omni" / "sources_fresh.py")
print("omni/sources_fresh.py: %d источников" % n)
return 0
if __name__ == "__main__":
raise SystemExit(main())