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

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

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

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

Извлечение текста и атрибутов

Селекторы настроены, карточки найдены — осталось забрать данные, и здесь выясняется, что chromedp text читает узел не так, как TextContent, а атрибут из разметки расходится со свойством живого элемента. Разберём извлечение данных go-программой из отрисованного DOM: шесть читающих действий, ловушку пустой строки и выбор между атрибутом и свойством.

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

Страница, открытая chromedp, — уже не HTML-ответ, а живое дерево: браузер собрал его, отрисовал и продолжает обновлять по мере работы скриптов. Colly на этом шаге закончила бы работу, а chromedp только начинает: всё, что видно посетителю, доступно программе. Извлечение данных go-программой устроено одинаково для всех действий этого урока: действие дожидается узлов по селектору, берёт первый совпавший, исполняет на нём браузерную функцию и расфасовывает результат в переменную, адрес которой вы передали указателем. Указатель здесь не украшение, а канал доставки: в него записывается значение, поэтому в Run передают &name, &html, &ok. Как и в четвёртом уроке про селекторы, Query-действия сами переспрашивают документ с коротким интервалом, и отдельное «подождать» перед чтением не нужно. Отсюда и стиль кода: чтение — это список действий внутри одного Run, где каждая строка кладёт свой кусок данных в свою переменную, а ошибка любой строки останавливает весь прогон. Плата за удобство — типизация: каждая переменная объявляется до Run, и тип её должен совпадать с тем, что вернёт действие: строка для текста, bool для ok, карта для атрибутов.

chromedp text: видимый текст узла#

chromedp.Text возвращает innerText — то, что посетитель видит на экране, со всеми переносами и регистром, как это отрисовал браузер. Внутри действия лежит маленький JavaScript-сниппет, и у него есть условие: у узла должны быть ненулевые offsetWidth, offsetHeight или непустые клиентские прямоугольники. Если элемент скрыт, сниппет возвращает пустую строку — и действие завершается успешно, без ошибки. Это решение библиотеки ломает ожидания у половины новичков: ошибка сигнализирует о неточном селекторе или истёкшем дедлайне, а про скрытый элемент текст молчит. Вторая особенность innerText — зависимость от рендера: содержимое display:none-детей в результат не попадёт, текст из script и style тоже, а строки перенесутся примерно так, как они выглядят на экране. Для витрин и карточек это ровно то, что нужно: заголовок, цена, статус — всё это текст, который пользователь реально видит. Для скрытых заглушек и аккордеонов нужен TextContent, о котором ниже.

TextContent, InnerHTML и OuterHTML: сырой уровень#

Три действия снимают зависимость от рендера и отдают сырьё. TextContent читает textContent поддерева: весь текст всех потомков подряд, независимо от видимости — включая содержимое скрытых блоков и тегов script и style. Плата за полноту — отсутствие форматирования: колонки таблицы слипнутся в одну строку, и разбирать их придётся вручную, зато ничего не потеряется. InnerHTML возвращает разметку поддерева без самого узла, OuterHTML — узел целиком вместе с открывающим и закрывающим тегом. Практика простая: текст на экране — Text; данные из скрытых областей — TextContent; вложенная структура вроде таблицы позиций, которую хочется разобрать дома, — InnerHTML, а дальше goquery или стандартная библиотека encoding/html, те же приёмы, что и в HTTP-скрапинге. Разбор разметки у себя дешевле, чем десять чтений по ячейкам: один обмен с браузером — и таблица у вас в строке, дальше она режется без участия Chrome. OuterHTML в продакшен-извлечении редкость, зато незаменим при отладке: сохранили узел в файл, открыли глазами, увидели, что селектор ловит не тот узел, который вы имели в виду. Так находят и вторую половину багов чтения: не «узел не нашёлся», а «узел нашёлся не тот» — класс, который встречается четыре раза, и первый из них не карточка.

Карточка товара: текст, скрытая спецификация, ссылка

GO
var name, spec, href string
var ok bool

