Files
omni-parse/tools/build_vault.py
ENI 2320acf3c5
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
vault ключей + мост abuse-radar + китайские источники: всё в одном реестре
- omni/vault.py: пул ключей парсеров (env-first, data/vault.json, ротация, cooldown 401/402/403=1ч, 429=10м), только маски в выводе
- tools/build_vault.py: сборка пула из MASTER_KEYS.csv и локальных дампов (значения не в git)
- tools/vault_check.py: живая проверка с маскированием
- searchers: firecrawl/tavily/exa/serper/jina/apify берут ключ из vault, если нет env
- omni radar: домены+каналы из radar_server.db -> реестр, --import-posts заливает посты каналов
- radar-источники помечены opt_in: общий обход не тормозят (869 против 3337 всего)
- omni/sources_cn.py + tools/wave_cn.py: 9 живых китайских источников (52pojie, nodeseek, kanxue, anquanke, 4hou, xz.aliyun, ithome, cnbeta, v2ex)
- net.get(proxy=...): принудительный прокси для CF-площадок
- /api/keys + строка ключей в панели
- tests/test_vault_radar.py: 7 тестов, всего 29 passed
2026-10-26 23:31:37 +03:00

139 lines
4.7 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.

"""Единый инвентарь ключей для парсеров: MASTER_KEYS.csv -> data/vault.json.
Собирает всё, что лежит по диску (MASTER_KEYS.csv, пулы keyhunter, fl_bot), и
раскладывает по сервисам. Файл data/vault.json в .gitignore — ключи не уезжают в репо.
Запуск:
python tools/build_vault.py # собрать
python tools/build_vault.py --stats # только показать, что получилось
"""
import argparse
import csv
import json
import re
import sys
from collections import defaultdict
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
OUT = ROOT / "data" / "vault.json"
SOURCES = [
Path("C:/Users/User/tmp/keys_inventory/MASTER_KEYS.csv"),
Path("C:/Users/User/Desktop/_DOCS/ALL_WORKING_KEYS_WITH_BALANCE.txt"),
]
# service в MASTER_KEYS -> имя сервиса в vault
ALIAS = {
"firecrawl.dev": "firecrawl",
"tavily": "tavily",
"exa": "exa",
"serper": "serper",
"jina": "jina",
"apify": "apify",
"scrapingant": "scrapingant",
"brightdata": "brightdata",
"scraperapi": "scraperapi",
"zenrows": "zenrows",
"twocaptcha": "2captcha",
"2captcha": "2captcha",
"capmonster": "capmonster",
"telegram": "telegram",
"proxy": "proxy",
}
KEY_RE = re.compile(r"^[A-Za-z0-9_\-\.:]{16,200}$")
def norm_service(s):
s = (s or "").strip().lower()
for k, v in ALIAS.items():
if s == k or s.startswith(k):
return v
return s
def good_key(k):
k = (k or "").strip().strip('"').strip("'")
if not k or " " in k or len(k) < 16:
return ""
return k if KEY_RE.match(k) else ""
def from_csv(path, bag):
for r in csv.DictReader(path.open(encoding="utf-8", errors="ignore")):
svc = norm_service(r.get("service"))
key = good_key(r.get("api_key"))
if svc and key:
bag[svc].append({"key": key, "status": (r.get("status") or "").strip(),
"quota": (r.get("quota_balance") or "").strip(),
"src": Path(r.get("source_file") or path.name).name})
return True
def from_txt(path, bag):
"""Строки вида TAG|key или TAG:key или key — берём только пары с тегом."""
pat = re.compile(r"^\s*([A-Za-z0-9_\-]{2,20})\s*[|:]\s*([A-Za-z0-9_\-\.]{16,200})\s*$")
for line in path.read_text(encoding="utf-8", errors="ignore").splitlines():
m = pat.match(line)
if m:
svc = norm_service(m.group(1))
bag[svc].append({"key": m.group(2), "status": "unknown", "quota": "", "src": path.name})
return True
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--stats", action="store_true", help="только показать текущий vault")
a = ap.parse_args()
if a.stats:
if not OUT.exists():
print("vault.json нет — запусти без --stats")
return 1
d = json.loads(OUT.read_text(encoding="utf-8"))
total = sum(len(v) for v in d.values())
print("сервисов %d, ключей %d" % (len(d), total))
for s, rows in sorted(d.items(), key=lambda x: -len(x[1]))[:25]:
alive = sum(1 for r in rows if r.get("status") in ("live", "alive", "ok", "live-verified-2026-10-24"))
print(" %-22s %5d %s" % (s, len(rows), ("(живых %d)" % alive) if alive else ""))
return 0
bag = defaultdict(list)
used = 0
for p in SOURCES:
if not p.exists():
continue
try:
if p.suffix == ".csv":
from_csv(p, bag)
else:
from_txt(p, bag)
used += 1
except Exception as e:
print("skip %s: %s" % (p.name, type(e).__name__))
# дедуп по значению ключа внутри сервиса
out = {}
for svc, rows in bag.items():
seen, clean = set(), []
for r in rows:
if r["key"] in seen:
continue
seen.add(r["key"])
clean.append(r)
out[svc] = clean
OUT.parent.mkdir(parents=True, exist_ok=True)
OUT.write_text(json.dumps(out, ensure_ascii=False, indent=0), encoding="utf-8")
total = sum(len(v) for v in out.values())
print("файлов прочитано %d | сервисов %d | ключей %d | записано в %s"
% (used, len(out), total, OUT))
for s in ("firecrawl", "tavily", "exa", "serper", "jina", "apify", "2captcha", "telegram", "proxy"):
if s in out:
print(" %-12s %d" % (s, len(out[s])))
return 0
if __name__ == "__main__":
sys.exit(main())