golang

Go-juggler и протокол Juggler

  • вторник, 11 августа 2026 г. в 00:00:12
https://habr.com/ru/articles/1068462/

Вступление

ИИ-агентам нужно ходить по настоящему вебу. Но настоящий веб враждебен к автоматизации: Playwright блокируют, headless-версия Chrome палится по отпечаткам, а стелс-плагины просто становятся частью отпечатка. Если вы когда-нибудь писали скраперы или инструменты для агентов, вы знаете, как это бывает - всё работает локально, а потом продакшен разваливается за Cloudflare-челленджем.

Несколько проектов пытались решить эту проблему со стороны браузера. Проект https://camoufox.com патчит Firefox на уровне C++, так что navigator.hardwareConcurrency, WebGL-рендереры, AudioContext, геометрия экрана и WebRTC подменяются ещё до того, как JavaScript их увидит. Браузер https://github.com/jo-inc/camofox-browser оборачивает этот движок в REST API, заточенный под агентов: снимки доступности вместо раздутого HTML, стабильные ссылки на элементы для кликов и изоляция сессий.

Остаётся только одна дыра: инструментарий вокруг этой экосистемы завязан на JavaScript/Python. Если вы живёте в Go - а весь стек Go-агентов, взорвавшийся за последние пару лет, весомый аргумент в его пользу - вам оставалось писать сырые вызовы curl.

Пакет https://github.com/yvv4git/go-juggler исправляет это. Это Go-клиент для протокола автоматизации Juggler (того самого, который патчит и расширяет Camoufox) под лицензией MIT. Он управляет Firefox/Camoufox из Go с единственной зависимостью и чистым, слоистым API.

Что такое Juggler

Juggler - это протокол автоматизации браузера, разработанный для Firefox ещё до того, как в браузере появился CDP (Chrome DevTools Protocol). Репозиторий https://github.com/puppeteer/juggler принадлежит проекту Puppeteer. Это отдельный модуль внутри Firefox, а не часть ядра браузера, что упрощает его патчинг и контроль над тем, что раскрывается странице.

Проводной формат повторяет привычную CDP-раскладку:

{ "id": 1, "method": "Browser.newPage", "params": { ... } }
{ "id": 1, "result": { ... } }
{ "method": "Page.crashed", "params": { ... } }

Camoufox патчит Juggler, чтобы дать ему собственную изолированную "копию" страницы для работы. Playwright может свободно читать и редактировать свою версию, тогда как реальная страница остаётся нетронутой и не может обнаружить чтения, слушатели или внедрённых агентов. Ввод проходит через исходные обработчики ввода Firefox, поэтому клики и нажатия клавиш ведут себя точно как человеческие. Это та самая стелс-основа, на которой стоит go-juggler.

Что такое go-juggler

Пакет go-juggler - это Go-клиент для протокола Juggler. Он автоматизирует браузеры с поддержкой Juggler, такие как Firefox и Camoufox, и предоставляет два способа управления ими:

1. Высокоуровневый REST-клиент (juggler.NewClient("http://localhost:9377")), который общается с запущенной HTTP-обёрткой camofox-browser - здесь сегодня живёт вся готовая к использованию магия.

2. Низкоуровневый слой транспорта и протокола (pipe и WebSocket-каналы с CDP-стилем фрейминга сообщений), который напрямую говорит на проводном протоколе Juggler - основа для запуска и подключения к процессу браузера внутри процесса.

Установка:

go get github.com/yvv4git/go-juggler

Быстрый старт

docker run -d -p 9377:9377 --name camoufox yvv4docker/camoufox:latest

Затем минимальная программа, проходящая весь жизненный цикл вкладки:

package main

package main

import (
    "context"
    "log"

    "github.com/yvv4git/go-juggler"
)

func main() {
    ctx := context.Background()
    c := juggler.NewClient("http://localhost:9377")

    tab, _ := c.OpenTab(ctx, "demo", "http://example.com")
    snap, _ := c.Snapshot(ctx, tab.TabID, "demo")
    c.Click(ctx, tab.TabID, "demo", "e1", "")
    c.CloseTab(ctx, tab.TabID, "demo")
}