err := chromedp.Run(ctx,
        chromedp.WaitVisible(".product-card", chromedp.ByQuery),
        chromedp.Text(".product-card h1", &name),
        chromedp.TextContent(".product-card .specs", &spec),
        chromedp.AttributeValue(".product-card a", "href", &href, &ok),
)

Каждая строка кладёт своё значение в свою переменную: Text берёт видимый заголовок, TextContent вытаскивает текст блока спецификаций, AttributeValue читает href первой ссылки и ставит ok в true, потому что атрибут на месте. WaitVisible на контейнере дисциплинирует прогон: чтение начинается только после того, как карточка отрисована целиком.

AttributeValue и Attributes: атрибуты из разметки#

AttributeValue читает один атрибут из карты атрибутов узла — той, что собрана из HTML-разметки. Сигнатура с двумя указателями выглядит непривычно: chromedp.AttributeValue(sel, "href", &href, &ok), но именно ok несёт смысл: атрибут есть — true, атрибута нет — false, и ошибки в обоих случаях не будет. Отсутствие атрибута — нормальный ответ, а не авария: у большинства ссылок не бывает title, у картинок без описания нет alt. Проверяйте ok, а не стройте догадки. Когда нужны сразу все атрибуты узла — витрины часто держат в data-свойствах идентификатор товара, цену в копейках и флаги наличия — есть Attributes, заполняющая map[string]string одним обменом с браузером. Для коллекций то же самое делает AttributesAll с опцией ByQueryAll: на выходе срез карт, по одной на узел, порядок совпадает с порядком совпадений селектора. Это первый инструмент экономии, когда карточек много: одна команда вместо дюжины. Один нюанс расфасовки: карта в Go не сохраняет порядок атрибутов, поэтому для отчётов, где колонки важны, значения вынимают по ключам явно, а не перебирают карту.

Атрибут и свойство: Value против AttributeValue#

Тонкое место, на котором спотыкаются даже опытные скраперы: у полей ввода value существует в двух лицах. Атрибут value — это снимок из разметки, начальное значение, которое браузер подставил при отрисовке. Свойство value — текущее содержимое поля, живущее в JavaScript-объекте элемента и меняющееся с каждым вводом. chromedp.Value читает именно свойство: универсальный механизм JavascriptAttribute исполняет на узле функцию this[n] и возвращает то, что лежит в свойстве n. AttributeValue с именем value читает атрибут, и после того, как программа или пользователь что-то напечатали, эти два значения расходятся. То же касается checked у чекбоксов и disabled у кнопок и почти любого состояния, которое страница меняет на лету: атрибут в разметке мог остаться прежним, а свойство уже новое. Типовой сценарий — фильтр каталога: страница загрузилась с дефолтным регионом в поле, скрипт региональной переустановки тут же подменил его на ваш город, и атрибут остался со старым значением. Правило выбора короткое: актуальное состояние — свойство, декларация из HTML — атрибут. Стоимость проверки — один обмен с браузером, дешевле обойтись без неё не получится.

Расхождение в цифрах

GO
// в разметке: <input id="coupon" value="ГОД2023">

var fromHTML, current string
var ok bool

err := chromedp.Run(ctx,
        chromedp.AttributeValue("#coupon", "value", &fromHTML, &ok),
        chromedp.SendKeys("#coupon", "-7", chromedp.ByID),
        chromedp.Value("#coupon", &current),
)

AttributeValue вернёт ГОД2023 и ok в true — так написано в HTML. SendKeys допечатает дефис и семёрку к текущему содержимому поля, и Value честно вернёт ГОД2023-7. Разница между двумя чтениями и есть разница между снимком разметки и живым свойством; во всех сценариях с формами и фильтрами актуальное значение берут через Value.

Шесть читающих действий урока#

