# mirror-forge Node.js-проект для зеркалирования сайта в локальную папку `mirror/` с помощью Playwright (headless Chromium). Проект включает два сценария: 1. `crawl.js` — обходит страницы сайта, сохраняет HTML и ресурсы (CSS/JS/изображения/шрифты и т.д.). 2. `fix-links.js` — пост-обработка уже скачанного зеркала: ищет внешние URL в HTML, догружает недостающие файлы и заменяет ссылки на локальные относительные пути. --- ## Возможности - Обход внутренних страниц с очередью URL и ограничением по количеству страниц. - Параллельная загрузка (`CONCURRENCY`) для ускорения зеркалирования. - Перехват `response` в браузере и сохранение ассетов по `Content-Type`. - Автоповторы при ошибках (`MAX_RETRIES`). - Извлечение новых ссылок из финального DOM страницы, а не только из сырого HTML. - Корректное завершение по `Ctrl+C` (graceful shutdown). - Нормализация путей для Windows (замена недопустимых символов). - Поддержка query-строк через хеширование имени файла, чтобы избежать перезаписи. - Опциональная замена абсолютных ссылок в HTML на относительные (`FIX_LINKS`). - Принудительное сохранение HTML в UTF-8 (``). --- ## Стек и требования - Node.js 18+ - npm - Playwright (`playwright`) - ОС: Windows / Linux / macOS > В `package.json` проект работает в режиме ES Modules (`"type": "module"`). --- ## Установка ```bash npm install npx playwright install ``` Если зависимостей ещё нет, альтернативно: ```bash npm i playwright npx playwright install ``` --- ## Быстрый старт ### 1) Скачивание сайта ```bash node crawl.js ``` ### 2) Дополнительная фиксация ссылок (опционально) ```bash node fix-links.js ``` --- ## Как устроен `crawl.js` ### Основной поток 1. В очередь добавляется `BASE`. 2. URL обрабатываются батчами по `CONCURRENCY`. 3. Для каждой страницы: - выполняется `page.goto(..., waitUntil: "domcontentloaded")`; - даётся дополнительная пауза `WAIT_AFTER_LOAD`; - выполняется попытка дождаться `networkidle`; - через `page.on("response")` сохраняются ассеты; - HTML сохраняется в `mirror/`; - извлекаются новые ссылки и добавляются в очередь. 4. При ошибках применяются автоповторы до `MAX_RETRIES`. ### Что скачивается - HTML-документы. - Скрипты, стили, изображения, шрифты. - XHR/fetch-ответы (с ограничениями, см. ниже). ### Что фильтруется/ограничивается - Обход ограничен разрешёнными URL-префиксами (`ALLOWED_URL_PREFIXES`) и origin, вычисленными из них. - JSON-ответы без расширения `.json` пропускаются в загрузчике ассетов, чтобы не перезаписывать HTML. - URL с неподдерживаемыми схемами (`data:`, `javascript:`, `mailto:`, `tel:`, `blob:`) игнорируются. - Ассеты (по расширению) не попадают в очередь страниц. --- ## Конфигурация `crawl.js` Ключевые параметры находятся вверху файла: - `BASE` — стартовый URL обхода. - `OUT_DIR` — папка вывода (по умолчанию `mirror`). - `MAX_PAGES` — лимит страниц для обхода. - `CONCURRENCY` — количество одновременно открытых вкладок. - `WAIT_AFTER_LOAD` — задержка после `domcontentloaded`. - `MAX_RETRIES` — число повторных попыток для страницы. - `REQUEST_TIMEOUT` — таймаут навигации `page.goto`. - `USER_AGENT` — User-Agent браузера. - `FIX_LINKS` — включить замену ссылок в HTML на относительные в момент сохранения. - `ALLOWED_URL_PREFIXES` — список URL-префиксов, которые разрешено обходить/скачивать. --- ## Как формируются локальные пути - URL-path сохраняется как путь внутри `mirror/`. - Путь, заканчивающийся на `/`, сохраняется как `.../index.html` (через `index` + расширение). - Query-строка хешируется (`md5`, 8 символов) и добавляется к имени файла. - Если у URL нет расширения, оно определяется по `Content-Type`; если определить нельзя — используется `.html`. - Символы, недопустимые в Windows-путях, заменяются на `_`. --- ## Как устроен `fix-links.js` `fix-links.js` предназначен для пост-обработки уже скачанного зеркала. Что делает скрипт: 1. Рекурсивно находит HTML/HTM-файлы в `mirror/`. 2. Извлекает URL из `src/href/srcset/poster/data-src/url(...)`. 3. Для внешних и отсутствующих относительных ссылок: - проверяет домены по правилам `ALLOWED_DOMAINS` и `SKIP_DOMAINS`; - скачивает ресурсы в локальную структуру с ограничением по конкурентности; - заменяет абсолютные ссылки на относительные пути. 4. Ведёт статистику: обработано файлов, исправлено ссылок, скачано/пропущено/ошибки. Ключевые параметры в `fix-links.js`: - `MIRROR_DIR` — папка зеркала. - `DOWNLOAD_TIMEOUT` — таймаут загрузки одного ресурса. - `DOWNLOAD_CONCURRENCY` — число одновременных загрузок при пост-обработке. - `MAX_RETRIES` — число повторов загрузки. - `ALLOWED_DOMAINS` — явно разрешённые домены для скачивания. - `SKIP_DOMAINS` — домены, которые нужно пропускать. --- ## Логи и статус ### `crawl.js` - `[start]` — старт обхода. - `[config]` — активные параметры. - `[crawled]` — страница сохранена. - `[retry x/y]` — повторная попытка. - `[gave up]` — страница не скачана после всех повторов. - `[done]` — итоговая статистика. ### `fix-links.js` - `📄 Обрабатываю` — файл в обработке. - `📥 Скачиваю` / `✅ Скачано` / `❌ Не удалось скачать`. - При неудачной загрузке исходная ссылка сохраняется, чтобы не превращать рабочий внешний URL в битый локальный путь. - Итоговый блок `📊 СТАТИСТИКА`. --- ## Ограничения - Это не «пиксель-в-пиксель офлайн-клон» сложных SPA/PWA. - Контент, подгружаемый только после действий пользователя (скролл/клик/hover), может не попасть в зеркало. - Внешние сервисы аналитики/соцсети/CDN могут оставаться внешними ссылками (зависит от доменных правил). - API-данные с динамическими эндпоинтами не гарантируется получить полностью. --- ## Рекомендации по эксплуатации - Начинайте с меньшего `MAX_PAGES` и `CONCURRENCY`, затем увеличивайте. - При частых таймаутах увеличьте `REQUEST_TIMEOUT` и/или `WAIT_AFTER_LOAD`. - Если сервер ограничивает запросы, уменьшите `CONCURRENCY`. - Перед массовым запуском проверьте политики сайта (`robots.txt`, Terms of Service) и правовые ограничения. --- ## Типичный workflow 1. Настроить `BASE` и `ALLOWED_URL_PREFIXES` в `crawl.js`. 2. Запустить `node crawl.js`. 3. Проверить структуру `mirror/`. 4. При необходимости запустить `node fix-links.js`. 5. Открывать локальные HTML из `mirror/` для анализа/архивации/офлайн-просмотра. --- ## Структура проекта ```text . ├─ crawl.js ├─ fix-links.js ├─ package.json ├─ README.md └─ mirror/ # создаётся после запуска ``` --- ## Лицензия ISC (см. `package.json` и `LICENSE`).