Обзор chromedp: настоящий браузер тоже требует правильных условий готовности

Последнее обновление: August 17, 2026
Обзор chromedp: настоящий браузер тоже требует правильных условий готовности
AI-сводка

chromedp — это библиотека на чистом Go с лицензией MIT, которая управляет настоящим Chrome через Chrome DevTools Protocol. Она читает DOM после выполнения JavaScript на странице прямо из программы на Go, без отдельного WebDriver или среды Node. Модуль Go встраивается в приложение, но для запуска всё равно нужен внешний исполняемый файл Chrome, жизненный цикл которого контролируется через контексты Go. Установка свелась к одному go get и Chrome, который пришлось предоставить самому: создать allocator, вывести из него context и передать Run список действий.

chromedp — это библиотека на чистом Go с лицензией MIT, которая управляет настоящим Chrome через Chrome DevTools Protocol. Она читает DOM после выполнения JavaScript на странице прямо из программы на Go, без отдельного WebDriver или Node runtime. Модуль Go встраивается в приложение, но для запуска всё равно нужен внешний исполняемый файл Chrome, жизненный цикл которого контролируется через контексты Go.

Установка свелась к одному go get и Chrome, который пришлось предоставить самому: создать allocator, вывести из него context и передать Run список действий. На этом macOS arm64-хосте с уже прогретым headless shell на диске медиана для свежего процесса до первого результата скрипта составила 102 мс. Это локальный ориентир, а не утверждение, что запуск никогда не становится узким местом. На тестовом стенде, где ссылка вставляется через 800 миллисекунд после загрузки, два из четырёх способов чтения вернули результат до того, как ссылка вообще появилась.

Браузер был настоящим, контент был на месте, а код просто не ждал достаточно долго. Именно этот разрыв оказался самым полезным уроком chromedp, и это не баг — это разница между «я отрендерил страницу» и «я дождался именно того, что хотел увидеть». Вся мифология вокруг headless-браузеров часто сводит эти вещи к одному, но wait-стратегия, а не сам браузер, решает, получите ли вы данные. Все числа здесь получены на локальном стенде, который я контролирую, с заранее зафиксированной эталонной разметкой до любого прогона, а сырые сводки лежат в папке chromedp нашего репозитория бенчмарков.

Что такое chromedp на самом деле

Стек chromedp намеренно минимален. Никакого сервера Selenium. Никакой прослойки WebDriver. Никакого Node runtime под капотом. Ваша программа на Go открывает WebSocket-соединение с экземпляром Chrome и напрямую общается с ним по CDP — по сути, по тому же wire-протоколу, который использует Puppeteer, только без JavaScript-обвязки.

Когда я проверял 27 июля 2026 года, у репозитория было 13 212 звёзд и 178 открытых issues, лицензия — MIT. Протестированная мной версия — v0.16.0, это самый свежий тег в репозитории. Важно отметить заранее, чтобы не было путаницы: на странице GitHub Releases всё ещё последним объектом релиза указан v0.15.1 (опубликован 2026-04-01), тогда как go get github.com/chromedp/chromedp@latest разрешается в v0.16.0. Здесь версии в Go modules и на GitHub Releases разошлись. Не поломка, но раздражает, когда пытаешься понять, что именно у тебя запущено.

Ментальная модель здесь — это контексты Go во всей их полноте. Сначала вы создаёте allocator context (он знает, как запускать Chrome), затем на его основе выводите browser context, а потом вызываете chromedp.Run(ctx, actions...) со списком Actions. Дочерний context от browser context — это новая вкладка. Отменили context — исчезает и соответствующий объект. Если вы уже работали с конкурентностью в Go, это ощущается очень естественно; если нет, то наш гайд по началу работы с веб-скрейпингом на Go будет мягче, чем godoc chromedp.

Ещё один важный рубеж: chromedp отдаёт вам отрендеренный DOM. Он не превращает его автоматически в структурированные данные. Всё, что вы извлекаете из DOM — поля, таблицы, цены, — это код, который вы пишете и поддерживаете сами. Это драйвер, а не полноценный скрейпер-фреймворк.

Что происходит внутри

В chromedp всё является Action, а Run выполняет срез таких действий по порядку на целевом объекте. Navigate, Click, Evaluate, OuterHTML, WaitVisible — все они реализуют один интерфейс, их можно комбинировать, и все они по сути являются CDP-командами, обёрнутыми в типы Go. Именно эта унификация — лучшее решение в дизайне библиотеки: и «сахар», и низкоуровневый протокол живут на одном уровне.

