---
title: "Селекторы: как находить элементы"
description: "chromedp селекторы на Go: ByID, ByQuery, ByQueryAll и xpath go через DOM.performSearch, условия NodeVisible и QueryAfter — как искать элементы надёжно."
course: "Chromedp в Go: автоматизация Chrome без Selenium"
url: "https://golangmaster.ru/courses/chromedp/lessons/04-selektory/"
published: "2026-09-19"
updated: "2026-09-21"
minutes: 20
---

# Селекторы: как находить элементы

*Курс «Chromedp в Go: автоматизация Chrome без Selenium», урок 4 из 25. ~20 минут. Обновлено 21.09.2026. Источник: https://golangmaster.ru/courses/chromedp/lessons/04-selektory/*

Селектор — половина успеха браузерного парсера: chromedp селекторы определяют, найдёт ли программа кнопку, цену или строку таблицы, и не упадёт ли на следующем редизайне. Разберём четыре стратегии поиска, работу с xpath go в chromedp, механику QueryAfter и практику стойких путей к элементам — на примерах, которые проверяются в консоли браузера за минуту.

Программа из прошлого урока отлично открывает страницу — и падает на первом же действии: узел по селектору так и не нашёлся, Run отработал дедлайн. В браузере элемент на глазах есть, в коде страницы тоже, а chromedp его не видит. Причин у этого три, и все три — про поиск. Либо селектор описывает не тот узел: путь скопирован из инспектора и привязан к порядку соседей, который изменился. Либо элемент существует, но невидим — скрыт CSS или лежит в закрытой вкладке, а действие требует видимости. Либо контент ещё не доехал: селектор правильный, но к моменту проверки узла в дереве нет. Поиск элементов — навык, который в браузерной автоматизации ценится выше запоминания функций: тот же самый Run с другим селектором работает и надёжен, а с плохим — рассыпается на каждом релизе целевого сайта. В этом уроке — система: какие режимы поиска есть у chromedp, чем они отличаются под капотом, как в него встраивается XPath и что делает селектор стойким к редизайнам.

### chromedp селекторы: четыре пути к одному узлу

У chromedp селекторы устроены интереснее, чем кажется: строка и режим поиска — это две разные вещи. Строка — то, что вы хотите найти: «h1.title», «#price», целое XPath-выражение. Режим — то, как браузер будет искать: он выбирается опциями BySearch, ByQuery, ByQueryAll, ByID и задаётся последним аргументом действия. По умолчанию действует BySearch — браузерная команда поиска, которая понимает три вида запросов сразу: CSS-селекторы, XPath-выражения и даже обычный текст. Поэтому WaitVisible("h1") без опций работает и как CSS, и, если начать строку с двойного слэша, как XPath. Для точного контроля есть точные режимы: ByQuery ищет через querySelector — один первый узел по CSS, ByQueryAll — через querySelectorAll — все узлы сразу, ByID — быстрый поиск по атрибуту id. Зачем точные режимы, если есть универсальный? Скорость и предсказуемость: querySelector и querySelectorAll — прямые браузерные команды с понятной семантикой, а универсальный поиск устроен иначе и для простых CSS-случаев избыточен. Правило выбора простое: есть id — берите ByID, обычная CSS-логика — ByQuery или ByQueryAll, текст и оси — XPath через дефолт.

### ByQuery и ByID: точные режимы в действии

```go
var title, price string
err := chromedp.Run(ctx,
        chromedp.WaitVisible("h1.title", chromedp.ByQuery),
        chromedp.Text("h1.title", &title, chromedp.ByQuery),
        chromedp.WaitVisible("#price", chromedp.ByID),
        chromedp.Text("#price", &price, chromedp.ByID),
)
```

ByQuery прогоняет строку через document.querySelector — первый узел по CSS-селектору; ByID сначала дописывает решётку, если её нет, и ищет тем же querySelector по атрибуту id. Опция указывается последним аргументом каждого действия, поэтому в одном Run можно смешивать режимы под разные элементы: заголовок по CSS, цена по id — у каждого своя механика поиска.

