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

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

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

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

Скриншоты страниц и элементов

Скриншот в отчёте мониторинга — это аргумент, поэтому целый класс задач chromedp — съёмка отрисованных страниц. Действие chromedp скриншот работает на трёх уровнях: окно, вся страница и отдельный элемент; разберём скриншот сайта go-кодом: выбор команды, размер окна, качество и пустые кадры.

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

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

chromedp скриншот: три команды на три уровня#

Весь захват в chromedp стоит на одной команде протокола, а библиотека даёт три обёртки. CaptureScreenshot снимает вьюпорт браузера — то, что влезает в окно; формат по умолчанию PNG. FullScreenshot включает захват за пределами видимой области и снимает страницу целиком; действие принимает качество от 0 до 100, и от него зависит формат: сотня даёт PNG, всё остальное — JPEG. Screenshot по селектору снимает первый совпавший элемент: chromedp вычисляет прямоугольник из getBoundingClientRect, округляет координаты до пикселей и захватывает этот участок холста. Трёх уровней хватает на все типовые задачи: быстрый кадр для дашборда, архив длинных страниц, точечная съёмка карточки или графика. Поэтому chromedp скриншот выглядит как одно действие после Navigate и ожидания — вся разница между кадрами в выборе команды и в том, что случится до нажатия, а не после. Общего у всех трёх и немного: все они возвращают срез байтов, все снимают текущее состояние рендера и ни одна не умеет пересниматься сама — если момент выбран неудачно, кадр придётся сделать заново.

Полная программа: окно 1280 на 800 и FullScreenshot

GO
package 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 с качеством ниже сотни, если границы позволяют.

Вьюпорт и элемент в одном прогоне

GO
var viewport, chart []byte

err := chromedp.Run(ctx,
        chromedp.WaitVisible("#chart"),
        chromedp.CaptureScreenshot(&viewport),
        chromedp.Screenshot("#chart", &chart, chromedp.NodeVisible),
)

CaptureScreenshot захватывает только видимую область окна. Screenshot по селектору снимает первый совпавший узел: находит элемент, считает прямоугольник, округляет координаты и вырезает этот участок. Опция NodeVisible добавляется к нему по умолчанию, здесь показана явно — для читаемости, потому что съёмка идёт по живой отрисовке, а не по разметке.

Дождаться рендера, а не нажатия кнопки#

Скриншот — момент, остановленный во времени, и его качество решается выбором момента. Ранний захват — самый частый брак: в кадре спиннер, пустой контейнер, недорисованный график. Базовая защита — 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 растёт на тяжёлых страницах, и час непрерывных скриншотов — повод перезапустить контекст или вынести съёмку в пул браузеров, где время жизни процесса определяется задачей, а не длиной списка. И помните про качество кадра: переснятое — это прогон заново, навигация и ожидания; дешевле дождаться рендера, чем объяснять заказчику, почему в отчёте двести серых прямоугольников. Хороший тон — контроль брака в самой программе: проверка размера буфера и сравнение соседних кадров по контрольным суммам ловят пустые съёмки раньше, чем они доедут до отчёта.

Файлы, которые остаётся записать, — тоже часть задачи. 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),
// )

Соберите программу съёмки

Восстановите пропуски: размер окна, захват всей страницы и точечная съёмка карточки цены.

opts := append(chromedp.DefaultExecAllocatorOptions[:], chromedp.(1280, 800), ) var buf, price []byte err := chromedp.Run(ctx, chromedp.Navigate(url), chromedp.WaitVisible("body"), chromedp.(&buf, 90), chromedp.("#price", &price, chromedp.NodeVisible), )

Расставьте программу съёмки

Соберите по порядку: опции аллокатора, контекст, буфер и захват.

Строки

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

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

Карточки: съёмка страниц

Термин

chromedp.CaptureScreenshot

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

Снимает вьюпорт — видимую часть окна. Формат по умолчанию PNG. Быстрый кадр для дашборда.

1 / 6

Что дальше: второй артефакт рендера — документ на бумаге; восьмой урок печать в 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 главного заголовка, называя файлы датой и адресом. Затем запустите её по трём страницам подряд и проверьте, что память процесса не растёт линейно, — если растёт, перезапускайте контекст после каждой страницы. Финал — сознательный брак: снимите страницу до окончания загрузки и убедитесь, что проверка размера буфера и контрольная сумма ловят пустой кадр.

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

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

Скриншот получается узким, вёрстка сломана. Что настроено не так?

Окно стартует с дефолтными размерами: WindowSize не задан. Задайте размер опцией аллокатора при NewExecAllocator — WindowSize(1280, 800) или шире — и переснимите. Десктопные макеты на узком вьюпоре складываются в мобильную раскладку, и это ожидаемое поведение страницы, а не баг браузера. Проверьте также, что аллокатор создан заново: размер, заданный уже запущенному процессу, на кадр не повлияет.

Почему Screenshot снимает элемент ниже видимой области?

Видимость для chromedp — это наличие размеров и клиентских прямоугольников, а не попадание в экран. Действие считает прямоугольник элемента и снимает его с флагом захвата за пределами вьюпорта. Если кадр всё же выходит пустым, проверьте, не скрыт ли элемент стилями, — тогда добавится WaitVisible и проверка координат. Для точных кадров с масштабом есть ScreenshotScale с явным коэффициентом.

Как снять страницу, где картинки подгружаются при прокрутке?

Прокрутите страницу до конца, дайте запросам уехать, вернитесь наверх и снимайте: уже загруженные изображения не перезапрашиваются. Прокрутка делается действием Evaluate с window.scrollTo. Для длинных списков помогает поэтапная прокрутка с паузами: так все плейсхолдеры успевают превратиться в картинки до съёмки. Там же, в десятом уроке, показан и предикат готовности через Poll — им проверяют, что все позиции приехали.

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

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

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

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

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

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

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