Это важно, потому что «сахар» здесь сознательно тонкий. chromedp построен поверх cdproto — сгенерированного набора типизированных Go-обвязок, охватывающих весь DevTools Protocol, а chromedp godoc показывает оба слоя рядом. Если удобного action нет, вы просто переходите к domain-вызову — network.Enable(), page.CaptureScreenshot(), runtime.Evaluate() — внутри того же Run. Между «удобным API» и «настоящим API» здесь нет стены, и это выгодно отличает chromedp от не всех браузерных драйверов.

Именно wait-действия в повседневной работе требуют больше всего здравого смысла, и их здесь больше, чем обычно используют:

Wait-действиеНа что оно ждёт
WaitReady(sel)пока узел не прикрепится к DOM
WaitVisible(sel)пока узел не станет действительно видимым
WaitNotPresent(sel) / WaitNotVisible(sel)обратные варианты, полезны для спиннеров
Poll(js, res)периодически выполняет JavaScript-предикат, пока тот не станет true

Ещё один важный механизм — управление процессами, потому что именно оно решает, останется ли браузер висеть после завершения программы. chromedp запускает Chrome через exec.CommandContext из Go. Отмена этого context убивает процесс. Одна эта деталь объясняет и корректное поведение, и неприятный крайний случай, с которым я столкнулся в тестах.

Установка: бинарник Go плюс Chrome, который вы должны дать сами

go get github.com/chromedp/chromedp без проблем разрешился в v0.16.0, и дерево зависимостей не содержит импортов cgo. Поэтому фраза «чистый Go, без внешних зависимостей», которую часто повторяют, действительно верна — но только в отношении Go-модуля.

Это не относится к runtime. chromedp управляет внешним Chrome, и если Chrome на машине нет, выполнение падает сразу. Во всех своих замерах я явно передавал нужный исполняемый файл через chromedp.ExecPath, указывая на Chrome for Testing 151.0.7922.10 headless shell. Это не претензия — чтобы управлять браузером, нужен браузер. Но «без внешних зависимостей» и «вам нужно поставить рядом с бинарником ещё и 155 МБ Chrome» — это две очень разные истории развёртывания, и в README обычно подчёркивают только одну.

Ещё одна ловушка при настройке отняла у меня время, и её стоит знать до того, как вы напишете первый код. В issue chromedp #1591 описано, что раннер go test в Go 1.25+ отменяет NewExecAllocator во время старта; тот же код как собранный бинарник работает нормально. Я собрал отдельный probe-бинарник через go build и запускал именно его для каждого замера, а не что-либо через go test. Версия Go была 1.26.5, macOS arm64. Если ваш первый опыт с chromedp — это тест, который падает во время запуска Chrome, сначала прочитайте этот issue, а уже потом обвиняйте свой код.

Практика: четыре способа прочитать одну и ту же страницу, два из них пустые

Measured results chart: Which read strategy saw each link?

Тестовый стенд — это локальный сервер на 127.0.0.1, который отдаёт три типа контента, отличающиеся только тем, когда они попадают в DOM: статический <a> в исходных байтах, <a>, созданный встроенным <script> во время начального парсинга, и <a>, созданный setTimeout через заданное число миллисекунд после события load. Маркеры и href для двух ссылок, создаваемых скриптом, собираются в JavaScript из фрагментов строк, поэтому в исходных байтах нет ни одной непрерывной литеральной строки. Следовательно, если что-то «нашлось», это доказывает, что Chrome выполнил JavaScript, а не просто прочитал HTML.

Recall считается в Python по заранее зарегистрированным эталонным маркерам, а не внутри Go-probe, так что probe не может «подглядывать» ответ. Каждый способ запускался три раза; наборы найденного были идентичны во всех трёх прогонах.

Способ чтенияСсылка в статическом HTMLВставлена на этапе парсингаВставлена через 800 мс после loadВремя
Navigate + чтение без ожиданиянайденонайденопропущено317 мс
WaitReady("body")найденонайденопропущено107 мс
WaitVisible("#delayed-injected")найденонайденонайдено912 мс
Poll до появления маркеранайденонайденонайдено972 мс

Две строки возвращаются с двумя ссылками из трёх. Наивное чтение промахивается, потому что Navigate завершается на событии load, а третьей ссылки к этому моменту ещё не существует. WaitReady("body") промахивается по более тонкой причине, и на практике это даже хуже: body прикрепляется к DOM на load, поэтому ожидание считается выполненным мгновенно, и кажется, что вы сделали всё правильно. Оно вернулось за 107 мс, быстрее, чем путь без ожидания, и дало ту же неполную страницу.

