---
title: "CSS-селекторы в Colly: от тегов до комбинаций"
description: "CSS-селекторы для парсинга в colly: тег, класс, id, потомок и прямая вложенность, атрибуты и nth-of-type — таблица форм и правила устойчивого селектора."
course: "Colly: парсинг и веб-скрапинг на Go"
url: "https://golangmaster.ru/courses/colly/lessons/04-css-selektory/"
published: "2026-09-19"
updated: "2026-09-21"
minutes: 18
---

# CSS-селекторы в Colly: от тегов до комбинаций

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

CSS-селекторы — язык запросов к дереву страницы, и в colly он живёт внутри OnHTML: одна строка решает, какие узлы увидит ваш колбэк, а какие пройдут мимо. Разберём формы от простого тега до комбинаций с атрибутами и псевдоклассами, соберём таблицу «что выбирает — когда использовать» и научимся балансировать между точностью и устойчивостью.

Ситуация, знакомая каждому первому вечеру со скрапером: во вкладке Elements вы глазами нашли нужный элемент, написали селектор по памяти — и получили либо пустоту, либо полстраницы лишнего. Дело не в colly и не в сайте, а в том, что селектор — это точный запрос к дереву, а не описание «где примерно лежит текст». Хорошая новость: язык маленький. Пара десятков форм покрывает все задачи скрапинга, и в этом уроке мы разберём их на двух песочницах — цитатах и книжном каталоге books.toscrape.com. Каждый селектор ниже можно проверить руками: поиском по документу в devtools, а потом в настоящей программе. К концу урока у вас будет внутренняя таблица соответствий: вот это — для повторяющихся карточек, вот это — для единственного блока, вот это — для ссылок, отфильтрованных по адресу. И второе умение, не менее важное: понимать, когда селектор слишком умный и сломается при следующем релизе сайта. Примеры урока, как и везде в курсе, запускаются локально: скраперу нужна сеть, которой у браузерной песочницы нет, — а вот проверять селекторы в devtools можно прямо сейчас.

### Три простые формы: тег, класс, идентификатор

Базовые формы вы уже видели в прошлых уроках. Селектор-тег — span, a, h1 — выбирает все узлы с таким тегом: span.text на странице цитат поймает и тексты, но вместе с ними и служебные span, поэтому тегом пользуются как частью более длинных запросов. Селектор-класс начинается с точки: .price_color собирает все элементы с классом price_color — на books.toscrape.com это цены всех двадцати книг страницы. Селектор-идентификатор начинается с решётки: #content — единственный узел с таким id, главный контейнер страницы. Colly передаёт строку селектора в goquery, тот компилирует её и ищет совпадения в DOM-дереве ответа — вы пишете обычный CSS, никаких особых диалектов. На практике выбор формы простой: класс — рабочая лошадка для повторяющихся данных, идентификатор — редкая роскошь привязки к конкретному блоку, тег в одиночку — почти всегда слишком широкая сеть. Комбинация из двух-трёх форм — article.product_pod, div.quote, li.next a — закрывает большинство селекторных задач каталогов.

### Три правила на книжном каталоге