Запуск полного примера жизненного цикла против живого экземпляра даёт вывод примерно такого вида:

health: ok=true engine=camoufox sessions=0
tab opened: 60bf9c8c-e6d9-4be0-ba09-738058215af6
navigated to rutube.ru
snapshot (128908 chars):
- img
- banner:
  - button "Закрыть меню навигации" [e1]:
    ...
  - button "Вход" [e9]
- navigation:
  - list:
    - listitem:
      - link "Главная" [e10]:
tab closed
session closed

Маркеры [e1], [e9], [e10] - это ключевая идея: снимок - это дерево доступности, где у каждого интерактивного элемента есть стабильная ссылка. Агенты находят и кликают элементы по ссылкам вместо борьбы с CSS-селекторами.

Возможности

  • Health - проверить статус браузера (движок, соединение, память)

  • OpenTab - открыть новую вкладку и перейти по URL

  • Navigate - загрузить URL в существующей вкладке

  • Snapshot - получить ARIA-дерево страницы (ссылки на элементы)

  • Click - кликнуть по элементу по ссылке или CSS-селектору

  • Type - заполнить поле ввода по ссылке или селектору

  • Press - нажать клавишу клавиатуры (Enter, Tab, Escape и т.д.)

  • Scroll - прокрутить страницу вверх/вниз на N пикселей

  • Back - перейти назад по истории

  • Forward - перейти вперёд по истории

  • Refresh - перезагрузить текущую страницу

  • Links - вывести все ссылки на странице с пагинацией

  • Screenshot - сделать PNG-скриншот (страницы или вьюпорта)

  • Evaluate - выполнить произвольный JavaScript в контексте страницы

  • NetworkRequests - получить все загруженные ресурсы (навигация + подресурсы)

  • PollNetworkRequests - опрашивать ресурсы с течением времени с дедупликацией

  • Stats - получить состояние вкладки (URL, посещённые URL, ссылки)

  • ListTabs - вывести все вкладки в сессии (URL, заголовок)

  • CloseTab - закрыть вкладку

  • CloseSession - уничтожить всю сессию и все её вкладки

Три из них заслуживают более пристального взгляда.

Токен-эффективные снимки

Вызов Snapshot возвращает ARIA-дерево доступности вместо сырого HTML. Это огромный выигрыш для LLM-агентов, где каждый байт стоит токенов: снимок доступности примерно на 90% меньше соответствующего HTML, и он уже структурирован по семантическим ролям (button, link, list item) с прикреплёнными ссылками.

Инъекция JavaScript

Evaluate выполняет произвольный JavaScript в контексте страницы - удобно для разовых изменений, которые API не покрывает. Пример inject устанавливает заголовок документа и читает его обратно - проверяемый побочный эффект:

document.title = "Hi, friends!"; document.title

Примечание: эндпоинт /evaluate требует camofox-browser >= 1.4.0. Клиент обнаруживает более старый сервер (HTTP 404) и прямо сообщает вам об этом, а не молча падает.

Перехват сетевых запросов

NetworkRequests читает Performance API браузера и возвращает всё, что загрузила страница, это основной документ плюс подресурсы (CSS, скрипты, изображения, трекеры) - в хронологическом порядке. PollNetworkRequests расширяет это: он семплирует в течение заданного времени и объединяет результаты с дедупликацией, так что поздно загружающаяся реклама и трекеры аналитики тоже захватываются.

total requests: 6

 1. [NAVIGATION  ] https://www.wikipedia.org/
 2. [CSS         ] https://www.wikipedia.org/portal/wikipedia.org/assets/img/sprite-e49fbf32.svg
 3. [SCRIPT      ] https://www.wikipedia.org/portal/wikipedia.org/assets/js/index-34f340e24a.js
 4. [SCRIPT      ] https://www.wikipedia.org/portal/wikipedia.org/assets/js/gt-ie9-507b16b6be.js
 5. [IMG         ] https://www.wikipedia.org/portal/wikipedia.org/assets/img/Wikipedia-logo-v2.png
 6. [IMG         ] https://www.wikipedia.org/static/favicon/wikipedia.ico

