Voltar ao blog

Como contornar a detecção de navegador headless ao fazer scraping: disfarçando Selenium e Puppeteer

Descubra como contornar a proteção contra navegadores headless ao fazer scraping: técnicas de mascaramento com Selenium, Puppeteer e Playwright, incluindo exemplos de código e configuração de proxy.

📅10 de janeiro de 2026
```html

Sites modernos aprenderam a reconhecer navegadores automatizados (Selenium, Puppeteer, Playwright) e bloqueiam suas solicitações. Navegadores headless deixam dezenas de rastros digitais, pelos quais sistemas anti-bot detectam a automação em milissegundos. Neste guia, vamos explorar todos os métodos de mascaramento de navegadores headless com exemplos de código em Python e JavaScript, para que seus scrapers funcionem de forma estável sem bloqueios.

O artigo é destinado a desenvolvedores que trabalham com web scraping, automação de testes ou coleta de dados de sites protegidos. Vamos discutir os detalhes técnicos da detecção e soluções práticas para contornar a proteção.

Como os sites detectam navegadores headless: métodos principais

Sistemas anti-bot utilizam uma verificação em múltiplas camadas do navegador, analisando centenas de parâmetros. Navegadores headless diferem dos normais em muitos aspectos que não podem ser ocultados apenas trocando o User-Agent. Compreender os métodos de detecção é o primeiro passo para um mascaramento eficaz.

Marcadores JavaScript de automação

O método mais comum é a verificação de propriedades JavaScript que aparecem apenas em navegadores automatizados:

  • navigator.webdriver — retorna true em Selenium e Puppeteer
  • window.chrome — ausente no Chrome headless
  • navigator.plugins.length — igual a 0 no modo headless
  • navigator.languages — frequentemente um array vazio ou contém apenas "en-US"
  • navigator.permissions — a API funciona de maneira diferente no modo headless

Análise do Chrome DevTools Protocol

Puppeteer e Playwright controlam o navegador através do Chrome DevTools Protocol (CDP). A presença de uma conexão CDP pode ser detectada através de verificações JavaScript específicas que analisam objetos window.cdc_ ou verificam anomalias no comportamento de eventos de mouse e teclado.

Canvas e WebGL Fingerprinting

Navegadores headless geram impressões de Canvas e WebGL idênticas, pois utilizam renderização por software em vez de hardware. Sistemas anti-bot criam um elemento Canvas invisível, desenham texto ou formas nele e calculam o hash da imagem. Se milhares de usuários têm um hash idêntico — isso é um sinal de bots.

Análise comportamental

Sistemas modernos (DataDome, PerimeterX, Cloudflare Bot Management) analisam movimentos do mouse, velocidade de rolagem, padrões de cliques. Navegadores headless executam ações instantaneamente e sem atrasos naturais, o que revela a automação. Eventos também são analisados: em um navegador normal, sempre ocorre um evento mousemove antes do clique, enquanto bots frequentemente clicam sem movimento prévio do mouse.

Importante: Sistemas anti-bot modernos utilizam aprendizado de máquina para analisar centenas de parâmetros simultaneamente. Mascarar apenas um parâmetro (por exemplo, User-Agent) não protegerá contra bloqueios — é necessária uma abordagem abrangente.

Remoção de navigator.webdriver e outros marcadores JavaScript

A propriedade navigator.webdriver é o método mais simples de detecção de Selenium e outras ferramentas WebDriver. Em um navegador normal, essa propriedade retorna undefined, enquanto em um automatizado — true. É possível removê-la executando código JavaScript antes de carregar a página.

Selenium (Python): remoção do webdriver através do CDP

Para Selenium, é necessário usar o Chrome DevTools Protocol para executar JavaScript antes de carregar qualquer página:

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

options = Options()
options.add_argument('--disable-blink-features=AutomationControlled')

driver = webdriver.Chrome(options=options)

# Remoção de navigator.webdriver através do CDP
driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {
    'source': '''
        Object.defineProperty(navigator, 'webdriver', {
            get: () => undefined
        });
    '''
})

driver.get('https://example.com')

A opção --disable-blink-features=AutomationControlled desativa a flag que o Chrome adiciona no modo de automação. Esta é uma proteção básica que deve ser combinada com outros métodos.

Puppeteer (Node.js): mascaramento através de Page.evaluateOnNewDocument

No Puppeteer, utiliza-se o método page.evaluateOnNewDocument() para executar código antes de carregar a página:

const puppeteer = require('puppeteer');

(async () => {
    const browser = await puppeteer.launch({
        headless: true,
        args: ['--disable-blink-features=AutomationControlled']
    });
    
    const page = await browser.newPage();
    
    // Remoção do webdriver e outros marcadores
    await page.evaluateOnNewDocument(() => {
        Object.defineProperty(navigator, 'webdriver', {
            get: () => undefined
        });
        
        // Adição do objeto chrome
        window.chrome = {
            runtime: {}
        };
        
        // Emulação de plugins
        Object.defineProperty(navigator, 'plugins', {
            get: () => [1, 2, 3, 4, 5]
        });
    });
    
    await page.goto('https://example.com');
})();

Playwright: opções de mascaramento integradas

O Playwright possui um mascaramento mais avançado por padrão, mas ainda assim requer configuração adicional:

const { chromium } = require('playwright');

(async () => {
    const browser = await chromium.launch({
        headless: true,
        args: ['--disable-blink-features=AutomationControlled']
    });
    
    const context = await browser.newContext({
        userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
    });
    
    const page = await context.newPage();
    
    await page.addInitScript(() => {
        Object.defineProperty(navigator, 'webdriver', {
            get: () => undefined
        });
    });
    
    await page.goto('https://example.com');
})();

Mascaramento do Chrome DevTools Protocol

Puppeteer e Playwright deixam rastros de conexão CDP que podem ser detectados através da análise de objetos window. Sistemas anti-bot buscam variáveis com o prefixo cdc_, $cdc_ ou __webdriver_, que são criadas pelo Chrome ao se conectar através do DevTools Protocol.

Remoção de variáveis CDP

O seguinte script remove todas as variáveis relacionadas à automação do objeto window:

await page.evaluateOnNewDocument(() => {
    // Remoção de todas as variáveis com o prefixo cdc_
    const cdcProps = Object.keys(window).filter(prop => 
        prop.includes('cdc_') || 
        prop.includes('$cdc_') || 
        prop.includes('__webdriver_')
    );
    
    cdcProps.forEach(prop => {
        delete window[prop];
    });
    
    // Redefinição de document.__proto__ para ocultar CDP
    const originalQuery = document.querySelector;
    document.querySelector = function(selector) {
        if (selector.includes('cdc_')) return null;
        return originalQuery.call(this, selector);
    };
});

Uso de versões patchadas do Chromium

Existem builds modificadas do Chromium que não deixam rastros de CDP. Por exemplo, a biblioteca puppeteer-extra com o plugin puppeteer-extra-plugin-stealth aplica automaticamente dezenas de patches para mascarar CDP.

Configuração de User-Agent e cabeçalhos HTTP

Navegadores headless utilizam strings de User-Agent desatualizadas ou não realistas. Por exemplo, o Puppeteer adiciona por padrão a palavra "HeadlessChrome" no User-Agent. Além disso, é necessário configurar cabeçalhos adicionais que estão presentes nas solicitações de navegadores normais.

User-Agent atualizados para mascaramento

Utilize User-Agent recentes de navegadores reais. Aqui estão exemplos para 2024:

# Chrome 120 no Windows 10
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36

# Chrome 120 no macOS
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36

# Firefox 121 no Windows
Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0

Configuração de cabeçalhos no Selenium

from selenium.webdriver.chrome.options import Options

options = Options()
options.add_argument('user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36')

# Cabeçalhos adicionais através do CDP
driver.execute_cdp_cmd('Network.setUserAgentOverride', {
    "userAgent": 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
    "platform": 'Win32',
    "acceptLanguage": 'en-US,en;q=0.9'
})

Configuração de cabeçalhos no Puppeteer

await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36');

await page.setExtraHTTPHeaders({
    'Accept-Language': 'en-US,en;q=0.9',
    'Accept-Encoding': 'gzip, deflate, br',
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
    'Sec-Fetch-Site': 'none',
    'Sec-Fetch-Mode': 'navigate',
    'Sec-Fetch-User': '?1',
    'Sec-Fetch-Dest': 'document'
});

Os cabeçalhos Sec-Fetch-* são críticos — eles apareceram no Chrome 76+ e sua ausência revela versões antigas de navegadores ou bots.

Emulação de Canvas e WebGL Fingerprint

Canvas e WebGL fingerprinting são métodos poderosos de detecção, pois navegadores headless geram impressões idênticas. Sistemas anti-bot criam um Canvas invisível, desenham texto nele e calculam o hash dos pixels. Se milhares de solicitações têm o mesmo hash — são bots.

Adicionando ruído ao Canvas

O seguinte script adiciona ruído aleatório à impressão do Canvas, tornando cada solicitação única:

await page.evaluateOnNewDocument(() => {
    const originalToDataURL = HTMLCanvasElement.prototype.toDataURL;
    const originalToBlob = HTMLCanvasElement.prototype.toBlob;
    const originalGetImageData = CanvasRenderingContext2D.prototype.getImageData;
    
    // Função para adicionar ruído
    const addNoise = (canvas, context) => {
        const imageData = originalGetImageData.call(context, 0, 0, canvas.width, canvas.height);
        for (let i = 0; i < imageData.data.length; i += 4) {
            imageData.data[i] += Math.floor(Math.random() * 10) - 5;
            imageData.data[i + 1] += Math.floor(Math.random() * 10) - 5;
            imageData.data[i + 2] += Math.floor(Math.random() * 10) - 5;
        }
        context.putImageData(imageData, 0, 0);
    };
    
    HTMLCanvasElement.prototype.toDataURL = function() {
        addNoise(this, this.getContext('2d'));
        return originalToDataURL.apply(this, arguments);
    };
});

Emulação de parâmetros WebGL

WebGL revela informações sobre a placa de vídeo e drivers. No modo headless, esses parâmetros revelam renderização por software:

await page.evaluateOnNewDocument(() => {
    const getParameter = WebGLRenderingContext.prototype.getParameter;
    WebGLRenderingContext.prototype.getParameter = function(parameter) {
        // Emulação de uma placa de vídeo real
        if (parameter === 37445) {
            return 'Intel Inc.';
        }
        if (parameter === 37446) {
            return 'Intel Iris OpenGL Engine';
        }
        return getParameter.call(this, parameter);
    };
});

O parâmetro 37445 é UNMASKED_VENDOR_WEBGL, e 37446 é UNMASKED_RENDERER_WEBGL. No modo headless, eles retornam "Google SwiftShader", o que revela a automação.

Selenium Stealth: soluções prontas para Python

A biblioteca selenium-stealth aplica automaticamente dezenas de patches para mascarar o Selenium. Esta é a solução mais simples para desenvolvedores Python, que não requer configuração manual de cada parâmetro.

Instalação e configuração básica

pip install selenium-stealth
from selenium import webdriver
from selenium_stealth import stealth
from selenium.webdriver.chrome.options import Options

options = Options()
options.add_argument("--headless")
options.add_argument("--no-sandbox")
options.add_argument("--disable-dev-shm-usage")

driver = webdriver.Chrome(options=options)

# Aplicação dos patches stealth
stealth(driver,
    languages=["en-US", "en"],
    vendor="Google Inc.",
    platform="Win32",
    webgl_vendor="Intel Inc.",
    renderer="Intel Iris OpenGL Engine",
    fix_hairline=True,
)

driver.get("https://bot.sannysoft.com")
driver.save_screenshot("test.png")

A biblioteca remove automaticamente navigator.webdriver, adiciona window.chrome, emula plugins, mascara WebGL e aplica mais de 20 patches. Isso cobre 80% dos casos de detecção.

Configuração avançada com proxies

Para um mascaramento completo, combine selenium-stealth com proxies residenciais — eles fornecem IPs reais de usuários domésticos, o que é crítico para contornar sistemas anti-bot avançados:

from selenium.webdriver.common.proxy import Proxy, ProxyType

proxy = Proxy()
proxy.proxy_type = ProxyType.MANUAL
proxy.http_proxy = "ip:port"
proxy.ssl_proxy = "ip:port"

capabilities = webdriver.DesiredCapabilities.CHROME
proxy.add_to_capabilities(capabilities)

driver = webdriver.Chrome(desired_capabilities=capabilities, options=options)
stealth(driver, languages=["en-US", "en"], vendor="Google Inc.", platform="Win32")

Puppeteer Extra Stealth Plugin para Node.js

Para Puppeteer, existe o plugin puppeteer-extra-plugin-stealth, que é a solução mais avançada para mascarar navegadores headless no ecossistema JavaScript. Ele contém 23 módulos independentes, cada um mascarando um aspecto específico da automação.

Instalação e uso básico

npm install puppeteer-extra puppeteer-extra-plugin-stealth
const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');

puppeteer.use(StealthPlugin());

(async () => {
    const browser = await puppeteer.launch({ 
        headless: true,
        args: ['--no-sandbox', '--disable-setuid-sandbox']
    });
    
    const page = await browser.newPage();
    await page.goto('https://bot.sannysoft.com');
    await page.screenshot({ path: 'test.png' });
    await browser.close();
})();

O que o Stealth Plugin mascara

O plugin aplica automaticamente os seguintes patches:

  • Remoção de navigator.webdriver
  • Adição do objeto window.chrome
  • Emulação da API navigator.permissions
  • Mascaramento de navigator.plugins e navigator.mimeTypes
  • Emulação de Canvas e WebGL fingerprint
  • Mascaramento de User-Agent e idiomas
  • Correção de anomalias no iframe contentWindow
  • Emulação da API Battery, Media Devices, WebRTC

Configuração com proxies e parâmetros adicionais

const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
const AdblockerPlugin = require('puppeteer-extra-plugin-adblocker');

puppeteer.use(StealthPlugin());
puppeteer.use(AdblockerPlugin({ blockTrackers: true }));

(async () => {
    const browser = await puppeteer.launch({
        headless: true,
        args: [
            '--proxy-server=http://your-proxy:port',
            '--disable-web-security',
            '--disable-features=IsolateOrigins,site-per-process'
        ]
    });
    
    const page = await browser.newPage();
    
    // Autenticação do proxy
    await page.authenticate({
        username: 'your-username',
        password: 'your-password'
    });
    
    await page.setViewport({ width: 1920, height: 1080 });
    await page.goto('https://example.com', { waitUntil: 'networkidle2' });
})();

Playwright: configuração de anti-detectores

O Playwright possui uma arquitetura mais sofisticada em comparação ao Puppeteer e deixa menos rastros de automação. No entanto, para contornar sistemas anti-bot avançados, é necessária configuração adicional. Para o Playwright, existe a versão do plugin stealth — playwright-extra.

Instalação do playwright-extra

npm install playwright-extra playwright-extra-plugin-stealth playwright
const { chromium } = require('playwright-extra');
const stealth = require('playwright-extra-plugin-stealth');

chromium.use(stealth());

(async () => {
    const browser = await chromium.launch({ headless: true });
    const context = await browser.newContext({
        userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
        viewport: { width: 1920, height: 1080 },
        locale: 'en-US',
        timezoneId: 'America/New_York'
    });
    
    const page = await context.newPage();
    await page.goto('https://bot.sannysoft.com');
    await page.screenshot({ path: 'test.png' });
    await browser.close();
})();

Configuração do contexto do navegador para máxima mascaramento

O Playwright permite a criação de contextos de navegador isolados com configurações individuais. Isso é útil para multi-contas ou scraping paralelo:

const context = await browser.newContext({
    userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
    viewport: { width: 1440, height: 900 },
    locale: 'en-US',
    timezoneId: 'America/Los_Angeles',
    permissions: ['geolocation'],
    geolocation: { latitude: 37.7749, longitude: -122.4194 },
    colorScheme: 'light',
    deviceScaleFactor: 2,
    isMobile: false,
    hasTouch: false,
    // Configuração de proxy para o contexto
    proxy: {
        server: 'http://your-proxy:port',
        username: 'user',
        password: 'pass'
    }
});

Os parâmetros geolocation e timezoneId devem corresponder ao IP do proxy, caso contrário, os sistemas anti-bot detectarão a discrepância (por exemplo, IP da Califórnia, mas timezone de Nova York).

Rotação de proxies para reduzir o risco de bloqueio

Mesmo um navegador headless perfeitamente mascarado pode ser bloqueado se usar um único IP para centenas de solicitações. Sistemas anti-bot modernos analisam a frequência de solicitações de um único IP e bloqueiam atividades suspeitas. A rotação de proxies é um elemento obrigatório de proteção durante o scraping.

Tipos de proxies para scraping: comparação

Tipo de proxy Velocidade Trust Score Melhor para
Datacenter Muito alta (50-200 ms) Baixa Sites simples, scraping em massa
Residenciais Média (300-1000 ms) Alta Sites protegidos, redes sociais
Móveis Baixa (500-2000 ms) Muito alta Aplicativos móveis, Instagram, TikTok

Para scraping de sites protegidos (marketplaces, redes sociais, plataformas de anúncios), são recomendados proxies residenciais, pois eles possuem IPs reais de usuários domésticos e não entram em listas negras de sistemas anti-bot.

Implementação da rotação de proxies no Puppeteer

const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');

puppeteer.use(StealthPlugin());

const proxyList = [
    'http://user1:pass1@proxy1:port',
    'http://user2:pass2@proxy2:port',
    'http://user3:pass3@proxy3:port'
];

async function scrapeWithRotation(urls) {
    for (let i = 0; i < urls.length; i++) {
        const proxy = proxyList[i % proxyList.length];
        
        const browser = await puppeteer.launch({
            headless: true,
            args: [`--proxy-server=${proxy}`]
        });
        
        const page = await browser.newPage();
        
        try {
            await page.goto(urls[i], { waitUntil: 'networkidle2' });
            const data = await page.evaluate(() => document.body.innerText);
            console.log(data);
        } catch (error) {
            console.error(`Erro em ${urls[i]}:`, error);
        } finally {
            await browser.close();
        }
        
        // Atraso entre solicitações
        await new Promise(resolve => setTimeout(resolve, 2000));
    }
}

scrapeWithRotation([
    'https://example1.com',
    'https://example2.com',
    'https://example3.com'
]);

Rotação através de proxies baseados em sessão

Alguns provedores de proxies (incluindo ProxyCove) oferecem rotação baseada em sessão — cada solicitação recebe automaticamente um novo IP sem a necessidade de reiniciar o navegador. Isso é implementado através de um formato especial de URL de proxy:

// Formato: username-session-RANDOM:password@gateway:port
const generateSessionProxy = () => {
    const sessionId = Math.random().toString(36).substring(7);
    return `http://username-session-${sessionId}:password@gateway.proxycove.com:12321`;
};