```go
c := colly.NewCollector()

c.OnHTML(".price_color", func(e *colly.HTMLElement) {
        fmt.Println("цена:", e.Text)
})

c.OnHTML("article.product_pod h3 a", func(e *colly.HTMLElement) {
        fmt.Println("название:", e.Attr("title"))
})

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

Первое правило берёт цены по классу: .price_color стоит ровно у тех узлов, где живёт цена. Второе поднимается от карточки article.product_pod к заголовку и ссылке внутри — а значение берёт из атрибута title, потому что в тексте ссылки название обрезано. Два правила работают независимо и печатают по двадцать значений каждый.

### Потомок и прямая вложенность: пробел против угловой скобки

Комбинатор — то, что стоит между двумя простыми селекторами, — и он же источник половины ошибок. Пробел означает любого потомка на любой глубине: article.product_pod a находит все ссылки внутри карточки, и те, что лежат сразу в ней, и те, что спрятаны в h3 или image_container. Угловая скобка — прямого ребёнка: article.product_pod > a найдёт только ссылки, лежащие непосредственно внутри карточки, без одного уровня вложенности — и на books.toscrape.com их не окажется, потому что ссылки спрятаны внутри h3 и div. Выбор между ними — вопрос глубины, в которой вы уверены. Пробел устойчив к мелким перестройкам разметки: вставьте сайт ещё один обёрточный div, и селектор с пробелом продолжит работать, а со скобкой — отвалится. Зато скобка защищает от ложных совпадений, когда целевой тег встречается и в карточке, и глубже внутри вложенного блока. Практическое правило: пока не нарвались на ложные совпадения, пишите пробел; скобку добавляйте, когда без неё селектор ловит лишнее.

### Атрибутные селекторы: фильтр по значению

Когда классов не хватает, включаются атрибуты. Форма [attr] выбирает узлы, у которых атрибут есть вовсе: [data-id] — все элементы с data-id. Форма [attr=value] требует точное значение: input[type="text"] — только текстовые поля. Для скрапинга самой полезной оказалась частичная проверка: [attr^=value] — значение начинается с префикса. На books.toscrape.com ссылки каталога ведут на адреса вида catalogue/page-2.html и catalogue/mediocre-xxx/index.html, и селектор a[href^="/catalogue/"] ловит только внутренние ссылки каталога, отсекая навигацию и внешние адреса — готовый фильтр для обхода, которому не нужен никакой код. Обратный префикс — [attr$=value]: img[src$=".jpg"] выбирает картинки по расширению. Атрибуты также спасают там, где классы сгенерированы сборкой: href, src, title, data-* обычно стабильнее имён классов, потому что отвечают за поведение, а не за оформление. Единственная цена — чуть более многословная строка, зато селектор читается как условие на естественном языке.

### Псевдоклассы: nth-of-type и first-of-type

Псевдоклассы описывают положение узла среди соседей. li:nth-of-type(2) выбирает второй элемент li среди однотипных соседей внутри родителя — счёт ведётся с единицы, а не с нуля, и это привычная ловушка для тех, кто пришёл из индексов Go. :first-of-type берёт первый однотипный узел, :last-of-type — последний. В скрапинге они нужны нечасто, но когда нужны — спасают полностью: колонка «второй столбец каждой строки таблицы», первый абзац карточки, второй div в блоке без классов. Две оговорки. Первая: of-type считает узлы одного тега, а не одного класса — .price:nth-of-type(2) означает «второй элемент своего тега, если у него есть класс price», и это не то же самое, что «второй price». Вторая: псевдоклассы жестоко завязаны на порядок соседей, а порядок — любимая вещь дизайнеров для перестановок. Если задачу можно решить без нумерации — контейнером, классом, атрибутом — решайте без неё; nth-of-type оставьте для случаев, когда у узла буквально нет других примет.

### Селектор — что выбирает — когда использовать

| Селектор | Что выбирает | Когда использовать |
| --- | --- | --- |
| span | все span на странице | внутри более длинных комбинаций, редко один |
| .price_color | все узлы с этим классом | повторяющиеся данные: карточки, цены, строки |
| #content | единственный узел с этим id | привязка к главному блоку страницы |
| .quote span | любые span внутри .quote на любой глубине | дефолт для вложенности |
| .quote > span | только span — прямые дети .quote | когда пробел ловит лишнее из глубины |
| a[href^="/catalogue/"] | ссылки, ведущие внутрь каталога | фильтрация ссылок для обхода без кода |
| img[src$=".jpg"] | картинки с таким расширением | выкачивание файлов по типу |
| li:nth-of-type(2) | второй li среди соседей своего тега | положение без классов; счёт с единицы |

### Что именно поддерживает colly: базовое подмножество CSS3

Селекторы в OnHTML — это CSS3 в исполнении goquery, библиотеки разбора HTML, на которой colly собирает DOM и компилирует запросы. Говоря точнее: colly умеет базовое подмножество CSS3 — теги, классы, идентификаторы, комбинаторы вложенности, атрибутные условия и распространённые псевдоклассы. Экзотика современных браузеров и редкие псевдоклассы в этой реализации не гарантируются, и перечислять «что сломано» по памяти опасно: поведение зависит от версии библиотеки, а гугл-выдержки легко устаревают. Рабочая тактика практичнее: селектор сначала проверяется на живой странице поиском по документу в devtools, потом — в программе. Совпало в браузере, но не совпало в colly — значит, форма не из поддерживаемого подмножества, и её переписывают через базовые формы: иногда достаточно разбить один хитрый селектор на два правила OnHTML и связать их вложенным обходом, о котором пойдёт речь в следующем уроке. Так вы никогда не застрянете: базовое подмножество закрывает девять задач из десяти.

> **Живое место: селектор-паровоз.** Скопированный из devtools селектор вида .catalog > div.row > div.col-md-3 > div.thumbnail > div.caption > h4 > a работает сегодня и умирает молча завтра: дизайнеры вставят один анонимный div — и на выходе ноль элементов без единой ошибки в логе. Точка баланса между точностью и устойчивостью выглядит так: поднимитесь от цели к ближайшему осмысленному контейнеру и напишите самую короткую пару, которая однозначно ведёт к данным — div.caption h4 a вместо семи звеньев. И всегда обрабатывайте случай «ноль совпадений»: молчаливая пустота дороже любой ошибки компиляции.

### Правила устойчивого селектора

- самая короткая комбинация, которая однозначно определяет данные, — лучше всего контейнер плюс цель
- семантические теги и осмысленные классы вместо позиционных номеров детей
- атрибуты href, src, data-* вместо хэшированных имён классов
- nth-of-type — только когда у узла нет других примет
- селектор проверен на второй и последней странице каталога, а не только на первой
- в коде есть реакция на ноль найденных элементов

### Проверка: пробел или скобка

**Вопрос:** Чем селектор .quote > span отличается от .quote span?

- Ничем, это синонимы
- ✅ Скобка берёт только прямых детей .quote, пробел — любых потомков на любой глубине
- Пробел берёт только прямых детей, скобка — любых потомков
- Скобка выбирает первый совпавший элемент, пробел — все

Пробел — комбинатор потомка: он найдёт span и в самом контейнере, и внутри его детей. Угловая скобка — комбинатор прямого ребёнка: один уровень вложенности. Пробел устойчивее к перестройкам, скобка точнее против ложных совпадений.

### Проверка: атрибутный префикс

**Вопрос:** Что выберет селектор a[href^="/catalogue/"] на books.toscrape.com?

- Все ссылки страницы
- ✅ Только ссылки, чей адрес начинается с /catalogue/
- Только ссылки, чей адрес заканчивается на /catalogue/
- Ничего: атрибутные селекторы colly не поддерживает

Циркумфлекс в квадратных скобках — «начинается с». Селектор отфильтрует внутренние ссылки каталога, оставив за бортом навигацию и внешние адреса; это готовый фильтр для обхода без единой строки кода.

### Сколько узлов соберёт селектор

```go
<div class="quote">
  <span class="text">"Quote text here."</span>
  <span>by <small class="author">Jane Austen</small></span>
  <div class="tags">
    <a class="tag" href="/tag/truth/">truth</a>
  </div>
