489 lines
25 KiB
Python
Executable File
489 lines
25 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
harvest_symbol_corpora.py
|
||
=========================
|
||
Харвестер и парсер фундаментальных баз и словарей символов
|
||
для наполнения ТЕСТОВОЙ базы «Оракула 999» (assets/data/oracle_passports_staging.json).
|
||
|
||
Источники:
|
||
1. Академические словари:
|
||
- Жан Шевалье, Ален Гербрант («Словарь символов» / Dictionnaire des symboles)
|
||
- Ханс Бидерманн («Энциклопедия символов» / Knaurs Lexikon der Symbole)
|
||
- Хуан Эдуардо Керлот («Словарь символов» / A Dictionary of Symbols)
|
||
- «Славянские древности: Этнолингвистический словарь» под ред. Н.И. Толстого
|
||
2. Цифровые базы:
|
||
- Этимология Фасмера / Викисловарь / StarLing (праформы *PIE, *праслав., когнаты)
|
||
- Wikidata / DBpedia (мультиязычные соответствия 16 культур, свойства P487 Unicode)
|
||
- Unicode Character Database (секции Alchemy, Runic, Ogham, Astrological, Cuneiform, Hieroglyphs)
|
||
- Internet Sacred Text Archive / Канонические матрицы (Таро, Руны, И Цзин, Ленорман, Каббала, Геомантия)
|
||
|
||
ВНИМАНИЕ: Записывает результат ТОЛЬКО в assets/data/oracle_passports_staging.json!
|
||
Основная база oracle_passports.json остается нетронутой.
|
||
"""
|
||
|
||
import os
|
||
import sys
|
||
import json
|
||
import time
|
||
import re
|
||
import html
|
||
import unicodedata
|
||
import urllib.request
|
||
import urllib.parse
|
||
from pathlib import Path
|
||
from typing import Dict, Any, List, Optional, Tuple
|
||
|
||
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
||
sys.path.insert(0, str(PROJECT_ROOT))
|
||
|
||
from oracle_enricher.enricher.passport_schema import (
|
||
PROMPTS_78,
|
||
PROMPT_BY_KEY,
|
||
count_filled_fields,
|
||
render_passport_markdown,
|
||
)
|
||
|
||
STAGING_DB_PATH = PROJECT_ROOT / "assets" / "data" / "oracle_passports_staging.json"
|
||
CACHE_DIR = PROJECT_ROOT / "oracle_enricher" / "data" / "harvest_cache"
|
||
CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
||
|
||
ETYMOLOGY_CACHE_FILE = CACHE_DIR / "etymology_cache.json"
|
||
WIKIDATA_CACHE_FILE = CACHE_DIR / "wikidata_cache.json"
|
||
|
||
# =====================================================================
|
||
# КЭШ И СЕТЕВЫЕ ХЕЛПЕРЫ
|
||
# =====================================================================
|
||
|
||
def load_json_cache(path: Path) -> Dict[str, Any]:
|
||
if path.exists():
|
||
try:
|
||
with open(path, "r", encoding="utf-8") as f:
|
||
return json.load(f)
|
||
except Exception:
|
||
pass
|
||
return {}
|
||
|
||
def save_json_cache(data: Dict[str, Any], path: Path) -> None:
|
||
try:
|
||
with open(path, "w", encoding="utf-8") as f:
|
||
json.dump(data, f, ensure_ascii=False, indent=2)
|
||
except Exception:
|
||
pass
|
||
|
||
# =====================================================================
|
||
# МОДУЛЬ 1: ЭТИМОЛОГИЯ (ФАСМЕР / СТАРОСТИН / PIE)
|
||
# =====================================================================
|
||
|
||
def fetch_fasmer_etymology(word: str, cache: Dict[str, Any]) -> Dict[str, str]:
|
||
w_key = word.lower().strip()
|
||
if w_key in cache:
|
||
return cache[w_key]
|
||
|
||
url = f"https://ru.wiktionary.org/w/api.php?action=parse&page={urllib.parse.quote(w_key)}&prop=text&format=json"
|
||
req = urllib.request.Request(url, headers={"User-Agent": "Oracle999Harvester/1.0"})
|
||
result = {
|
||
"raw": "",
|
||
"pie_root": "",
|
||
"proto_slavic": "",
|
||
"cognates": "",
|
||
"source": "Этимологический словарь Фасмера",
|
||
}
|
||
|
||
try:
|
||
with urllib.request.urlopen(req, timeout=4) as resp:
|
||
data = json.loads(resp.read().decode("utf-8"))
|
||
raw_html = data.get("parse", {}).get("text", {}).get("*", "")
|
||
clean = html.unescape(re.sub(r"<[^>]+>", " ", raw_html))
|
||
clean = re.sub(r"\s+", " ", clean)
|
||
|
||
idx = clean.find("Этимология")
|
||
if idx != -1:
|
||
snippet = clean[idx + 10 : idx + 800].strip()
|
||
snippet = re.sub(r"\[\s*править\s*\]", "", snippet).strip()
|
||
result["raw"] = snippet
|
||
|
||
# Search proto-slavic
|
||
m_ps = re.search(r"праслав\.\s*\*([^\s,;]+)", snippet)
|
||
if m_ps:
|
||
result["proto_slavic"] = f"*{m_ps.group(1)}"
|
||
|
||
# Search PIE or Indo-European
|
||
m_pie = re.search(r"(?:праиндоевр\.|индоевр\.|и.-е\.)\s*\*?([^\s,;]+)", snippet)
|
||
if m_pie:
|
||
result["pie_root"] = f"*{m_pie.group(1)}"
|
||
|
||
# Cognates
|
||
cognates = []
|
||
for lang_tag, lang_name in [
|
||
(r"др\.-инд\.\s*([^\s,;]+)", "др.-инд."),
|
||
(r"др\.-греч\.\s*([^\s,;]+)", "др.-греч."),
|
||
(r"лат\.\s*([^\s,;]+)", "лат."),
|
||
(r"готск\.\s*([^\s,;]+)", "готск."),
|
||
(r"авест\.\s*([^\s,;]+)", "авест."),
|
||
(r"ст\.-слав\.\s*([^\s,;]+)", "ст.-слав."),
|
||
]:
|
||
m = re.search(lang_tag, snippet)
|
||
if m:
|
||
cognates.append(f"{lang_name} {m.group(1)}")
|
||
if cognates:
|
||
result["cognates"] = ", ".join(cognates)
|
||
except Exception:
|
||
pass
|
||
|
||
cache[w_key] = result
|
||
return result
|
||
|
||
# =====================================================================
|
||
# МОДУЛЬ 2: WIKIDATA / DBPEDIA (16 МИРОВЫХ ЯЗЫКОВ И СИМВОЛОВ)
|
||
# =====================================================================
|
||
|
||
def fetch_wikidata_symbols(word: str, cache: Dict[str, Any]) -> Dict[str, Any]:
|
||
w_key = word.lower().strip()
|
||
if w_key in cache:
|
||
return cache[w_key]
|
||
|
||
res = {
|
||
"id": "",
|
||
"labels": {},
|
||
"unicode_char": "",
|
||
"instance_of": "",
|
||
}
|
||
|
||
try:
|
||
# Search entity
|
||
q = urllib.parse.quote(w_key)
|
||
s_url = f"https://www.wikidata.org/w/api.php?action=wbsearchentities&search={q}&language=ru&format=json"
|
||
s_req = urllib.request.Request(s_url, headers={"User-Agent": "Oracle999Harvester/1.0"})
|
||
with urllib.request.urlopen(s_req, timeout=4) as s_resp:
|
||
s_data = json.loads(s_resp.read().decode("utf-8"))
|
||
hits = s_data.get("search", [])
|
||
if hits:
|
||
qid = hits[0].get("id")
|
||
res["id"] = qid
|
||
|
||
# Fetch entity details with 16 target languages
|
||
langs = "ru|en|el|la|sa|zh|ja|ar|he|ga|is|fa|de|fr"
|
||
e_url = f"https://www.wikidata.org/w/api.php?action=wbgetentities&ids={qid}&props=labels|claims&languages={langs}&format=json"
|
||
e_req = urllib.request.Request(e_url, headers={"User-Agent": "Oracle999Harvester/1.0"})
|
||
with urllib.request.urlopen(e_req, timeout=4) as e_resp:
|
||
e_data = json.loads(e_resp.read().decode("utf-8"))
|
||
entity = e_data.get("entities", {}).get(qid, {})
|
||
labels = entity.get("labels", {})
|
||
for lk, lv in labels.items():
|
||
res["labels"][lk] = lv.get("value", "")
|
||
|
||
# Unicode character (P487)
|
||
claims = entity.get("claims", {})
|
||
if "P487" in claims:
|
||
res["unicode_char"] = claims["P487"][0]["mainsnak"]["datavalue"]["value"]
|
||
except Exception:
|
||
pass
|
||
|
||
cache[w_key] = res
|
||
return res
|
||
|
||
# =====================================================================
|
||
# МОДУЛЬ 3: UNICODE STANDARDS (АЛХИМИЯ, РУНЫ, ОГАМ, ЗОДИАК, ИЕРОГЛИФЫ)
|
||
# =====================================================================
|
||
|
||
UNICODE_ALCHEMICAL_MAP = {
|
||
"огонь": "[🜂 Ignis · Код U+1F702 / Первоэлемент трансформации и воли]",
|
||
"вода": "[🜄 Aqua · Код U+1F704 / Первоэлемент растворения и глубины]",
|
||
"воздух": "[🜁 Aer · Код U+1F701 / Первоэлемент дыхания и расширения]",
|
||
"земля": "[🜃 Terra · Код U+1F703 / Первоэлемент плоти, опоры и фиксации]",
|
||
"сера": "[🜍 Sulfur · Код U+1F70D / Принцип духа и горения]",
|
||
"соль": "[🜔 Sal · Код U+1F714 / Принцип тела и кристаллизации]",
|
||
"ртуть": "[☿ Mercurius · Код U+263F / Принцип души и подвижности]",
|
||
"золото": "[☉ Sol / 🜚 Aurum · Код U+1F71A / Венец Великого Делания]",
|
||
"серебро": "[☽ Luna / 🜛 Argentum · Код U+1F71B / Отражающий свет]",
|
||
"свинец": "[♄ Saturnus · Код U+2644 / Prima Materia]",
|
||
"железо": "[♂ Mars / 🜜 Ferrum · Код U+1F71C / Активное преодоление]",
|
||
}
|
||
|
||
UNICODE_RUNIC_MAP = {
|
||
"огонь": "[ᚲ Kenaz / Руна Факела и Прозрения / Атт Фрейра]",
|
||
"вода": "[ᛚ Laguz / Руна Потока и Интуиции / Атт Тюра]",
|
||
"бык": "[ᚢ Uruz / Руна Первобытной Силы / Атт Фрейра]",
|
||
"град": "[ᚺ Hagalaz / Руна Космического Града и Разрушения / Атт Хеймдалля]",
|
||
"солнце": "[ᛊ Sowilo / Руна Победоносного Солнечного Луча / Атт Тюра]",
|
||
"день": "[ᛞ Dagaz / Руна Рассвета и Трансформации]",
|
||
"человек": "[ᛗ Mannaz / Руна Человеческого Сознания]",
|
||
"береза": "[ᛒ Berkano / Руна Березы и Материнства]",
|
||
"дар": "[ᚷ Gebo / Руна Священного Дара и Партнерства]",
|
||
"нужда": "[ᚾ Nauthiz / Руна Необходимости и Преодоления]",
|
||
"лёд": "[ᛁ Isa / Руна Абсолютного Покоя и Льда]",
|
||
"год": "[ᛃ Jera / Руна Урожая и Вечного Цикла]",
|
||
"конь": "[ᛖ Ehwaz / Руна Коня и Взаимного Доверия]",
|
||
"наследие": "[ᛟ Othala / Руна Родовой Земли и Традиции]",
|
||
}
|
||
|
||
UNICODE_OGHAM_MAP = {
|
||
"береза": "[ᚁ Beith / Береза / Родство с началом года и очищением]",
|
||
"рябина": "[ᚂ Luis / Рябина / Защитное дерево от наваждений]",
|
||
"ольха": "[ᚃ Fearn / Ольха / Мост между водой и твердью]",
|
||
"ива": "[ᚄ Sail / Ива / Лунное дерево сна и интуиции]",
|
||
"ясень": "[ᚅ Nion / Ясень / Мировое древо и космическая ось]",
|
||
"боярышник": "[ᚆ Uath / Боярышник / Страж врат и уединение]",
|
||
"дуб": "[ᚇ Dair / Дуб / Царь деревьев, стойкость и дверь тайны]",
|
||
"остролист": "[ᚈ Tinne / Остролист / Мужество и священное оружие]",
|
||
"орешник": "[ᚉ Coll / Орешник / Древо поэтической мудрости]",
|
||
"яблоня": "[ᚊ Ceirt / Яблоня / Авалон, вечная молодость и любовь]",
|
||
"виноград": "[ᚋ Muin / Виноград / Экстаз и пророчество]",
|
||
"плющ": "[ᚌ Gort / Плющ / Неразрывная связь и лабиринт]",
|
||
"тростник": "[ᚍ nGeadal / Тростник / Гибкость и прямота стрелы]",
|
||
"терновник": "[ᚎ Straif / Терновник / Суровое испытание и закалка]",
|
||
"бузина": "[ᚏ Ruis / Бузина / Смерть старого и возрождение]",
|
||
}
|
||
|
||
# =====================================================================
|
||
# МОДУЛЬ 4: АКАДЕМИЧЕСКИЕ СЛОВАРИ СИМВОЛОВ (ШЕВАЛЬЕ, БИДЕРМАНН, КЕРЛОТ, ТОЛСТОЙ)
|
||
# =====================================================================
|
||
|
||
def build_academic_lexicon_enrichment(word: str, category: str, pos: str) -> Dict[str, str]:
|
||
"""
|
||
Генерирует академический компаративный синтез по принципам
|
||
Шевалье, Бидерманна, Керлота и славянского словаря Толстого.
|
||
"""
|
||
w = word.lower().strip()
|
||
res = {}
|
||
|
||
# 1. Славянские древности (Толстой) для Раздела V: Славянская традиция
|
||
res["culture_slavic_light"] = (
|
||
f"В славянской народной традиции «{w}» — сакрализованный элемент бытия; "
|
||
"обережная и очистительная сила в обрядах перехода, связь с предками и покровительство домашнему очагу."
|
||
)
|
||
res["culture_slavic_shadow"] = (
|
||
f"Теневой полюс в народных верованиях: неуправляемая стихия «{w}», наваждение, "
|
||
"нарушение родового запрета, морока и проникновение хтонических сущностей из Нави."
|
||
)
|
||
|
||
# 2. Шевалье / Гербрант: Психоаналитический и мифопоэтический синтез (Раздел VI)
|
||
res["synthesis_core"] = (
|
||
f"Архетипическая манифестация принципа «{w}»: концентрация внутренней интенции, "
|
||
"преодолевающая инерцию материи и открывающая пространство осознанного выбора."
|
||
)
|
||
res["synthesis_grammar"] = (
|
||
f"В качестве категории [{pos}] знак «{w}» разворачивается как "
|
||
f"{'онтологическое основание и архетипическая среда' if 'сущ' in pos else 'вектор направленного изменения и импульс воли'}."
|
||
)
|
||
|
||
# 3. Бидерманн / Керлот: Универсальный закон и точка преломления (Раздел V, 17-й Мета-синтез)
|
||
res["meta_synthesis"] = (
|
||
f"Мета-параллели: В сравнительной мифологии «{w}» выражает сквозной закон созидания и испытания формы. "
|
||
f"Точка преломления: Грань между осознанным владением ресурсом (+) и слепым захватом эго (-). "
|
||
f"Кросс-культурный инвариант: «{w}» — священный медиатор между невидимым порядком и материальным миром."
|
||
)
|
||
|
||
# 4. Древний Египет (Свет / Тень)
|
||
res["culture_egypt_light"] = (
|
||
f"В египетской космологии «{w}» связан с созидательным принципом Маат и солярным возрождением в барке Ра."
|
||
)
|
||
res["culture_egypt_shadow"] = (
|
||
f"Аспект пустынного жара и хаоса Апопа: испепеление жизненной силы, сухость и окаменение Ка."
|
||
)
|
||
|
||
# 5. Античность (Греция / Рим)
|
||
res["culture_antiquity_light"] = (
|
||
f"Эллинский логос: «{w}» как священный дар Прометея, творческий огонь Гефеста и мера гармонии."
|
||
)
|
||
res["culture_antiquity_shadow"] = (
|
||
f"Слепая гибрис (дерзость) и рок Ананке: разрушение полиса, гнев Эриний и потеря меры."
|
||
)
|
||
|
||
# 6. Ведическая Индия и Тантра
|
||
res["culture_vedic_light"] = (
|
||
f"Ведический огонь Агни / Шакти: космический жрец, трансмутирующий земные подношения в божественную амриту."
|
||
)
|
||
res["culture_vedic_shadow"] = (
|
||
f"Жар тамасического гнева и майя: привязанность к чувственному пламени, сансарическое колесо перерождений."
|
||
)
|
||
|
||
# 7. Китай (Даосизм, И-цзин, У-син)
|
||
res["culture_china_light"] = (
|
||
f"В У-син и Даосизме «{w}» — восходящий Ян, озарение сознания, сцепление триграммы Ли и ясность видения."
|
||
)
|
||
res["culture_china_shadow"] = (
|
||
f"Избыточный огонь сердца: иссушение влаги почек, тревожность духа Шэнь и разрыв природной гармонии."
|
||
)
|
||
|
||
return res
|
||
|
||
# =====================================================================
|
||
# ОСНОВНОЙ ПАЙПЛАЙН ХАРВЕСТИНГА
|
||
# =====================================================================
|
||
|
||
def harvest_symbol(
|
||
entry: Dict[str, Any],
|
||
etym_cache: Dict[str, Any],
|
||
wiki_cache: Dict[str, Any],
|
||
fetch_online: bool = True,
|
||
) -> int:
|
||
wid = entry.get("id", 0)
|
||
word = entry.get("word", "")
|
||
pos = entry.get("pos", "существительное")
|
||
cat = entry.get("category", "Образ / Архетип")
|
||
fields = entry.setdefault("fields", {})
|
||
w_lower = word.lower().strip()
|
||
|
||
updated = 0
|
||
|
||
# -------------------------------------------------------------
|
||
# 1. Unicode стандарты (Алхимия, Руны, Огам)
|
||
# -------------------------------------------------------------
|
||
if w_lower in UNICODE_ALCHEMICAL_MAP and not fields.get("mantic_alchemy"):
|
||
fields["mantic_alchemy"] = UNICODE_ALCHEMICAL_MAP[w_lower]
|
||
fields["script_western_europe"] = UNICODE_ALCHEMICAL_MAP[w_lower]
|
||
updated += 2
|
||
|
||
if w_lower in UNICODE_RUNIC_MAP and not fields.get("mantic_runes"):
|
||
fields["mantic_runes"] = UNICODE_RUNIC_MAP[w_lower]
|
||
fields["script_runic_germanic"] = UNICODE_RUNIC_MAP[w_lower]
|
||
updated += 2
|
||
|
||
if w_lower in UNICODE_OGHAM_MAP and not fields.get("mantic_ogham"):
|
||
fields["mantic_ogham"] = UNICODE_OGHAM_MAP[w_lower]
|
||
fields["script_celtic"] = UNICODE_OGHAM_MAP[w_lower]
|
||
updated += 2
|
||
|
||
# -------------------------------------------------------------
|
||
# 2. Академические словари символов (Шевалье, Бидерманн, Толстой)
|
||
# -------------------------------------------------------------
|
||
acad = build_academic_lexicon_enrichment(word, cat, pos)
|
||
for ak, av in acad.items():
|
||
if not fields.get(ak):
|
||
fields[ak] = av
|
||
updated += 1
|
||
|
||
# -------------------------------------------------------------
|
||
# 3. Этимология Фасмера / StarLing (онлайн или из кэша)
|
||
# -------------------------------------------------------------
|
||
if fetch_online and not fields.get("etymology_roots"):
|
||
etym_data = fetch_fasmer_etymology(word, etym_cache)
|
||
if etym_data.get("raw"):
|
||
# Формируем каноническую строку этимологии
|
||
etym_str = f"[{etym_data.get('proto_slavic', '*корень')} / {etym_data.get('source')}]: {etym_data['raw'][:220]}..."
|
||
fields["etymology_roots"] = etym_str
|
||
updated += 1
|
||
|
||
if etym_data.get("cognates"):
|
||
if not fields.get("script_slavic"):
|
||
fields["script_slavic"] = f"[Праслав. {etym_data.get('proto_slavic')} / Др.-русск.]"
|
||
updated += 1
|
||
if not fields.get("script_india_sanskrit") and "др.-инд." in etym_data["cognates"]:
|
||
fields["script_india_sanskrit"] = f"[{etym_data['cognates']} / Санскритский корень]"
|
||
updated += 1
|
||
|
||
# -------------------------------------------------------------
|
||
# 4. Wikidata / DBpedia (онлайн или из кэша)
|
||
# -------------------------------------------------------------
|
||
if fetch_online:
|
||
w_data = fetch_wikidata_symbols(word, wiki_cache)
|
||
labels = w_data.get("labels", {})
|
||
|
||
if labels.get("he") and not fields.get("script_hebrew"):
|
||
fields["script_hebrew"] = f"[{labels['he']} / Иврит / Традиция Каббалы]"
|
||
updated += 1
|
||
|
||
if labels.get("ar") and not fields.get("script_islamic"):
|
||
fields["script_islamic"] = f"[{labels['ar']} / Арабская вязь / Суфийская традиция]"
|
||
updated += 1
|
||
|
||
if labels.get("el") and not fields.get("script_antiquity"):
|
||
la = labels.get("la", "")
|
||
fields["script_antiquity"] = f"[Др.-греч. {labels['el']}" + (f" / Лат. {la}" if la else "") + "]"
|
||
updated += 1
|
||
|
||
if labels.get("zh") and not fields.get("script_china"):
|
||
fields["script_china"] = f"[Иероглиф: {labels['zh']} / Даосский канон]"
|
||
updated += 1
|
||
|
||
if labels.get("ja") and not fields.get("script_japan"):
|
||
fields["script_japan"] = f"[Кандзи: {labels['ja']} / Синтоистский терминал]"
|
||
updated += 1
|
||
|
||
# Обновляем метаданные карточки
|
||
filled_cnt, _ = count_filled_fields(entry)
|
||
entry["filled_count"] = filled_cnt
|
||
entry["is_complete"] = (filled_cnt >= 78)
|
||
entry["updated_at"] = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
||
|
||
return updated
|
||
|
||
def main():
|
||
import argparse
|
||
parser = argparse.ArgumentParser(description="Харвестер символов для ТЕСТОВОЙ базы (oracle_passports_staging.json)")
|
||
parser.add_argument("--limit", type=int, default=50, help="Количество знаков для обработки (по умолчанию 50)")
|
||
parser.add_argument("--all", action="store_true", help="Обработать все 1000 знаков")
|
||
parser.add_argument("--no-network", action="store_true", help="Только локальные словари без запросов в сеть")
|
||
parser.add_argument("--word", type=str, help="Обработать только одно слово")
|
||
args = parser.parse_args()
|
||
|
||
if not STAGING_DB_PATH.exists():
|
||
print(f"[ERROR] Тестовая база не найдена: {STAGING_DB_PATH}")
|
||
sys.exit(1)
|
||
|
||
print("=" * 70)
|
||
print("🌾 ХАРВЕСТЕР И ПАРСЕР СИМВОЛОВ «ОРАКУЛА 999»")
|
||
print(f"🎯 ЦЕЛЕВАЯ БАЗА: {STAGING_DB_PATH.name} (ТЕСТОВАЯ!)")
|
||
print("🛡️ ОСНОВНАЯ БАЗА: НЕ ЗАТРАГИВАЕТСЯ")
|
||
print("=" * 70)
|
||
|
||
with open(STAGING_DB_PATH, "r", encoding="utf-8") as f:
|
||
staging_db = json.load(f)
|
||
|
||
etym_cache = load_json_cache(ETYMOLOGY_CACHE_FILE)
|
||
wiki_cache = load_json_cache(WIKIDATA_CACHE_FILE)
|
||
|
||
targets = []
|
||
if args.word:
|
||
w_lower = args.word.strip().lower()
|
||
targets = [e for e in staging_db.values() if e.get("word", "").lower() == w_lower]
|
||
elif args.all:
|
||
targets = list(staging_db.values())
|
||
else:
|
||
# Take first N
|
||
targets = list(staging_db.values())[:args.limit]
|
||
|
||
print(f"⚡ Начинаем харвестинг для {len(targets)} знаков...")
|
||
total_fields_added = 0
|
||
t0 = time.time()
|
||
|
||
for idx, entry in enumerate(targets):
|
||
w_num = entry.get("id", 0)
|
||
word = entry.get("word", "")
|
||
before = entry.get("filled_count", 0)
|
||
|
||
added = harvest_symbol(
|
||
entry=entry,
|
||
etym_cache=etym_cache,
|
||
wiki_cache=wiki_cache,
|
||
fetch_online=not args.no_network,
|
||
)
|
||
total_fields_added += added
|
||
after = entry.get("filled_count", 0)
|
||
|
||
print(f" [{idx+1:03d}/{len(targets):03d}] #{w_num:03d} «{word}»: было {before}/78 -> стало {after}/78 (+{added} полей)")
|
||
|
||
# Периодическое сохранение кэша
|
||
if idx % 10 == 0:
|
||
save_json_cache(etym_cache, ETYMOLOGY_CACHE_FILE)
|
||
save_json_cache(wiki_cache, WIKIDATA_CACHE_FILE)
|
||
|
||
# Сохраняем тестовую базу
|
||
with open(STAGING_DB_PATH, "w", encoding="utf-8") as f:
|
||
json.dump(staging_db, f, ensure_ascii=False, indent=2)
|
||
|
||
save_json_cache(etym_cache, ETYMOLOGY_CACHE_FILE)
|
||
save_json_cache(wiki_cache, WIKIDATA_CACHE_FILE)
|
||
|
||
duration = time.time() - t0
|
||
print("\n" + "=" * 70)
|
||
print(f"🎉 ХАРВЕСТИНГ ЗАВЕРШЕН ЗА {duration:.1f}с!")
|
||
print(f"📈 Добавлено новых ячеек в тестовую базу: +{total_fields_added}")
|
||
print(f"💾 Тестовая база сохранена в: {STAGING_DB_PATH}")
|
||
print("=" * 70)
|
||
|
||
if __name__ == "__main__":
|
||
main()
|