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

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

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

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

OnRequest и OnResponse: запросы под контролем

Обходчик из прошлого урока уже ходит по каталогу сам, но за кадром остаётся главное: куда именно ушли запросы и что вернулось. Colly onrequest и colly onresponse — два колбэка, которые показывают каждый шаг: адрес перед отправкой, статус и тело после загрузки. Разберём их аргументы, порядок вызовов и Abort для отсечения мусора до сети.

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

До сих пор наш скрапер был чёрным ящиком: c.Visit со стартовой страницы — и через мгновение OnHTML уже перебирает карточки товаров. Между этими двумя строками colly успевает построить HTTP-запрос, отправить его, получить заголовки, скачать тело и отдать его парсеру — и весь этот конвейер остаётся невидимым, пока вы сами не подпишетесь на его этапы. Невидимость дорого стоит. Когда сайт отвечает 403, вы узнаёте об этом по пустому выводу, а не по логу. Когда каталог редиректит на страницу входа, скрапер честно «работает»: разбирает страницу логина, ничего там не находит и молчит — вы тратите вечер на перечитывание селекторов, хотя проблема случилась на два шага раньше. А когда обход тянет картинки и архивы, вы платите трафиком и временем, потому что отменять неучтённые запросы негде. В этом уроке вешаем датчики на каждый этап конвейера: увидим запрос до отправки, ответ после загрузки, точку между заголовками и телом — и научимся отсекать мусор до того, как он уйдёт в сеть.

colly onrequest: последняя точка перед отправкой#

Регистрируется обработчик методом OnRequest с сигнатурой func(r colly.Request), и вызывается colly в последний момент перед уходом запроса в сеть — когда адрес уже разобран, глубина посчитана, очередь дала добро. В руках у вас весь запрос целиком. r.URL — это url.URL с хостом, путём и query-параметрами: от проверки префикса пути до разбора расширения файла доступно без строковых фокусов. r.Method говорит, что предстоит — GET или POST. r.Depth — та самая глубина обхода из восьмого урока, по которой colly отсекает рекурсию; в журнале она подсказывает, откуда пришёл запрос. У объекта есть и номер: r.ID уникален в пределах коллектора, и по нему удобно склеивать запрос с ответом в общем журнале. Самое ценное — r.Headers: это указатель на реальные заголовки запроса, и всё, что здесь выставить методом Set, отправится на сервер как есть. Именно в OnRequest живут User-Agent, Referer и Accept-Language — механике и границам этих манипуляций посвящён отдельный урок про заголовки и User-Agent. И последнее: OnRequest — последняя точка, где запрос ещё можно отменить, об этом ниже.

Журнал запроса и ответа

GO
c.OnRequest(func(r *colly.Request) {
        log.Println("->", r.Method, r.URL, "глубина", r.Depth)
})

c.OnResponse(func(r *colly.Response) {
        log.Printf("<- %d %s, %d байт", r.StatusCode, r.Request.URL, len(r.Body))
})

OnRequest печатает метод и адрес до отправки, OnResponse — статус, итоговый адрес и размер тела после загрузки. r.Request.URL в OnResponse — адрес последнего запроса цепочки: после редиректа он отличается от стартового. Две стрелки в логе превращают невидимый конвейер в читаемый диалог — любая пауза, пустой вывод или подозрительный код теперь имеют адрес и причину.

colly onresponse: тело уже скачано#

