🎯 Um navegador em nuvem personalizável e anti-detecção alimentado por Chromium desenvolvido internamente, projetado para rastreadores web e agentes de IA. 👉Experimente agora
De volta ao blog

Como Realizar Consultas em Massa no ChatGPT para GEO: Uma Solução Automatizada Usando o Navegador em Nuvem Scrapeless

Michael Lee
Michael Lee

Expert Network Defense Engineer

14-Nov-2025
Tenha Vantagem em Cada Região!

Automatize consultas do ChatGPT com o Scrapeless e desbloqueie insights GEO precisos. Não adivinhe—saiba.

Introdução

Desde o início deste ano, as estratégias de SEO de muitas empresas passaram por mudanças fundamentais. Cada vez mais, os usuários não abrem mais a busca do Google quando precisam de informações. Em vez disso, eles perguntam sobre produtos, serviços e marcas diretamente no ChatGPT, Claude ou Gemini.

Isso significa: Sua visibilidade no ChatGPT está se tornando o novo parâmetro de competitividade da marca.

O problema é que — o ChatGPT não possui um recurso de "ranking" e nem uma "ferramenta de análise de palavras-chave".

Você simplesmente não pode saber:

  • Minha marca está aparecendo nas respostas do ChatGPT?
  • O ChatGPT recomenda meus concorrentes?
  • As respostas são diferentes entre países ou idiomas?

Para abordar essas questões, o primeiro passo é:

👉 Realizar consultas em massa das respostas do ChatGPT para coletar dados e extrair insights acionáveis


O que é GEO?

Otimização de Motor Generativo (GEO) é a prática de criar e otimizar conteúdo para que apareça em respostas geradas por IA em plataformas como Google AI Overviews, Modo AI, ChatGPT e Perplexity.

No passado, o sucesso significava ter uma classificação alta nas páginas de resultados de mecanismos de busca (SERPs). Olhando para o futuro, pode não haver mais o conceito de “top ranking.” Em vez disso, você precisa ser a recomendação preferida—a solução que as ferramentas de IA escolhem ativamente apresentar em suas respostas.

Os objetivos centrais da otimização GEO não se limitam mais a cliques, mas se concentram em três métricas-chave:

  • Visibilidade da marca: Aumentar a probabilidade de sua marca aparecer em respostas geradas por IA.
  • Autoridade da fonte: Garantir que seu domínio, conteúdo ou dados sejam selecionados como uma referência de confiança pela IA.
  • Consistência narrativa e posicionamento positivo: Certificar-se de que a IA descreva sua marca de forma profissional, precisa e positiva.

Isso significa que a lógica tradicional de SEO baseada em "ranking de palavras-chave" está gradualmente dando lugar aos mecanismos de citação de fontes de IA.

As marcas devem evoluir de "pesquisáveis" para confiáveis, citadas e recomendadas ativamente.


Por que automatizar consultas em massa no ChatGPT?

De uma perspectiva de marketing e SEO, o ChatGPT se tornou um novo canal para descoberta e exposição de conteúdo.

No entanto, existem três principais pontos problemáticos:

  1. Sem visibilidade sobre a cobertura da marca
    As empresas não podem saber se seus produtos estão sendo indexados, mencionados ou recomendados pelo ChatGPT. Sem dados, é impossível criar estratégias de otimização ou distribuição de conteúdo direcionadas.

  2. Falta de insights a nível GEO
    As respostas do ChatGPT variam dependendo da região, idioma e até mesmo do fuso horário. Um produto recomendado para um usuário dos EUA pode não aparecer para um usuário japonês. Para estratégias internacionais, as empresas precisam entender essas diferenças.

  3. Ferramentas de SEO tradicionais não podem fornecer esses dados
    As ferramentas de SEO existentes (por exemplo, Ahrefs, Semrush) têm capacidades limitadas e não conseguem monitorar completamente as respostas do ChatGPT. Isso significa que uma nova abordagem é necessária para monitorar a exposição da marca dentro dos canais de busca de IA.

Portanto, o objetivo central de consultar em massa o ChatGPT é coletar, analisar e otimizar sistematicamente a presença da sua marca nas respostas do ChatGPT. Isso ajuda as empresas a:

  • Identificar perguntas de alto potencial já mencionadas pelo ChatGPT;
  • Descobrir lacunas de conteúdo que não foram abordadas;
  • Desenvolver estratégias de otimização GEO direcionadas.

Por que escolher o Scrapeless Cloud Browser?

Muitos podem considerar chamar diretamente a API do OpenAI para realizar consultas em massa.

No entanto, na prática, a abordagem da API tem limitações óbvias:

  • Os resultados são facilmente influenciados por preferências históricas e contexto, tornando-os menos objetivos.
  • É difícil mudar rapidamente os IPs para simular acesso de diferentes localizações geográficas.
  • Os custos de consultas em massa são muito altos (cobrados por token, tornando-se caros em grande escala).

É exatamente aqui que entra o Scrapeless Cloud Browser.


O que é o Scrapeless Browser?

Scrapeless Browser é um navegador em nuvem projetado para tarefas de extração de dados e automação. Ele permite que você acesse o ChatGPT a partir da nuvem de uma maneira que imita de perto o comportamento de um usuário real, oferecendo resultados mais precisos e completos.

