Перейти к содержимому
ГоМастер

Поиск по урокам

Найдите урок, курс, термин или раздел

Урок 18 из 25 ~16 мин +20 XP

URL-фильтры: AllowedDomains, URLFilters, Depth

colly AllowedDomains — та самая опция, которая отделяет плановый обход каталога от истории про скрапера, за ночь собравшего половину интернета по ссылкам из футера. Задали домены, фильтры путей и глубину — обходчик ходит строго по периметру; забыли — и он честно пойдёт по каждой ссылке, на которую наткнётся. Разберём все ограничители colly: AllowedDomains и DisallowedDomains, URLFilters на регулярках, MaxDepth и AllowURLRevisit — и то, как они работают вместе, на кейсе каталога и блога на двух поддоменах.

Содержание урока · 11 разделов

Утро после ночного запуска: скрапер каталога отработал восемь часов, в журнале — десятки тысяч запросов, в выборке — половина интернета. Товаров, как было триста, так и осталось, зато в логе LinkedIn, Telegram-канал, форум о рыбалке и пара личных блогов: всё это были ссылки из футера, и обходчик им поверил. В colly навигация по ссылкам — одна строка, e.Request.Visit из колбэка; библиотека делает ровно то, что ей велели, и ходит по каждой ссылке без разбору. Правила «куда можно» она сама не придумает — их нужно задать до первого Visit, а не после первого бана. Четыре ограничителя закрывают практически весь спектр задач: AllowedDomains держит хост, URLFilters — пути, MaxDepth — глубину, AllowURLRevisit — повторные адреса. Разница между двумя утрами — в четырёх строках конфигурации, которые пишутся один раз и дальше работают сами.

AllowedDomains: периметр в одну опцию#

colly.AllowedDomains принимает список хостов и превращает его в жёсткое правило: перед каждым запросом colly сверяет хост адреса с этим списком, и если хоста в нём нет, Visit возвращает ошибку Forbidden domain, а запрос не уходит в сеть — ни одного байта на чужой домен не полетит. По умолчанию список пуст, и пустой список означает «разрешено всё» — самый дорогой дефолт в библиотеке. Сравнение точное, по строке: shop.example.com не покрывает ни www.shop.example.com, ни cdn.shop.example.com — все реальные варианты хостов перечисляйте явно. Схема роли не играет: http и https одного хоста для AllowedDomains — один и тот же домен, ограничения по протоколу эта опция не задаёт. И есть второй выключатель, DisallowedDomains: чёрный список, который работает всегда, даже когда AllowedDomains пуст; домен, попавший в оба списка, считается запрещённым — запрет сильнее разрешения. Разрешений обычно два-три, зато они покрывают весь ландшафт: витрину, блог, поддомен со статикой — что не перечислено, для скрапера не существует.

Скрапер с периметром из двух хостов

GO
package main

import (
        "log"

        "github.com/gocolly/colly/v2"
)

func main() {
        c := colly.NewCollector(
                colly.AllowedDomains("shop.example.com", "blog.example.com"),
        )

        c.OnHTML("a[href]", func(e *colly.HTMLElement) {
                link := e.Request.AbsoluteURL(e.Attr("href"))
                if link == "" {
                        return
                }
                if err := e.Request.Visit(link); err != nil {
                        log.Println("не пошёл:", link, "—", err)
                }
        })

        if err := c.Visit("https://shop.example.com/catalog/"); err != nil {
                log.Fatal(err)
        }
}

Оба хоста — витрина и блог — в белом списке, поэтому внутренние переходы между ними работают, а каждая посторонняя ссылка печатает в журнал Forbidden domain и в сеть не уходит. Лог результата Visit здесь не украшение: по нему видно, какие адреса отклонил периметр, и видно же, когда отклонений становится подозрительно много. Проверка AbsoluteURL на пустоту отсекает href без адреса — якоря и пустые ссылки.

Ошибка до запроса: где живёт Forbidden domain#

Все отклонения периметра происходят до сети: проверка срабатывает при Visit, до того как colly соберёт и отправит HTTP-запрос. Отсюда два следствия, которые экономят часы отладки. Первое: ошибку вы получаете как возвращаемое значение Visit, а не через OnError — OnError вызывается на сетевые и HTTP-сбои уже ушедших запросов, а здесь до сети дело просто не дошло; OnRequest тоже молчит. Второе: ошибка таится, если результат Visit из колбэка не проверять — в половине примеров на GitHub пишут вызов без присваивания, и обходчик вежливо игнорирует каждый запрет без единого сообщения в лог. Ошибки навигации однотипны и легко узнаются по тексту, а в пакете colly они вынесены в именованные константы: ErrForbiddenDomain для чужого домена, ErrForbiddenURL для ссылки, отклонённой регуляркой, ErrMaxDepth при превышении глубины, ErrAlreadyVisited при повторном адресе. Логируйте их всегда, а игнорируйте осознанно — сверяя с этими константами, а не со строками. Практичная привычка — один журнал навигации на весь обход: адрес, глубина и результат Visit. Такой журнал превращает отладку периметра из гадания в чтение: видно, где маршрут пошёл не туда, где упёрся в глубину и где чужой домен отклонён честно. Заодно он станет аргументом в разговоре с владельцем сайта, если тот спросит, что это за трафик и по каким адресам вы ходили.