OnResponse с сигнатурой func(r *colly.Response) вызывается, когда тело ответа целиком лежит в памяти. Что внутри: r.StatusCode — число, а не строка; r.Body — срез байт, тот самый HTML или JSON; r.Headers — заголовки ответа, из них r.Headers.Get("Content-Type") достаёт тип содержимого; r.Request — связанный объект запроса с адресом и глубиной. Обратите внимание: отдельного поля ContentType у Response нет — тип берётся из заголовков, и запомнить это стоит заранее, чтобы не искать несуществующее поле по советам старых статей. Тело — сырые байты, и это удобно: string(r.Body) позволяет поискать подстроку по сырой разметке ещё до селекторов, len(r.Body) — посчитать размер, а сравнение размеров между прогонами — заметить, что сайт начал отдавать заглушку вместо каталога. На этот колбэк приходят и не-HTML ответы: JSON-эндпоинты, файлы, заглушки — всё, что вернул сервер с успешным кодом. Тело можно отправить в свой парсер или сохранить на диск методом r.Save("page.html") — быстрый снапшот страницы для отладки селекторов без браузера и curl. Имя файла, если сервер передал его в Content-Disposition, подскажет r.FileName().

Порядок вызовов: конвейер от Visit до OnScraped#

Колбэки colly срабатывают в строгом порядке, и он одинаков для каждой страницы, сколько бы обработчиков вы ни зарегистрировали. Знать конвейер наизусть полезно по двум причинам: сразу видно, на каком этапе пропала строка лога, и ясно, где ставить проверку — раньше значит дешевле. Выглядит конвейер так: OnRequest, затем сеть, затем OnResponseHeaders, OnResponse, все OnHTML по порядку регистрации — и финальный OnScraped, который замыкает страницу целиком. Разберём шаги по одному.

  1. OnRequest — запрос собран и скоро уйдёт; здесь живут заголовки, журнал и Abort.
  2. Сеть — HTTP-клиент отправляет запрос; редиректы транспорт догоняет сам, colly видит финальную страницу.
  3. OnResponseHeaders — получены статус и заголовки ответа, тело ещё не читалось.
  4. OnResponse — тело скачано целиком и лежит в r.Body как срез байт.
  5. OnHTML (и OnXML) — DOM разобран, селекторы отрабатывают в порядке регистрации обработчиков.
  6. OnScraped — страница обработана целиком; удобная финальная точка для сводки по ней.

OnResponseHeaders: решение до скачивания тела#

Между заголовками и телом есть отдельный колбэк — OnResponseHeaders(func(r *colly.Response)). Тело в этот момент ещё не скачивалось: доступен r.StatusCode, доступны r.Headers, а r.Body пуст. Это идеальное место для решений, которым хватает заголовков. Видали Content-Type text/html — отдаём страницу парсеру; увидели application/json — это эндпоинт, и данные достаются без разбора DOM; заметили image/ — можно сразу отказаться от ответа. То же с размером из заголовков: огромный файл виден в очереди до того, как съест трафик. Тонкость, о которой молчат туториалы: OnResponseHeaders срабатывает у всех дошедших ответов, включая 403 и 404 — заголовки-то уже приехали, и вы успеете залогировать код раньше, чем colly решит судьбу ответа. Abort работает и здесь: вызванный в этом колбэке, он обрывает докачку тела, а в OnError приходит ошибка ErrAbortedAfterHeaders — так экономят трафик на тяжёлых ответах, которые заведомо не нужны. Разница с OnResponse только в моменте, но момент дорогой: после заголовков тело ещё можно не качать, после — уже поздно.

Фильтр по типу содержимого

GO
c.OnResponseHeaders(func(r *colly.Response) {
        ct := r.Headers.Get("Content-Type")

        if strings.HasPrefix(ct, "image/") {
                r.Request.Abort() // тело картинки не докачиваем
                return
        }

        log.Println("тип:", ct, "статус:", r.StatusCode)
})

Content-Type приезжает в заголовках, поэтому решение принимается до чтения тела. Отмена делается через r.Request.Abort(): проверка в колбэке вернёт false, библиотека закроет тело не читая, а в OnError прилетит ErrAbortedAfterHeaders — это штатный способ отказаться от ответа, а не поломка, и пугаться такой строки в журнале не нужно.

Куда уходят 4xx, 5xx и редиректы#

