textmachine/eval/pkg7/bkrs_lookup.py

101 lines
4.9 KiB
Python
Raw Permalink 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
"""Полигон, пакет-7 (Z4-добор): словарные статьи БКРС для списка терминов.
Зачем. В фазе A «что говорит БКРС» осталось UNCLEAR по ВСЕМ терминам: страница считалась
JS-рендеримой. На деле bkrs.info ставит одноразовый куки-челлендж (`ca=<hex>`) и перезагружает
страницу; воспроизведя куку, получаем СЕРВЕРНЫЙ html со статьёй. Словарная улика — сильнейший
класс из доступных нам (сильнее площадочной и сильнее аннотации), поэтому она закрывает больше
строк §A4.3, чем весь остальной добор.
Лицензия источника (со страницы «Скачать словарь», дамп 2026.06.10): «Базы можно использовать
свободно в любых целях. Можно указать данный сайт как источник.» — то есть провенанс-класс
«серый→зелёный при указании источника», и мы источник указываем.
$0: сетевой GET, ни одной модели. Вежливость: пауза между запросами, один проход по списку.
"""
from __future__ import annotations
import argparse
import html
import json
import re
import sys
import time
import urllib.parse
import urllib.request
from pathlib import Path
UA = ("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/120.0 Safari/537.36")
BASE = "https://bkrs.info/slovo.php?ch="
def get(url: str, cookie: str | None = None, timeout: int = 30) -> str:
req = urllib.request.Request(url, headers={"User-Agent": UA})
if cookie:
req.add_header("Cookie", cookie)
with urllib.request.urlopen(req, timeout=timeout) as r:
return r.read().decode("utf-8", "replace")
def challenge_cookie(sample_html: str) -> str | None:
m = re.search(r'"(ca=[0-9a-f]+)', sample_html)
return m.group(1) if m else None
def extract_entry(page: str, term: str) -> str:
"""Тело словарной статьи: от заголовка-термина до служебного хвоста страницы."""
txt = html.unescape(re.sub(r"<(script|style)[^>]*>.*?</\1>", " ", page, flags=re.S))
txt = re.sub(r"<[^>]+>", "\n", txt)
lines = [x.strip() for x in txt.split("\n") if x.strip()]
# Статья начинается ПОСЛЕ последнего вхождения служебного пункта меню «Контакты»/«Войти»
start = 0
for i, l in enumerate(lines):
if l in ("Войти", "Контакты", "Тёмная тема"):
start = i + 1
body = lines[start:]
# и заканчивается перед подвалом
stop = len(body)
for i, l in enumerate(body):
if l.startswith(("В начало", "Обсуждение", "Комментарии", "Ошибки", "Пожаловаться",
"Добавить", "Правка", "Примеры", "Похожие")):
stop = i
break
return " ".join(body[:stop]).strip()
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--terms", required=True, type=Path, help="файл: по термину в строке")
ap.add_argument("--out", required=True, type=Path)
ap.add_argument("--sleep", type=float, default=1.2)
a = ap.parse_args()
terms = [t.strip() for t in a.terms.read_text(encoding="utf-8").splitlines()
if t.strip() and not t.startswith("#")]
first = get(BASE + urllib.parse.quote(terms[0]))
cookie = challenge_cookie(first)
if not cookie:
print("челлендж-кука не найдена — страница отдалась сразу", file=sys.stderr)
rows = []
for i, t in enumerate(terms, 1):
try:
page = get(BASE + urllib.parse.quote(t), cookie)
entry = extract_entry(page, t)
except Exception as e: # сеть — не повод останавливать проход
entry = f"__ОШИБКА__ {e}"
found = bool(entry) and "__ОШИБКА__" not in entry and "Не найдено" not in entry
rows.append({"src": t, "found": found, "entry": entry})
print(f"[{i}/{len(terms)}] {t}\t{'OK' if found else 'НЕТ'}\t{entry[:110]}", file=sys.stderr)
time.sleep(a.sleep)
a.out.write_text(json.dumps(rows, ensure_ascii=False, indent=1), encoding="utf-8")
ok = sum(r["found"] for r in rows)
print(f"\nитого: {ok}/{len(rows)} статей найдено → {a.out}", file=sys.stderr)
return 0
if __name__ == "__main__":
raise SystemExit(main())