---
title: "User-Agent и заголовки: когда сайт капризничает"
description: "colly user agent и заголовки: UserAgent-опция, Accept-Language и Referer в OnRequest, что видит сервер и почему TLS-отпечаток colly не маскирует."
course: "Colly: парсинг и веб-скрапинг на Go"
url: "https://golangmaster.ru/courses/colly/lessons/14-zagolovki-useragent/"
published: "2026-09-19"
updated: "2026-09-21"
minutes: 16
---

# User-Agent и заголовки: когда сайт капризничает

*Курс «Colly: парсинг и веб-скрапинг на Go», урок 14 из 25. ~16 минут. Обновлено 21.09.2026. Источник: https://golangmaster.ru/courses/colly/lessons/14-zagolovki-useragent/*

colly user agent меняется одной опцией, но за заголовком стоит целая дисциплина: сервер судит о клиенте по User-Agent, Accept-Language, Referer и по порядку заголовков, и скрапер, чей запрос не похож на браузерный, получает упрощённый контент или 403. Разберём, что менять, что оставлять и где заканчивается техника.

Сайт, который открывается в браузере без вопросов, вдруг отвечает скраперу 403 — или открывает, но вместо карточек показывает заглушку «Доступ ограничен». Чаще всего это не про IP и не про скорость: сервер посмотрел на сам запрос и не узнал в нём посетителя. HTTP-запрос — это не только адрес: с ним едут заголовки, и по ним сервер сортирует клиентов на браузеры, роботов и всё прочее. Скрапер из прошлых уроков шлёт честную подпись colly — и часть сайтов режет её без разговоров. Урок про то, как выглядят запросы изнутри: какой User-Agent шлёт colly по умолчанию, как подставить свой, зачем скраперу Accept-Language и Referer — и где проходит граница между настройкой совместимости и маскировкой.

### colly user agent: опция NewCollector

По умолчанию каждый запрос colly подписывает собственным User-Agent — строкой со ссылкой на репозиторий библиотеки. Честно, но с последствиями: парсинг User-Agent — древний приём фильтрации, и «коллекторские» подписи режут охотно и массово, от CDN до самодельных middleware. Меняется подпись одной опцией: colly.UserAgent("строка") в NewCollector задаёт заголовок всем запросам коллектора. Строку берут из реального браузера — откройте devtools, вкладку Network, скопируйте значение из заголовков любого запроса; у многих разработчиков рядом с проектом давно лежит проверенная константа. Второй путь — заголовки на каждый запрос: в OnRequest у r.Headers вызывается обычный Set из http.Header, и там же ставится всё остальное, что не хочется вшивать в конструктор. Два механизма не спорят, а дополняют: опция задаёт личность, OnRequest — манеру каждого захода.

### User-Agent и заголовки на каждый запрос

```go
package main

import (
        "fmt"

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

func main() {
        c := colly.NewCollector(
                colly.UserAgent("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"),
        )

        c.OnRequest(func(r *colly.Request) {
                r.Headers.Set("Accept-Language", "ru-RU,ru;q=0.9,en;q=0.8")
                r.Headers.Set("Referer", "http://books.toscrape.com/")
                fmt.Println("отправляю", r.URL.String())
        })

        c.OnResponse(func(r *colly.Response) {
                fmt.Println("статус", r.StatusCode)
        })

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

colly.UserAgent в конструкторе закрывает подпись разом для всех запросов. В OnRequest r.Headers — стандартный http.Header: Set добавляет Accept-Language с приличными весами и Referer, имитирующий переход с главной каталога. OnRequest срабатывает на каждый запрос — включая пагинацию и повторные Visit, поэтому глобальные заголовки ставят именно здесь, а не перед каждым Visit вручную.

### Что сервер видит в скрапере

Прежде чем менять заголовки, посмотрите на них глазами сервера: httpbin.org/headers возвращает JSON со всем, что дошло до него. Сравните вывод двух прогонов — от colly по умолчанию и от настроенного скрапера — и разница станет наглядной: подпись библиотеки, отсутствие Accept-Language, пустой Referer. Сервер судит по совокупности: браузерный User-Agent при полном отсутствии других заголовков — уже подозрение; идеальный набор заголовков при странном поведении — второе; и так до полного профиля клиента. Для подавляющего большинства задач до глубокого анализа дело не доходит: достаточно правдоподобного набора заголовков и вежливого темпа, чтобы сервер отличил вас от дешёвых ботов и отдал обычную страницу.

### Заголовки скрапера: что сообщает и когда менять

| Заголовок | Что сообщает серверу | Когда менять |
| --- | --- | --- |
| User-Agent | кто стучится: браузер, версия, платформа | почти всегда: браузерная строка или честное имя своего бота |
| Accept-Language | на каком языке ожидать контент | когда у сайта языковые версии и нужен конкретный язык |
| Referer | с какой страницы «пришли» | когда контент отдаётся только с внутренней навигации |
| Accept / Accept-Encoding | какие форматы клиент умеет читать | редко: colly и net/http ставят разумные значения |
| Cookie | чьи данные показывать | это урок про формы: сессия после логина |

### Честное имя бота: когда свой UA лучше

Контринтуитивный совет напоследок: браузерная строка — не всегда лучший выбор. Если вы скрапите источники с публичной политикой — открытые данные, каталоги для партнёров, сайты, где роботы приветствуются, — честное имя с контактом работает лучше. Во-первых, белый список: админы видят GoMasterBot/1.0 в логах, понимают, что это не дешёвый скрипт, и в случае проблем знают, кому написать, прежде чем бить блокировкой. Во-вторых, диагностика от обратной стороны: по внятному UA вам могут точнее настроить лимиты, а по анонимному браузерному — нет. Практика выглядит как строка вида имя-версии-контакт в одном заголовке. Но у подхода есть условие применимости, и оно жёсткое: он живёт только там, где владелец сайта в курсе и не против. На остальных ресурсах честность подписи выливается в мгновенный 403 — и тогда остаётся совместимость, о которой шла речь выше.

### Проверка: что реально увидел сервер

```go
c := colly.NewCollector(
        colly.UserAgent("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36"),
)