Успешные ответы проходят конвейер целиком и заканчиваются в OnHTML и OnScraped. Всё остальное colly проводит по другой ветке: ответы с кодами ошибок — 403, 404, 500 — попадают в OnError, и OnResponse с OnHTML их не видят; подробно эту ветку и повтор запросов мы разбирали в уроке про OnError и повторы. Разделение логичное: страница с 404 обычно не несёт данных, и гонять её в DOM — только тратить память, а вот журнал ошибок обязан знать о каждом провале. Редиректы — отдельная история: перенаправления с заголовком Location HTTP-транспорт проходит сам, colly даже не узнаёт о промежуточных адресах — в OnResponse прилетает уже финальная страница с кодом 200, а r.Request.URL показывает её итоговый адрес. Поэтому «проверить статус 301 в OnResponse» не получится: до вас дойдёт только конец цепочки, и это к лучшему — бегать за редиректами вручную не нужно. Тело ответа с ошибкой по умолчанию не разбирается вовсе — опция ParseHTTPErrorResponse меняет это поведение, если страницы ошибок несут данные.

Abort: отсекаем мусор до отправки#

В восьмом уроке мы фильтровали ссылки до Visit — это санитария очереди. Но запрос может оказаться мусорным и позже: пагинация ведёт на страницы-приложения в PDF, каталог раздаёт картинки прямыми ссылками, а вы по ошибке собрали их в очередь. Для таких случаев в OnRequest есть рубильник r.Abort(): запрос, помеченный aborted, в сеть не уходит вообще. Причём это не ошибка — OnError не вызовется, Visit завершится тихо, очередь продолжит работу со следующим адресом. Связка получается двухуровневой: фильтр ссылок держит очередь чистой на этапе планирования, Abort — последняя проверка на этапе исполнения, когда URL уже поставлен в очередь и до сети остался один шаг. Разницу видно в журнале: провал попадает в OnError, а отмена — тишина, поэтому отмены стоит логировать прямо в OnRequest, печатая адрес перед Abort-ом. Мой рабочий минимум: Abort на расширения статики и архивные форматы, которые точно не несут данных, — трафик экономится заметнее всего именно здесь, потому что отменённый запрос не отправляется вовсе.

Отмена статики в OnRequest

GO
c.OnRequest(func(r *colly.Request) {
        for _, ext := range []string{".jpg", ".png", ".gif", ".pdf", ".zip"} {
                if strings.HasSuffix(r.URL.Path, ext) {
                        r.Abort()
                        return
                }
        }

        log.Println("грузим:", r.URL)
})

Путь запроса достаётся из r.URL.Path — это разобранный *url.URL, без схемы и хоста. Суффикс проверяется по списку расширений, и совпавший запрос помечается Abort-ом прямо в колбэке. Отменённые адреса не попадают ни в OnResponse, ни в OnError — в журнале их видно только по отсутствию парной стрелки: request ушёл, response не пришёл.

Кто что видит на каждом этапе#

ЭтапЧто доступноТипичное применение
OnRequestr.URL, r.Headers, r.Depth, r.Abortжурнал, заголовки запроса, отсечение статики
OnResponseHeadersStatusCode и заголовки ответа; Body ещё пустфильтр по Content-Type, ранний лог
OnResponseStatusCode, Body, Headers, Request, Saveразбор JSON, снапшоты, подсчёт байт
OnHTMLDOM-элементы, e.Requestизвлечение данных селекторами
OnErrorResponse без тела, errжурнал провалов, очередь повторов
OnScrapedтот же Responseсводка по обработанной странице

Проверка: судьба 404

Сайт ответил 404. Какие колбэки вызовутся и в каком порядке?

Проверка: Abort

В OnRequest для всех адресов с суффиксом .pdf вызван r.Abort(). Что увидит программа?

Что напечатает программа при 500?

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

c.OnRequest(func(r *colly.Request) {
        fmt.Println("->", r.URL)
})

c.OnResponse(func(r *colly.Response) {
        fmt.Println("<-", r.StatusCode)
})

c.OnError(func(r *colly.Response, err error) {
        fmt.Println("!", r.StatusCode, err)
})

