Agente de IA na web: um Chrome real com GPU para o Claude
Um agente de IA que age na web (preencher um formulário, comparar ofertas, acompanhar um painel) precisa de um navegador real a correr em contínuo, noutro sítio que não o seu portátil. Dois erros fazem com que seja identificado como robô logo na primeira página:
- lançá-lo em modo headless: o Chrome anuncia-se então a si próprio
como
HeadlessChromeno seu User-Agent; - lançá-lo sem GPU: a placa gráfica passa a ser
SwiftShader(uma GPU emulada), ou o WebGL desaparece por completo.
Um PC de secretária nunca tem estas características. A regra é, portanto, simples: um Chrome real, em janela, num ecrã virtual, com uma GPU real.
Este artigo mostra como pôr o Chrome a correr com a GPU para um agente Claude, compara Xvfb e Xorg «dummy», cobre o Selenium e explica o que a GPU muda (ou não) face à deteção de bots da Cloudflare. Todas as medições são reproduzíveis: o script é público.
A arquitetura
- O agente (Claude Code ou o Claude Agent SDK) decide as ações;
- o Playwright MCP expõe o navegador ao agente sob a forma de ferramentas
(
browser_navigate,browser_click,browser_type,browser_snapshot…) e controla o Chromium por CDP (Chrome DevTools Protocol); - o Chromium corre em modo de janela num ecrã virtual Xvfb e faz a renderização na GPU via ANGLE/Vulkan;
- o x11vnc permite-lhe ver o ecrã e retomar o controlo (para uma autenticação de dois fatores, por exemplo).
O desenrolar de uma ação:
As medições
Condições
- Servidor de teste Linux GPU, VM configurada como a oferta Perf 2 (4 vCPU), GPU Intel UHD 770 partilhada entre várias VM (a VM vê o seu próprio dispositivo GPU, /dev/dri, com o driver Linux padrão), Debian 13;
- Chromium 156, Playwright MCP 0.0.83, Selenium 4.31 + chromedriver 156,
Xvfb (Xorg 21) e
xserver-xorg-video-dummy0.4.0; - a GPU não estava a ser usada por outras VM durante as medições;
- medições de 29/10/2026.
Como é calculado
O banco de ensaio é uma página WebGL 2 (webgl-bench.html):
- um canvas de 1920×1080;
- um fragment shader que calcula 64 senos/cossenos por píxel, ou seja, cerca de 265 milhões de senos/cossenos por imagem;
?draws=Ndesenha N imagens em ecrã inteiro por frame (requestAnimationFrame);- após cada frame,
readPixelslê um píxel: isso obriga o navegador a terminar a renderização; caso contrário, poderia pôr os comandos em fila de espera e mostrar uma pontuação lisonjeira mas falsa; - ao fim de 5 s: imagens/s = número de imagens ÷ duração. Verifica-se também que o programa está bem ligado e que o píxel lido tem a cor certa.
O Chrome é lançado com --disable-gpu-vsync --disable-frame-rate-limit para
não ficar limitado a 60 imagens/s. A carga de CPU é a diferença dos
contadores de /proc/stat apenas durante o teste, expressa em vCPU ocupados.
chromium --headless=new --use-angle=vulkan --ignore-gpu-blocklist \
--disable-gpu-vsync --disable-frame-rate-limit \
--enable-logging=stderr --v=0 "file:///root/webgl-bench.html?draws=10" \
2>&1 | grep -m1 RESULT
Resultado 1: a GPU faz a diferença (×20)
--headless=new, 10 imagens por frame:
| Renderização | Imagens 1080p/s | CPU ocupada |
|---|---|---|
| SwiftShader (por omissão, CPU) | 13 | 3,9 vCPU de 4 |
UHD 770, --use-angle=vulkan | ~260 | 0,6 vCPU |
UHD 770, --use-angle=gl-egl | ~230 | — |
Com a GPU, a renderização é 20 vezes mais rápida e o processador fica livre. Para um agente, é o ponto-chave: sem GPU, uma página pesada (mapa, painel, vídeo) satura os 4 vCPU, e o agente, o Playwright e o ecrã virtual disputam o que sobra.
Resultado 2: Xvfb ou Xorg dummy, tanto faz
Ouve-se muitas vezes que é preciso substituir o Xvfb pelo Xorg com o driver dummy para «parecer mais real». Medido, em modo de janela, 1 imagem por frame:
| Ecrã virtual | Sem opções | SwiftShader forçado | GPU (--use-angle=vulkan) |
|---|---|---|---|
| Xvfb | WebGL desativado | 9,2 imagens/s | 67,6 imagens/s |
| Xorg + driver dummy | WebGL desativado | 8,7 imagens/s | 58,8 imagens/s, depois contexto WebGL perdido |
- Nenhum dos dois usa a GPU por si só: o
glxinfomostrallvmpipe(renderização por software) em ambos; - sem opções, o Chrome desativa o WebGL em ambos;
- com o driver dummy, o nosso teste com GPU perdeu o contexto WebGL ao fim
de algumas imagens (
eglCreateWindowSurface failed with error EGL_BAD_MATCH). O Xvfb, por seu lado, manteve-se estável.
A escolha do ecrã virtual é, portanto, secundária: é a GPU que conta para o desempenho, na maior parte das vezes. Fique pelo Xvfb, mais simples, e acrescente as duas opções de GPU ao Chrome.
Resultado 3: que motor de renderização o site vê
É o que um script de deteção lê (WEBGL_debug_renderer_info):
| Configuração | Motor de renderização WebGL anunciado |
|---|---|
--headless=new sem opções | SwiftShader (CPU emulada) |
| Xvfb ou dummy, sem opções | sem WebGL |
| Selenium + Xvfb, sem opções | sem WebGL |
| Selenium + Xvfb, opções de GPU | Intel(R) UHD Graphics 770 |
| Playwright MCP, em janela no Xvfb, opções de GPU | Intel(R) UHD Graphics 770 |
| headless, opções de GPU | Intel(R) UHD Graphics 770 |
Resultado 4: headless = identificado de imediato
O que um script JavaScript lê numa linha na página, nas quatro configurações (Chromium 156):
| Headless sem GPU | Headless + GPU | Janela Xvfb sem GPU | Janela Xvfb + GPU | |
|---|---|---|---|---|
navigator.userAgent | ❌ HeadlessChrome/156… | ❌ HeadlessChrome/156… | ✅ Chrome/156… | ✅ Chrome/156… |
| Placa gráfica (WebGL) | ❌ SwiftShader | ✅ Intel UHD Graphics 770 | ❌ nenhuma (WebGL desligado) | ✅ Intel UHD Graphics 770 |
screen | ❌ 800×600 | ❌ 800×600 | ✅ 1920×1080 | ✅ 1920×1080 |
| Tamanho da janela | 780×580 | 780×580 | 945×1060 | 1919×1079 |
- O modo headless denuncia-se a si próprio: a palavra
HeadlessChromeestá escrita no User-Agent, enviado em cada pedido. É o sinal mais simples de bloquear, e todos os sistemas anti-bots o fazem. O ecrã de 800×600 confirma o diagnóstico; - sem GPU, mesmo em janela, o WebGL desaparece: nenhum navegador de secretária moderno está nesta situação;
- só a última coluna se parece com um posto de trabalho real: um Chrome em janela num ecrã 1080p, com uma placa gráfica Intel real.
Corrigir o User-Agent à mão não chega: o modo headless tem outras diferenças (tamanho do ecrã, comportamento da renderização, API em falta), e um User-Agent que não bate certo com o resto é, ele próprio, suspeito. O mais fiável é não usar o modo headless de todo: num servidor, é exatamente para isso que serve o Xvfb.
Resultado 5: em janela, a GPU alivia enormemente a CPU
O modo de janela tem um custo: o Chrome tem de desenhar a página inteira no ecrã. Sem GPU, é o processador que faz tudo. A mesma animação WebGL 1080p, Chrome em janela no Xvfb, frequência normal (60 imagens/s no máximo), carga de CPU medida em toda a VM durante o teste:
| Chrome em janela no Xvfb | Imagens/s | CPU ocupada (VM de 4 vCPU) |
|---|---|---|
| Sem GPU (SwiftShader) | 9,8 | 3,4 vCPU: a VM está saturada |
| Sem GPU, cena 3× mais pesada | 4,0 | 3,7 vCPU |
| Com a GPU UHD 770 | 60 (fluido) | 0,3 vCPU |
| Com a GPU, cena 3× mais pesada | 60 | 0,3 vCPU |
Com a GPU, o Chrome mostra 6 vezes mais imagens usando mais de 10 vezes menos CPU, ou seja, cerca de 70 vezes menos CPU por imagem. Sem GPU, a VM fica saturada e nem sequer chega às 10 imagens/s: o agente, o Playwright e o resto do sistema lutam pelas migalhas, cada ação demora mais e as páginas pesadas tornam-se inutilizáveis. Com a GPU, sobram 3,7 vCPU livres para o trabalho útil.
É a combinação vencedora: o modo de janela evita ser identificado, e a GPU torna-o gratuito para o processador.
O que a GPU muda face à Cloudflare
A Cloudflare (Bot Management, Turnstile) combina muitos sinais: impressão
digital TLS e HTTP/2, reputação do endereço IP, testes JavaScript na página,
comportamento (ritmo, movimentos), modelos estatísticos. Um navegador
sem GPU denuncia-se depressa nos testes JavaScript: SwiftShader como
placa gráfica, ou nenhum WebGL, é uma assinatura típica de navegador
automatizado num datacenter.
Com a GPU, o Chrome anuncia uma placa Intel real e renderiza a página como um PC de secretária. Na prática, isso reduz os falsos positivos: CAPTCHA ou bloqueios em sites onde o seu agente tem todo o direito de ir.
Mas convém ser claro: CDP + driver + bom IP não torna o navegador indetetável.
- o controlo por CDP deixa, ele próprio, vestígios detetáveis;
- os endereços de datacenter são conhecidos como tal;
- o comportamento de um agente (velocidade, encadeamento das ações) deteta-se;
- as regras mudam constantemente: o que passa hoje será bloqueado amanhã.
A abordagem certa: fazer-se reconhecer
Para um agente legítimo, o caminho duradouro é ser identificado em vez de escondido:
- Web Bot Auth («signed agents» na Cloudflare): o agente assina os seus pedidos HTTP com uma chave Ed25519 (HTTP Message Signatures, RFC 9421) e publica a sua chave pública num diretório; os sites podem então autorizá-lo explicitamente;
- um User-Agent honesto, o respeito pelo
robots.txte pelos termos de utilização, um débito razoável; - nos seus próprios sites atrás da Cloudflare (testes E2E, monitorização), uma regra WAF que deixa passar o IP do seu servidor.
Implementação
1. Pacotes
apt install chromium xvfb x11vnc nodejs npm
2. Ecrã virtual e VNC (systemd)
# /etc/systemd/system/xvfb.service
[Unit]
Description=Xvfb :99
[Service]
ExecStart=/usr/bin/Xvfb :99 -screen 0 1920x1080x24 -nolisten tcp
Restart=always
[Install]
WantedBy=multi-user.target
# /etc/systemd/system/x11vnc.service
[Unit]
Description=x11vnc em :99 (apenas localhost)
After=xvfb.service
Requires=xvfb.service
[Service]
ExecStart=/usr/bin/x11vnc -display :99 -localhost -forever -shared -nopw
Restart=always
[Install]
WantedBy=multi-user.target
systemctl enable --now xvfb x11vnc
# a partir do seu computador:
ssh -L 5900:localhost:5900 root@seu-servidor # depois um cliente VNC em localhost:5900
O VNC só escuta em localhost: só se lhe acede pelo túnel SSH.
3. Playwright MCP com a GPU
Ficheiro /root/mcp.json:
{
"browser": {
"browserName": "chromium",
"launchOptions": {
"executablePath": "/usr/bin/chromium",
"headless": false,
"args": ["--use-angle=vulkan", "--ignore-gpu-blocklist", "--no-sandbox"]
}
}
}
Com o Claude Code instalado no servidor:
claude mcp add playwright --env DISPLAY=:99 -- \
npx @playwright/mcp@latest --config /root/mcp.json
Ou como servidor HTTP, se o agente corre noutro sítio (túnel SSH para a porta):
DISPLAY=:99 npx @playwright/mcp@latest --config /root/mcp.json --port 8931
# ponto de acesso MCP: http://localhost:8931/mcp
Para verificar, peça ao agente que abra uma página que mostre o motor de
renderização WebGL: deve anunciar Intel(R) UHD Graphics 770.
4. Selenium + Xvfb
apt install python3-selenium chromium-driver
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
o = webdriver.ChromeOptions()
o.binary_location = "/usr/bin/chromium"
for a in ["--use-angle=vulkan", "--ignore-gpu-blocklist",
"--no-sandbox", "--window-size=1920,1080"]:
o.add_argument(a)
driver = webdriver.Chrome(options=o, service=Service("/usr/bin/chromedriver"))
xvfb-run -a -s "-screen 0 1920x1080x24" python3 meu_script.py
Com o Selenium 4.31 do Debian, indique explicitamente o chromedriver via
Service(...): caso contrário, não o encontra (NoSuchDriverException).
Perguntas frequentes
Headless ou em janela no Xvfb?
Em janela, sem hesitar. O headless anuncia-se como HeadlessChrome e é
bloqueado na maioria dos sites protegidos. Além disso, o modo de janela
permite seguir o agente por VNC e retomar o controlo. Reserve o headless
para os seus próprios testes, nos seus próprios sites.
E o computer use do Claude (capturas de ecrã + cliques)? Precisa de um ecrã real: Xvfb + um navegador em janela, exatamente o esquema acima. A GPU renderiza cada captura mais depressa e deixa a CPU para o resto.
Uma oferta Perf 1 é suficiente? Para um agente que navega em sites comuns, sim. As medições acima foram feitas com 4 vCPU (Perf 2), útil se o agente abrir vários separadores ou páginas pesadas.
Experimentar
Servidor Linux GPU com Intel UHD 770 na VM, Mesa e Vulkan já instalados. A partir de 14,99 € s/ IVA/mês, acesso root, IPv6 dedicado. Veja também: Chrome com GPU num servidor Linux e Xvfb: guia prático.