Добавлена ​​начальная реализация Go Crawler с файлом README и конфигурацией.

This commit is contained in:
Dmitriy Fofanov
2026-02-22 01:27:19 +03:00
parent 8fee94e062
commit 18003745d1
5 changed files with 901 additions and 1 deletions
+130 -1
View File
@@ -1,2 +1,131 @@
# go-crawler
# Go Crawler — быстрый краулер/зеркалатор сайта на Go
Проект делает локальную «зеркальную» копию сайта: обходит страницы, сохраняет HTML на диск и параллельно скачивает ассеты (CSS/JS/картинки/шрифты и т. п.). Для рендеринга страниц используется headless Chrome через `chromedp`, поэтому краулер подходит для сайтов, где контент появляется после выполнения JavaScript.
> Важно: используйте краулер только на сайтах, которые вы имеете право скачивать и архивировать. Учитывайте правила сайта и его ограничения (включая robots.txt и rate limits). Авторизация/капчи/антибот в проекте не реализованы.
## Основные возможности
- **Параллельный обход страниц**: несколько воркеров, каждый со своим экземпляром браузера.
- **Параллельная загрузка ассетов**: отдельные HTTP-воркеры скачивают ресурсы быстрее, чем через браузер.
- **Ограничение по доменам**: скачиваются только URL из `ALLOWED_ORIGINS`.
- **Нормализация URL**: убирается `#fragment`, приводятся относительные ссылки к абсолютным, удаляется хвостовой `/` (кроме корня) — это уменьшает дубли.
- **Дедупликация**: страницы и ассеты не обрабатываются повторно.
- **Сохранение на диск в структуру, похожую на URL**:
- директории превращаются в `index.html` (например, `/docs/``/docs/index.html`),
- query-параметры хешируются и добавляются к имени файла, чтобы разные варианты URL не перезатирали друг друга.
- **Опциональная «починка» ссылок**: абсолютные ссылки на разрешённые домены переписываются в относительные, чтобы зеркало открывалось локально.
- **Принудительный UTF8 meta charset**: чтобы уменьшить проблемы с кодировкой в сохранённых HTML.
- **Корректное завершение** по `Ctrl+C`.
## Требования
- Go 1.21+
- Установленный Chrome/Chromium (используется `chromedp`)
## Быстрый старт
```bash
go mod tidy
# Запуск без сборки
go run .
# Или сборка
go build -o crawler.exe .
./crawler.exe
```
После запуска в консоли будет прогресс вида `[crawled] ...` и итоговая статистика (pages/assets/links fixed/time).
## Конфигурация
Вся конфигурация сейчас задаётся константами в начале файла `crawl.go`.
Ключевые параметры:
```go
const (
BASE_URL = "https://rutracker.org/forum/" // Стартовая точка обхода
OUT_DIR = "rutracker.org" // Папка, куда сохраняется зеркало
MAX_PAGES = 50000 // Лимит страниц (защита от бесконечного обхода)
WORKERS = 10 // Кол-во браузеров (воркеров страниц)
ASSET_WORKERS = 20 // Кол-во HTTP-воркеров для ассетов
WAIT_AFTER_LOAD = 2 * time.Second // Пауза после Navigate для прогрузки JS
REQUEST_TIMEOUT = 30 * time.Second // Таймаут навигации
FIX_LINKS = true // Переписывать ссылки на относительные
)
var ALLOWED_ORIGINS = []string{
"https://rutracker.org",
"https://www.rutracker.org",
}
```
### Как подобрать `ALLOWED_ORIGINS`
Если сайт грузит ресурсы с CDN/поддоменов, их нужно добавить в `ALLOWED_ORIGINS`, иначе ассеты и ссылки на них будут пропускаться.
### Тюнинг производительности
- Увеличивайте `WORKERS`, если упираетесь в скорость рендеринга страниц (но возрастут CPU/RAM).
- Увеличивайте `ASSET_WORKERS`, если узкое место — скачивание ассетов.
- `WAIT_AFTER_LOAD` критичен для SPA/React/Vue сайтов: слишком маленькое значение даст «пустые» HTML.
## Как это работает (в общих чертах)
1. В `pageQueue` кладётся нормализованный `BASE_URL`.
2. `WORKERS` воркеров берут URL страницы, открывают новую вкладку, делают `Navigate`, ждут `WAIT_AFTER_LOAD`, сохраняют `OuterHTML`.
3. Во время загрузки страницы подписка на `network` события собирает URL ресурсов; «не‑страницы» отправляются в `assetQueue`.
4. HTML парсится регулярками, извлекаются ссылки (`href`, `src`, `data-src`, а также некоторые JSONполя вроде `"url"`, `"pathname"`).
5. Если включён `FIX_LINKS`, ссылки на разрешённые домены переписываются в относительные пути на локальные файлы.
6. HTML и ассеты сохраняются в `OUT_DIR` по схеме URL→путь.
## Что считается страницей, а что ассетом
Функция `isPageURL()` относит URL к ассетам по расширению (например, `.js`, `.css`, изображения, шрифты, видео/аудио, `.json`, `.xml`, `.pdf`, архивы). Всё остальное считается страницей и идёт в очередь обхода.
## Архитектура
```
┌─────────────────────────────────────────────────────────────┐
│ main() │
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │Worker 1 │ │Worker 2 │ │Worker 3 │ │ ... │ × WORKERS
│ │(Browser)│ │(Browser)│ │(Browser)│ │(Browser)│ │
│ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │
│ │ │ │ │ │
│ └────────────┴─────┬──────┴────────────┘ │
│ │ │
│ pageQueue (chan string) │
│ │ │
│ ┌──────────────────┴──────────────────┐ │
│ │ │ │
│ ┌────┴────┐ ┌─────────┐ ┌─────────┐ ┌───┴───┐ │
│ │Asset W1 │ │Asset W2 │ │Asset W3 │ │ ... │ × ASSET_WORKERS
│ │ (HTTP) │ │ (HTTP) │ │ (HTTP) │ │ (HTTP)│ │
│ └─────────┘ └─────────┘ └─────────┘ └───────┘ │
│ │ │
│ assetQueue (chan AssetRequest) │
└─────────────────────────────────────────────────────────────┘
```
## Ограничения и нюансы
- **Robots/лимиты**: проект не читает `robots.txt` и не ограничивает частоту запросов тонко. Для «бережного» обхода уменьшайте `WORKERS`/`ASSET_WORKERS` и/или добавляйте задержки (пока это только `WAIT_AFTER_LOAD`).
- **Авторизация и сессии**: нет логина, cookieменеджмента, обхода капч/антибота.
- **Полнота зеркала**: ссылки извлекаются регулярками и сетью браузера; динамически генерируемые URL могут быть пропущены.
- **Очень большие сайты**: `MAX_PAGES` — обязательная страховка. Также очереди и карты дедупликации держатся в памяти.
- **Шумный вывод**: есть отладочные строки `[debug] Found ...`, а также `[skip-external]`/`[skip-asset]` — при необходимости их можно убрать/загейтить флагом.
## Типичный сценарий использования
1. Задайте `BASE_URL` и `OUT_DIR`.
2. Заполните `ALLOWED_ORIGINS` (включая поддомены/CDN, если они нужны).
3. Настройте параллельность (`WORKERS`, `ASSET_WORKERS`) и тайминги (`WAIT_AFTER_LOAD`, `REQUEST_TIMEOUT`).
4. Запустите `go run .` и дождитесь `[done] ...`.
## Лицензия
Лицензия в репозитории не указана.