const browser = await puppeteer.launch({
    args: [`--proxy-server=${generateSessionProxy()}`]
});

Como verificar a qualidade do mascaramento: ferramentas de teste

Após configurar o mascaramento, é necessário verificar quão bem seu navegador headless imita um usuário comum. Existem vários sites especializados que analisam dezenas de parâmetros do navegador e mostram quais rastros de automação permaneceram.

Principais ferramentas de teste

  • bot.sannysoft.com — verifica mais de 15 parâmetros de detecção, incluindo webdriver, objeto Chrome, plugins, Canvas
  • arh.antoinevastel.com/bots/areyouheadless — especializado na detecção de Chrome headless
  • pixelscan.net — análise avançada de fingerprint com visualização de todos os parâmetros
  • abrahamjuliot.github.io/creepjs — a análise mais detalhada (mais de 200 parâmetros), mostra o nível de confiança do navegador
  • iphey.com — verificação do IP para pertencimento a proxies e VPNs

Automação de testes

Crie um script para verificar automaticamente o mascaramento após cada alteração nas configurações:

const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');

puppeteer.use(StealthPlugin());

async function testStealth() {
    const browser = await puppeteer.launch({ headless: true });
    const page = await browser.newPage();
    
    // Teste 1: Sannysoft
    await page.goto('https://bot.sannysoft.com');
    await page.screenshot({ path: 'test-sannysoft.png', fullPage: true });
    
    // Teste 2: Are you headless
    await page.goto('https://arh.antoinevastel.com/bots/areyouheadless');
    const headlessDetected = await page.evaluate(() => {
        return document.body.innerText.includes('You are not Chrome headless');
    });
    console.log('Headless detectado:', !headlessDetected);
    
    // Teste 3: Propriedade Webdriver
    const webdriverPresent = await page.evaluate(() => navigator.webdriver);
    console.log('navigator.webdriver:', webdriverPresent);
    
    // Teste 4: Objeto Chrome
    const chromePresent = await page.evaluate(() => !!window.chrome);
    console.log('window.chrome presente:', chromePresent);
    
    await browser.close();
}