### CSS-селекторы: ByQuery и ByQueryAll

CSS — рабочий язык поиска в большинстве задач, и хромедповские режимы отображаются на него один в один. ByQuery — это document.querySelector: вернуть первый узел, подходящий под селектор. ByQueryAll — document.querySelectorAll: вернуть коллекцию всех подходящих. Разница проявляется в поведении действий: Text с ByQueryAll прочитает текст всё равно первого узла — действие применяется к первому элементу результата, но условие готовности ослабляется: достаточно одного совпадения. Настоящее применение ByQueryAll — действия над списками: Nodes соберёт массив узлов для обхода, а опция AtLeast задаст минимальное число совпадений, ниже которого запрос считается не выполнившимся — полезно для строк таблицы, где «пусто» и «не доехало» надо различать. По умолчанию достаточно одного совпадения: пустой каталог и «строки ещё не доехали» для ByQueryAll выглядят одинаково, и именно AtLeast разделяет эти состояния — с AtLeast(10) запрос добросовестно будет ждать десятую строку. Сам язык CSS пересказывать здесь не будем: классы, атрибуты, комбинаторы и псевдоклассы — то же, что вы знаете по goquery из Colly, и если эта база проседает, лучший тренажёр — консоль браузера: document.querySelectorAll(".card") в DevTools отвечает на вопрос «что найдёт этот селектор» мгновенно и без запуска программы.

### XPath в chromedp: xpath go на живой странице

XPath — второй язык поиска, встроенный в chromedp через универсальный режим: строка, начинающаяся с двойного слэша, обрабатывается как XPath-выражение. Возможности, за которые xpath go ценят в парсерах, — это поиск по тексту и движение по дереву в обе стороны. CSS умеет искать вниз: от родителя к потомкам и соседям. XPath умеет и вверх: найти кнопку можно от текста внутри неё, а затем подняться к карточке parent::, в которой лежит цена. Самый частый паттерн практики — элементы без устойчивых классов, зато с надписью: //a[contains(text(), "Следующая")] находит кнопку пагинации по тексту, какой бы класс ей ни дали дизайнеры. chromedp селекторы с XPath пишутся той же строкой в тех же действиях — отдельного API нет, и это удобно: одно и то же действие Text принимает и CSS, и XPath. Платить приходится читаемостью: длинные оси и предикаты в коде труднее воспринимаются, чем CSS-селекторы, поэтому рабочая пропорция — CSS для структуры, XPath для текста и редких подъёмов к родителю. Проверять выражения проще всего там же, в консоли: document.evaluate отвечает за XPath в DevTools.

### XPath: кнопка пагинации по тексту

```go
var next string
err := chromedp.Run(ctx,
        chromedp.WaitVisible("//a[contains(text(), 'Следующая')]"),
        chromedp.Text("//a[contains(text(), 'Следующая')]", &next),
)
```

Оба действия без опций работают в универсальном режиме: строка с двойным слэшем — это XPath-выражение, которое находит ссылку по тексту, а не по классу. Предикат contains(text(), ...) проверяет подстроку, поэтому селектор переживает вложенные span-ы и мелкие правки вёрстки кнопки; перед запуском программы выражение проверяется в консоли через document.evaluate — там вернётся тот же узел.

### QueryAfter: что происходит между селектором и действием

Каждое действие с селектором в chromedp — это конвейер, и полезно видеть его звенья. Сначала запрос ищет узлы выбранным режимом: поиска нет — документ переспрашивается с интервалом в несколько миллисекунд. Затем проверяются условия готовности: по умолчанию достаточно, чтобы узлы нашлись, опции NodeVisible, NodeEnabled и другие добавляют свои требования — они и превращают поиск в ожидание. Наконец, действие исполняется на найденных узлах: текст читается из первого, клик идёт по первому, значение ставится в первое. Хуком в этот конвейер служит QueryAfter — функция, которая выполняется после того, как селектор совпал и условия выполнены: в неё попадают контекст и найденные узлы, и там можно сделать всё, чего нет в готовых действиях — обойти список узлов, прочитать несколько атрибутов разом, собрать структуру. Половина «невозможных» задач решается на этом уровне без экзотики. Механику условий с поллингом мы разобрали в прошлый раз, а тонкая настройка — свои интервалы, свои предикаты — ждёт в [уроке про умные ожидания](/courses/chromedp/lessons/09-ozhidaniya-elementov/); здесь важен сам принцип: селектор без условия — это поиск, селектор с условием — ожидание.