Comparado aos chamados tradicionais de API, o Scrapeless Cloud Browser se destaca de várias maneiras:

  • Sem interferência de preferências de conta
    Todas as consultas são executadas em ambientes de navegação isolados, sem login, garantindo resultados objetivos e confiáveis.

  • Simulação GEO multi-região
    Proxies residenciais embutidos de mais de 195 países, ISPs estáticos e IPs ilimitados permitem a simulação fácil de usuários de diferentes locais.

  • Alta concorrência e baixo custo
    Suporta mais de 1.000 instâncias simultâneas por tarefa, cobradas por tempo, com custos muito inferiores aos APIs tradicionais.

  • Compatibilidade nativa com frameworks principais
    Migre projetos existentes de Puppeteer ou Playwright com uma única linha de código—sem necessidade de adaptação extra.

  • Anti-detectação inteligente e depuração visual
    Tratamento embutido para Cloudflare, reCAPTCHA e outras proteções, com suporte para depuração de Live View e gravação de sessões.

Leitura recomendada: Como Bypassar a Proteção do Cloudflare e Turnstile Usando a Scrapeless | Guia Completo

Em resumo, o Scrapeless Cloud Browser permite que você realize consultas "de perspectiva de usuário ChatGPT" em massa de maneira eficiente, econômica e precisa—sem registrar centenas de contas ChatGPT—e extrair automaticamente resultados estruturados.


Exemplo: Consultando em massa o ChatGPT usando Scrapeless Browser

O Scrapeless Browser é um serviço de navegador headless baseado em nuvem compatível com os principais frameworks de automação, como Puppeteer e Playwright. Usando-o, você não precisa manter um navegador local, proxy ou nó; você pode iniciá-lo com apenas uma linha de código de conexão.


1. Instalar Dependências

bash Copy
npm install puppeteer-core @scrapeless-ai/sdk node-fetch

2. Configurar o Scrapeless Browser e Conectar

ts Copy
import puppeteer, { Browser, Page } from 'puppeteer-core';
import { Scrapeless, PuppeteerLaunchOptions } from '@scrapeless-ai/sdk';

const scrapeless = new Scrapeless();

async function connectBrowser(): Promise<Browser> {
  const options: PuppeteerLaunchOptions = {
    session_name: 'Batch ChatGPT',
    session_ttl: 600,
    fingerprint: {
      platform: 'macOS',
      localization: { timezone: 'America/New_York' },
      args: { '--window-size': '1920,1080' },
    },
  };

  const { browserWSEndpoint } = await scrapeless.browser.createSession(options);
  const browser = await puppeteer.connect({ browserWSEndpoint, defaultViewport: null });
  return browser;
}

Obtenha sua chave API do Scrapeless Browser fazendo login no Painel do Scrapeless.

Obtenha sua chave API do Scrapeless Browser

💡 Vantagem do Scrapeless #1: Ambiente Sem Configuração

  • Não é necessário manter as instâncias do navegador localmente, consumo mínimo de recursos e fácil escalabilidade.
  • Projetos existentes em Puppeteer podem migrar para o Scrapeless com mudanças mínimas.
  • Simulação fácil de ambientes reais de usuários, melhorando a furtividade e taxas de sucesso.
  • Ideal para tarefas de automação em larga escala, como consultas em massa para o ChatGPT.

3. Automatizar O Acesso ao ChatGPT e Inserir Prompt

ts Copy
async function queryChatGPT(browser: Browser, prompt: string): Promise<string> {
  const page = await browser.newPage();
  await page.goto('https://chatgpt.com/');

  const inputSelector = '[placeholder="Pergunte qualquer coisa"]';
  await page.waitForSelector(inputSelector, { visible: true });
  await page.type(inputSelector, prompt);
  await page.keyboard.press('Enter');

  await page.waitForSelector('[data-message-author-role="assistant"]');
  const result = await page.evaluate(() => {
    const messages = document.querySelectorAll('[data-message-author-role="assistant"]');
    return messages[messages.length - 1]?.textContent || '';
  });

  await page.close();
  return result;
}

💡 Vantagem do Scrapeless #2: Ambiente Web Real

  • Automatiza interações em páginas web reais (digitação, clique, envio).
  • Captura conteúdo renderizado dinamicamente.
  • Resultados correspondem ao que um usuário real veria ao visitar o ChatGPT.

4. Suporte a Vários Tipos de Extração de Resultados de Pesquisa

4.1 Extrair Respostas Textuais do ChatGPT

ts Copy
let gptAnswer: string;
gptAnswer = await waitForChatGPTResponse(page);

4.2 Extrair Cartões de Imagem

ts Copy
let gptImageCards: ChatgptResponse['image_cards'] = [];
// Use seletores para extrair imagens e construir { url, posição }

4.3 Extrair Produtos Recomendados

ts Copy
const gptRecommendProducts: ChatgptResponse['products'] = [];
// Use seletores para extrair links, títulos e imagens de produtos

4.4 Extrair Citações/Referências

ts Copy
let gptCitations: ChatgptResponse['citations'] = [];
// Use os botões de rodapé para extrair links de citações, ícones, títulos e descrições
ts Copy
let gptLinksAttached: ChatgptResponse['links_attached'] = [];
// Use seletores de link Markdown para extrair links e seus textos

4.6 Extrair HTML do Corpo da Página

