Содержание урока · 10 разделов
- chromedp скриншот: три команды на три уровня
- Размер окна: WindowSize и узкий кадр по умолчанию
- Скриншот сайта go-элементом: Screenshot и ScreenshotNodes
- Дождаться рендера, а не нажатия кнопки
- Четыре способа снять экран
- Чек-лист кадра, который не придётся переснимать
- Скриншоты в цикле: память и имена
- Песочница урока
- Коротко: что запомнить
- Вопросы и ответы
В отчёте мониторинга цен цифры подкрепляются картинкой: цена лежит в базе, скриншот — в хранилище. С числами спорят редко, с картинкой почти никогда, поэтому съёмке отдают задачи, которые не сводятся к извлечению данных: зафиксировать вид страницы на момент времени, сохранить доказательство цены на дату, приложить кадр к алерту, показать клиенту форму, которую он должен увидеть. Отдельный класс — сравнение вёрстки: два кадра одной страницы в разные дни показывают деградацию макета раньше, чем она всплывёт в обращениях пользователей. Это работа другого рода, чем чтение из шестого урока: она не возвращает значения в переменные, а отдаёт байты готового изображения, которые остаётся записать на диск. И эти байты — артефакт рендера: HTTP-клиент страницу не отрисует, взять кадр можно только из настоящего браузера со всей его логикой шрифтов, стилей и раскладки. В chromedp три команды захвата, и весь навык — выбрать свою, задать окно и дождаться момента. Разберём их по очереди, от целой страницы до отдельного узла.
chromedp скриншот: три команды на три уровня#
Весь захват в chromedp стоит на одной команде протокола, а библиотека даёт три обёртки. CaptureScreenshot снимает вьюпорт браузера — то, что влезает в окно; формат по умолчанию PNG. FullScreenshot включает захват за пределами видимой области и снимает страницу целиком; действие принимает качество от 0 до 100, и от него зависит формат: сотня даёт PNG, всё остальное — JPEG. Screenshot по селектору снимает первый совпавший элемент: chromedp вычисляет прямоугольник из getBoundingClientRect, округляет координаты до пикселей и захватывает этот участок холста. Трёх уровней хватает на все типовые задачи: быстрый кадр для дашборда, архив длинных страниц, точечная съёмка карточки или графика. Поэтому chromedp скриншот выглядит как одно действие после Navigate и ожидания — вся разница между кадрами в выборе команды и в том, что случится до нажатия, а не после. Общего у всех трёх и немного: все они возвращают срез байтов, все снимают текущее состояние рендера и ни одна не умеет пересниматься сама — если момент выбран неудачно, кадр придётся сделать заново.
Полная программа: окно 1280 на 800 и FullScreenshot
GOpackage main
import (
"context"
"os"
"github.com/chromedp/chromedp"
)
func main() {
opts := append(chromedp.DefaultExecAllocatorOptions[:],
chromedp.WindowSize(1280, 800),
)
allocCtx, cancelAlloc := chromedp.NewExecAllocator(context.Background(), opts...)
defer cancelAlloc()
ctx, cancel := chromedp.NewContext(allocCtx)
defer cancel()
var buf []byte
err := chromedp.Run(ctx,
chromedp.Navigate("https://example.com"),
chromedp.WaitVisible("body"),
chromedp.FullScreenshot(&buf, 90),
)
if err != nil {
panic(err)
}
os.WriteFile("page.jpg", buf, 0o644)
}Опции запуска собираются через append: DefaultExecAllocatorOptions — базовый набор флагов библиотеки, WindowSize добавляет исходный размер окна. Контекст аллокатора передаётся в NewContext, и браузер стартует уже настроенным. FullScreenshot захватывает страницу целиком за пределами вьюпорта; при качестве 90 это JPEG, поэтому файл с расширением .jpg, а os.WriteFile кладёт его на диск.
Размер окна: WindowSize и узкий кадр по умолчанию#
Headless-окно в chromedp стартует без флага window-size — с невысокой дефолтной высотой и шириной, и страница раскладывается на нём, как будто у посетителя карманный телефон. Скриншот десктопной витрины из такого кадра выходит сломанным: колонки наползают, меню схлопывается в бургер, футер переезжает. Исходный размер задаётся опцией WindowSize(width, height), и это опция аллокатора, а не действие внутри Run: размер живёт с процессом браузера, а не с сессией страницы. Набор опций собирают через append по копии DefaultExecAllocatorOptions — сам глобальный срез менять нельзя, он читается всеми запускаемыми браузерами, — и передают в NewExecAllocator. Только поверх контекста аллокатора создаётся привычный NewContext, в котором живёт вкладка. Если нужны кадры мобильной витрины, вместо ручного подбора размеров есть Emulate с готовыми профилями устройств — они подменяют и вьюпорт, и сенсорный ввод, и строку агента. Отсюда порядок любого скриншота сайта go-программой: сначала кадр, потом страница, потом съёмка; второе окно другого размера — это второй процесс, а не изменение размеров текущего.
Скриншот сайта go-элементом: Screenshot и ScreenshotNodes#
Элементные съёмки нужны чаще, чем кажется: карточка товара в архив, график в отчёт, форма с багом в тикет. Screenshot(sel, &buf) снимает первый узел, совпавший с селектором, и добавляет условие NodeVisible по умолчанию — скрытые элементы никто не снимает. Механика повторяет команду Capture node screenshot из панели DevTools, но с нюансом, о котором честно говорят комментарии в исходниках: chromedp не отправляет несколько вспомогательных команд протокола, что шлёт панель, и на экзотических страницах кадр может отличаться — тогда помогает прокрутка или расширение окна. Масштаб задаёт ScreenshotScale с явным коэффициентом. Когда одним прямоугольником нужно снять группу узлов, узлы достают действием Nodes в срез и передают в ScreenshotNodes(nodes, scale, &buf): считается общий охват от самого левого верхнего до самого правого нижнего. Так собирают кадр из карточки с плавающими элементами или пары соседних блоков, которые в одиночку смысла не имеют. И не забывайте про формат: элементная съёмка всегда PNG — вес кадра на больших областях быстро растёт, поэтому гигантские карточки снимают FullScreenshot с качеством ниже сотни, если границы позволяют.
Вьюпорт и элемент в одном прогоне
GOvar viewport, chart []byte
err := chromedp.Run(ctx,
chromedp.WaitVisible("#chart"),
chromedp.CaptureScreenshot(&viewport),
chromedp.Screenshot("#chart", &chart, chromedp.NodeVisible),
)CaptureScreenshot захватывает только видимую область окна. Screenshot по селектору снимает первый совпавший узел: находит элемент, считает прямоугольник, округляет координаты и вырезает этот участок. Опция NodeVisible добавляется к нему по умолчанию, здесь показана явно — для читаемости, потому что съёмка идёт по живой отрисовке, а не по разметке.
Локально кадр идеальный, в Docker на странице русские названия превращаются в серые квадраты или вопросительные знаки. Сломалось не приложение, а контейнер: в нём нет шрифтов, и Chrome рисует tofu на месте непрочитанных глифов. Движок рендера на месте, вёрстка на месте, а буквы не из чего собрать. Лечение — пакеты с кириллическими шрифтами в образ; список и флаги разобраны в уроке про Docker ближе к концу курса. Проверка простая: первый скриншот после сборки образа должен быть русскоязычной страницы, и его лучше посмотреть своими глазами, а не просто убедиться в существовании файла. Тот же образ затем отдаёт и PDF — там кириллица пропадает точно так же.
Дождаться рендера, а не нажатия кнопки#
Скриншот — момент, остановленный во времени, и его качество решается выбором момента. Ранний захват — самый частый брак: в кадре спиннер, пустой контейнер, недорисованный график. Базовая защита — WaitVisible на узле, который несёт смысл кадра: снимаем карточку — дожидаемся карточки, снимаем график — дожидаемся холста. Но видимость не равна готовности: шрифты доезжают позже разметки, анимации доигрывают, ленивые картинки ещё не приехали. Для сложных случаев пригодятся умные ожидания: дождаться JavaScript-условия, что данные приехали, и только потом снимать. Для ленивых списков работает приём: прокрутить страницу до низа, дать картинкам загрузиться, вернуться наверх и снимать — уже загруженные изображения при съёмке не пропадут, а пустые плейсхолдеры в кадре не нужны никому. Отдельная категория — canvas и графики: они рисуются скриптом в момент, который странице виднее, и единственная надёжная проверка готовности — условие на JavaScript из данных самой страницы, например флаг, который рисующий код выставляет в конце. Проверенный порядок — дожидаться, снимать, открывать файл глазами хотя бы в первый день работы парсера: брак в скриншотах узнаётся быстрее, чем в числах.
Четыре способа снять экран#
| Действие | Что снимает | Когда брать |
|---|---|---|
| chromedp.CaptureScreenshot | вьюпорт браузера, формат PNG по умолчанию | быстрый кадр: то, что влезло в окно |
| chromedp.FullScreenshot | всю страницу за пределами вьюпорта; 100 — PNG, иначе JPEG | длинные страницы, архивы, отчёты по вёрстке |
| chromedp.Screenshot | первый элемент по селектору, PNG | карточка, график, форма — точечный кадр |
| chromedp.ScreenshotNodes | общий прямоугольник переданных узлов | группа элементов из Nodes как один кадр |
Чек-лист кадра, который не придётся переснимать#
- задать размер окна через WindowSize: скриншот зависит от вьюпорта, а не от пожеланий
- дождаться рендера: WaitVisible на ключевом узле перед съёмкой
- дать шрифтам и анимациям доиграть: маленькая пауза после видимости не портит кадр
- ленивые картинки прокрутить в зону отрисовки или отключить ленивость
- выбрать формат сознательно: качество 100 — PNG, всё прочее — JPEG
- записать буфер на диск сразу и назвать файл датой, объектом и типом кадра
Скриншоты в цикле: память и имена#
Буфер в действиях перезаписывается на каждом вызове, поэтому в цикле хватает одного var buf []byte — при условии, что вы отдаёте файл на диск до следующей съёмки, а не копите кадры. Имя собирайте из данных: дата, объект, тип захвата; такой архив потом ищется по имени и раскладывается по папкам без ручной работы. Для длинных серий следите за памятью процесса: Chrome растёт на тяжёлых страницах, и час непрерывных скриншотов — повод перезапустить контекст или вынести съёмку в пул браузеров, где время жизни процесса определяется задачей, а не длиной списка. И помните про качество кадра: переснятое — это прогон заново, навигация и ожидания; дешевле дождаться рендера, чем объяснять заказчику, почему в отчёте двести серых прямоугольников. Хороший тон — контроль брака в самой программе: проверка размера буфера и сравнение соседних кадров по контрольным суммам ловят пустые съёмки раньше, чем они доедут до отчёта.
Параметр качества в FullScreenshot выбирает не только степень сжатия, но и формат: сотня превращает съёмку в PNG, всё ниже — в JPEG. Для мониторинга и архивов рабочая точка — 85-95: кадр длинной страницы тяжелеет в разы, а глаз разницу почти не видит. Если нужен пиксель-в-пиксель для отчёта по вёрстке — берите 100 и не экономьте вес. Отдельный случай — сравнение кадров между собой: JPEG искажает пиксели, поэтому пары для попиксельного сравнения снимают сотней, иначе различия добавит само сжатие.
Файлы, которые остаётся записать, — тоже часть задачи. os.WriteFile принимает права в восьмеричной записи: 0o644 означает чтение и запись владельцу и чтение всем остальным — для кадров мониторинга этого достаточно. Путь лучше собирать из конфигурации, а не из константы: кадры мониторинга и кадры отчётов часто живут в разных бакетах, и в тестах место записи подменяется временной папкой. Если серия пишется в общее хранилище, имя из даты и идентификатора объекта гарантирует уникальность без блокировок; а в архиве с тысячами снимков за месяц уже через неделю выясняется, что имена вида page.jpg друг друга затирают. Финальный штрих — метаданные рядом с файлом: одна строка JSON с адресом, временем и качеством съёмки спасает разборы споров о том, что именно показано на кадре.
Проверка: вьюпорт
Что именно захватывает chromedp.CaptureScreenshot?
Проверка: формат кадра
chromedp.FullScreenshot(&buf, 100) — что окажется в буфере?
Элемент ниже фолда
Прочитайте код и выберите, что он выведет:
// #chart живёт на 3000 пикселей ниже видимой области
// var chart []byte
// err := chromedp.Run(ctx,
// chromedp.Screenshot("#chart", &chart, chromedp.NodeVisible),
// )Соберите программу съёмки
Восстановите пропуски: размер окна, захват всей страницы и точечная съёмка карточки цены.
Расставьте программу съёмки
Соберите по порядку: опции аллокатора, контекст, буфер и захват.
Строки
Ваша программа
Нажимайте на строки слева, чтобы собрать программу
Карточки: съёмка страниц
Что дальше: второй артефакт рендера — документ на бумаге; восьмой урок печать в PDF: счета и отчёты печатает тот же отрисованный HTML в файл для бухгалтерии. И напоминание о практике: съёмка требует локального Go и Chrome, на сайте такой кадр не снять — тематическая программа в песочнице показывает общую механику без настоящего рендера. Для серверов первым делом откройте урок про Docker и серверное окружение: именно там живут шрифты кириллицы и флаги запуска, на которых держится кадр в контейнере.
Песочница урока
Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.
Запустите код, чтобы увидеть вывод.
Коротко: что запомнить
- Три команды захвата: CaptureScreenshot — вьюпорт, FullScreenshot — вся страница, Screenshot — элемент по селектору.
- Формат FullScreenshot зависит от качества: 100 — PNG без потерь, всё прочее — JPEG.
- WindowSize — опция аллокатора: размер окна задаётся при запуске браузера, а не действием в Run.
- Screenshot добавляет NodeVisible по умолчанию: видимость — это наличие размеров, а не нахождение в видимой части экрана.
- ScreenshotNodes снимает группу узлов общим прямоугольником после Nodes.
- Кадр делают ожидания: WaitVisible на смысловом узле, прокрутка ленивых списков, JS-условия готовности.
- Кириллица в Docker требует шрифтов в образе — иначе серые квадраты.
- В цикле буфер перезаписывается: файл на диск сразу, имя из данных, память процесса — под контролем.
- Имя файла и права записи — часть программы: дата и объект в имени, права 0o644, метаданные рядом.
Домашнее задание
Соберите утилиту архивной съёмки: она принимает URL, открывает страницу в окне 1440 на 900, дожидается основного узла, снимает FullScreenshot с качеством 90 и отдельным кадром Screenshot главного заголовка, называя файлы датой и адресом. Затем запустите её по трём страницам подряд и проверьте, что память процесса не растёт линейно, — если растёт, перезапускайте контекст после каждой страницы. Финал — сознательный брак: снимите страницу до окончания загрузки и убедитесь, что проверка размера буфера и контрольная сумма ловят пустой кадр.
Личные заметкиПусто
Вопросы и ответы по уроку
Скриншот получается узким, вёрстка сломана. Что настроено не так?
Окно стартует с дефолтными размерами: WindowSize не задан. Задайте размер опцией аллокатора при NewExecAllocator — WindowSize(1280, 800) или шире — и переснимите. Десктопные макеты на узком вьюпоре складываются в мобильную раскладку, и это ожидаемое поведение страницы, а не баг браузера. Проверьте также, что аллокатор создан заново: размер, заданный уже запущенному процессу, на кадр не повлияет.
Почему Screenshot снимает элемент ниже видимой области?
Видимость для chromedp — это наличие размеров и клиентских прямоугольников, а не попадание в экран. Действие считает прямоугольник элемента и снимает его с флагом захвата за пределами вьюпорта. Если кадр всё же выходит пустым, проверьте, не скрыт ли элемент стилями, — тогда добавится WaitVisible и проверка координат. Для точных кадров с масштабом есть ScreenshotScale с явным коэффициентом.
Как снять страницу, где картинки подгружаются при прокрутке?
Прокрутите страницу до конца, дайте запросам уехать, вернитесь наверх и снимайте: уже загруженные изображения не перезапрашиваются. Прокрутка делается действием Evaluate с window.scrollTo. Для длинных списков помогает поэтапная прокрутка с паузами: так все плейсхолдеры успевают превратиться в картинки до съёмки. Там же, в десятом уроке, показан и предикат готовности через Poll — им проверяют, что все позиции приехали.
Термины урока
Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.
Изучайте дальше
Следующая ступень учебного пути
Testcontainers в Go: интеграционные тесты с Docker
Курс testcontainers-go: Postgres, Redis, Kafka и Mongo в тестах через Docker, wait-стратегии, сети, параллельность, CI и итоговый стенд. 25 уроков.
- Поднимать реальные зависимости в Docker из кода теста
- Использовать модули Postgres, Redis, MongoDB, Kafka и RabbitMQ
- Ждать готовность сервиса wait-стратегиями вместо sleep
- Собирать стенды из нескольких контейнеров и своего образа