c.Visit("http://books.toscrape.com/broken")

Соберите пару запрос-ответ

Восстановите регистрацию колбэков, отмену статики и сохранение страницы.

c.(func(r *colly.Request) { if strings.HasSuffix(r.URL.Path, ".zip") { r.() } }) c.(func(r *colly.Response) { fmt.Println(r., len(r.Body)) r.("catalog/page.html") })

Соберите конвейер по строкам

Расставьте строки в порядке конвейера: от запроса до ответа, ошибки — рядом с ответом.

Строки

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

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

Карточки: запрос и ответ

Термин

OnRequest

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

Колбэк перед отправкой запроса: r.URL, r.Headers, r.Depth. Здесь выставляют заголовки, ведут журнал и вызывают Abort.

1 / 6

Что дальше#

Конвейер виден, каждый запрос подписан — остался вопрос темпа: обходчик ставит запросы в очередь так быстро, как успевает сеть, и без ограничителей это подозрительно похоже на DDoS. Успокоим его в следующем уроке про LimitRule: там появятся Delay, RandomDelay и лимит одновременных запросов на домен. Примеры этого урока, как и весь сетевой код курса, запускаются локальным Go — в браузерной песочнице сети нет. Соберите журнал запроса и ответа, прогоните его по books.toscrape.com и полистайте терминал: каталог из пары страниц прочитывается как диалог, и каждая стрелка в нём — вызов одного из колбэков этого урока. Полный маршрут по курсу — на странице коли-курса.

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

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

Вывод

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

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

  • OnRequest вызывается перед отправкой: отсюда видны r.URL, r.Method и r.Depth, здесь выставляются заголовки и работает Abort.
  • Порядок конвейера: OnRequest → сеть → OnResponseHeaders → OnResponse → OnHTML → OnScraped.
  • OnResponseHeaders видит статус и Content-Type до чтения тела — место для решений, которым HTML не нужен.
  • OnResponse получает StatusCode и r.Body []byte, но только у успешных ответов; 4xx и 5xx уходят в OnError.
  • Abort в OnRequest отменяет запрос до сети: трафик не тратится, OnError не вызывается, очередь идёт дальше.
  • Редиректы http-транспорт проходит сам: в OnResponse приходит финальная страница, r.Request.URL показывает её адрес.
  • r.Save(fileName) пишет тело на диск — быстрый снапшот страницы для отладки селекторов.

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

Добавьте в обходчик из восьмого урока пару OnRequest и OnResponse с журналом: адрес, метод, статус, размер тела. Отсеките Abort-ом картинки и архивы в OnRequest, а в OnResponseHeaders — ответы с типом image/. Запустите локально на books.toscrape.com и посчитайте по логу, сколько запросов сэкономил Abort.

Личные заметкиПусто
Хранятся только в вашем браузере (Colly · OnRequest и OnResponse: запросы под контролем)

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

Почему OnResponse не срабатывает на странице с кодом 404?

Ответ с ошибкой colly проводит по ветке OnError: OnResponse и OnHTML видят только успешные ответы. Заголовки при этом доходят — OnResponseHeaders успевает отработать. Тело страницы ошибки можно получить опцией ParseHTTPErrorResponse, но селекторам там обычно ловить нечего.

Чем OnResponseHeaders отличается от OnResponse?

Моментом вызова и наполнением: OnResponseHeaders срабатывает сразу после заголовков, когда Body ещё пуст — видно только StatusCode и Headers. OnResponse вызывается после скачивания тела. Если решение зависит только от заголовков, не ждите тело — фильтруйте или отменяйте раньше.

Можно ли в OnRequest узнать, куда именно уйдёт запрос?

Да: r.URL — разобранный *url.URL с хостом, путём и query-параметрами, r.Method — метод, r.Depth — глубина обхода. Заголовки меняются прямо через r.Headers.Set. Объект тот же, что и e.Request в OnHTML, поэтому AbsoluteURL и Visit доступны и отсюда.

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

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

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

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

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

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

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