ts Copy
const body = await page.evaluate(() => document.body.innerHTML);
```pt
const cleanBody = body
  .replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
  .replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
  .replace(/\s+/g, ' ')
  .trim();

Aqui, a limpeza do HTML é realizada: as tags <script>, <style>, <svg>, <img> são removidas para obter um conteúdo limpo do corpo.

💡 Vantagem Scrapeless #3: Extração de Resultados de Múltiplos Tipos

  • Uma única solicitação pode buscar múltiplos tipos de informações estruturadas, sem a necessidade de várias chamadas ou de combinar diferentes ferramentas.
  • Não apenas texto, mas também imagens, produtos, citações, links anexados e HTML limpo podem ser extraídos.
  • Cada tipo de dado é empacotado como um array de objetos (por exemplo, ChatgptResponse['products']), facilitando a saída direta para JSON, CSV ou Webhooks, e suportando fluxos de trabalho de automação downstream.

5.1 Isolamento em Nível de Sessão

ts Copy
const { session_name, task_id, ... } = input;

browser = await this.connectToBrowser({
  session_name,              // Cada tarefa pode especificar um nome de sessão diferente
  session_ttl: 600,          // Tempo de vida da sessão
  session_recording,
  proxy_url,
  // ...
});

Usando o parâmetro session_name, diferentes consultas podem usar sessões de navegador separadas, alcançando isolamento em nível de sessão.

ts Copy
async solver(input: QueryChatgptRequest, ...): Promise<BaseOutput> {
  let browser: Browser;
  
  try {
    // Criar uma nova conexão de navegador para cada chamada
    browser = await this.connectToBrowser(...);
    const page = await browser.newPage();
    // Executar tarefas
  } finally {
    // Fechar o navegador após a tarefa ser concluída
    await page.close();
    await browser.close();
  }
}

Cada chamada de solver() irá:

  • Criar uma instância de navegador independente
  • Limpar automaticamente após o uso no bloco finally

5.3 Isolamento de Proxy

ts Copy
const { proxy_url } = input;

browser = await this.connectToBrowser({
  proxy_url,  // Cada tarefa pode usar um proxy diferente
  // ...
});

const proxy_country = /-country_([A-Z]{2,3})/.exec(proxy_url)?.[1] || 'ANY';

Diferentes tarefas podem alcançar isolamento em nível de rede usando diferentes valores de proxy_url.

5.4 Isolamento de Impressão Digital

ts Copy
fingerprint: {
  platform: 'macOS',
  localization: {
    timezone: 'America/New_York',
  },
  args: {
    '--window-size': '1920,1080',
  },
}

💡 Vantagem Scrapeless #4: Estabilidade Através do Isolamento

  • Cada consulta do ChatGPT é executada em uma sessão de navegador independente, prevenindo interferências
  • Evita a contaminação de Cookies, LocalStorage ou impressões digitais, melhorando as taxas de sucesso das solicitações
  • Pode executar um grande número de consultas simultaneamente na mesma máquina sem conflitos de instância do Puppeteer
  • Aumenta a estabilidade e a confiabilidade em cenários de alta concorrência

6. Troca Global de GEO: Obtenha Respostas de Diferentes Regiões

6.1 Geolocalização Globalizada

ts Copy
const proxy_country = /-country_([A-Z]{2,3})/.exec(proxy_url)?.[1] || 'ANY';

6.2 Localização de Impressão Digital

ts Copy
fingerprint: {
  platform: 'macOS',
  localization: {
    timezone: 'America/New_York',
  },
  args: {
    '--window-size': '1920,1080',
  },
}

💡 Vantagem Scrapeless #5: 195+ Nódulos de País, Proxy Automático & Simulação Localizada

  • Seleção Automática de IP do País
    O código do país é extraído do proxy_url (por exemplo, -country_US, -country_JP), e o Scrapeless automaticamente direciona solicitações para IPs residenciais na região correspondente.

  • Nenhuma Manutenção de Pool de Proxy Necessária
    O backend gere automaticamente os nódulos globais, assim os usuários não precisam configurar ou atualizar listas de proxy sozinhos.

  • Ambiente de Navegador Localizado
    fingerprint.localization.timezone pode definir o fuso horário. Combinado com sessões independentes, simula o ambiente da região alvo, afetando a exibição de conteúdo e resultados de busca específicos da região.

  • Obtenha Resultados Localizados Reais
    O ChatgptResponse.country_code retornado indica a localização geográfica da solicitação, tornando mais precisa a análise de SEO internacional, monitoramento de marca ou análise de conteúdo sensível à região.

7. Extraia Resultados e Suporte a Múltiplos Formatos de Saída

7.1 Saída Estruturada

No método startChat, os dados capturados são encapsulados em um objeto unificado ChatgptResponse:

ts Copy
resolve({
  prompt,
  success: true,
  answer: answerResponse,           // texto / html / bruto
  country_code: proxy_country,      // informações geográficas
  citations: gptCitations,
  links_attached: gptLinksAttached,
  image_cards: gptImageCards,
  products: gptRecommendProducts,
  url: _url,
});

💡 Vantagem Scrapeless #6: Saída Estruturada

  • Cada tarefa de consulta gera um objeto estruturado.
  • Inclui campos como resposta de texto, links anexados, imagens, produtos recomendados, citações, código do país e URL.
  • Objetos estruturados podem ser usados diretamente para automação, sem necessidade de análise adicional.

7.2 Múltiplos Métodos de Saída

No método solver, os resultados podem ser enviados ou retornados:

  1. Saída Webhook
ts Copy

this.pushToMessage(payload, webhook);

Copy
2. **Retorno da Função**

```ts
return createResponse(JSON.stringify(payload), payload.url);