ДействиеЧто возвращаетКогда брать
chromedp.TextinnerText видимого узла; пустая строка, если элемент скрыттекст, как его видит посетитель: заголовки, цены, статусы
chromedp.TextContenttextContent поддерева целиком, видимость не важнаскрытые блоки, аккордеоны, содержимое script и style
chromedp.InnerHTMLразметку поддерева без внешнего тегавложенные структуры, которые хочется разобрать у себя
chromedp.OuterHTMLузел целиком с тегамисохранение фрагмента для отладки и отчёта
chromedp.AttributeValueодин атрибут из разметки плюс флаг okhref, alt, data-свойства; отсутствие атрибута — не ошибка
chromedp.Valueтекущее JS-свойство valueактуальное содержимое полей после ввода

Где данные прячутся чаще всего#

  • data-атрибуты витрин: идентификатор товара, цена в копейках, флаги наличия — читаются AttributeValue и Attributes
  • href ссылок: относительные пути дорасширяются до полного адреса уже в Go
  • value полей фильтров после ввода — только Value, атрибут врёт
  • innerHTML таблицы позиций: одна команда вместо десятка селекторов по ячейкам
  • alt и title картинок: пояснения, которые авторы оставляют в атрибутах
  • текст бейджей состояния — «Товар заканчивается», «Наличие уточняйте» — только Text по видимому узлу

Чтение в цикле: цена переспросов#

Каждое читающее действие — это обмен с браузером: селектор уехал, узел найден, функция исполнена, результат вернулся. На десятке полей это незаметно, но цикл по двумстам карточкам, где на каждую читается пять значений, превращается в тысячу обменов, и время обхода растёт линейно. Первый уровень экономии — группировать: AttributesAll вынимает атрибуты всех карточек одним действием, InnerHTML карточки снимает сразу всё поддерево, и разбор идёт уже без браузера, чистым Go. Второй уровень — JavaScript: одно выражение chromedp.Evaluate соберёт массив значений со всей страницы за один обмен — об этом десятый урок. Третий — работа с таблицами целиком: когда страница и есть большая таблица, приёмы из урока про скрапинг таблиц сокращают число действий на порядок. Экономить стоит тогда, когда обход реально медленный: переписывать чтение двух полей ради двух миллисекунд — работа в минус. И не путайте группировку с халтурой: InnerHTML вместо пяти Text — это не «сложнее», это другая точка разбора, переносимая в тесты, где браузера нет вовсе.

Отдельная дисциплина — отличать пустые данные от пустой выдачи. Если селектор не нашёл ни одного узла, Run вернёт ошибку: действие честно признаёт, что искать нечего. Если узел нашёлся, но данных в нём нет, ошибки не будет — будет пустая строка или false от ok. Это разные состояния каталога: первая ситуация означает, что страница не та, сломался селектор или сайт вёл себя иначе; вторая — что товар без цены или спецификации, и это данные тоже, только пугающие. В логе они должны выглядеть по-разному: первая — ошибка прогона с адресом страницы, вторая — строка в отчёте с пометкой. Соблазн свалить всё в одну кучу велик, пока не наступает день, когда сайт поменял вёрстку, и нужно за минуту понять, сломался парсер или кончился товар.

Проверка: Text на скрытом узле

Блок цены свёрстан с display:none. Что вернёт chromedp.Text по этому блоку?

Проверка: отсутствующий атрибут

У ссылки нет атрибута title. Что вернёт chromedp.AttributeValue(sel, "title", &v, &ok)?

Что окажется в переменных

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

// <input id="q" value="тапочки">
// err := chromedp.Run(ctx,
//   chromedp.AttributeValue("#q", "value", &attr, &ok),
//   chromedp.SetValue("#q", "носок", chromedp.ByID),
//   chromedp.Value("#q", &current),
// )

Соберите чтение карточки

Восстановите действия: видимый текст, текст скрытого блока, атрибут ссылки и текущее значение поля.

err := chromedp.Run(ctx, chromedp.(".card h3", &name), chromedp.(".card .hidden", &spec), chromedp.(".card a", "href", &href, &ok), chromedp.("#qty", &qty), )

Расставьте извлечение по порядку

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

Строки

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

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

Карточки: чтение данных

Термин

chromedp.Text

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

Читает innerText первого узла. Требует видимости: у скрытого элемента вернёт пустую строку без ошибки.

