Загрузка данных


Ты работаешь в текущем проекте:

/home/work/skil-scrap/adt-markdownify-meta

Задача:
Реализовать только файл:

scripts/scrape_meta.py

Важно:
- Не трогай другие файлы.
- Не меняй config_loader.py.
- Не меняй markdown_writer.py.
- Не меняй html_to_markdown.py.
- Не меняй run_exporter.py.
- Мы НЕ используем API META.
- Мы НЕ используем requests/httpx/aiohttp/urllib.
- Мы работаем через браузерный скраппинг Playwright, как в hr-resume-scrapper.

Нужно реализовать:

1. Импорты:

from pathlib import Path
import time

from playwright.sync_api import sync_playwright
from playwright.sync_api import TimeoutError as PlaywrightTimeoutError

from scripts.html_to_markdown import build_meta_markdown
from scripts.markdown_writer import write_markdown, write_index, safe_filename

2. Функцию read_meta_urls(meta_urls_file: str) -> list

Логика:
- читает файл meta_urls.txt;
- если файла нет, выбрасывает FileNotFoundError с понятным текстом;
- пропускает пустые строки;
- пропускает строки, начинающиеся с "#";
- поддерживает формат:

СберНаЛадони - https://mapp.sberbank.ru/techcookbook-meta

- если строка содержит " - ", то всё до " - " считается name, всё после считается url;
- если строка начинается с http:// или https://, то name = "META page", url = строка;
- возвращает список словарей:

[
  {
    "name": "СберНаЛадони",
    "url": "https://mapp.sberbank.ru/techcookbook-meta"
  }
]

- если после чтения нет ни одной ссылки, выбрасывает ValueError.

3. Функцию save_html(temp_html_dir: str, title: str, html: str) -> Path

Логика:
- создаёт папку temp_html_dir, если её нет;
- делает безопасное имя файла через safe_filename(title);
- сохраняет HTML в файл с расширением .html;
- кодировка utf-8;
- возвращает Path к созданному файлу.

4. Функцию launch_browser(playwright, config: dict)

Логика:
- читает config["browser"]["type"];
- если type = "chromium", запускает playwright.chromium.launch;
- если type = "sberbrowser", запускает playwright.chromium.launch с executable_path из config["browser"]["path_to_exe"];
- headless берётся из config["browser_settings"]["headless"];
- slow_mo берётся из config["browser_settings"]["slow_mo"];
- если type = "sberbrowser", но path_to_exe пустой, выбрасывает ValueError с понятным текстом;
- если type неизвестный, выбрасывает ValueError.

5. Функцию export_meta(config: dict)

Логика:
- читает из config:
  - meta_urls_file
  - output_dir
  - temp_html_dir
  - browser_settings.viewport_width
  - browser_settings.viewport_height
  - browser_settings.wait_until
  - browser_settings.page_load_timeout
  - scraping.max_retries
  - scraping.retry_delay
  - scraping.request_delay

- читает список ссылок через read_meta_urls;
- запускает браузер через sync_playwright;
- создаёт browser context с viewport;
- для каждой ссылки:
  - выводит в консоль прогресс;
  - открывает новую страницу;
  - делает page.goto(url, wait_until=wait_until, timeout=page_load_timeout);
  - получает HTML через page.content();
  - сохраняет HTML через save_html;
  - конвертирует HTML в Markdown через build_meta_markdown;
  - если build_meta_markdown вернул title, использует его;
  - если title пустой, использует name из meta_urls.txt;
  - сохраняет Markdown через write_markdown;
  - добавляет результат в exported_files;
  - закрывает страницу;
  - делает паузу request_delay секунд;

- если страница не открылась, повторяет попытку max_retries раз;
- между попытками ждёт retry_delay секунд;
- если после всех попыток страница не выгрузилась, выводит ошибку и идёт к следующей ссылке;
- после всех ссылок создаёт output/README.md через write_index;
- закрывает браузер.

6. Требования к ошибкам:
- если META требует авторизацию, скрипт не должен сохранять логины, пароли, cookie или token;
- если headless = false, пользователь сможет войти вручную в открытом браузере;
- ошибки должны быть понятными;
- при ошибке одной ссылки скрипт не должен падать полностью, а должен перейти к следующей ссылке.

7. Требования к коду:
- Код должен быть простым и понятным.
- Не добавляй API.
- Не добавляй endpoint-ы.
- Не добавляй requests/httpx/aiohttp/urllib.
- Не добавляй токены, cookie, login, password.
- Все файлы сохраняй в utf-8.

После реализации покажи полный код файла scripts/scrape_meta.py.