> **Живое место: селектор из инспектора — худший из вариантов.** В DevTools есть команда Copy selector, и её выдача выглядит авторитетно — а на деле это машинно-сгенерированный путь вида div.items > div:nth-child(3) > span:nth-child(2), привязанный к позиции узла среди соседей. Сломается он при первом же изменении порядка элементов: сайт добавит рекламный блок — и все позиции сдвинутся. Спорное мнение с условием: если вы контролируете сайт, требуйте data-атрибуты для ключевых элементов; если не контролируете — стройте путь от стабильного якоря, id или текста, и проверяйте его на нескольких страницах каталога, а не на первой попавшейся.

### Стратегии поиска в chromedp

| Режим | Механика браузера | Когда брать |
| --- | --- | --- |
| BySearch (по умолчанию) | DOM.performSearch: CSS, XPath или текст | не думаем о режиме: строка сама скажет, что она такое |
| ByQuery | document.querySelector — первый узел по CSS | обычные задачи по CSS: один заголовок, одна кнопка |
| ByQueryAll | document.querySelectorAll — все узлы | списки и таблицы: Nodes, AtLeast, обход коллекции |
| ByID | поиск по атрибуту id | элементы с id: самая короткая и быстрая дорога |
| ByJSPath | JS-путь к элементу через Runtime.evaluate | узлы вне обычного DOM, например внутри ShadowDOM |

### Стойкий селектор: анатомия

Стойкость селектора — это его связь со стабильными вещами на сайте, а не с текущей модой вёрстки. Ранжирование надёжности в практике простое. Первый ранг — атрибуты, которые существуют для программ: id и data-атрибуты, особенно data-testid, которые тестировщики оставляют именно для автоматизации; ищутся они и обычным CSS — селектор вида [data-testid="add-to-cart"] читается как смысл и переживает любые редизайны вёрстки. Второй ранг — семантические классы и структурные теги с осмысленными именами: класс price или тег table — вещь, которую дизайнеры переименовывают неохотно. Третий ранг — комбинированные пути: стабильный родитель плюс простое правило внутри, вроде div.card span.price. За пределами рангов остаются позиции — nth-child и индексы в XPath-предикатах: они описывают не смысл, а случайный порядок. Длина пути — отдельный сигнал: чем короче путь, тем меньше у него точек отказа, поэтому селектор вида #price лучше цепочки из шести уровней. Последний штрих дисциплины — проверка на выборке: хороший селектор совпадает на первой странице каталога, на десятой и на карточке товара, а не только там, где вы его написали. Для строк таблиц с пагинацией эта проверка — обязательная, и в двадцать первом уроке она встраивается в сам сценарий скрапинга.

> **Консоль браузера — ваш REPL для селекторов.** Не проверяйте селекторы запусками программы: document.querySelector и document.querySelectorAll в консоли DevTools дают ответ мгновенно, а document.evaluate работает для XPath. Один нюанс, о котором забывают: инспектор показывает DOM после скриптов, и селектор, найденный в Elements, может не иметь смысла в исходном ответе сервера — для chromedp это не проблема, он видит ту же живую страницу, но привычка различать «исходник» и «DOM» экономит время на диагностике.

### Чек-лист стойкого селектора

