Содержание урока · 10 разделов
- chromedp селекторы: четыре пути к одному узлу
- CSS-селекторы: ByQuery и ByQueryAll
- XPath в chromedp: xpath go на живой странице
- QueryAfter: что происходит между селектором и действием
- Стратегии поиска в chromedp
- Стойкий селектор: анатомия
- Чек-лист стойкого селектора
- Песочница урока
- Коротко: что запомнить
- Вопросы и ответы
Программа из прошлого урока отлично открывает страницу — и падает на первом же действии: узел по селектору так и не нашёлся, 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: точные режимы в действии
GOvar 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: кнопка пагинации по тексту
GOvar 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 — функция, которая выполняется после того, как селектор совпал и условия выполнены: в неё попадают контекст и найденные узлы, и там можно сделать всё, чего нет в готовых действиях — обойти список узлов, прочитать несколько атрибутов разом, собрать структуру. Половина «невозможных» задач решается на этом уровне без экзотики. Механику условий с поллингом мы разобрали в прошлый раз, а тонкая настройка — свои интервалы, свои предикаты — ждёт в уроке про умные ожидания; здесь важен сам принцип: селектор без условия — это поиск, селектор с условием — ожидание.
В 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 лучше цепочки из шести уровней. Последний штрих дисциплины — проверка на выборке: хороший селектор совпадает на первой странице каталога, на десятой и на карточке товара, а не только там, где вы его написали. Для строк таблиц с пагинацией эта проверка — обязательная, и в двадцать первом уроке она встраивается в сам сценарий скрапинга.
Не проверяйте селекторы запусками программы: 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: CSS-язык тот же, изменился только исполнитель — вместо разбора ответа поиск ведётся в живом DOM.
Проверка: режим по умолчанию
Какой режим поиска chromedp использует без явных опций?
Проверка: XPath или CSS
В каком случае XPath оправдан перед CSS-селектором?
Что вернёт Text с ByQueryAll
Прочитайте код и выберите, что он выведет:
// <ul>
// <li class="tag">go</li>
// <li class="tag">chromedp</li>
// <li class="tag">cdp</li>
// </ul>
// chromedp.Text(".tag", &s, chromedp.ByQueryAll)Подберите режимы поиска
Восстановите опции: заголовок по CSS, цена по id, узлы карточек коллекцией.
Расставьте чтение карточки товара
Соберите по порядку программу: контекст, переход и два чтения в разных режимах.
Строки
Ваша программа
Нажимайте на строки слева, чтобы собрать программу
Карточки: стратегии поиска
Что дальше: находить элементы мы научились — пора забирать из них данные. В шестом уроке — извлечение текста и атрибутов: Text против TextContent, InnerHTML, значения атрибутов и разница между содержимым и видимым текстом. Пятый урок про клики и ввод — про действия над найденными узлами; селекторы из этого урока станут его первым аргументом. А если пойдёте дальше по курсу, вернётесь сюда: к девятому уроку — за поллингом по своим условиям, к двадцать первому — за таблицами и пагинацией, где стойкость селектора проверяется деньгами.
Песочница урока
Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.
Запустите код, чтобы увидеть вывод.
Коротко: что запомнить
- Селектор в 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.
Термины урока
Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.
Изучайте дальше
Следующая ступень учебного пути
Testcontainers в Go: интеграционные тесты с Docker
Курс testcontainers-go: Postgres, Redis, Kafka и Mongo в тестах через Docker, wait-стратегии, сети, параллельность, CI и итоговый стенд. 25 уроков.
- Поднимать реальные зависимости в Docker из кода теста
- Использовать модули Postgres, Redis, MongoDB, Kafka и RabbitMQ
- Ждать готовность сервиса wait-стратегиями вместо sleep
- Собирать стенды из нескольких контейнеров и своего образа