Чтобы подтвердить механизм, а не гадать, я изменял задержку вставки и снова прогонял оба крайних варианта (recall-summary.json):

Задержка вставки после loadЧтение без ожидания видит элементWaitVisible видит элементВремя WaitVisible
0 мсда (гонка)да109 мс
100 мснетда208 мс
400 мснетда519 мс
800 мснетда911 мс
1500 мснетда1625 мс

Время WaitVisible на этом стенде следует за задержкой вставки — 100→208, 400→519, 800→911, 1500→1625 — это признак того, что ожидание блокировалось до появления узла, а не читало страницу слишком рано. Строка 0 мс — это граничный случай: setTimeout(…, 0) может сработать раньше немедленного чтения, поэтому путь без ожидания иногда успевает поймать элемент. Начиная со 100 мс в этом проходе путь без ожидания не находил его ни разу.

В продакшене та же ошибка по времени может дать валидный HTML с нулём извлечённых строк и завершение с кодом 0, если пайплайн не проверяет количество результатов. Это правдоподобный сценарий отказа, подтверждённый поведением стенда, а не зафиксированный здесь инцидент. Рендеринг — это только половина задачи; чтение должно ждать условия на уровне приложения, связанного с нужными данными.

WaitReady и WaitVisible не конкурируют — они отвечают на разные вопросы

Часто говорят, что WaitVisible «надёжнее», чем WaitReady. Формулировка слишком расплывчатая и может вредить. На странице, где узел прикреплён к DOM, но скрыт стилем display: none, различие видно очень чётко (waitsem-summary.json, три одинаковых прогона):

Целевой узелДействиеРезультатВремя
прикреплён, display:noneWaitReadyвозвращает результат~6 мс
прикреплён, display:noneWaitVisibleтаймаут, context deadline exceeded4000 мс
видимый узелWaitVisible, query по умолчаниювозвращает результат4–12 мс
видимый узелWaitVisible, ByIDвозвращает результат1–2 мс
видимый узелWaitVisible, ByQueryвозвращает результат1 мс

WaitReady означает «узел прикреплён». WaitVisible означает «узел видим». Задайте не то ожидание — и либо пройдёте мимо контента, который так и не отрисовался, либо будете ждать весь таймаут на узле, который вообще не должен был стать видимым. Само поведение дедлайна корректное — честный context deadline exceeded ровно через 4 секунды, без зависания и без зомби-состояния — и это уже лучше, чем у некоторых драйверов.

Одна из известных ловушек у меня не воспроизвелась. В issue #440 сообщается, что WaitVisible("#id") зависает при query по умолчанию, но на v0.16.0 это не подтвердилось — query по умолчанию, ByID и ByQuery во всех запусках возвращали результат на видимом узле. Не воспроизведено — это не то же самое, что исправлено: это один тип селектора на одной странице, и этого недостаточно, чтобы закрыть issue.

defer cancel(), который вы пропустили, держит всю конструкцию

Счётчики процессов, а не возвращаемые значения. В каждом прогоне жизненного цикла использовался уникальный --user-data-dir, а реальные процессы Chrome browser считались через pgrep с исключением renderer-детей. Каждый путь запускался три раза (lifecycle-summary.json).

Путь выхода (macOS, по 3 прогона)Что произошло с запущенным chrome-headless-shellВремя
Отменить context и allocatorисчез13, 13 и 12 мс
Завершить процесс Go без отменыпереживает вашу программу — до выхода probe браузерных процессов было ноль, после выхода стало одно; по одному осиротевшему процессу в каждом из трёх прогонов—

Отмена работает чисто, быстро и именно так, как обещает exec.CommandContext. (Все осиротевшие процессы затем были принудительно убиты harness’ом; хост был очищен.)

Это известное, задокументированное, зависящее от платформы поведение — замер мой, но открытие не моё. Трекер chromedp рассматривал это с нескольких сторон: #774 описывает такой же невыход на FreeBSD, #752 сообщает о зависших процессах Chromium на macOS, а #562 и #1566 объясняют механизм. Я добавил к этому счётчик процессов и время по обе стороны сравнения — именно того, чего нет в качественных описаниях.

