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

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

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

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

Ошибки и повторы: OnError и таймауты

Скрапер печатает пустой вывод, сайт уже час отдаёт 403, а вы пятый раз перечитываете селекторы — потому что colly onerror в программе не зарегистрирован, и все ошибки умирают в тишине. Разберём OnError по аргументам, таймаут через SetRequestTimeout, опцию ParseHTTPErrorResponse и ручные повторы Visit: встроенного Retry в colly нет, повтор запроса делается руками.

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

Пятница, отчёт по ценам нужен к понедельнику. Скрапер запущен, лог чистый, вывод пустой: ни товаров, ни ошибок. Двадцать минут уходит на перечитывание селекторов — на странице ведь что-то есть! А на самом деле сайт уже час отвечает 403, и каждый запрос умирает в одном и том же месте: в колбэке, которого не существует. Colly не печатает ошибок сама — всё, что не зарегистрировано, происходит молча. Это первая привычка, которую нужно поменять после первых уроков курса: не «селекторы сломались», а «я не подписался на ошибки». В этом уроке подключаем OnError, ставим таймауты и учим скрапер переживать плохую сеть — повторять провалившиеся запросы руками, потому что кнопки Retry в библиотеке не появилось.

colly onerror: один колбэк на все беды#

Регистрируется обработчик методом OnError, и сигнатура у него двойная: func(r *colly.Response, err error). Colly вызывает его в двух случаях. Первый — запрос не дошёл: DNS не разрешился, соединение отвергнуто, истёк таймаут. Второй — ответ пришёл, но с кодом 300 и выше: 403 Forbidden, 404 Not Found, 500 Internal Server Error. Причём во втором случае тот же ответ уже не попадёт в OnResponse и OnHTML — colly по умолчанию не разбирает HTML у страниц с кодом ошибки, об этом ниже. Теперь аргументы. err — текст случившегося: для HTTP-ошибок colly собирает его из статуса, поэтому err будет словом Forbidden или Not Found; для сетевых бед — сообщение от http.Client, например с пометкой Client.Timeout exceeded. r.Request.URL — адрес, на котором всё стряслось. r.StatusCode — код ответа, а если ответа не было вовсе, он останется нулём. Именно ноль отличает обрыв сети от честной сорок четвёрки — этим мы воспользуемся в политике повторов.

Минимальный обработчик

GO
c.OnError(func(r *colly.Response, err error) {
        log.Printf("%s -> код %d: %v", r.Request.URL, r.StatusCode, err)
})

Один Printf закрывает половину вопросов при отладке: адрес, код и текст ошибки в одной строке журнала. Ноль в r.StatusCode значит, что до HTTP дело не дошло — обрыв на уровне сети. Ставьте OnError рядом с остальными колбэками, а не после первой сотни Visit: каждый незарегистрированный запрос до этого момента исчезал бесследно.

Откуда берутся ошибки#

Ошибки в скрапере делятся на две семьи, и лечатся они по-разному. Сетевые случаются до HTTP: домен не разрешился, сервер отверг соединение, канал оборвался посередине страницы или истёк таймаут. Это временные беды — сайт через минуту может ожить, поэтому их повторяют. HTTP-ошибки приходят с кодом: 403 — вас узнали и не пускают, 404 — ссылка мертва, 5xx — сервер задыхается сам по себе, иногда вообще без вашей вины. Повтор тут полезен только для 5xx: сорок четвёрка от повторов не оживёт, а 403 без смены заголовков или адреса выхода в сеть ответит тем же. Классическая ошибка новичка — повторять всё подряд: получаете второй 403 через две секунды после первого и бан на сутки после десятого. Смотрите в таблицу ниже — она честнее любого интуитивного «а вдруг теперь пройдёт».

Ошибка — причина — что делать#

Ошибка в OnErrorПричинаЧто делать
no such host (DNS)домен опечатан или умерисправить URL; повтор бесполезен
connection refusedсервер не принимает соединениеповторить позже с задержкой
Client.Timeout exceededтаймаут: тяжёлая страница или тормозящий серверподнять SetRequestTimeout, повторить
StatusCode 0обрыв до ответа: сеть, DNS, таймаутповторить с задержкой
StatusCode 403антибот или блокировкане ретрай: заголовки, User-Agent, прокси
StatusCode 404ссылка мертвавыбросить из очереди, проверить селектор ссылки
StatusCode 5xxсерверная ошибка на стороне сайтаповторить с задержкой — часто помогает

SetRequestTimeout: таймаут вместо ожидания#

У HTTP-клиента colly есть таймаут по умолчанию — 10 секунд; это значение задано при создании клиента внутри библиотеки. Для каталога с лёгкими страницами хватает, а для тяжёлых витрин с большими таблицами — нет: страница рвётся, и в OnError сыплется одно и то же сообщение с пометкой Client.Timeout exceeded. Порог меняется методом SetRequestTimeout: c.SetRequestTimeout(30 * time.Second) — и клиент готов ждать полминуты. Разумный диапазон — от 10 до 60 секунд в зависимости от сайта; таймаут в несколько минут превращает скрапер в очередь из висящих запросов. Помните и про обратную сторону: длинный таймаут не лечит мёртвый сервер, он лишь дольше притворяется, что лечит. Если сайт стабильно отвечает за полсекунды, таймаут в 30 секунд только маскирует проблемы сети, которые стоило бы увидеть сразу. В песочнице этот код не проверить — интерпретатор в браузере живёт без сети, запускайте локально.

Таймаут и обработчик вместе

GO
c := colly.NewCollector()
c.SetRequestTimeout(30 * time.Second)

c.OnError(func(r *colly.Response, err error) {
        log.Printf("%s -> %d %v", r.Request.URL, r.StatusCode, err)
})

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

time — стандартный пакет, Duration понимает и 30time.Second, и 500time.Millisecond. Таймаут живёт на HTTP-клиенте, поэтому задаётся один раз на коллектор, а не на каждый запрос.

ParseHTTPErrorResponse: HTML при 4xx и 5xx#

По умолчанию colly парсит только успешные ответы: у страницы с кодом 300 и выше HTML не разбирается, OnHTML и OnResponse не вызываются, всё заканчивается OnError. Поведение разумное — в девяти случаях из десяти тело 404 это служебная страница, селекторам там ловить нечего. Но бывают сайты, где даже страница ошибки несёт данные: код причины в JSON, форма повторной отправки, текст блокировки с номером обращения. Опция colly.ParseHTTPErrorResponse() в конструкторе разрешает colly гнать такие ответы через обычный конвейер: OnResponse получит код 403 в r.StatusCode и тело в r.Body, OnHTML попытается применить селекторы. Цена включения: ваши селекторы теперь обязаны переживать страницы-ошибки, а журнал — различать честные данные и обломки. Включайте точечно, когда без тела ошибки действительно нельзя.

Разбор тела ошибки

GO
c := colly.NewCollector(colly.ParseHTTPErrorResponse())

c.OnResponse(func(r *colly.Response) {
        log.Printf("код %d, тело %d байт", r.StatusCode, len(r.Body))
})

Теперь и 403, и 500 попадут в OnResponse: код в r.StatusCode, тело — в r.Body как срез байт. OnError при этом тоже сработает: опция не отменяет колбэк ошибок, она лишь добавляет разбор тела.

Повтор запроса руками: очередь и второй проход#

colly повтор запроса делает только по вашей команде: встроенного Retry в библиотеке нет, и авторы не стали угадывать, какая ошибка заслуживает повтора. Паттерн живёт в двух частях. В OnError провальные URL складываются в срез — но не все подряд, а только те, что имеют смысл повторить: обрывы сети и 5xx. После основного прохода программа спит заданное время через time.Sleep и делает второй проход по очереди. Подводный камень, о котором молчат туториалы: colly помечает URL посещённым ещё до ответа сервера, поэтому повторный c.Visit того же адреса тем же коллектором вернёт ошибку уже посещённого URL и до сети не дойдёт. Лекарств два: строить коллектор функцией и для второго прохода создать свежий — или разрешить повторные визиты опцией colly.AllowURLRevisit(). Первый путь надёжнее: свежий коллектор заодно сбрасывает накопленные куки и счётчики.

Программа с ретраем

GO
package main