Кейс: каталог и блог на двух поддоменах#

Типовой ландшафт магазина: витрина на shop.example.com, контент-блог на blog.example.com, внутренние ссылки ходят между ними туда и обратно. Периметр задаётся одним списком: оба поддомена в AllowedDomains — и обход свободно гуляет из каталога в статью и назад, собирая и товары, и тексты. Как только список сужается до одного хоста, вторая половина маршрута перестаёт существовать: ссылки на блог начинают возвращать Forbidden domain, и это не сбой, а ровно то, что вы просили. Отсюда рабочий приём отладки: журнал отклонений — карта вашего маршрута. Если в нём масса отклонённых адресов нужного раздела, расширяйте список; если там только соцсети и партнёрские баннеры, периметр подобран верно. И ещё раз про поддомены: shop и blog принадлежат одному владельцу, но для colly это два разных хоста — один не покрывает другой, пишите оба. Тот же список стоит пополнить хостом стейджинга, если витрина ссылается на него из превью: лучше держать обход на тестовом контуре под явным контролем, чем однажды обнаружить полстейджинга в своей выборке.

URLFilters: регулярка на каждый URL#

Домены отвечают на вопрос «куда», но не «по каким путям»: AllowedDomains впустит и каталог, и корзину, и поиск — всё на своём хосте. За пути отвечает второй слой: colly.URLFilters принимает набор регулярных выражений, и посетятся только те адреса, которые совпали хотя бы с одной из них. Регулярка применяется к полному адресу — со схемой и хостом, поэтому шаблон пишут целиком, например со строки https:// и до конца пути. colly.DisallowedURLFilters — зеркало: адрес, совпавший с запретной регуляркой, не посетится вовсе. Когда заданы и тот и другой набор, запрет побеждает разрешение — так удобно вырезать точечный мусор вроде сортировок и UTM-меток изнутри разрешённого раздела, не переписывая весь белый список. Регулярки компилируются один раз при старте, а сверяются с каждым кандидатом в очередь — на тысячах адресов это работает незаметно, поэтому усложнять шаблоны предостережением о производительности не стоит: усложнение оправдано только точностью. Типовой сетап каталога: URLFilters пропускает только пути, начинающиеся с раздела каталога, а DisallowedURLFilters отсекает страницы с параметром сортировки — обход перестаёт гонять один и тот же список в десяти вариантах.

Два слоя фильтров: раздел и мусорные пути

GO
c := colly.NewCollector(
        colly.AllowedDomains("shop.example.com"),
        colly.URLFilters(
                regexp.MustCompile(`^https://shop\.example\.com/catalog/.*$`),
        ),
        colly.DisallowedURLFilters(
                regexp.MustCompile(`\?sort=`),
        ),
)

Обе регулярки скомпилированы один раз на старте: MustCompile паникует при ошибке в шаблоне, и это правильное поведение для константы, известной заранее. Фильтр сверяется с полным адресом, поэтому в шаблон включены схема и хост. Двухслойная защита: URLFilters пускает только в каталог, DisallowedURLFilters вырезает сортировки; ссылка, попавшая под обе, не посетится — запрет сильнее. Домен при этом дублируем AllowedDomains: фильтры и домены — разные уровни периметра.

MaxDepth: глубина по вашей линейке#

Механику глубины разбирали в уроке про навигацию по ссылкам, здесь — то, что важно для периметра. colly.MaxDepth(n) ограничивает рекурсию обхода: стартовый Visit имеет глубину 1, каждый вложенный — на единицу глубже, и адрес, чья глубина превысила n, отклоняется с ошибкой Max depth limit reached. Дефолт — ноль, то есть без ограничений. Глубина и доменные фильтры решают разные задачи и не заменяют друг друга: AllowedDomains не даст уйти на чужой хост, но на своём позволит забрести в архив трёхлетней давности; MaxDepth ограничивает вглубь, но на каждом уровне разрешены все пути. Текущую глубину видно в колбэках через e.Request.Depth — удобно логировать её рядом с адресом, чтобы маршрут читался как лестница, а неожиданный скачок глубины сразу бросался в глаза. Пагинация — отдельная ловушка: она идёт цепочкой, и при MaxDepth(2) третья страница каталога не загрузится, хотя домен и путь в полном порядке; для сквозной пагинации нужен стоп-крючок из урока про пагинацию каталога, а не фиксированная глубина. Практический ориентир для каталога: категории и детальные карточки укладываются в глубину 2-3, и этого обычно достаточно — как только маршрут начинает требовать глубины 5+, это повод пересмотреть не цифру, а маршрут.