Сам механизм привязан к build tags, и это полезно понимать. В исходниках v0.16.0 файл allocate_linux.go задаёт Pdeathsig = SIGKILL для дочернего процесса, так что Linux получает сигнал смерти родителя на уровне ядра. Файл allocate_other.go, который компилируется на macOS, делает этот вызов пустой операцией. В darwin нет эквивалентного сигнала, поэтому Chrome никто не убивает, когда ваша программа завершается. При этом текст godoc звучит как общее обещание — что команда по умолчанию «посылает SIGKILL всем открытым браузерам при выходе Go-программы» — а ограничение на Linux спрятано только в исходниках с build tags, которые нужно специально читать. Называть это переобещанием документации — справедливо; называть это багом chromedp — нет.

Практический вывод в любом случае один: на macOS defer cancel() — это несущая конструкция. Пропустите его, и каждый запуск будет оставлять процесс браузера. Linux я не тестировал, поэтому не обобщаю результат про осиротевшие процессы на эту платформу — исходники намекают, что там всё иначе, а намёк — это не измерение.

Cold start, конкурентность и скучные вещи, которые определяют ваш деплой

Measured results chart: Cold start and two concurrency shapes

102 мс — медиана от свежего процесса, allocator, context, навигации на localhost и первого Evaluate по пяти процессам, диапазон 98–111 мс (coldstart-summary.json). На этом macOS arm64-стенде с прогретым headless shell на диске старт был незначительным по сравнению с ожиданием отложенного контента. Контейнеры, холодные файловые системы, CI, serverless-среды и навигация в продакшене не измерялись.

По конкурентности chromedp даёт два варианта: один браузер с несколькими дочерними контекстами (вкладками) или несколько независимых браузеров. Четыре навигации, по три прогона каждая (concurrency-summary.json):

РежимWall time (p50)ДиапазонПиковое число процессов Chrome browser
Общий браузер, 4 дочерних контекста214 мс209–2191
4 отдельных браузера264 мс261–2784

Измеряемый вывод здесь — число процессов: один процесс Chrome browser против четырёх для этих четырёх простых локальных навигаций. Диапазоны wall time не пересекались, но это всё ещё скорее направленный сигнал, а не полноценный throughput-бенчмарк. RSS и PSS не измерялись, так что этот тест не доказывает экономию памяти.

Небольшой probe по ошибочным сценариям здесь недостаточно подробен, чтобы делать вывод о надёжности: в черновике не указано, какой именно механизм — HTTP status, ошибка навигации, событие или логика harness’а — зафиксировал каждое условие. Считайте обработку 500/dead-link неописанной, пока не опубликованы точный результат API и сырой артефакт.

Не тестировались, а значит не входят в рамки этих чисел: поведение жизненного цикла на Linux, конкурентность выше N=4 или с реальной работой на каждой странице, дельты памяти (я считал процессы, а не RSS), перехват сети и захват запросов, а также открытые сообщения о таймаутах WaitReady в #168 и #1593 — там речь об intermittent timeout, тогда как я измерял семантику ожидания, а это другой вопрос. Одна машина, одна сборка Chrome.

chromedp — не единственный Go CDP-драйвер на этой площадке: rod прошёл через тот же самый стенд, harness, хост и сборку Chrome в ту же сессию, и у него будет отдельный разбор.

Плюсы и минусы

Плюсы:

  • Настоящий доступ к CDP — удобные actions и сырые вызовы cdproto можно комбинировать в одном Run, так что вы не упираетесь в потолок API.
  • Локальный baseline cold start: 102 мс p50 при разбросе 98–111 мс на протестированном macOS-стенде.
  • Отмена забирает Chrome примерно за 13 мс, стабильно, каждый раз.
  • Дочерние контексты делят один процесс браузера на N вкладок (1 процесс вместо 4 при отдельных браузерах).
  • Детерминированность в тестах — наборы recall, семантика ожидания и поведение жизненного цикла совпадали во всех трёх повторах.
  • Чёткая обработка дедлайнов: WaitVisible на недостижимом условии возвращал корректный context deadline exceeded ровно через 4 секунды, а не зависал.
  • Модуль на чистом Go (без cgo), лицензия MIT; при runtime всё равно нужен внешний Chrome executable.

