"""Управление состоянием: state.json в папке раздела. Позволяет: - Сохранять прогресс (какие темы обработаны, какие страницы пройдены). - Продолжать парсинг с места остановки (той же командой). - Мержить состояния воркеров в единый state.json (после завершения). Файл state.json (раздел): { "topics_done": {"123": "Название темы", "456": "..."}, "pages_done": [1, 2, 3], "file_index": 5, "topic_count": 432 } Файл worker_N/state.json: Аналогичная структура, но только для одного воркера. """ import json import logging from pathlib import Path log = logging.getLogger(__name__) def _state_file(data_dir: Path, section_slug: str) -> Path: """Полный путь к state.json для раздела. Args: data_dir: Корневая папка с данными (напр. ./vwts_data). section_slug: Короткое имя раздела. Returns: Path к state.json. """ section_dir = data_dir / section_slug section_dir.mkdir(parents=True, exist_ok=True) return section_dir / "state.json" def load(data_dir: Path, section_slug: str) -> dict: """Загрузить state.json раздела. Args: data_dir: Корневая папка с данными. section_slug: Короткое имя раздела. Returns: Словарь состояния. Если файла нет — пустой state. """ path = _state_file(data_dir, section_slug) if path.exists(): try: return json.loads(path.read_text(encoding="utf-8")) except (json.JSONDecodeError, OSError) as exc: log.warning("⚠️ Ошибка чтения %s: %s", path, exc) # Пустой state по умолчанию return {"topics_done": {}, "pages_done": [], "file_index": 0, "topic_count": 0} def save(data_dir: Path, section_slug: str, state: dict): """Сохранить state.json раздела. Args: data_dir: Корневая папка с данными. section_slug: Короткое имя раздела. state: Словарь состояния для сохранения. """ path = _state_file(data_dir, section_slug) path.write_text( json.dumps(state, ensure_ascii=False, indent=2), encoding="utf-8", ) def merge_workers(data_dir: Path, section_slug: str): """Собрать состояния всех воркеров в единый state.json раздела. Проходит по worker_*/state.json, сливает topics_done, pages_done, суммирует topic_count. Args: data_dir: Корневая папка с данными. section_slug: Короткое имя раздела. """ root = data_dir / section_slug # ── Собираем все worker_*/state.json ──────────────────────────────── merged = {"topics_done": {}, "pages_done": [], "file_index": 0, "topic_count": 0} for worker_state_path in sorted(root.glob("worker_*/state.json")): try: worker_state = json.loads( worker_state_path.read_text(encoding="utf-8"), ) merged["topics_done"].update(worker_state.get("topics_done", {})) merged["pages_done"].extend(worker_state.get("pages_done", [])) merged["topic_count"] += worker_state.get("topic_count", 0) except (json.JSONDecodeError, OSError) as exc: log.warning("⚠️ Ошибка чтения %s: %s", worker_state_path, exc) # Чистим дубликаты и сортируем merged["pages_done"] = sorted(set(merged["pages_done"])) # Определяем следующий file_index по количеству part_*.jsonl existing_parts = list(root.glob("part_*.jsonl")) merged["file_index"] = max(0, len(existing_parts) - 1) # ── Пишем ─────────────────────────────────────────────────────────── save(data_dir, section_slug, merged) log.info("🔗 Мерж состояний: %d воркеров → state.json", len(list(root.glob("worker_*"))))