AllowURLRevisit: один адрес дважды#

По умолчанию повторный Visit того же адреса не отправляет запрос в сеть и возвращает ошибку URL already visited: colly ведёт список посещённых и отклоняет дубли. Это встроенная защита от циклов — без неё две страницы, ссылающиеся друг на друга, закольцевали бы обход насовсем. Обратное включается опцией colly.AllowURLRevisit() или присваиванием полю AllowURLRevisit = true, и нужно это в трёх сценариях: адрес-эндпоинт возвращает разные данные при повторных запросах, страницу надо собрать заново после действия формы, тесты сверяют поведение на одних и тех же URL. Для монотонного обхода каталога дедупликацию не выключают: она экономит и трафик, и чужое терпение, и место в вашем же журнале. Нюанс на стыке: список посещённых живёт внутри одного коллектора и умирает вместе с процессом, поэтому повторные Visit — норма между запусками программы; внутри одного обхода дубли отсекаются сами, и это ровно то поведение, которое хочется по умолчанию.

Крючки, на которых периметр рвётся#

  • ссылки из футера и шапки: соцсети, мессенджеры, партнёрские баннеры, счётчики аналитики
  • www и голый домен: example.com и www.example.com — два разных хоста для точного сравнения
  • поддомены сервисов: img., cdn., static. — картинки и скрипты каталога часто живут отдельно
  • http и https в регулярках путей: шаблон с одной схемой отсекает половину адресов
  • параметризованные дубли: ?sort=, ?utm_source=, ?from= — один каталог в десятке вариантов
  • href без http: mailto:, tel:, javascript: — AbsoluteURL их пропустит, а http-клиент схему не поймёт; фильтруйте по схеме до Visit
  • страницы действий: /logout, /delete, /checkout — ссылка на выход из аккаунта в обходчике с cookies дорого обойдётся; режьте их DisallowedURLFilters

Итоговый периметр: все ограничители вместе

GO
c := colly.NewCollector(
        colly.AllowedDomains("shop.example.com", "blog.example.com"),
        colly.URLFilters(
                regexp.MustCompile(`^https://shop\.example\.com/catalog/.*$`),
                regexp.MustCompile(`^https://blog\.example\.com/posts/.*$`),
        ),
        colly.DisallowedURLFilters(
                regexp.MustCompile(`\?sort=`),
        ),
        colly.MaxDepth(2),
)

c.OnHTML("a[href]", func(e *colly.HTMLElement) {
        link := e.Request.AbsoluteURL(e.Attr("href"))
        if link == "" {
                return
        }
        err := e.Request.Visit(link)
        if err != nil && err != colly.ErrForbiddenDomain &&
                err != colly.ErrForbiddenURL &&
                err != colly.ErrAlreadyVisited {
                log.Println("не пошёл:", link, "—", err)
        }
})

Финальный сетап собирает всё из урока: два домена, два пути-разрешения, запрет сортировок и глубина два. В колбэке отбрасываются известные ошибки навигации — Forbidden domain, ForbiddenURL и уже посещённые адреса — а любая другая печатается в журнал: сетевые сбои и сюрпризы не должны проходить мимо. Сравнение идёт с именованными константами пакета, а не со строками, поэтому опечатка в тексте ошибки не пройдёт мимо проверки.

Проверка: белый список хостов

В AllowedDomains указан shop.example.com. Колбэк передаёт в Visit ссылку на пост стороннего сервиса. Что произойдёт?

Проверка: два набора фильтров

Заданы URLFilters, пропускающие только раздел каталога, и DisallowedURLFilters с шаблоном на сортировки. Ссылка на каталог с параметром sort попадает под обе регулярки. Что сделает colly?

Что загрузится при MaxDepth(1)?

Прочитайте код и выберите, что он выведет:

c := colly.NewCollector(
        colly.MaxDepth(1),
)

c.OnHTML("a.category", func(e *colly.HTMLElement) {
        // ссылки на категории: /catalog/mebel/, /catalog/svet/
        e.Request.Visit(e.Attr("href"))
})

// в main:
// err := c.Visit("https://shop.example.com/")

Соберите периметр каталога

Восстановите опции коллектора и обработку ссылок в колбэке навигации.

