---
title: "Извлечение текста и атрибутов"
description: "chromedp text на Go: Text и TextContent, InnerHTML и OuterHTML, AttributeValue против Value — извлечение данных go-парсером с таблицей действий и ловушками."
course: "Chromedp в Go: автоматизация Chrome без Selenium"
url: "https://golangmaster.ru/courses/chromedp/lessons/06-izvlechenie-teksta/"
published: "2026-09-19"
updated: "2026-09-21"
minutes: 18
---

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

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

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

Страница, открытая chromedp, — уже не HTML-ответ, а живое дерево: браузер собрал его, отрисовал и продолжает обновлять по мере работы скриптов. Colly на этом шаге закончила бы работу, а chromedp только начинает: всё, что видно посетителю, доступно программе. Извлечение данных go-программой устроено одинаково для всех действий этого урока: действие дожидается узлов по селектору, берёт первый совпавший, исполняет на нём браузерную функцию и расфасовывает результат в переменную, адрес которой вы передали указателем. Указатель здесь не украшение, а канал доставки: в него записывается значение, поэтому в Run передают &name, &html, &ok. Как и в четвёртом уроке про [селекторы](/courses/chromedp/lessons/04-selektory/), 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.

> **Живое место: пустая строка без ошибки.** Парсер каталога ходит по вкладкам товара; у второй вкладки блок цены свёрстан с display:none до клика. Text возвращает пустую строку, действие проходит успешно, программа пишет в отчёт цену «пусто» — и так сотнями строк. Сломалось не извлечение, а предположение, что успех означает данные. Лечение в три слоя: WaitVisible на читаемом узле перед Text, TextContent для областей, которые обязаны быть скрытыми, и проверка длины результата с отдельной веткой лога, куда попадает и адрес страницы, и название вкладки. Спорное мнение с условием: читать вслепую быстрее, но цена одного тихого прогона с пустыми ценами — неделя недоверия к отчётам.

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

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

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

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

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

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

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

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

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

- ✅ Пустую строку; действие завершится без ошибки
- Ошибку ErrNotVisible — элемент скрыт
- Текст цены: Text игнорирует видимость
- Панику — читаемая переменная не заполнится

Сниппет Text проверяет offsetWidth, offsetHeight и клиентские прямоугольники: у скрытого узла они пусты, возвращается пустая строка, и действие считается успешным. Ошибку даёт неверный селектор или истёкший дедлайн, а видимость проверяют WaitVisible и опция NodeVisible.

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

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

- ✅ ok=false, ошибка nil, v останется прежним
- Run вернёт ошибку отсутствия атрибута
- ok=true, а v получит пустую строку
- Панику: два указателя обязательны

AttributeValue перебирает карту атрибутов узла; не найдя совпадения, ставит ok в false и возвращается без ошибки. Отсутствие атрибута — штатный ответ: у большинства узлов не существует большинства атрибутов. Ошибка появляется, только если по селектору не нашлось ни одного узла.

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

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

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

- ✅ attr=тапочки, ok=true, current=носок
- attr=носок, ok=true, current=носок
- attr=тапочки, current=тапочкиносок
- Ошибка: SetValue не меняет свойство value

AttributeValue читает снимок из разметки — тапочки. SetValue присваивает свойство value через JavaScript и возбуждает события input и change, поэтому Value возвращает носок. Допечатки нет: SetValue заменяет значение целиком, в отличие от SendKeys из прошлого урока.

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

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

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

Подсказка: Видимый текст — Text; всё поддерево — TextContent; атрибут с флагом — AttributeValue; свойство value — Value.

Правильные ответы в пропусках: `Text`, `TextContent`, `AttributeValue`, `Value`

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

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

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

1. `var name, href string`
2. `var ok bool`
3. `err := chromedp.Run(ctx,`
4. `  chromedp.WaitVisible(".product", chromedp.ByQuery),`
5. `  chromedp.Text(".product h3", &name),`
6. `  chromedp.AttributeValue(".product a", "href", &href, &ok),`
7. `)`
8. `if err != nil {`
9. `  panic(err)`
10. `}`

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

- **chromedp.Text** — Читает innerText первого узла. Требует видимости: у скрытого элемента вернёт пустую строку без ошибки.
- **chromedp.TextContent** — Читает textContent всего поддерева независимо от видимости, но без форматирования — колонки слипаются.
- **InnerHTML и OuterHTML** — Разметка поддерева и узел целиком. Позволяют перенести разбор в Go: goquery, encoding/html.
- **chromedp.AttributeValue** — Читает атрибут из карты разметки в строку и флаг ok. Атрибута нет — ok=false без ошибки.
- **chromedp.Value** — Читает текущее JS-свойство value поля. После ввода расходится с атрибутом — актуальное состояние именно здесь.
- **Attributes и AttributesAll** — Все атрибуты узла в map[string]string; версия с All — срез карт по всем совпадениям селектора с ByQueryAll.

> **Проверяйте результат чтения.** Чтение — такой же кандидат на проверку, как ввод из пятого урока: длина строки, ok от атрибута, число прочитанных элементов. Пара лишних строк в цикле превращает тихую пустоту в громкую ошибку с номером карточки в логе. Для InnerHTML-разбора тот же принцип работает после: ячейка не распарсилась — это тоже аномалия, а не повод молча пропустить строку.

Данные прочитаны — пора снимать артефакты рендера. Седьмой урок про [скриншоты страниц и элементов](/courses/chromedp/lessons/07-skrisnty/) покажет три уровня захвата: окно браузера, вся страница и отдельный узел. Девятый урок про [умные ожидания](/courses/chromedp/lessons/09-ozhidaniya-elementov/) закроет случаи, когда данных ещё нет и ждать нужно условие, а не узел. Оговорка про практику: всё это проверялось локально — понадобится локальный Go и установленный Chrome; интерактивы сайта и тематическая программа в песочнице механику подскажут, но живой DOM они не заменят.

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

- Чтение в 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 вернёт пустую строку без ошибки, — добавьте проверку длины и отдельную ветку лога для этого случая.

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

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

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

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

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

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

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