- есть id или data-атрибут — путь начинается с них, а не с позиции среди соседей
- в пути нет nth-child и индексных предикатов, привязанных к случайному порядку
- селектор читается как смысл («цена в карточке»), а не как маршрут по слоям вёрстки
- длина минимальна: каждый дополнительный уровень — дополнительная точка отказа
- проверен в консоли браузера на нескольких страницах, включая пустые и граничные случаи
- режим поиска выбран осознанно: ByID для id, ByQuery для CSS, XPath для текста и осей

Оговорка про песочницу, как и обещали в начале курса: разбор селекторов здесь текстовый и интерактивный — запустить настоящую страницу из браузера песочница не может, для этого нужен локальный Go и установленный Chrome, а вся практика по поиску узлов выполняется в вашем DevTools и терминале. Зато навык переносится один в один: строка селектора, найденная в консоли, работает и в chromedp без изменений. Между делом обратите внимание, как много из этого урока вы уже умеете, если проходили [Colly](/courses/colly/): CSS-язык тот же, изменился только исполнитель — вместо разбора ответа поиск ведётся в живом DOM.

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

**Вопрос:** Какой режим поиска chromedp использует без явных опций?

- ByQuery — document.querySelector
- ByQueryAll — document.querySelectorAll
- ✅ BySearch — универсальный поиск, понимающий CSS, XPath и текст
- ByJSPath — JS-путь к элементу

По умолчанию работает BySearch — браузерная команда поиска, которая распознаёт CSS-селекторы, XPath-выражения и обычный текст. Точные режимы ByQuery, ByQueryAll и ByID включают явно, когда нужна предсказуемая механика querySelector.

### Проверка: XPath или CSS

**Вопрос:** В каком случае XPath оправдан перед CSS-селектором?

- Всегда — XPath мощнее, CSS устарел
- ✅ Когда нужно найти элемент по тексту или подняться вверх по дереву
- Только для элементов с id
- Только на SPA-страницах

Сильные стороны XPath — предикаты по тексту вроде //a[contains(text(), «Далее»)] и оси, позволяющие подняться к родителю. CSS ищет вниз по дереву и по атрибутам; для структуры он короче и читабельнее.

### Что вернёт Text с ByQueryAll

```go
// <ul>
//   <li class="tag">go</li>
//   <li class="tag">chromedp</li>
//   <li class="tag">cdp</li>
// </ul>
// chromedp.Text(".tag", &s, chromedp.ByQueryAll)
```

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

- ✅ go — текст первого узла коллекции
- go chromedp cdp — тексты всех узлов через пробел
- chromedp — текст последнего узла
- Ошибка: ByQueryAll нельзя совмещать с Text

ByQueryAll собирает все узлы, но действие применяется к первому: Text читает innerText первого элемента результата. Все узлы коллекции нужны для обхода — через Nodes и QueryAfter, а не для чтения одного текста.

### Подберите режимы поиска

Восстановите опции: заголовок по CSS, цена по id, узлы карточек коллекцией.

```go
var title, price string
var cards []*cdp.Node
err := chromedp.Run(ctx,
        chromedp.Text("h1", &title, chromedp.___),
        chromedp.Text("#price", &price, chromedp.___),
        chromedp.Nodes(".card", &cards, chromedp.___),
)
```

Подсказка: Один узел по CSS — ByQuery; атрибут id — ByID; вся коллекция узлов — ByQueryAll.

Правильные ответы в пропусках: `ByQuery`, `ByID`, `ByQueryAll`

### Расставьте чтение карточки товара

Соберите по порядку программу: контекст, переход и два чтения в разных режимах.

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

1. `var title, price string`
2. `ctx, cancel := chromedp.NewContext(context.Background())`
3. `defer cancel()`
4. `err := chromedp.Run(ctx,`
5. `  chromedp.Navigate("https://example.com/catalog/42"),`
6. `  chromedp.WaitVisible("h1", chromedp.ByQuery),`
7. `  chromedp.Text("h1", &title, chromedp.ByQuery),`
8. `  chromedp.Text("#price", &price, chromedp.ByID),`
9. `)`

### Карточки: стратегии поиска