💡 Vantagem Scrapeless #7: Suporte Nativo para Integração de Pipeline de Dados

  • Dados estruturados podem ser enviados diretamente para sistemas externos ou ferramentas de automação (por exemplo, n8n, Zapier, Airtable).
  • Não é necessário desenvolver interfaces extras ou processar dados manualmente, permitindo integração de automação em tempo real.
  • Ao se conectar a sistemas ou bancos de dados internos, não é necessário realizar parsing ou conversão adicional, suportando múltiplas saídas de pipeline de dados.
  • O resultado de cada tarefa de consulta é um objeto estruturado, facilitando análises, estatísticas ou exportação para CSV/JSON.

Código Completo

Copy
import puppeteer, { Browser, Page, Target } from 'puppeteer-core';
import fetch from 'node-fetch';
import { PuppeteerLaunchOptions, Scrapeless } from '@scrapeless-ai/sdk';
import { Logger } from '@nestjs/common';

export interface BaseInput {
  task_id: string;
  proxy_url: string;
  timeout: number;
}

export interface BaseOutput {
  url: string;
  data: number[];
  collection?: string;
  dataType?: string;
}

export interface QueryChatgptRequest extends BaseInput {
  prompt: string;
  webhook?: string;
  session_name?: string;
  web_search?: boolean;
  session_recording?: boolean;
  answer_type?: 'text' | 'html' | 'raw';
}

export interface ChatgptResponse {
  prompt: string;
  task_id?: string;
  duration?: number;
  answer?: string;
  url: string;
  success: boolean;
  country_code: string;
  error_reason?: string;
  links_attached?: Partial<{ position: number; text: string; url: string }>[];
  citations?: Partial<{ url: string; icon: string; title: string; description: string }>[];
  products?: Partial<{ url: string; title: string; image_urls: (string | null)[] }>[];
  image_cards?: Partial<{ position: number; url: string }>[];
}

interface StartChatParams extends QueryChatgptRequest {
  page: Page;
  browser: Browser;
}

export class ChatgptService {
  logger = new Logger(this.constructor.name);
  scrapeless = new Scrapeless();

  private timeoutMultiplier = 2;
  private defaultTimeout = 3 * 60 * 1000;
  private internalErrorSymbol = '[InternalError]:';