testStealth();

Lista de verificação para mascaramento bem-sucedido

Seu navegador headless está corretamente mascarado se:

  • navigator.webdriver retorna undefined
  • window.chrome existe e contém o objeto runtime
  • navigator.plugins.length é maior que 0
  • O fornecedor e o renderizador do WebGL mostram uma placa de vídeo real, e não SwiftShader
  • A impressão do Canvas é única para cada sessão
  • O User-Agent corresponde à versão atual do Chrome/Firefox
  • O IP do proxy não está em listas negras (verificação através do iphey.com)
  • Timezone e locale correspondem à geolocalização do IP

Conclusão

O mascaramento de navegadores headless é uma tarefa complexa que requer atenção a dezenas de parâmetros. Sistemas anti-bot modernos utilizam aprendizado de máquina e analisam centenas de características do navegador simultaneamente, portanto, a simples troca do User-Agent já não funciona. Para um scraping bem-sucedido, é necessário combinar vários métodos de proteção.

Os principais elementos de um mascaramento eficaz incluem a remoção de marcadores JavaScript de automação (navigator.webdriver, variáveis CDP), emulação de Canvas e WebGL fingerprint, configuração de cabeçalhos HTTP realistas e uso de proxies de qualidade. Soluções prontas (selenium-stealth para Python, puppeteer-extra-plugin-stealth para Node.js) cobrem 80% dos casos, mas para contornar proteções avançadas é necessária configuração adicional.

Um ponto crítico é a escolha do proxy. Mesmo um navegador perfeitamente mascarado será bloqueado se usar IPs de listas negras ou fizer muitas solicitações de um único IP. Para scraping de sites protegidos, recomendamos o uso de proxies residenciais com rotação automática — eles oferecem uma alta pontuação de confiança e risco mínimo de bloqueios, pois utilizam IPs reais de usuários domésticos em vez de endereços de servidores.

Teste regularmente a qualidade do mascaramento através de serviços especializados (bot.sannysoft.com, pixelscan.net) e adapte as configurações para mudanças nos sistemas anti-bot. O scraping é uma corrida constante entre desenvolvedores de bots e criadores de proteção, portanto, uma configuração que funciona hoje pode precisar de atualização em alguns meses.

```