</div>

// Селектор: .quote span
// Вопрос: сколько вызовов колбэка даст OnHTML(".quote span")?
```

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

- Один — на span.text, единственный с классом
- ✅ Два — span с цитатой и span со словом by, автор не span
- Три — включая small внутри второго span
- Ноль: span лежит внутри div, а селектор начинается с .quote

Комбинатор-пробел ищет span любой глубины внутри .quote: сюда попадают span.text и span-обёртка со словом by. small.author — узел другого тега, и под селектор span он не подпадает, даже хотя лежит внутри второго span. Селектор по классу .quote означает «любой элемент с этим классом», а не «прямой родитель».

### Восстановите селекторы книжного скрапера

Допишите правила: класс цены, путь от карточки к ссылке заголовка и фильтр ссылок пагинации по началу адреса.

```go
c := colly.NewCollector()

c.OnHTML("___", func(e *colly.HTMLElement) {
        fmt.Println("цена:", e.Text)
})

c.OnHTML("article.product_pod h3 ___", func(e *colly.HTMLElement) {
        fmt.Println("название:", e.Attr("title"))
})

c.OnHTML("___[href___="/catalogue/"]", func(e *colly.HTMLElement) {
        fmt.Println("ссылка:", e.Attr("href"))
})
```

Подсказка: Цена — класс с точкой; внутри h3 нас интересует ссылка; фильтр по началу значения атрибута пишется циркумфлексом в квадратных скобках.

Правильные ответы в пропусках: `.price_color`, `a`, `a`, `^`

### Соберите скрапер с двумя правилами

Расставьте строки в порядке исполнения программы: пакеты, коллектор, правила, запуск.

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

1. `package main`
2. `import (`
3. `  "fmt"`
4. `  "github.com/gocolly/colly/v2"`
5. `)`
6. `func main() {`
7. `  c := colly.NewCollector()`
8. `  c.OnHTML(".price_color", func(e *colly.HTMLElement) { fmt.Println(e.Text) })`
9. `  c.OnHTML("h3 a", func(e *colly.HTMLElement) { fmt.Println(e.Attr("title")) })`
10. `  err := c.Visit("http://books.toscrape.com/")`
11. `  if err != nil {`
12. `    fmt.Println(err)`
13. `  }`
14. `}`

### Карточки: язык селекторов

- **.class** — Все узлы с этим классом; классы носят сколько угодно элементов — основа выборки повторяющихся данных.
- **#id** — Единственный узел с этим идентификатором; привязка к конкретному блоку страницы.
- **A B (пробел)** — Комбинатор потомка: B любой глубины внутри A. Устойчив к появлению новых обёрток.
- **A > B** — Комбинатор прямого ребёнка: B на один уровень ниже A. Точнее, но чувствительнее к перестройкам разметки.
- **[attr^=value]** — Узлы, чей атрибут начинается с value; фильтр ссылок по префиксу адреса без кода.
- **:nth-of-type(n)** — N-й узел среди однотипных соседей; счёт с единицы. Крайнее средство, когда нет классов и атрибутов.

### Что дальше

Селекторы отвечают на вопрос «что искать», но не рассказывают, как colly их выполняет. Следующий урок — [механика OnHTML](/courses/colly/lessons/05-onhtml-kolbeki/): сколько раз вызовется колбэк, в каком порядке срабатывают правила и как связывать вложенные выборки через ForEach. Потом — [способы достать значение](/courses/colly/lessons/06-izvlechenie-dannyh/): e.Text, e.Attr и семейство Child-методов, которые добирают данные из глубины выбранного узла. А разобранная здесь разметка цитат пригодится в каждом из них.

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

- Селектор — точный запрос к DOM-дереву; colly передаёт строку в goquery, который компилирует её и находит узлы.
- Три простые формы: тег для длинных комбинаций, .class для повторяющихся данных, #id для единственного блока.
- Пробел — любой потомок, угловая скобка — прямой ребёнок; по умолчанию пишут пробел, скобку добавляют против ложных совпадений.
- Атрибутные формы [attr], [attr=value], [attr^=value], [attr$=value] фильтруют ссылки и картинки по адресам без кода.
- Псевдоклассы nth-of-type и first-of-type описывают положение среди соседей; счёт с единицы и крайнее средство по устойчивости.
- colly поддерживает базовое подмножество CSS3 через goquery; экзотику проверяют на живой странице и переписывают базовыми формами.
- Длинные селекторы-паровозы умирают молча при чужом релизе: самая короткая однозначная комбинация плюс реакция на ноль совпадений.

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

На books.toscrape.com соберите три правила OnHTML: цены всех книг страницы, полные названия из атрибута title ссылок в h3 и ссылки пагинации из li.next. Потом сломайте свой селектор специально: замените .price_color на div.row и посмотрите, что окажется в выводе. После этого возьмите Copy selector на карточке товара и сократите его минимум вдвое без потери набора совпадений — проверяйте поиском по документу.

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

### Какие CSS-селекторы поддерживает colly?

Базовое подмножество CSS3 через goquery: теги, классы, идентификаторы, комбинаторы пробела и скобки, атрибутные условия и распространённые псевдоклассы вроде nth-of-type. Экзотические формы проверяют на живой странице; если не сработало — переписывают базовыми формами.

### Почему селектор из devtools в colly не находит элементы?

Copy selector выдаёт абсолютный путь с номерами детей (nth-child), который считает положение блока на конкретной странице. Он ломается при любом изменении разметки. Сократите путь до повторяющейся части — контейнер плюс цель.

### Скобка или пробел — что лучше?

По умолчанию пробел: он выживает при появлении новых обёрточных div. Угловую скобку берут, когда пробел ловит лишнее из глубины карточки — например, одноимённые элементы в подвале блока. Выбор всегда проверяют на второй странице каталога.