import (
        "log"
        "time"

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

var failed []string

func newScraper() *colly.Collector {
        c := colly.NewCollector()
        c.SetRequestTimeout(30 * time.Second)

        c.OnHTML("h3.product-title", func(e *colly.HTMLElement) {
                log.Println(e.Text)
        })

        c.OnError(func(r *colly.Response, err error) {
                log.Printf("упало: %s -> %d %v", r.Request.URL, r.StatusCode, err)
                if r.StatusCode == 0 || r.StatusCode >= 500 {
                        failed = append(failed, r.Request.URL.String())
                }
        })

        return c
}

func main() {
        c := newScraper()
        c.Visit("http://books.toscrape.com/catalogue/page-1.html")

        if len(failed) > 0 {
                log.Println("второй проход по", len(failed), "URL")
                time.Sleep(10 * time.Second)
                rc := newScraper()
                for _, u := range failed {
                        time.Sleep(2 * time.Second)
                        if err := rc.Visit(u); err != nil {
                                log.Println("повтор не удался:", err)
                        }
                }
        }
}

newScraper возвращает настроенный коллектор — колбэки не приходится регистрировать дважды. В очередь попадают только обрывы (StatusCode 0) и серверные ошибки; 403 и 404 в повтор не идут. Паузы между попытками — минимальная вежливость: сайт уже показал, что ему тяжело. Всё, что ходит по сети, запускается локальным Go после go get github.com/gocolly/colly/v2.

Политика повторов: что, сколько и как часто#

Режим повтора — это политика, и её стоит писать осознанно. Сколько попыток: двух почти всегда хватает; третий проход по URL, который упал дважды, обычно не стоит времени. С какой паузой: фиксированные 10 секунд — нормальный старт, растущая пауза, где каждая попытка вдвое длиннее предыдущей, — вежливее для задыхающегося сервера. Что повторять: обрывы и 5xx — да; 4xx — нет, там чинить надо не ретрай, а заголовки и User-Agent, о чём отдельный урок про капризы сайтов; если сайт узнаёт вас по адресу выхода — решение в уроке про прокси. Отдельная ловушка — c.Async = true: колбэки вызываются из разных горутин, append к общему срезу становится гонкой данных, и очередь провальных URL придётся защищать мьютексом или каналом — полный разбор в уроке про параллельный скрапинг. Пока Async не включён, обычный срез безопасен: колбэки идут последовательно.

Проверка: аргументы OnError

Сайт ответил 403. Что получит зарегистрированный OnError?

Проверка: таймауты

Сайт не отвечает, SetRequestTimeout не вызван. Что произойдёт?

Что выведет программа при 403?

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

c := colly.NewCollector()

c.OnHTML("h1", func(e *colly.HTMLElement) {
        fmt.Println("HTML:", e.Text)
})

c.OnError(func(r *colly.Response, err error) {
        fmt.Println("Ошибка:", r.StatusCode, err)
})

err := c.Visit("http://books.toscrape.com/blocked")
fmt.Println("Visit вернул:", err)

Восстановите обработчик ошибок

Допишите регистрацию колбэка ошибок, тип первого аргумента и секунду в таймауте.

c := colly.NewCollector() c.(, func(r *colly.Response, err error) { log.Printf("%s -> %d %v", r.Request.URL, r.StatusCode, err) }) c.SetRequestTimeout(30 * time.)

Соберите скелет с обработкой ошибок

Расставьте строки программы: от объявления пакета до первого Visit.

Строки

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

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

Карточки: ошибки и повторы

Термин

OnError

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

Колбэк ошибок colly: func(r *colly.Response, err error). Вызывается и при сетевых сбоях, и при ответах с кодом 300+; OnHTML при этом молчит.

1 / 6

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

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

Вывод

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

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

  • OnError(func(r *colly.Response, err error)) — единственное место, где видны DNS-сбои, таймауты и ответы 4xx/5xx.
  • Без зарегистрированного OnError скрапер молчит: пустой вывод при 403 выглядит как сломанные селекторы.
  • У HTTP-ошибки StatusCode настоящий, а err — текст статуса; у сетевого обрыва StatusCode равен нулю.
  • Клиент colly имеет таймаут по умолчанию 10 секунд; тяжёлым страницам ставят SetRequestTimeout(30 * time.Second).
  • По умолчанию HTML у ответов с кодом 300+ не разбирается; нужно тело ошибки — colly.ParseHTTPErrorResponse().
  • Своего Retry нет: очередь провальных URL, пауза и второй проход свежим коллектором — URL помечаются посещёнными до ответа.
  • Повторяют обрывы и 5xx; 403 и 404 от повторов не чинятся.

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

Возьмите скрапер с пагинацией из восьмого урока и доверьте ему переживать беды: зарегистрируйте OnError с журналом адреса, кода и текста ошибки, поставьте SetRequestTimeout, соберите очередь провальных URL и сделайте второй проход свежим коллектором после паузы в 10 секунд. Затем намеренно сломайте домен в одном URL и убедитесь по журналу, что StatusCode там нулевой. Проверьте себя: что попадает в очередь, а что нет — и почему.

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

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

Есть ли в colly автоматические повторы запросов?

Нет, встроенного Retry не существует. Провальные URL собирают в OnError и повторяют вручную: пауза через time.Sleep и повторный Visit. Для повторных визитов нужен свежий коллектор или опция AllowURLRevisit, потому что colly помечает URL посещённым ещё до ответа сервера.

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

По умолчанию colly разбирает только успешные ответы: код 300 и выше завершается OnError, и тело не доходит до селекторов. Если тело ошибки нужно, включайте colly.ParseHTTPErrorResponse() в конструкторе — но тогда селекторы обязаны переживать страницы-ошибки.

Какой таймаут выбрать для скрапера?

Старт — 10 секунд из коробки; для тяжёлых страниц поднимайте до 30-60 секунд методом SetRequestTimeout. Таймаут в минуты не лечит мёртвый сервер — он лишь дольше держит запрос висящим и тормозит весь обход.

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

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

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

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

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

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

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