Минусы:

  • Требует внешний Chrome во время выполнения; репутация «без зависимостей» относится только к Go-модулю.
  • WaitReady("body") — ловушка, которая кажется правильной и тихо пропускает контент, пришедший после load: в моём тесте оно вернулось за 107 мс с неполной страницей.
  • Наивный путь Navigate + чтение пропускает всё, что было вставлено через ≈100 мс после load, детерминированно и без ошибки.
  • На macOS выход без cancel() оставляет осиротевший браузер (3/3 прогона). Это известное и зависящее от платформы поведение, но на него легко наткнуться.
  • Формулировка godoc про SIGKILL при выходе звучит универсально, хотя механизм находится в Linux-only исходниках с build tags.
  • go test на Go 1.25+ может отменять старт allocator’а (#1591); лучше собирать бинарник.
  • Он отдаёт DOM, а не структурированные данные — каждое поле вы должны парсить сами и поддерживать этот код.
  • Самый новый тег (v0.16.0) опережает последний объект GitHub Release (v0.15.1), что на короткое время сбивает с толку при проверке версии.

Для кого chromedp подходит, а кому лучше пройти мимо

Если ваш сервис уже написан на Go и вам нужен внутри него настоящий браузер, chromedp — почти очевидный выбор. Никакого Node-процесса, за которым нужно следить, никакого WebDriver-сервера, который надо держать живым, один собранный бинарник плюс Chrome, который вы ставите или поставляете сами. Модель контекстов ложится на примитивы конкурентности Go настолько естественно, что жизненный цикл браузера начинает подчиняться той же дисциплине defer, что и остальной код. А если вам нужно что-то, чего не покрывает удобный API — события сети CDP, точные хук-события жизненного цикла страницы, трюки на уровне протокола — вы просто переходите в cdproto, не выходя из библиотеки.

Он также хорошо подходит, когда нужен явный контроль над ожиданиями. Wait-действия здесь — это примитивы, а не эвристики; они делают ровно то, что обещают. Это плюс, если вы уже понимаете, что правильно выбрать ожидание теперь ваша задача.

Лучше пройти мимо, если ваша команда не пишет на Go — именно обязательство по языку здесь и есть настоящая цена, а не сама библиотека. Пройти мимо стоит и в том случае, если вам нужна удобная авто-логика ожидания, которая угадывает за вас: chromedp не будет угадывать, он просто выполнит ваш запрос и вернёт то, что страница имела на тот момент. Пропустите его, либо закладывайте серьёзные ресурсы, если вам нужны структурированные записи, а не DOM: каждое поле придётся писать, тестировать и чинить при изменении сайта. И если ваш реальный запрос звучит как «просто достань данные вот с этих 500 URL», то поднимать браузерную оркестрацию на Go — это слишком много механики для такой задачи. Наш гайд по browser automation объясняет, когда эта механика оправдана, а когда нет.

Альтернативы, включая то, где здесь место нашему стеку

В категории браузерных драйверов есть rod — ещё один Go CDP-драйвер, а Playwright и Puppeteer — варианты на Node, которые мы сравниваем в разборе Playwright против Puppeteer. Их контракты ожидания не взаимозаменяемы. Playwright автоматически ждёт actionability перед многими действиями; это всё равно не сообщает ему, когда данные приложения действительно перестали догружаться после действия. chromedp даёт более низкоуровневые примитивы ожидания и оставляет и actionability, и готовность данных на уровне приложения на усмотрение вызывающего кода. Если вы смотрите на поле шире, наш обзор open-source скрейперов, которые мы тестировали охватывает статические краулеры и библиотеки извлечения данных по другую сторону этой границы.

Связанный обзор: обзор Browserless.

Управляемый сервис извлечения — это уже другая категория. Он меняет контроль над браузером и селекторами на аутсорс-рендеринг и формирование схемы. Это может быть полезно, когда результат нужен в виде структурированных записей, а не DOM, тогда как chromedp лучше подходит, когда браузер должен оставаться под контролем вашего Go-сервиса. Мы создаём Thunderbit, один из таких сервисов, но не запускали его на этом стенде; поэтому этот тест не позволяет делать сравнение с chromedp по эквивалентности, задержке, качеству извлечения или стоимости.

Попробовать Thunderbit для извлечения веб-данных

Итог

Стоит ли использовать chromedp? Да, если вы пишете на Go и хотите управлять настоящим браузером. На этом локальном стенде он получил первый результат скрипта за 102 мс p50, завершал Chrome примерно за 13 мс после отмены и использовал один процесс браузера для четырёх одновременных вкладок. Это ограниченные наблюдения, а не универсальные обещания по производительности; устойчивое преимущество — прямой доступ к CDP из Go, когда удобный слой перестаёт хватать.

Только правильно оценивайте обещания, потому что репутация местами перегибает в двух вещах. «Чистый Go, без зависимостей» описывает модуль; в runtime вы всё равно поставляете и управляете бинарником Chrome. А фраза «используйте headless-браузер и получите динамический контент» верна только тогда, когда ваше ожидание привязано к нужному узлу — наивное чтение и WaitReady("body") оба возвращали мне страницу с отсутствующим контентом, который был вставлен через 800 мс после load, и так было каждый раз, без ошибок. На macOS defer cancel() — это не стиль, а необходимость; уберите его, и вы начнёте утекать браузерами на каждый запуск, что является известным поведением платформы, но всё равно остаётся вашей проблемой. Если вы сделаете эти три вещи правильно, chromedp — один из самых предсказуемых браузерных драйверов, которые я измерял. Если ошибиться, он будет тихо возвращать не то, и для скрейпера это самый неприятный тип отказа.

Попробовать Thunderbit для извлечения веб-данных Get Started Free

Частые вопросы

chromedp действительно видит контент, отрендеренный JavaScript? Да — но только если ожидание привязано к нужному узлу. В тестовом стенде, где ссылка вставлялась через 800 мс после события load, Navigate с чтением её пропускал, как и WaitReady("body"), тогда как WaitVisible на этом узле и JavaScript polling оба находили её в трёх из трёх прогонов. При sweep по задержкам путь без ожидания пропускал узел уже начиная со 100 мс. Рендеринг необходим; правильно подождать — это то, что делает его достаточным.

В чём разница между WaitReady и WaitVisible? WaitReady ждёт, пока узел будет прикреплён к DOM. WaitVisible ждёт, пока он станет действительно видимым. На узле, который прикреплён, но скрыт стилем display: none, WaitReady возвращал результат примерно за 6 мс, а WaitVisible доходил до 4-секундного дедлайна и возвращал аккуратный context deadline exceeded. Нельзя сказать, что одно «надёжнее» другого — они отвечают на разные вопросы, и выбрать не то ожидание здесь и есть главная ловушка.

Нужно ли мне реально использовать defer cancel() с chromedp? На macOS — да. Отмена context и allocator убирала запущенный Chrome за 12–13 мс во всех прогонах; если завершать процесс Go без отмены, браузерный процесс оставался сиротой во всех трёх прогонах. Это известное поведение, зависящее от платформы: tracker chromedp документирует тот же паттерн на других не-Linux системах, а сигнал убийства родителя, который это решает, находится в Linux-only исходниках с build tags. Linux я не тестировал, поэтому считайте результат про сироту привязанным к macOS.

Нужен ли chromedp отдельно установленный Chrome? Да. Сам Go-модуль написан на чистом Go без cgo, но он управляет внешним браузером и без него сразу падает. Я явно указывал chromedp.ExecPath на Chrome for Testing 151.0.7922.10 headless shell. Плюс в том, что старт довольно быстрый: полный cold cycle до первого результата скрипта в моих замерах имел медиану 102 мс на пяти свежих процессах, в диапазоне 98–111 мс.

Лучше делить один браузер между вкладками или поднимать отдельные браузеры? Если цель — минимизировать число browser-процессов, лучше использовать дочерние контексты. Четыре локальные навигации через один браузер использовали 1 процесс Chrome browser; четыре отдельных браузера — 4. По wall time общий вариант тоже был лучше (214 мс против 264 мс медианы), но четыре простые локальные страницы — это не throughput-бенчмарк. Память я не измерял. Отдельные браузеры всё ещё могут быть правильным выбором, если нужна более жёсткая изоляция сессий, разные прокси или меньшая зона поражения при сбое; эти компромиссы вне рамок этого теста.

Ke
Ke
Технический директор в Thunderbit | Senior Data Scientist и эксперт по ML Имея почти десятилетний опыт в машинном обучении и data science, Кэ Шэнь — выпускник Колумбийского университета и бывший Senior Data Scientist в Walmart Labs. Обладая глубокой, признанной коллегами экспертизой в Python, R, Java и статистике, он делится проверенными на практике наблюдениями о том, как переводить сложные AI-алгоритмы из теории в production-grade архитектуру.
Содержание
Thunderbit · AI-агент для веб-данных

Извлекай данные с любой страницы за 1 клик

Нам доверяют более 250 000 пользователей
доступен бесплатный тариф
От веб-страницы к таблице
Опиши, что тебе нужно, — AI-агент Thunderbit соберет это и экспортирует в Excel, Google Sheets, Airtable или Notion. Старт бесплатный.
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week