Загрузка данных
Ты работаешь на корпоративном MacBook.
Нужно реализовать проект adt-markdownify-meta почти с самого начала, опираясь на пример проекта hr-resume-scrapper-master.
Проект-пример:
Путь к примеру:
укажи фактический путь к hr-resume-scrapper-master на этом MacBook
Новый проект:
adt-markdownify-meta
Задача нового проекта:
Сделать skill для выгрузки страниц META в Markdown.
META — это корпоративная веб-система, открывается через SberBrowser.
Нужно получить HTML открытой страницы META, конвертировать его в Markdown и сохранить результат в output/.
Важно:
- Не использовать API META.
- Не добавлять endpoint-ы.
- Не использовать requests/httpx/aiohttp/urllib.
- Не использовать token/cookie/login/password.
- Не сохранять cookies.
- Не сохранять localStorage/sessionStorage.
- Не делать автоматическую авторизацию.
- Не скачивать PDF.
- Не реализовывать логику резюме.
- Новый проект должен работать именно с HTML страницы META.
- Код должен быть простым, читаемым и похожим по структуре на hr-resume-scrapper.
Главная идея:
Переиспользовать из hr-resume-scrapper:
1. Структуру проекта.
2. YAML-конфиг.
3. Поиск конфигурационного файла.
4. Запуск SberBrowser через Playwright.
5. Настройки browser.type.
6. Настройки path_to_exe.
7. headless, slow_mo, viewport, timeout.
8. retries и delays.
9. Сохранение результатов в output.
10. README.md с инструкцией.
11. SKILL.md с описанием skill.
Не переносить:
1. PDF download.
2. expect_download.
3. pdfplumber.
4. markdownify для PDF.
5. resume_urls как обязательный формат резюме.
6. Логику поиска кнопки “Резюме”.
7. Логику кандидатов.
Новый проект должен поддерживать два режима:
1. browser
Автоматически запускает SberBrowser через Playwright.
Используется на macOS как основной простой режим.
2. cdp_existing
Подключается к уже открытому SberBrowser через remote-debugging-port=9222.
Этот режим оставить как резервный.
Основной режим для корпоративного MacBook сейчас: browser.
Структура проекта должна быть такая:
adt-markdownify-meta/
├── README.md
├── SKILL.md
├── requirements.txt
├── meta_exporter.yaml.example
├── meta_urls.txt
├── run_exporter.py
├── scripts/
│ ├── __init__.py
│ ├── config_loader.py
│ ├── scrape_meta.py
│ ├── html_to_markdown.py
│ └── markdown_writer.py
├── output/
└── temp_html/
Если файлов нет — создать.
Если файлы есть — можно аккуратно перезаписать чистой реализацией.
Не удалять .venv.
Не удалять весь проект целиком.
Не запускать полный scraping автоматически после реализации.
После реализации выполнить только проверку синтаксиса.
============================================================
1. requirements.txt
============================================================
Сделать requirements.txt:
playwright==1.40.0
PyYAML==6.0.1
beautifulsoup4==4.12.3
html2text==2024.2.26
Важно:
Версии взять максимально близко к старому проекту, где использовался playwright==1.40.0.
Если в старом проекте используется другая точная версия PyYAML — можно использовать её.
Не добавлять requests/httpx/aiohttp/urllib.
============================================================
2. .gitignore
============================================================
Создать .gitignore:
.venv/
venv/
__pycache__/
*.pyc
.DS_Store
meta_exporter.yaml
output/
temp_html/
.env
*.log
Важно:
meta_exporter.yaml не коммитить.
meta_exporter.yaml.example коммитить.
============================================================
3. meta_exporter.yaml.example
============================================================
Сделать конфиг:
mode: "browser"
meta_urls_file: "meta_urls.txt"
output_dir: "output"
temp_html_dir: "temp_html"
browser:
type: "sberbrowser"
path_to_exe: "/Applications/SberBrowser.app/Contents/MacOS/SberBrowser"
browser_settings:
headless: false
slow_mo: 1000
viewport_width: 1920
viewport_height: 1080
wait_until: "domcontentloaded"
page_load_timeout: 60000
scraping:
max_retries: 3
retry_delay: 2
request_delay: 1
cdp:
endpoint_url: "http://127.0.0.1:9222"
target_url_contains: "sberbank.ru"
fallback_title: "META page"
wait_timeout_seconds: 60
poll_interval_seconds: 2
Важно:
Для macOS основной mode: "browser".
Путь к SberBrowser должен быть таким же, как в старом проекте hr-resume-scrapper, если там указан другой путь.
Если путь отличается, взять путь из старого проекта.
============================================================
4. meta_urls.txt
============================================================
Создать пример:
# Формат:
# Название страницы - URL
# или просто URL
SberGeo Functional Subsystem - https://meta.sberbank.ru/meta/earp/functionalSubsystem/236500
Файл должен поддерживать:
- пустые строки;
- комментарии через #;
- строки формата "Название - URL";
- строки, где указан только URL.
============================================================
5. scripts/config_loader.py
============================================================
Реализовать config_loader.py по аналогии со старым проектом.
Нужны функции:
find_config(start_dir: Path, max_depth: int = 3) -> Path
Логика:
- ищет meta_exporter.yaml от текущей директории вверх до 3 уровней;
- если найден только meta_exporter.yaml.example, не использовать его как рабочий конфиг;
- вывести понятную ошибку:
"Найден только meta_exporter.yaml.example. Скопируйте его в meta_exporter.yaml"
load_config(path: Path) -> dict
Логика:
- читает YAML;
- если файл пустой — ValueError;
- если YAML не dict — ValueError.
get_config() -> dict
Логика:
- вызывает find_config(Path.cwd());
- вызывает load_config().
validate_config(config: dict)
Проверяет:
- mode;
- meta_urls_file;
- output_dir;
- temp_html_dir.
Если mode == "browser", проверить:
- browser.type;
- browser.path_to_exe;
- browser_settings.headless;
- browser_settings.slow_mo;
- 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.
Если mode == "cdp_existing", проверить:
- cdp.endpoint_url;
- cdp.target_url_contains;
- cdp.wait_timeout_seconds;
- cdp.poll_interval_seconds.
Ошибки должны быть понятными.
============================================================
6. scripts/markdown_writer.py
============================================================
Реализовать:
safe_filename(name: str) -> str
Требования:
- заменить запрещённые символы \ / : * ? " < > | на "_";
- заменить двоеточия и слэши;
- схлопнуть пробелы;
- убрать пробелы по краям;
- если имя пустое — "meta_page";
- ограничить длину имени до 120 символов.
write_markdown(output_dir: str, title: str, url: str, markdown_body: str) -> Path
Формат файла:
# <title>
Источник: <url>
---
<markdown_body>
write_index(output_dir: str, exported_files: list) -> Path
Создать output/README.md:
# META export
## Выгруженные страницы
- [title](filename.md) — url
Если список пустой:
Нет выгруженных страниц.
============================================================
7. scripts/html_to_markdown.py
============================================================
Реализовать конвертацию HTML в Markdown.
Использовать:
- BeautifulSoup для очистки HTML;
- html2text для конвертации.
Функции:
clean_html(html: str) -> str
Удалить:
- script;
- style;
- noscript;
- svg;
- canvas.
extract_title(html: str, fallback: str = "META page") -> str
Логика:
1. Если есть h1 — использовать его.
2. Иначе title.
3. Иначе fallback.
normalize_blank_lines(text: str) -> str
Оставлять максимум две пустые строки подряд.
html_to_markdown(html: str) -> str
Настройки html2text:
- ignore_links = False;
- ignore_images = True;
- body_width = 0.
build_meta_markdown(html: str, url: str) -> dict
Возвращает:
{
"title": title,
"markdown": markdown
}
В начало markdown добавить блок:
## Проверка разделов META
- Иерархия АС > ФП > Модуль > Подмодуль > ТК: Не проверено
- Точки взаимодействия + API: Не проверено
- Интеграционные взаимодействия + API: Не проверено
- Стенды: Не проверено
- Технические ресурсы: Не проверено
---
Потом сам Markdown страницы.
============================================================
8. scripts/scrape_meta.py
============================================================
Главный файл логики.
Нужны импорты:
- time;
- Path;
- sync_playwright;
- TimeoutError as PlaywrightTimeoutError;
- build_meta_markdown;
- write_markdown;
- write_index;
- safe_filename.
Функция read_meta_urls(meta_urls_file: str) -> list
Формат:
- пустые строки пропускать;
- строки с # пропускать;
- "Название - URL" поддерживать;
- просто URL поддерживать;
- возвращать список dict:
{"name": name, "url": url}
Функция save_html(temp_html_dir: str, title: str, html: str) -> Path
Сохранять raw HTML в temp_html/<safe_title>.html.
Функция launch_browser(playwright, config: dict)
Логика:
- browser_type = config["browser"]["type"];
- path_to_exe = config["browser"].get("path_to_exe");
- headless = config["browser_settings"]["headless"];
- slow_mo = config["browser_settings"]["slow_mo"];
Если browser_type == "sberbrowser":
- проверить, что path_to_exe указан;
- передать executable_path=path_to_exe в chromium.launch.
Если browser_type == "chromium":
- запускать chromium.launch без executable_path.
Нужно использовать browser_options:
browser_options = {
"headless": headless,
"slow_mo": slow_mo,
}
Если path_to_exe есть и browser_type == "sberbrowser":
browser_options["executable_path"] = path_to_exe
Вернуть browser.
Функция export_one_page(page, name: str, url: str, config: dict) -> dict
Логика:
1. Взять:
- output_dir;
- temp_html_dir;
- wait_until;
- page_load_timeout.
2. Открыть страницу:
page.goto(
url,
wait_until=wait_until,
timeout=page_load_timeout,
)
3. После загрузки сделать небольшую паузу 2 секунды, чтобы SPA успела отрендериться.
4. Получить html = page.content().
5. Получить title:
- сначала page.title().strip();
- если пусто, использовать name;
- если name пустое, использовать "META page".
6. Сохранить HTML через save_html.
7. Конвертировать HTML через build_meta_markdown.
8. Сохранить Markdown через write_markdown.
9. Вернуть dict:
{
"title": markdown_title,
"url": url,
"path": filepath
}
Функция export_meta(config: dict)
Это основной browser mode.
Логика:
1. Прочитать meta_urls_file.
2. Если ссылок нет — ValueError.
3. Прочитать настройки:
- max_retries;
- retry_delay;
- request_delay;
- viewport_width;
- viewport_height.
4. Запустить Playwright.
5. Запустить browser через launch_browser.
6. Создать context:
context = browser.new_context(
viewport={
"width": viewport_width,
"height": viewport_height,
},
user_agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36"
)
7. Создать одну страницу:
page = context.new_page()
8. Для каждой ссылки из meta_urls:
- делать retry до max_retries;
- если ошибка одной ссылки — не падать всем проектом;
- печатать понятную ошибку;
- переходить к следующей ссылке.
9. Между ссылками делать time.sleep(request_delay).
10. После всех ссылок создать output/README.md через write_index.
11. Закрыть browser через browser.close() только в browser mode.
Важно:
browser.close() разрешён только в browser mode.
В cdp_existing browser.close() запрещён.
Функция find_existing_page(browser, target_url_contains: str)
Для cdp_existing:
- вывести все открытые вкладки;
- найти вкладку, где target_url_contains in page.url;
- вернуть первую найденную;
- если нет — вернуть None.
Функция export_meta_from_existing_browser(config: dict)
Резервный режим.
Логика:
- подключиться через pw.chromium.connect_over_cdp(endpoint_url);
- не запускать SberBrowser;
- не создавать new_page;
- не делать page.goto;
- найти существующую вкладку;
- ждать до 60 секунд;
- получить page.content;
- сохранить HTML и Markdown;
- создать README;
- вызвать browser.disconnect();
- не вызывать browser.close().
============================================================
9. run_exporter.py
============================================================
Реализовать:
from scripts.config_loader import get_config, validate_config
def main():
print("Запуск выгрузки META в Markdown...")
config = get_config()
validate_config(config)
mode = config.get("mode", "browser")
if mode == "browser":
from scripts.scrape_meta import export_meta
export_meta(config)
elif mode == "cdp_existing":
from scripts.scrape_meta import export_meta_from_existing_browser
export_meta_from_existing_browser(config)
else:
raise ValueError(f"Неизвестный mode: {mode}")
print("Выгрузка завершена.")
if __name__ == "__main__":
try:
main()
except FileNotFoundError as e:
print(f"[CONFIG ERROR] {e}")
except ValueError as e:
print(f"[VALUE ERROR] {e}")
except ImportError as e:
print(f"[IMPORT ERROR] {e}")
print("Проверьте зависимости: pip install -r requirements.txt")
except Exception as e:
print(f"[ERROR] {e}")
============================================================
10. README.md
============================================================
Сделать README.md с такими разделами:
# adt-markdownify-meta
## Назначение
Проект выгружает страницы META в Markdown.
## Основной режим на корпоративном MacBook: browser
В этом режиме скрипт сам запускает SberBrowser через Playwright по пути из конфига.
## Установка
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python -m playwright install chromium
Если используется SberBrowser через executable_path, отдельная установка chromium может быть не нужна, но playwright должен быть установлен как Python-библиотека.
## Настройка
cp meta_exporter.yaml.example meta_exporter.yaml
Проверить путь:
browser:
type: "sberbrowser"
path_to_exe: "/Applications/SberBrowser.app/Contents/MacOS/SberBrowser"
Если на корпоративном MacBook путь отличается, указать фактический путь.
## Входные ссылки
Файл meta_urls.txt:
Название страницы - URL
## Запуск
source .venv/bin/activate
python run_exporter.py
## Результат
- temp_html/*.html
- output/*.md
- output/README.md
## Резервный режим cdp_existing
Если автоматический запуск SberBrowser не подходит, можно запустить SberBrowser вручную с remote-debugging-port=9222 и использовать mode: cdp_existing.
## Ограничения
- API не используется.
- Авторизация автоматом не выполняется.
- Cookies/tokens не сохраняются.
- Скрипт работает только с тем, что доступно после ручной/корпоративной авторизации в SberBrowser.
============================================================
11. SKILL.md
============================================================
Сделать краткий SKILL.md:
# Skill: adt-markdownify-meta
## Назначение
Выгружает страницы META в Markdown.
## Вход
Список URL страниц META в meta_urls.txt или открытая вкладка META при cdp_existing.
## Выход
Markdown-файлы в output/ и raw HTML в temp_html/.
## Ограничения
- Не использует API.
- Не сохраняет cookies/tokens.
- Не выполняет автоматическую авторизацию.
============================================================
12. Проверка после реализации
============================================================
Выполнить только:
python -m py_compile run_exporter.py scripts/config_loader.py scripts/scrape_meta.py scripts/html_to_markdown.py scripts/markdown_writer.py
Не запускать python run_exporter.py автоматически.
В конце ответа показать:
1. Какие файлы созданы или переписаны.
2. Полный код run_exporter.py.
3. Полный код scripts/scrape_meta.py.
4. Полный код scripts/config_loader.py.
5. Полный код meta_exporter.yaml.example.
6. Краткую инструкцию запуска на корпоративном MacBook.
7. Подтверждение:
- browser mode запускает SberBrowser через executable_path;
- cdp_existing не закрывает SberBrowser;
- cdp_existing не вызывает page.goto;
- cdp_existing не вызывает new_page;
- API не используется;
- cookies/tokens не сохраняются.