1 / 6

Данные прочитаны — пора снимать артефакты рендера. Седьмой урок про скриншоты страниц и элементов покажет три уровня захвата: окно браузера, вся страница и отдельный узел. Девятый урок про умные ожидания закроет случаи, когда данных ещё нет и ждать нужно условие, а не узел. Оговорка про практику: всё это проверялось локально — понадобится локальный Go и установленный Chrome; интерактивы сайта и тематическая программа в песочнице механику подскажут, но живой DOM они не заменят.

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

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

Вывод

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

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

  • Чтение в chromedp — Query-действия: сами дожидается узлов, берут первый совпавший и расфасовывают результат в указатели.
  • Text возвращает innerText видимого узла; скрытый элемент даёт пустую строку без ошибки.
  • TextContent читает всё поддерево независимо от видимости; InnerHTML и OuterHTML отдают разметку для разбора в Go.
  • AttributeValue читает атрибут из разметки и ставит ok; отсутствие атрибута — штатный ответ, а не ошибка.
  • Value читает текущее свойство value: после ввода оно расходится с атрибутом из HTML.
  • Attributes и AttributesAll снимают все атрибуты узлов и режут число обменов в цикле.
  • Агрегаты дешевле собирать одним выражением Evaluate — тема десятого урока.
  • Нет узлов — ошибка Run; узел есть, данных нет — пустая строка: эти состояния каталога различают в логах.
  • Проверка длины и ok превращает тихую пустоту в ошибку, которую видно в логе.

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

Возьмите страницу с карточкой товара и соберите полное извлечение: название и цену через Text, скрытую спецификацию через TextContent, href и два data-атрибута через AttributeValue с проверкой ok, содержимое поля фильтра через Value после SetValue. Затем спрячьте цену стилем display:none и убедитесь, что Text вернёт пустую строку без ошибки, — добавьте проверку длины и отдельную ветку лога для этого случая.

Личные заметкиПусто
Хранятся только в вашем браузере (Chromedp · Извлечение текста и атрибутов)

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

Почему Text вернул пустую строку, хотя элемент на странице есть?

Элемент скрыт: сниппет Text проверяет offsetWidth, offsetHeight и клиентские прямоугольники и у невидимых узлов возвращает пустую строку, завершаясь успешно. Дождитесь видимости WaitVisible или читайте TextContent, если скрытость нормальна. Пустой результат без ошибки — сигнал про видимость, а не про селектор: неточный селектор даёт ошибку отсутствия узлов. Третий вариант — элемент пуст по данным, и это тоже проверяется длиной.

Чем Attributes отличается от AttributeValue?

AttributeValue читает один атрибут и возвращает пару строка плюс ok; Attributes снимает всю карту атрибутов первого узла в map[string]string одним обменом. В цикле по карточкам карта на карточку часто дешевле, чем пять отдельных AttributeValue, а AttributesAll распространяет тот же подход на все совпадения селектора сразу. Помните про порядок: карта его не сохраняет, для отчётов значения вынимают по ключам.

Как читать значение поля, которое пользователь или скрипт уже изменил?

Только chromedp.Value: он исполняет на узле чтение свойства value, то есть актуального состояния. Атрибут value в разметке остаётся исходным снимком и после ввода врёт. То же правило для checked и disabled: состояние живёт в свойствах, разметка хранит декларацию. Проверяется это элементарно: прочитайте оба значения после ввода и сравните — расхождение станет наглядным.

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

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

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

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

Testcontainers в Go: интеграционные тесты с Docker

Курс testcontainers-go: Postgres, Redis, Kafka и Mongo в тестах через Docker, wait-стратегии, сети, параллельность, CI и итоговый стенд. 25 уроков.

  • Поднимать реальные зависимости в Docker из кода теста
  • Использовать модули Postgres, Redis, MongoDB, Kafka и RabbitMQ
  • Ждать готовность сервиса wait-стратегиями вместо sleep
  • Собирать стенды из нескольких контейнеров и своего образа