  async solver(input: QueryChatgptRequest, checkTimeout: () => boolean): Promise<BaseOutput> {
    const { session_name, task_id, webhook, session_recording, proxy_url } = input;

    let browser: Browser;

    const startTime = performance.now();
    const successful = false;

    const getTotalDuration = () => {
      const endTime = performance.now();
      const totalDuration = ((endTime - startTime) / 1000).toFixed(2);
      return totalDuration;
    };

    const handleChatResponse = (data: Partial<ChatgptResponse>) => {
      const payload = { ...data, task_id, duration: getTotalDuration() };
      return payload;
    };

    const createResponse = (data: string, url = 'https://chatgpt.com'): BaseOutput => {
      return {
        url: url,
        data: Array.from(Buffer.from(data)),
        dataType: 'json',
      };
    };

    try {
      browser = await this.connectToBrowser(
        {
          session_name,
          session_ttl: 600,
          session_recording,
          proxy_url,
          fingerprint: {
            platform: 'macOS',
            localization: {
              timezone: 'America/New_York',
            },
            args: {
              '--window-size': '1920,1080',
            },
          },
        },
        checkTimeout,
      );

      const page = await browser.newPage();

      await this.fakePageDate(page);

      const chatParams: StartChatParams = { ...input, page, browser };
      const results = await this.startChat(chatParams);
      const payload = handleChatResponse(results);
      this.pushToMessage(payload, webhook);
      return createResponse(JSON.stringify(payload), payload.url);
    } catch (error) {
      if (error.success) {
        const payload = handleChatResponse(error);
        this.pushToMessage(payload, webhook);
        return createResponse(JSON.stringify(payload), error.url);
      }
      if (error.error_reason) {
        const errorMessage = error.error_reason;
        const payload = handleChatResponse(error);
        this.pushToMessage(payload, webhook);
        this.logger.warn(`Falha no processamento: ${errorMessage}`);
        throw { message: !errorMessage.includes(this.internalErrorSymbol) ? errorMessage : '' };
      }
      const errorMessage = error.message || 'Erro desconhecido';
      const payload = handleChatResponse({
        success: false,
        error_reason: errorMessage,
      });
      this.pushToMessage(payload, webhook);
      this.logger.warn(`Falha no processamento: ${errorMessage}`);
      throw error;
    } finally {
      const totalDuration = getTotalDuration();
      this.logger.log(
        `Processamento ${successful ? 'bem-sucedido' : 'concluído'} | Duração total: ${totalDuration} segundos`,
      );
    }
  }
```typescript
async format(data: Uint8Array): Promise<QueryChatgptRequest> {
    if (!data) {
      throw new Error('Nenhum dado de entrada válido');
    }
    const input = JSON.parse(data.toString()) as QueryChatgptRequest;

    if (!input.prompt) {
      this.logger.error(`prompt é obrigatório`);
      throw new Error('prompt é obrigatório');
    }

    return {
      ...input,
      timeout: input.timeout || this.defaultTimeout,
      web_search: input.web_search ?? true,
      session_name: input.session_name || 'Resposta do Chatgpt',
      session_recording: input.session_recording || false,
      answer_type: input.answer_type || 'texto',
    };
  }

  private startChat(params: StartChatParams): Promise<ChatgptResponse> {
    return new Promise(async (resolve, reject: (reason: ChatgptResponse) => void) => {
      const { prompt, answer_type, web_search, timeout, page, browser, proxy_url } = params;
      let action: string;
      let isAborted = false;
      let rawResponse: string;

      this.logger.debug((action = 'Conectando ao Navegador'));

      const proxy_country = /-country_([A-Z]{2,3})/.exec(proxy_url)?.[1] || 'QUALQUER';
      const query = new URLSearchParams({ q: prompt });
      if (web_search) {
        query.set('hints', 'search');
      }
      const baseUrl = 'https://chatgpt.com';
      const _url = `${baseUrl}/?${query.toString()}`;

      function waitForChatGPTResponse(page: Page): Promise<string> {
        return new Promise((resolve, reject) => {
          let retryCount = 0;
          const CHECK_INTERVAL = 500; // Verificar a cada 500ms

          const checkResponse = async () => {
            try {
              if (isAborted) {
                resolve('timeout');
                return;
              }
              const currentContent = await page.evaluate(() => {
                const assistantMessage = '[data-message-author-role="assistant"]';
                const $assistantMessages = document.querySelectorAll(assistantMessage);
                const lastMessage = $assistantMessages[$assistantMessages.length - 1];
                if (!lastMessage) return null;

                // Quando um botão de copiar maior que um aparece, significa que a resposta do gpt foi concluída
                const $answerCopyButtons = document.querySelectorAll('button[data-testid="copy-turn-action-button"]');
                if ($answerCopyButtons.length > 1) {
                  return lastMessage.textContent || 'Sem conteúdo';
                } else {
                  return null;
                }
              });

              // Se o conteúdo não mudou e não está vazio
              if (currentContent) {
                retryCount++;
                // Se o conteúdo for estável e não vazio, a resposta é considerada completa
                if (retryCount >= 3) {
                  // Conteúdo estável por 1,5 segundos
                  resolve(currentContent);
                  return;
                }
              }

              // Continuar a verificação
              setTimeout(checkResponse, CHECK_INTERVAL);
            } catch (error) {
              reject(error);
            }
          };

          // Começar a verificação
          checkResponse();
        });
      }

      async function receiveChatGPTStream() {
        const client = await page.createCDPSession();
        await client.send('Fetch.enable', {
          patterns: [
            {
              urlPattern: '*conversation',
              requestStage: 'Response',
            },
          ],
        });

        client.on('Fetch.requestPaused', async (event) => {
          const { requestId, request, responseHeaders } = event;
          const isSSE = responseHeaders?.some(
            (h) => h.name?.toLowerCase() === 'content-type' && h.value?.includes('text/event-stream'),
          );
          if (request.url.includes('/conversation') && isSSE) {
            try {
              const { body, base64Encoded } = await client.send('Fetch.getResponseBody', { requestId });
              rawResponse = base64Encoded ? Buffer.from(body, 'base64').toString('utf-8') : body;
            } catch (err) {
              console.warn('Falha ao obter resposta do stream', err.message);
            }
          }
          await client.send('Fetch.continueRequest', { requestId });
        });
      }

      function throwError(errorReason: string) {
        const error: ChatgptResponse = {
          prompt,
          success: false,
          country_code: proxy_country,
          error_reason: errorReason,
          url: _url,
        };
        reject(error);
      }

      const timeoutId = setTimeout(() => {
        isAborted = true;
        throwError(`Tempo limite de chat após ${timeout}ms`);
      }, timeout);

      try {
        this.logger.debug((action = 'Registrar CDP para capturar dados do stream do GPT (raw_response)'));
        await receiveChatGPTStream();
        this.logger.debug((action = 'Navegando para chatgpt.com'));

const navigateTimeout = 25_000 * this.timeoutMultiplier;
try {
await page.goto(_url, { timeout: navigateTimeout });
} catch {
throwError(Navegação para chatgpt.com Timeout (${navigateTimeout}ms));
return;
}

Copy
    // Adicionar ouvinte de mudança de URL
    page.on('framenavigated', async (frame) => {
      if (frame !== page.mainFrame()) return;
      const url = frame.url();
      if (!url.startsWith('https://auth.openai.com')) return;
      isAborted = true;
      throwError(`Redirecionado para a página de login da OpenAI ao <<${_url}>> - ${action}`);
      return;
    });

    if (isAborted) return;
    await this.wait(50, 150);
    this.logger.debug((action = 'Certifique-se de que a entrada existe'));
    const inputs = ['#prompt-textarea', '[placeholder="Pergunte qualquer coisa"]'];
    try {
      await Promise.race(
        inputs.map(async (input) => {
          await page.waitForSelector(input, {
            timeout: 20_000 * this.timeoutMultiplier,
            visible: true,
          });
          return input;
        }),
      );
    } catch {
      throwError('A região atual está indisponível ou redirecionada para a página de login');
      return;
    }

    if (isAborted) return;
    await this.wait(150, 250);
    this.logger.debug((action = 'Aguardando a Resposta do GPT'));
    let gptAnswer: string;
    try {
      gptAnswer = await waitForChatGPTResponse(page);
      this.logger.debug((action = 'Resposta do GPT recebida'));
    } catch (error: any) {
      this.logger.error(`Falha ao obter resposta: ${error.message}`);
      throwError(`Falha ao obter resposta do chatgpt`);
      return;
    }

    if (isAborted) return;
    await this.wait(150, 250);
    this.logger.debug((action = 'Obter cartões de imagem do chatgpt'));
    const imageCardsSelector = 'div.no-scrollbar:has(button img) img';
    const imageCardsLightBoxSelector = 'div[data-testid="modal-image-gen-lightbox"] ol li img';
    const imageCardsLightBoxCloseSelector = 'div[data-testid="modal-image-gen-lightbox"] button';
    let gptImageCards: ChatgptResponse['image_cards'] = [];
    try {
      const firstImageCard = await page.$(imageCardsSelector);
      if (firstImageCard) {
        firstImageCard.click();
        await page.waitForSelector(imageCardsLightBoxSelector);
        gptImageCards = await page.$$eval(imageCardsLightBoxSelector, (elements) => {
          return elements.map((element, index) => {
            const url = element.getAttribute('src') || '';
            return { url, position: index + 1 };
          });
        });
        await page.waitForSelector(imageCardsLightBoxCloseSelector);
        await page.click(imageCardsLightBoxCloseSelector);
      } else {
        this.logger.debug((action = 'Nenhum cartão de imagem encontrado'));
      }
    } catch (error: any) {
      this.logger.debug((action = `Obter cartões de imagem do chatgpt: ${error.toString()}`));
    }

    if (isAborted) return;
    await this.wait(300, 450);
    this.logger.debug((action = 'Obter produtos recomendados do chatgpt'));
    const closeButtonSelector = `button[data-testid="close-button"]`;
    const recommendProductsSelector = 'div.markdown div.relative > div.flex.flex-row:has(img):not(a) > div img';
    const recommendProductDetailsSelector = `section[screen-anchor="top"] div[slot="content"]`;
    const detailLinkSelector = `${recommendProductDetailsSelector} span a`;
    const gptRecommendProducts: ChatgptResponse['products'] = [];
    try {
      const recommendProducts = await page.$$(recommendProductsSelector);
      if (recommendProducts.length) {
        let lastUrl = '';
        for (const [index] of recommendProducts.entries()) {
          // O clique em links externos pode ser acionado
          let newPage: Page = null as unknown as Page;
          const targetCreatedHandler = async (target: Target) => {
            this.logger.debug((action = `Obter produtos recomendados do chatgpt: ${target.type()}`));
            try {
              if (target.type() === 'page') {
                const pageTarget = await target.page();
                const opener = await target.opener();
                if (opener && (opener as any)?._targetId === (page.target() as any)?._targetId) {
                  newPage = pageTarget as Page;
                }
              }
            } catch (e) {}
          };
          browser.once('targetcreated', targetCreatedHandler);

          // Clique no item recomendado
          await page.evaluate(
            (selector, index) => {
              const currentProduct = document.querySelectorAll(selector)?.[index];
javascript Copy
(currentProduct as HTMLElement)?.click();
                },
                recommendProductsSelector,
                index,
              );

              await this.wait(750, 950);
              browser.off('targetcreated', targetCreatedHandler);

              if (newPage) {
                const url = newPage.url();
                const title = await newPage.title();
                gptRecommendProducts.push({ url, title, image_urls: [] });
                await newPage.close();
                continue;
              }

              await page.waitForSelector(detailLinkSelector, { timeout: 20_000 * this.timeoutMultiplier });

              // Aguardar as informações serem alteradas
              let maxRetry = 30;
              while (maxRetry-- > 0) {
                const currentUrl = await page.$eval(detailLinkSelector, (el) => el.getAttribute('href') || '');
                if (currentUrl && currentUrl !== lastUrl) {
                  lastUrl = currentUrl;
                  break;
                }
                await this.wait(200, 300);
              }

              const info = await page.$eval(
                recommendProductDetailsSelector,
                (element, currentUrl) => {
                  const title = element.querySelector('div.text-xl')?.textContent || '';
                  const image_urls = Array.from(element.querySelectorAll('.no-scrollbar img')).map((img) =>
                    img.getAttribute('src'),
                  );
                  return { url: currentUrl, title, image_urls };
                },
                lastUrl,
              );

              gptRecommendProducts.push(info);
            }
            await page.click(closeButtonSelector);
          } else {
            this.logger.debug((action = 'Nenhum produto recomendado encontrado'));
          }
        } catch (error: any) {
          this.logger.debug((action = `Obter produtos recomendados do chatgpt: ${error.toString()}`));
        }

        if (isAborted) return;
        await this.wait(500, 1000);
        this.logger.debug((action = 'Obter citações do chatgpt'));
        const citationsEntranceSelector = `button.group\\/footnote`;
        const citationsContentLinkSelector = `section[screen-anchor="top"] div[slot="content"] a`;
        let gptCitations: ChatgptResponse['citations'] = [];
        await page.bringToFront();

        try {
          const citationsButton = await page.waitForSelector(citationsEntranceSelector, {
            timeout: 3_000,
          });
          if (citationsButton) {
            await citationsButton.click();
            const citationsContent = await page.waitForSelector(citationsContentLinkSelector, {
              timeout: 20_000 * this.timeoutMultiplier,
            });
            if (citationsContent) {
              gptCitations = await page.$$eval(citationsContentLinkSelector, (elements) => {
                return elements.map((element) => {
                  const url = element.href || '';
                  const icon = element.querySelector('img')?.getAttribute?.('src');
                  const title = element.querySelector('div:nth-child(2)')?.textContent || '';
                  const description = element.querySelector('div:nth-child(3)')?.textContent || '';
                  return { url, icon, title, description };
                });
              });
              await page.click(closeButtonSelector);
            }
          } else {
            this.logger.debug((action = 'Nenhuma citação encontrada'));
          }
        } catch (error: any) {
          this.logger.debug((action = `Obter citações do chatgpt: ${error.toString()}`));
        }

        // Em alguns casos, é necessário adicionar um elemento fixo à página
        // para impedir que o puppeteer clique em elementos aleatórios
        if (isAborted) return;
        this.logger.debug((action = 'Adicionar elementos fixos para evitar cliques inesperados'));
        await page.evaluate(() => {
          const element = document.createElement('div');
          element.style.position = 'fixed';
          element.style.top = '0';
          element.style.left = '0';
          element.style.width = '100%';
          element.style.height = '100%';
          element.style.zIndex = '1000';
          document.body.appendChild(element);
        });

        if (isAborted) return;
        await this.wait(150, 250);
        this.logger.debug((action = 'Obter links anexados do chatgpt'));
        const markdownLinksSelector = 'div.markdown a';
        let gptLinksAttached: ChatgptResponse['links_attached'] = [];
        try {
          gptLinksAttached = await page.$$eval(markdownLinksSelector, (elements) => {
            return elements.map((element, index) => {
              const url = element.href || '';
              const title = element.textContent || '';
              return { url, title, position: index + 1 };
            });
          });
        } catch (error: any) {

this.logger.debug((action = 'Nenhum link anexado encontrado'));
}

Copy
    this.logger.debug((action = 'Obtendo corpo'));
    const body = await page.evaluate(() => document.body.innerHTML);
    const cleanBody = body
      .replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '') // Remove tags de script
      .replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '') // Remove tags de estilo
      .replace(/<svg[^>]*>[\s\S]*?<\/svg>/gi, '') // Remove tags svg
      .replace(/<img[^>]*\/?>/gi, '') // Remove tags img
      .replace(/style="[^"]*"/gi, '') // Remove todos os atributos de estilo
      .replace(/class="[^"]*"/gi, '') // Remove todos os atributos de classe
      .replace(/<!--[\s\S]*?-->/g, '') // Remove comentários
      // Mapear substituições relacionadas
      .replace(/<span>·<\/span>/g, '') // Remove tags span com ·
      .replace(/<a href="https:\/\/www\.google\.com\/maps\/[^"]*"[^>]*>[\s\S]*?<\/a>/g, '') // Remove todos os links do google maps
      .replace(/<a href="tel:+[^"]*"[^>]*>[\s\S]*?<\/a>/g, '') // Remove todos os links de telefone
      .replace(/\s+/g, ' ') // Normaliza espaços
      .trim();

    this.logger.debug((action = 'Verificando resposta de erro'));
    const hasError = [
      'Algo deu errado ao gerar a resposta.',
      'Atividade incomum foi detectada em seu dispositivo.',
      'Ocorreu um erro. Ou o motor que você solicitou não existe ou houve outro problema ao processar seu pedido.',
    ].some((message) => cleanBody.includes(message));
    if (hasError) {
      throwError(`O ChatGPT está atualmente indisponível`);
      return;
    }

    this.logger.log((action = 'Chat com sucesso'));

    const answerMap: Record<QueryChatgptRequest['answer_type'], string> = {
      html: cleanBody,
      raw: rawResponse,
      text: gptAnswer,
    };
    const answerResponse = answerMap[answer_type] ?? answerMap.text;

    resolve({
      prompt,
      success: true,
      answer: answerResponse,
      country_code: proxy_country,
      citations: gptCitations,
      links_attached: gptLinksAttached,
      image_cards: gptImageCards,
      products: gptRecommendProducts,
      url: _url,
    });
  } catch (error: any) {
    if (!isAborted) {
      throwError(this.internalErrorSymbol + (error.message || String(error)));
    }
  } finally {
    clearTimeout(timeoutId);
    try {
      await page.close();
      await browser.close();
    } catch {}
  }
});

}

private async connectToBrowser(opt: PuppeteerLaunchOptions, checkTimeout: () => boolean) {
let browser;
try {
const { browserWSEndpoint } = await this.scrapeless.browser.createSession(opt);
browser = await Promise.race([
puppeteer.connect({ browserWSEndpoint, defaultViewport: null }),
new Promise((_, reject) => {
const interval = setInterval(() => {
if (checkTimeout()) {
clearInterval(interval);
browser?.close();
reject(new Error('Timeout de conexão com o navegador'));
}
}, 1000);
}),
]);
return browser;
} catch (error) {
this.logger.error(Falha na conexão com o navegador: ${error.message});
throw error;
}
}

private async pushToMessage(data: any, webhook?: string) {
if (!webhook) {
return;
}
try {
const res = await fetch(webhook, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
data: data,
content: data.answer_text,
}),
});
if (res.ok) {
this.logger.log('Webhook enviado com sucesso');
} else {
this.logger.error('Falha ao enviar webhook', await res.text());
}
} catch (err) {
this.logger.error('Exceção ao enviar webhook', err);
}
}

private async fakePageDate(page: Page) {
await page.evaluateOnNewDocument(() => {
// Hook nova data
const fixedDate = new Date();
// define aleatoriamente a data para 1-3 anos atrás
const days = 100 + Math.floor(Math.random() * 365 * 3 - 100);
fixedDate.setDate(fixedDate.getDay() - days);
// const fixedDate = new Date('2022-01-01T00:00:00Z');
const OriginalDate = Date;

Copy
  class FakeDate extends OriginalDate {
    constructor(...args: Parameters<typeof Date>) {
      super();
      if (args.length === 0) {
        return new OriginalDate(fixedDate);
      }
      return new OriginalDate(...args);
    }

    static now() {
      return fixedDate.getTime();
    }

    static parse(str: string) {
      return OriginalDate.parse(str);
    }

    static UTC(...args: Parameters<typeof Date.UTC>) {
javascript Copy
return OriginalDate.UTC(...args);
        }
      }

      Object.getOwnPropertyNames(OriginalDate).forEach((prop) => {
        FakeDate[prop as keyof typeof FakeDate] = OriginalDate[prop as keyof typeof OriginalDate] as any;
      });
      (window.Date as typeof FakeDate) = FakeDate;
    });
  }

  private async wait(fromMs: number, toMs: number) {
    const ms = fromMs + Math.random() * (toMs - fromMs);
    await new Promise((resolve) => setTimeout(resolve, ms));
  }
}

## Após a Consulta: Transforme Respostas Brutas em Estratégias Operacionais GEO Ação em 30 Minutos

Entenda rapidamente os resultados de busca e identifique temas de conteúdo:

1. Copie o JSON resultante → Abra [https://csvjson.com/json2csv](https://csvjson.com/json2csv) → obtenha CSV → cole no Excel.
2. Adicione duas novas colunas:
   - `brandCount` = `=SE(ÉNÚM(LOCALIZAR("SuaMarca",D2)),1,0)`
   - `gap` = `F2-E2` (A coluna F é a contagem de ocorrências de concorrentes, a coluna E é `brandCount`)

👉 **Conclusão:** Atualmente, ninguém "reclamou este tópico", então você pode imediatamente escrever um artigo como *"O que é ABCProxy?"* para capturar o espaço de resposta.

**Dicas:** Após executar 100 consultas em lote da próxima vez, classifique por `gap` em ordem decrescente → os 20 melhores resultados se tornam ideias de conteúdo prioritárias.

| Campo        | Significado Empresarial |
|--------------|------------------------|
| prompt       | Consulta original do usuário |
| answer_text  | Resposta completa do ChatGPT |
| brandCount   | Número de vezes que sua marca aparece na resposta |
| rivalCount   | Número de vezes que os concorrentes aparecem |
| gap          | `rivalCount - brandCount` → `0` = não reclamado, conteúdo prioritário; `>0` = selecionar tópico imediatamente, escrever artigo de comparação/classificação; `<0` = manter atualizações, continuar otimização |

---

## Conclusão

Com o Scrapeless Cloud Browser, você pode automatizar consultas do ChatGPT para alcançar a otimização GEO entre países e fusos horários e obter facilmente resultados de busca localizados e precisos. Seja para SEO internacional, monitoramento de marca ou análise de insights de mercado, o Scrapeless ajuda você a construir rapidamente um sistema de consulta automatizado eficiente, estável e escalável.

O Scrapeless não apenas oferece automação de navegador para dados GEO, mas também fornece ferramentas avançadas e estratégias de dados para controlar totalmente os mecanismos de citação de IA. Entre em contato conosco para desbloquear uma solução completa de dados GEO!

Olhando para o futuro, o Scrapeless continuará a se concentrar na tecnologia de navegador em nuvem, oferecendo às empresas extração de dados de alto desempenho, fluxos de trabalho de automação e suporte à infraestrutura de agentes de IA. Atendendo setores como finanças, varejo, comércio eletrônico, SEO e marketing, o Scrapeless fornece soluções personalizadas e orientadas a cenários para ajudar as empresas a se manterem à frente na era dos dados inteligentes.

O Scrapeless Browser é mais do que apenas uma ferramenta de automação — ele é:  
> **Uma infraestrutura escalável de "coleta de dados de ecossistema de pesquisa de IA"**  

Permitindo que você quantifique verdadeiramente a visibilidade da marca, a cobertura de palavras-chave e as tendências de conteúdo do ChatGPT.

---

<div style="padding: 20px 0; text-align: center;">
  <a
    style="
      margin: 8px;
      display: inline-block;
      text-decoration: none;
    "
    href="https://www.goproxy.com/register?link=https://app.scrapeless.com/passport/login?utm_source=official&utm_medium=blog&utm_campaign=chatgpt-geo"
  >
    <div
      style="
        font-weight: bold;
        width: 100%;
        max-width: 400px;
        padding: 12px 40px;
        background: #12A594;
        border-radius: 5px;
        border: 2px solid #12A594;
        color: #fff;
        cursor: pointer;
        box-sizing: border-box;
        font-size: 18px;
      "
    >
      👉 Faça login no Scrapeless Browser &gt;
    </div>
  </a>
</div>

Na Scorretless, acessamos apenas dados disponíveis ao público, enquanto cumprem estritamente as leis, regulamentos e políticas de privacidade do site aplicáveis. O conteúdo deste blog é apenas para fins de demonstração e não envolve atividades ilegais ou infratoras. Não temos garantias e negamos toda a responsabilidade pelo uso de informações deste blog ou links de terceiros. Antes de se envolver em qualquer atividade de raspagem, consulte seu consultor jurídico e revise os termos de serviço do site de destino ou obtenha as permissões necessárias.

Artigos mais populares

Catálogo