- **BySearch** — Режим по умолчанию: универсальная браузерная команда поиска, понимающая CSS-селекторы, XPath и обычный текст.
- **ByQuery** — document.querySelector: первый узел по CSS-селектору. Рабочая лошадка для одиночных элементов.
- **ByQueryAll** — document.querySelectorAll: все узлы под селектор. Для списков и таблиц — вместе с Nodes и опцией AtLeast.
- **ByID** — Поиск по атрибуту id — самый короткий и быстрый путь. Работает и с ведущим решёткой, и без неё.
- **XPath в chromedp** — Строка с двойным слэшем в универсальном режиме: поиск по тексту и оси вверх к родителю. Отдельного API не нужно.
- **QueryAfter** — Хук после совпадения селектора и выполнения условий: в функцию приходят контекст и узлы — место для всего, чего нет в готовых действиях.

Что дальше: находить элементы мы научились — пора забирать из них данные. В шестом уроке — [извлечение текста и атрибутов](/courses/chromedp/lessons/06-izvlechenie-teksta/): Text против TextContent, InnerHTML, значения атрибутов и разница между содержимым и видимым текстом. Пятый урок про [клики и ввод](/courses/chromedp/lessons/05-klik-vvod/) — про действия над найденными узлами; селекторы из этого урока станут его первым аргументом. А если пойдёте дальше по курсу, вернётесь сюда: к девятому уроку — за поллингом по своим условиям, к двадцать первому — за таблицами и пагинацией, где стойкость селектора проверяется деньгами.

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

- Селектор в chromedp — это строка плюс режим поиска: строка говорит «что», режим — «как искать».
- По умолчанию работает BySearch: универсальный поиск понимает CSS, XPath и текст — потому WaitVisible("h1") работает без опций.
- ByQuery — querySelector (первый узел), ByQueryAll — querySelectorAll (коллекция), ByID — самый короткий путь по id.
- XPath входит через универсальный режим: поиск по тексту //a[contains(text(), «Далее»)] и оси вверх к родителю.
- Действие применяется к первому узлу результата; коллекции обходят через Nodes, а AtLeast задаёт минимум совпадений.
- QueryAfter — хук после совпадения селектора и условий: поллинг, условия NodeVisible, свои операции над узлами.
- Стойкий селектор — от id и data-атрибутов, короткий, без позиций; проверяется в консоли браузера на нескольких страницах.

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

Откройте любой каталог с карточками товаров и соберите три селектора в консоли DevTools: цену, название и кнопку пагинации. Один из них сделайте через CSS, один — через id или data-атрибут, один — через XPath по тексту. Затем попробуйте сломать их: найдите вторую страницу каталога и проверьте, какие селекторы выжили. Выпишите победителей — они понадобятся в домашнем задании пятого урока, где по карточке нужно пройти кликом и вводом.

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

### Почему селектор из Copy selector в DevTools не работает надёжно?

Инспектор генерирует путь по позициям: nth-child(3), nth-child(2) — он описывает случайный порядок узлов, а не смысл. Любое изменение вёрстки сдвигает позиции, и селектор ломается. Стройте пути от id, data-атрибутов и семантических классов, а позиции оставьте как крайнюю меру.

### Работает ли XPath в chromedp без специальных опций?

Да: универсальный режим поиска по умолчанию понимает XPath-выражения — строка с двойным слэшем обрабатывается как XPath. Специальной опции для этого не нужно; точные режимы ByQuery, ByQueryAll и ByID нужны для предсказуемой CSS-механики querySelector.

### Чем отличается Text с ByQuery от Text с ByQueryAll?

Оба прочитают текст первого найденного узла, но с ByQueryAll условие готовности мягче: достаточно одного совпадения из возможных многих. Для чтения одного текста практичнее ByQuery; ByQueryAll имеет смысл для обхода списков через Nodes или QueryAfter. Практический ориентир: один конкретный элемент — ByQuery, коллекция для обхода — ByQueryAll с явным AtLeast.