c.OnResponse(func(r *colly.Response) {
        fmt.Println(string(r.Body))
})

c.Visit("https://httpbin.org/headers")
```

httpbin.org/headers — зеркало: сервер возвращает JSON с заголовками пришедшего запроса. Прогоните без UserAgent-опции и с ней — в поле User-Agent увидите сначала подпись colly, потом браузерную строку. Так же удобно проверять Referer и Accept-Language: сервер показывает то, что реально уехало, а не то, что вы думали поставить.

### Referer и Accept-Language: два частых каприза

Referer — заголовок с историей перехода. Некоторые сайты отдают контент только «изнутри»: прямая ссылка на карточку без Referer получает редирект на каталог или заглушку. Лечится в OnRequest: r.Headers.Set("Referer", адрес каталога) — и запрос выглядит пришедшим с внутренней страницы. Accept-Language решает другой вопрос: у мультиязычных сайтов версия контента выбирается по этому заголовку, и скрапер без него получает язык по умолчанию, который не всегда тот, что нужен. Оба заголовка честные: реальный браузер шлёт и то, и другое — вы не врёте, вы приводите запрос к общепринятому виду. Разница с cookie-сессией из [урока про формы](/courses/colly/lessons/13-formy-login/) простая: cookies — личность, заголовки — манера речи; и то и другое нужно, чтобы сайт узнавал клиента.

### Порядок заголовков: мелочь, которую видят

Кроме значений, сервер видит и порядок заголовков — и это тоже почерк. Браузеры шлют набор в устоявшемся порядке, HTTP-клиенты — в своём; расхождение не фатально для большинства сайтов, но входит в профиль клиента у продвинутых фильтров. Colly порядок штатно не перетасовывает, и гнаться за этим не нужно: если источник фильтрует по порядку заголовков, вы это узнаете после того, как остальные признаки уже выправлены. Практический порядок работы с капризным сайтом такой: снять запрос браузера, воспроизвести значения в OnRequest, проверить зеркалом httpbin — и только если не помогло, идти дальше по списку: cookies из урока про формы, затем прокси из следующего урока. Чаще всего до конца списка не доходит: одна подпись и один Referer решают девять историй из десяти.

### Accept-Encoding: не трогайте руками

Особый случай — Accept-Encoding, и у него правило противоположное: не менять. Браузер шлёт список форматов сжатия — gzip, deflate, br, zstd. Транспорт Go при отсутствии этого заголовка сам добавляет gzip и сам его расжимает: в OnResponse приходит уже читаемый HTML, и селекторы работают. Если же поставить Set("Accept-Encoding", "br") руками, сервер добросовестно отдаст brotli, который стандартный транспорт не расжимает, — и в r.Body окажутся байты, на которых парсинг молча даст ноль результатов. Такие поломки коварны: код ответа 200, тело есть, ошибок нет — а данных нет. Если подозреваете сжатие, смотрите на тело в OnResponse глазами: начало внятного HTML или россыпь нечитаемых байтов. Заголовок, который вы не понимаете до конца, лучше не отправлять вовсе — это правило распространяется на весь набор, а не только на сжатие.

### Проверка: точка установки заголовков

**Вопрос:** Где удобнее ставить заголовок, который должен уйти с каждым запросом коллектора?

- Перед каждым c.Visit вручную
- ✅ В OnRequest через r.Headers.Set — срабатывает на каждый запрос
- В OnResponse — после получения ответа
- В url.Parse перед Visit

OnRequest вызывается перед отправкой каждого запроса: Visit, POST, пагинация из колбэков — всё пройдёт через одну точку. r.Headers — стандартный http.Header, Set работает как в net/http. OnResponse увидит запрос, когда менять заголовки уже поздно.

### Проверка: подпись по умолчанию

**Вопрос:** Какой User-Agent шлёт colly без настройки?

- Строку Chrome из последней версии
- Пустой заголовок
- ✅ Собственную подпись со ссылкой на репозиторий библиотеки
- User-Agent операционной системы

Подпись colly честная, но распознаваемая: по ней сайты и CDN отсекают коллекторские запросы, поэтому опция colly.UserAgent — одна из первых настроек боевого скрапера.

### Что увидит httpbin в поле User-Agent?

```go
c := colly.NewCollector(
        colly.UserAgent("Mozilla/5.0 (compatible; GoMasterBot/1.0)"),
)
c.OnResponse(func(r *colly.Response) {
        fmt.Println(string(r.Body))
})
c.Visit("https://httpbin.org/headers")
```

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

- Подпись colly со ссылкой на репозиторий
- ✅ Mozilla/5.0 (compatible; GoMasterBot/1.0)
- Пустое поле User-Agent
- Заголовка User-Agent не будет вовсе

Опция задаёт заголовок всем запросам коллектора, и httpbin честно вернёт то, что пришло: вашу строку. Собственное ботовское имя — тоже легитимный выбор, если правила сайта допускают роботов; почему иногда это лучший вариант, станет ясно из [урока про антибот-защиту](/courses/colly/lessons/22-antibot-zaschita/).

### TLS-отпечаток: чего colly не маскирует

Честное слово про нижний этаж. Под заголовками лежит TLS-рукопожатие, и у каждого стека оно своё: Go из коробки даёт узнаваемый отпечаток, по которому продвинутые антибот-системы отличают клиента на Go от настоящего Chrome — даже когда заголовки идеальны. Colly это не маскирует: под капотом net/http, и подменить рукопожатие опцией нельзя. Практический вывод двоякий. Если сайт отдаёт контент по заголовкам — вы уже всё решили, и TLS никого не волнует. Если же сервер классифицирует клиентов по отпечатку, colly упрётся в потолок: дальше либо честный отказ от скрапинга этого источника, либо браузерный движок — это территория [урока про антибот-защиту](/courses/colly/lessons/22-antibot-zaschita/), где про chromedp и JavaScript-рендеринг отдельный разговор.

### Граница: совместимость — да, обход запретов — нет

Спорное место урока: менять User-Agent — нормально или нет? Моё мнение — менять можно и нужно, когда речь о совместимости. Браузерные строки — де-факто стандарт, который понимают серверы, а «коллекторская» подпись отсекается даже там, где скрапинг разрешён; вы не обходите запрет, вы делаете запрос понятным. Другое дело — подставлять браузерный UA, чтобы пролезть туда, где роботам прямо сказано «нельзя»: в robots.txt, в письме от владельца, в условиях использования. На этом месте заканчивается техническая задача и начинается решение о правилах — правовых и этических, и оно принимается не в терминале. [Урок про защиту от скрапинга](/courses/colly/lessons/22-antibot-zaschita/) разложит эту границу по полочкам: robots.txt, капчи, что colly не умеет и что делают вместо неё. Мой рабочий ориентир: настраивай заголовки так, как настроил бы их для публичного API, о котором можно сказать владельцу вслух.

> **Источник истины — devtools.** Прежде чем угадывать заголовки, откройте Network в devtools и скопируйте запрос, который отправляет браузер: User-Agent, Accept-Language, Referer. Цель — не копия один в один, а правдоподобный минимум. И проверяйте себя зеркалом httpbin.org/headers: сервер видит то, что реально уехало.

### Восстановите OnRequest

Поле запроса с заголовками и язык контента.

```go
c.OnRequest(func(r *colly.Request) {
        r.___.Set("___", "ru-RU,ru;q=0.9")
})
```

Подсказка: У colly.Request есть поле Headers стандартного типа http.Header; язык контента просится заголовком Accept-Language.

Правильные ответы в пропусках: `Headers`, `Accept-Language`

### Соберите скрапер с настроенной подписью

От опции User-Agent до печати статуса.

Правильный порядок строк:

1. `c := colly.NewCollector(colly.UserAgent("Mozilla/5.0 (compatible; GoMasterBot/1.0)"))`
2. `c.OnRequest(func(r *colly.Request) {`
3. `  r.Headers.Set("Accept-Language", "ru-RU,ru;q=0.9")`
4. `})`
5. `c.OnResponse(func(r *colly.Response) {`
6. `  fmt.Println(r.StatusCode)`
7. `})`
8. `c.Visit("https://httpbin.org/headers")`

### Карточки: заголовки и подписи

- **colly.UserAgent** — Опция NewCollector: задаёт заголовок User-Agent всем запросам коллектора. Без неё уезжает подпись colly со ссылкой на репозиторий.
- **r.Headers.Set** — Стандартный http.Header у colly.Request. Ставится в OnRequest — единственной точке, через которую проходят все запросы.
- **Referer** — Заголовок перехода. Нужен, когда сайт отдаёт контент только «изнутри»: прямая ссылка без Referer получает заглушку.
- **Accept-Language** — Просьба о языке контента. У мультиязычных сайтов определяет версию страницы, которую получит скрапер.
- **TLS-отпечаток** — Узнаваемый почерк рукопожатия Go-клиента. Colly его не маскирует; продвинутые антиботы классифицируют клиента по нему.
- **httpbin.org/headers** — Зеркало заголовков: сервер возвращает JSON с тем, что реально дошло. Основной инструмент проверки настройки заголовков.

### Что дальше по курсу

Заголовки — половина внешности скрапера; вторая половина — адрес, с которого сервер видит запросы: [урок про прокси](/courses/colly/lessons/15-proxy/) добавит пул адресов и ротацию. А если заголовки настроены, а сайт всё равно неприветлив — это уже не настройка, а защита: [честные пределы colly](/courses/colly/lessons/22-antibot-zaschita/) и что делают вместо неё.

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

- colly user agent задаётся опцией colly.UserAgent в NewCollector; по умолчанию уезжает подпись библиотеки.
- Заголовки на каждый запрос ставятся в OnRequest через r.Headers.Set — стандартный http.Header.
- Referer и Accept-Language — два частых каприза сайтов: переход «изнутри» и языковая версия контента.
- Проверка настроек — зеркало httpbin.org/headers: сервер возвращает то, что реально дошло.
- TLS-отпечаток colly не маскирует: продвинутые антиботы узнают Go-клиента по рукопожатию.
- Браерный User-Agent — настройка совместимости; маскировка ради обхода явных запретов — уже не техническая задача.

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

Прогоните скрапер с httpbin.org/headers дважды: без настройки и с colly.UserAgent плюс Accept-Language и Referer в OnRequest — сравните JSON между прогонами. Затем возьмите любой сайт, который вы скрапите, откройте в браузере devtools и составьте свой набор заголовков по образцу настоящего запроса. Отдельный шаг: поставьте себе честный ботовский UA вида GoMasterBot/1.0 и запишите, для каких источников такой вариант был бы правильнее браузерного.

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

### Достаточно ли сменить User-Agent, чтобы скрапер выглядел как браузер?

Для большинства сайтов — да: браузерный UA плюс разумные Accept-Language и Referer снимают простые фильтры. Но продвинутые антиботы смотрят шире: порядок заголовков, поведение запросов и TLS-отпечаток, который colly не маскирует.

### Почему colly не подменяет TLS-отпечаток?

Colly построена на net/http: рукопожатие выполняет стандартный TLS-стек Go, и опциями библиотеки это не меняется. Если источник классифицирует клиентов по отпечатку, нужны браузерные движки вроде chromedp — об этом отдельный урок курса.

### Можно ли слать браузерный User-Agent всегда?

Можно, и чаще всего это разумная настройка совместимости: многие сайты режут небраузерные UA независимо от правил. Но там, где роботам прямо запрещено, подмена подписи уже не техническая настройка, а обход запрета — такие решения принимаются вне кода.