c := colly.NewCollector( colly.("shop.example.com"), colly.(regexp.MustCompile(`^https://shop\.example\.com/catalog/.*$`)), ) c.OnHTML("a[href]", func(e *colly.HTMLElement) { link := e.Request.(e.Attr("href")) e.Request.(link) })

Скелет ограниченного обхода

Расставьте строки программы от объявления пакета до запуска обхода. Проверки ошибок опущены для краткости.

Строки

Ваша программа

Нажимайте на строки слева, чтобы собрать программу

Карточки: словарь периметра

Термин

AllowedDomains

нажмите, чтобы перевернуть
Определение

colly.AllowedDomains("shop.example.com", ...) — белый список хостов. Пустой список разрешает всё; чужой хост даёт ошибку Forbidden domain до отправки запроса.

1 / 6

Что дальше#

Периметр — техника; за её границей остаются правила игры: что сайт просит не собирать и где colly останавливается перед капчами и JS-рендерингом. Об этом следующий урок — про защиту от скрапинга и честные пределы Colly: там robots.txt, лимиты по IP и альтернативы для сайтов, которые не отдают HTML без браузера. А если нужна карта всего маршрута — от первого Visit до итогового проекта с монитором цен — она на странице курса Colly. Фильтры из этого урока лягут в основу каждого следующего скрапера курса, поэтому стоит дойти до домашнего задания руками: четыре строки конфигурации экономят ночи, а иногда и репутацию вашего IP.

Песочница урока

Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.

Вывод

Запустите код, чтобы увидеть вывод.

Коротко: что запомнить

  • AllowedDomains — белый список хостов: пустой список означает «разрешено всё», чужой хост — ошибку Forbidden domain до отправки запроса.
  • Сравнение хостов точное: www и поддомены перечисляются явно; DisallowedDomains — чёрный список, он сильнее белого.
  • URLFilters и DisallowedURLFilters — регулярки на полный адрес; при конфликте запрет побеждает разрешение.
  • MaxDepth ограничивает глубину: старт — 1, вложенный Visit — плюс один; дефолт 0 — без ограничений; текущая глубина — в e.Request.Depth.
  • Повторный Visit без AllowURLRevisit возвращает URL already visited — встроенная защита от циклов.
  • Все отклонения возвращаются из Visit до сети: OnError их не видит, сверяйтесь с константами ErrForbiddenDomain и ErrForbiddenURL.
  • Журнал отклонённых ссылок — диагностика периметра: подозрительно много Forbidden domain — проверяйте футерные ссылки и www-вариант хоста.

Домашнее задание

Возьмите обходчик из урока про навигацию и закройте периметр: AllowedDomains на два хоста — витрина и блог, URLFilters только на раздел каталога, MaxDepth(2). Запустите на books.toscrape.com и по журналу Visit посчитайте отклонённые ссылки. Затем уберите из списка www-вариант хоста, если он встретился, и объясните по логу, на чём именно встал обход.

Личные заметкиПусто
Хранятся только в вашем браузере (Colly · URL-фильтры: AllowedDomains, URLFilters, Depth)

Вопросы и ответы по уроку

Что вернёт colly при Visit на домен вне AllowedDomains?

Ошибку Forbidden domain — как обычное значение error из Visit. Запрос не уходит в сеть, OnHTML и OnError не срабатывают. В колбэке навигации ошибку проверяют и логируют: молчаливое игнорирование прячет и нормальные отклонения, и поломку маршрута.

Как разрешить поддомены вроде cdn.shop.example.com?

Точным перечислением: AllowedDomains сравнивает хосты по строке, и одна запись shop.example.com не покрывает ни cdn-, ни www-вариант. Перечислите все нужные хосты списком; широкого механизма «все поддомены» в библиотеке нет — глоб-шаблоны живут в LimitRule и к доменному периметру отношения не имеют. Альтернатива — URLFilters с регуляркой, где поддоменная часть шаблона шире.

Обход остановился на второй странице пагинации при MaxDepth(2) — это баг?

Нет: пагинация — цепочка переходов, каждая следующая страница на единицу глубже предыдущей. Фиксированная глубина для неё не подходит — ставьте стоп-условие по отсутствию ссылки «следующая» или по номеру страницы, как в уроке про пагинацию.

Термины урока

Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.

Следующая ступень учебного пути

Testify25 уроков~9 чпрактика25 уроков уже доступны

Testify: тестирование Go с assert и require

Интерактивный курс Testify для Go: assert и require, табличные тесты, suite, mock-объекты, Eventually, HTTP-тесты. 25 уроков с примерами и практикой в браузере.

  • Писать читаемые проверки через assert и require, а не лестницы if
  • Проверять ошибки, паники и краевые случаи без ручного кода
  • Сравнивать структуры, слайсы, карты и числа с плавающей точкой
  • Строить табличные тесты и сьюты с хуками подготовки