Архитектура: три слоя, одно направление

go-juggler/
├── juggler.go          # корневой пакет: публичный API-фасад
├── transport/          # байтовые каналы: pipe (fd 3/fd 4) и WebSocket
├── protocol/           # типы сообщений Juggler, JSON-кодирование/декодирование
├── browser/            # высокоуровневое управление браузером и вкладками
└── examples/           # запускаемые примеры программ
  • transport/ перемещает сырые байты. Transport - это двунаправленный канал, передающий JSON, разбитый по строкам. Есть две реализации: PipeTransport, работающий через унаследованную пару OS-pipe (fd 3/fd 4, классическая внутрипроцессная схема для запущенного браузера), и WebSocketTransport, подключающийся к эндпоинту автоматизации браузера. Транспорты безопасны для конкурентного использования и уважают отмену контекста - блокирующее чтение прерывается, как только контекст завершён.

  • protocol/ придаёт этим байтам смысл. Он определяет тип Message (id / method / params / result / error), трёхстороннюю классификацию (IsRequest, IsResponse, IsEvent) и JSON-хелперы кодирования/декодирования. Этот пакет остаётся независимым от любого транспорта.

  • browser/ - высокоуровневая поверхность. Browser оборачивает транспорт с насосом сообщений, чтобы вызывающие работали с типизированными хендлами вместо сырого JSON. Client - HTTP-клиент для REST API camofox-browser. Launcher - место, где реальный процесс браузера порождается и подключается.

Зависимости текут в одну сторону, сверху вниз: browser/ импортирует transport/, а protocol/ остаётся независимым. Корневой пакет реэкспортирует API браузера, так что большинству вызывающих достаточно одного импорта:

import "github.com/yvv4git/go-juggler"

Примеры программ

  • examples/basic - полный жизненный цикл вкладки (health, open, navigate, snapshot, close).

  • examples/tab - все 16 операций с вкладками в одной программе: open, snapshot, links, stats, evaluate, click, navigate, back, forward, type, press, scroll, refresh, screenshot, close.

  • examples/inject - инъекция JavaScript через Evaluate.

  • examples/requests - сопутствующий захват сетевых запросов с опросом и дедупликацией.

  • examples/screen - скриншоты вьюпорта и полной страницы в PNG.

  • examples/html - HTML страницы до и после загрузки динамического контента.

Пример с несколькими вкладками показывает, как масштабируются сессии:

opened 1: http://example.com/
opened 2: http://example.org/
opened 3: http://example.net/

total tabs: 3

  1. [Example Domain] http://example.com/
  2. [Example Domain] http://example.org/
  3. [Example Domain] http://example.net/

closed acd74c0c
closed 8c0951ac
closed 57adc6dd

session closed

Статус

go-juggler находится в активной разработке, и стоит чётко сказать, что сделано, а что нет.

Работает сегодня: полный HTTP REST-клиент для camofox-browser (все девятнадцать методов выше), слой транспорта (pipe + WebSocket с фреймингом, учитывающим контекст), типы сообщений протокола и все шесть примеров программ.

В дорожной карте: прямой путь - browser.Launch, порождающий исполняемый файл Camoufox с унаследованными pipe-fd и подключающийся по сырому протоколу Juggler, плюс полный API насоса сообщений Browser/Tab (navigate, reload, события страницы). Всё это помечено как TODO в исходниках и появится по мере завершения низкоуровневой поверхности протокола.

Если ваш сценарий - управление экземпляром Camoufox через REST API camofox-browser сегодня, go-juggler готов. Если вам нужно внутрипроцессное запускание браузера по сырым pipe - оно на подходе.

Итоги

Экосистема автоматизации антидетект-браузеров слишком долго была историей про JavaScript. go-juggler приносит те же стелс-возможности - подмену отпечатков на уровне C++, токен-эффективные ARIA-снимки, стабильные ссылки на элементы, изоляцию сессий - в экосистему Go с одним импортом и почти пустым деревом зависимостей.

Ссылки: