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

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

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

Урок 10 из 25 ~20 мин +50 XP

Выполнение JavaScript: Evaluate

Двести карточек на странице по пять значений в каждой — это тысяча обменов с браузером, если читать по одному действию. chromedp evaluate решает задачу одним вызовом: JavaScript исполняется в контексте страницы, а результат возвращается сразу в Go-переменную. Разберём js в браузере go-программой: механику возврата значений, undefined и сырые байты, контекст консоли DevTools и границы уместности.

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

У чтения из шестого урока есть экономический предел: каждое действие — отдельный обмен с браузером, и на больших страницах счёт идёт на сотни. Но у страницы под рукой есть исполнитель, который видит весь DOM целиком, — JavaScript. Один цикл Array.map по селектору собирает все заголовки за один проход, и именно это делает chromedp evaluate: отправляет выражение в контекст страницы, где доступны document, переменные скриптов сайта и всё состояние, которое фреймворк уже посчитал, и привозит результат в Go. Это не обход библиотеки, а её нижний этаж: сам протокол DevTools исполняет выражения той же командой Runtime.evaluate, и chromedp лишь даёт ей удобную обёртку. Отсюда и место действия в курсе: селекторы отвечают за поиск, чтение — за узлы, а Evaluate — за всё, что проще посчитать на месте, чем привозить по кусочкам. Скролл страницы из FAQ седьмого урока, агрегаты по таблицам, чтение данных SPA до того, как они попали в DOM, — всё это один и тот же механизм с разными выражениями. В этом уроке — как устроен возврат значений, какие ошибки он приносит и где заканчивается здравый смысл писать скрапер на JS-строках.

chromedp evaluate: выражение и указатель#

Сигнатура короткая: chromedp.Evaluate(expression, res). Первый аргумент — строка с выражением JavaScript, второй — указатель на приёмник результата; передадите nil, и результат будет проигнорирован, действие только исполнит код — так выглядит прокрутка window.scrollTo из седьмого урока. Механика возврата для обычных приёмников такая: результат выражения кодируется в JSON по значению, и стандартный json.Unmarshal раскладывает его в вашу переменную. Число ложится в int или float64, строка — в string, массив — в срез, объект — в карту или структуру с json-тегами. Это значит, что тип приёмника — ваша ответственность: выражение вернёт массив, а приёмник объявлен строкой — получите ошибку распаковки, и она будет честной ошибкой Go с описанием. Отсюда привычка подбирать пару «выражение плюс тип»: соберёте на JS массив строк — берите []string, посчитаете сумму — берите float64 или int, разберёте объект — карту map[string]any или структуру. Evaluate не дожидается ничего: он исполняет выражение в текущем состоянии страницы, поэтому перед ним стоят те же ожидания готовности, что и перед чтением узлов. Ошибка внутри выражения — обращение к несуществующему свойству, ошибка синтаксиса — возвращается как ошибка действия, и Run останавливается: тихого прохождения мимо не бывает.

Все заголовки карточек одним обменом

GO
var titles []string
err := chromedp.Run(ctx,
        chromedp.WaitVisible(".cards"),
        chromedp.Evaluate(
                `Array.from(document.querySelectorAll('.card h3'))
                        .map(e => e.textContent)`,
                &titles,
        ),
)
if err != nil {
        panic(err)
}
fmt.Println("карточек:", len(titles))

Выражение собирает все узлы по селектору и возвращает массив текстов; результат кодируется в JSON и распаковывается в срез строк. WaitVisible дожидается контейнера — Evaluate сам ничего не ждёт. Один обмен с браузером вместо сотни действий Text: на больших страницах разница видна в секундах, а не миллисекундах.

Возврат значений: undefined, null и сырые байты#

Три особые формы приёмника закрывают края случая, и о них стоит знать заранее. Первая — ошибка undefined: если выражение вернуло undefined или null, а приёмник не умеет быть nil — строка, число, структура, — Evaluate вернёт ошибку: encountered an undefined value или encountered a null value. Приёмники, которые nil умеют, — указатели, срезы, карты — примут undefined молча: срез останется nil, и это штатный путь выразить «данных нет». Вторая особая форма — сырые байты: приёмник []byte получает JSON-кодировку результата как есть, без распаковки в тип; это способ забрать значение сложной формы и разобрать его у себя, когда писать JS-разбор не хочется, а промежуточная структура не нужна. Третья — низкоуровневый приёмник *runtime.RemoteObject: вы получаете объект протокола без попытки конвертации — класс объекта, идентификатор, тип; живые объекты браузер держит у себя, пока страница не закрыта или не переезжает, и освобождать их можно командами runtime.ReleaseObject, если сессия длинная и объектов много. Для повседневных задач хватает первого пути; RemoteObject нужен, когда результат — функция, узел DOM или большой объект, который вы хотите исследовать по частям, а не тащить целиком в Go.

js в браузере go: границы уместности#

Сила Evaluate провоцирует: раз можно всё — почему не перенести в JS-строку половину парсера? Здесь полезна граница, которую стоит провести заранее. Уместное: агрегаты одним обменом, прокрутка, чтение состояния, которое не попало в DOM — переменные скрипта, объекты аналитики, данные в памяти фреймворка; проверка сложной готовности, которую селектором не выразить. Неуместное: валидация, ветвления и маршрутизация парсера внутри JS-строк — код без типов, без отладчика Go, без тестов, и каждая правка — редактирование строки в кавычках. Спорное мнение с условием: логика должна жить в Go, а JS — только читать и трогать страницу; исключение, при котором мнение можно перевернуть, одно — когда страница сама держит данные в недоступном селекторам виде, и короткий JS-запрос к её состоянию экономит десятки действий. Хороший маркер уместности: если выражение умещается в одну-две строки и не содержит ветвлений, это работа для Evaluate; если третье — сигнал вернуться к действиям библиотеки. Второй практический предел — аргументы: у Evaluate нет параметра для значений, аргументы подставляются в строку выражения. Подставляйте через JSON-кодирование и экранирование, а не конкатенацией: сырая склейка строк с данными с сайта — прямой путь к сломанному выражению, а в худшем случае к исполнению чужого кода в вашей сессии. Для предикатов с аргументами есть PollFunction с WithPollingArgs из девятого урока — там подстановка безопасна и встроена.

Пять способов забрать значение#

СпособЧто возвращаетКогда брать
Text, AttributeValue, Valueодно значение одного узлаточечное чтение по селектору
Evaluate с []string или структуройагрегат по значению: JSON в Go-типмассивы, суммы, объекты одним обменом
Evaluate с *[]byteсырую JSON-кодировку результатасложная форма, разбор на своей стороне
Evaluate с **runtime.RemoteObjectживой объект протокола: класс, ObjectIDфункции, узлы, большие объекты
EvaluateAsDevToolsто же плюс Command Line API консолискрипты с $ и $$, работа как в DevTools

EvaluateAsDevTools: контекст консоли#

Вторая обёртка над той же командой протокола — chromedp.EvaluateAsDevTools: выражение исполняется так, как его исполняет консоль панели разработчика. Разница в двух деталях: выражение живёт в контексте объекта группы console, и ему доступен Command Line API — те самые сокращения консоли, включая выбор элементов dollar-скриптами $ и $$. Для задач «сделай, как я сделал в DevTools» это прямой путь: выражение, проверенное в консоли, переносится в код без переписывания dollar-сокращений на чистый querySelector. У библиотеки на этом механизме стоят привычные действия: Title и Location из третьего урока — обёртки над EvaluateAsDevTools с выражениями document.title и document.location.toString(), так что вы уже пользовались этой обёрткой, не зная о ней. Цена за удобство обозначена в исходниках честно: не используйте EvaluateAsDevTools с недоверенным JavaScript — Command Line API расширяет возможности исполняемого кода, и запускать в этой песочнице стоит только то, что вы написали сами или проверили. Рядом живут и опции нижнего уровня: EvalIgnoreExceptions заставляет оценку не падать на исключениях, EvalAsValue принудительно кодирует результат по значению — те же рычаги, которыми настроена обычная обёртка.

Живой объект протокола

GO
var res *runtime.RemoteObject
err := chromedp.Run(ctx,
        chromedp.Evaluate(`window`, &res),
)
if err != nil {
        panic(err)
}
fmt.Println(res.ClassName)

Приёмник **runtime.RemoteObject получает объект протокола без конвертации: здесь это глобальное окно страницы с ClassName Window. У объекта есть идентификатор ObjectID, по которому к нему можно обращаться следующими командами протокола; живые объекты браузер держит до закрытия или навигации страницы, при длинной сессии их освобождают ReleaseObject.

Аргументы, исключения и порядок работы#

Соберём дисциплину оценки в правила, которые проверены на живых страницах. Выражение сначала живёт в консоли DevTools: написали, исполнили, увидели результат — и только потом переехало в Go; между консолью и Evaluate почти нет разницы, а цикл отладки в консоли в десять раз короче. Приёмник подбирается под форму результата и объявляется рядом с выражением: массив текстов — []string, сумма — число, объект — карта или структура; расхождение формы и типа даёт ошибку распаковки с понятным текстом. Undefined и null обрабатываются сознательно: если «нет данных» — норма, берите приёмники, умеющие nil, и проверяйте на nil, а не на ошибку. Для страницы, где результат появляется позже, Evaluate окружается ожиданиями: WaitVisible на узле или Poll с предикатом из девятого урока — оценка сама ждать не умеет. Крупные вычисления по сотням узлов делаются одним выражением с map и reduce: на стороне браузера это микросекунды, а расписывать ту же работу десятками действий — тратить обмены и время обхода. И держите выражения короткими: трёхэтажная JS-программа в строке — сигнал, что пора выносить её в отдельный файл или возвращаться к действиям библиотеки.

Практики безопасной оценки#

  • отлаживайте выражение в консоли DevTools, потом переносите в Go без изменений
  • тип приёмника объявляйте под форму результата: срез для массива, число для суммы
  • undefined и null принимайте приёмниками, умеющими nil, и проверяйте на nil
  • значения в выражение подставляйте через JSON-кодирование, не конкатенацией
  • аргументные предикаты собирайте на PollFunction с WithPollingArgs
  • перед оценкой дожидайтесь готовности: WaitVisible или Poll с предикатом
  • EvaluateAsDevTools — только для своих и проверенных скриптов

Что дальше: оценка JavaScript — последний инструмент низкого уровня перед большой темой данных. Одиннадцатый урок применит всё это к SPA и динамическому контенту, где разметку собирает фреймворк, а состояние живёт в его памяти; четвёртый урок про селекторы вспомнится в связи с ByJSPath — путём элемента, который тоже исполняется оценкой. Практика — локально: понадобится Go и Chrome, песочница сайта исполняет чистый Go без браузера, поэтому настоящие JSON-обмены вы увидите только в своём терминале. Первый честный замер для домашней работы: страница с сотней узлов, время цикла Text против одного Evaluate — цифры убедят надёжнее слов.

Проверка: undefined в приёмник

Выражение вернуло undefined, приёмник — строка var s string. Что произойдёт?

Проверка: nil вместо приёмника

Зачем в Evaluate передают nil вместо указателя на результат?

Проверка: контекст консоли

Чем EvaluateAsDevTools отличается от Evaluate?

Что окажется в sum

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

var sum int
// err := chromedp.Run(ctx,
//   chromedp.Evaluate(`1+2`, &sum),
// )

Соберите пару чтений

Восстановите приёмник агрегата и обёртку с контекстом консоли.

var titles []string var title string err := chromedp.Run(ctx, chromedp.Evaluate(expr, &), chromedp.(`document.title`, &title), )

Расставьте агрегат по порядку

Соберите по порядку: приёмник, Run с ожиданием и оценкой, обработка ошибки.

Строки

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

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

Карточки: выполнение JavaScript

Термин

chromedp.Evaluate

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

Исполняет выражение в контексте страницы. Результат — JSON по значению в приёмник; nil означает «исполнить и игнорировать». Ошибки скрипта возвращаются как ошибки действия.

1 / 6

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

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

Вывод

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

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

  • Evaluate исполняет выражение в контексте страницы: доступны document, состояние скриптов и всё, что видит консоль.
  • Результат возвращается по значению: JSON-кодирование и json.Unmarshal в приёмник; тип подбирается под форму результата.
  • nil вместо приёмника — исполнить код ради побочного эффекта: прокрутка, вызов функции, флаг.
  • undefined и null в приёмнике без nil — ошибки ErrJSUndefined и ErrJSNull; срезы, карты и указатели принимают их молча.
  • *[]byte даёт сырой JSON, **runtime.RemoteObject — живой объект протокола с ClassName и ObjectID.
  • EvaluateAsDevTools — контекст консоли и Command Line API: Title и Location из третьего урока — обёртки над ним.
  • Аргументы подставляются в строку через JSON-кодирование; для предикатов — PollFunction с WithPollingArgs.
  • Граница уместности: JS читает и считает, решает Go; выражение сначала проверяется в консоли DevTools.

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

Возьмите страницу с сотней однотипных узлов и сделайте два чтения одного и того же: цикл Text по селектору и один Evaluate с Array.from и map. Замерьте время обоих подходов и запишите разницу. Затем верните сумму числового поля одним выражением с reduce в float64, а объект window прочитайте через **runtime.RemoteObject и напечатайте ClassName. Финал — сломайте приёмник: прочитайте массив в строку и изучите текст ошибки распаковки.

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

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

Evaluate вернул encountered an undefined value — что это значит?

Выражение вернуло undefined или null, а приёмник не умеет быть nil: строка, число, структура. Проверьте выражение в консоли и решите, откуда отсутствие данных: если это норма, возьмите приёмник, умеющий nil — указатель, срез или карту, — и проверяйте на nil. Если отсутствие неожиданно, ищите причину на странице: неверный селектор или данные, которые ещё не приехали.

Можно ли передать аргументы в Evaluate?

Отдельного параметра нет: значения подставляются в строку выражения. Делайте это через JSON-кодирование с экранированием — конкатенация ломает выражение на кавычках и открывает дорогу инъекции. Для предикатов с аргументами есть готовый путь: PollFunction из девятого урока принимает значения через WithPollingArgs и передаёт их функции безопасно.

Зачем EvaluateAsDevTools, если есть Evaluate?

Когда скрипт написан и проверен в консоли DevTools с dollar-сокращениями: EvaluateAsDevTools даёт тот же Command Line API, и выражение переезжает в код без переписывания. Сюда же относятся задачи, где удобно работать как в консоли. Для недоверенного кода обёртка не предназначена: API расширяет возможности исполняемого скрипта, поэтому её область — ваши собственные и проверенные выражения.

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

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

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

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

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

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

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