🎯 Trình duyệt đám mây tùy chỉnh, chống phát hiện được hỗ trợ bởi Chromium tự phát triển, thiết kế dành cho trình thu thập dữ liệu webtác nhân AI. 👉Dùng thử ngay
Quay lại blog

Cách thực hiện các truy vấn theo lô trên ChatGPT cho GEO: Giải pháp tự động sử dụng Scrapeless Cloud Browser

Michael Lee
Michael Lee

Expert Network Defense Engineer

14-Nov-2025
Đạt được lợi thế tại mọi khu vực!

Tự động hóa các truy vấn ChatGPT với Scrapeless và mở khóa những hiểu biết GEO chính xác. Đừng đoán—hãy biết.

Giới thiệu

Kể từ đầu năm nay, chiến lược SEO của nhiều công ty đã trải qua những thay đổi cơ bản.
Ngày càng nhiều người dùng không mở tìm kiếm Google khi họ cần thông tin. Thay vào đó, họ hỏi về sản phẩm, dịch vụ và thương hiệu trực tiếp trong ChatGPT, Claude hoặc Gemini.

Điều này có nghĩa là: tính khả thi của bạn trong ChatGPT đang trở thành tiêu chuẩn mới cho sự cạnh tranh thương hiệu.

Vấn đề là — ChatGPT không có tính năng "xếp hạng" và không có "công cụ phân tích từ khóa."

Bạn đơn giản không thể biết:

  • Thương hiệu của tôi có xuất hiện trong câu trả lời của ChatGPT không?
  • ChatGPT có đề xuất các đối thủ cạnh tranh của tôi không?
  • Các câu trả lời có khác nhau giữa các quốc gia hoặc ngôn ngữ không?

Để giải quyết những câu hỏi này, bước đầu tiên là:

👉 Thực hiện các truy vấn hàng loạt các câu trả lời của ChatGPT để thu thập dữ liệu và rút ra những hiểu biết có thể hành động


GEO là gì?

Tối ưu hóa Động cơ Sinh ra (GEO) là thực tiễn tạo ra và tối ưu hóa nội dung để nó xuất hiện trong các câu trả lời do AI sinh ra trên các nền tảng như Google AI Overviews, AI Mode, ChatGPT và Perplexity.

Trong quá khứ, thành công có nghĩa là xếp hạng cao trên các trang kết quả của công cụ tìm kiếm (SERPs). Nhìn về tương lai, có thể sẽ không còn khái niệm "xếp hạng cao nhất." Thay vào đó, bạn cần trở thành sự đề xuất ưu tiên—giải pháp mà các công cụ AI chọn trình bày trong câu trả lời của họ.

Các mục tiêu cốt lõi của tối ưu hóa GEO không còn chỉ giới hạn ở số lần nhấp chuột mà còn tập trung vào ba chỉ số chính:

  • Tiềm năng thương hiệu: Tăng xác suất thương hiệu của bạn xuất hiện trong các câu trả lời do AI sinh ra.
  • Uy tín nguồn: Đảm bảo miền, nội dung, hoặc dữ liệu của bạn được chọn làm tham chiếu đáng tin cậy bởi AI.
  • Sự nhất quán trong câu chuyện và vị trí tích cực: Đảm bảo AI mô tả thương hiệu của bạn một cách chuyên nghiệp, chính xác và tích cực.

Điều này có nghĩa là logic SEO truyền thống dựa trên "xếp hạng từ khóa" đang dần nhường chỗ cho các cơ chế trích dẫn nguồn AI.

Các thương hiệu phải tiến hóa từ việc "có thể tìm kiếm" thành đáng tin cậy, được trích dẫn và được đề xuất một cách chủ động.


Tại sao nên tự động hóa các truy vấn hàng loạt ChatGPT?

Từ góc độ tiếp thị và SEO, ChatGPT đã trở thành một kênh mới cho việc khám phá và tiếp cận nội dung.

Tuy nhiên, có ba điểm yếu chính:

  1. Không có khả năng nhìn thấy phạm vi thương hiệu
    Các công ty không thể biết liệu sản phẩm của mình có được lập chỉ mục, đề cập hoặc được ChatGPT đề xuất hay không. Thiếu dữ liệu, không thể tạo ra các chiến lược tối ưu hóa hoặc phân phối nội dung nhắm mục tiêu.

  2. Thiếu hiểu biết ở cấp độ GEO
    Các câu trả lời của ChatGPT thay đổi tùy thuộc vào khu vực, ngôn ngữ, và thậm chí cả múi giờ. Một sản phẩm được đề xuất cho người dùng tại Mỹ có thể không xuất hiện cho người dùng Nhật Bản. Đối với các chiến lược quốc tế, các công ty phải hiểu những khác biệt này.

  3. Các công cụ SEO truyền thống không thể cung cấp dữ liệu này
    Các công cụ SEO hiện tại (ví dụ: Ahrefs, Semrush) có khả năng hạn chế và không thể theo dõi hoàn toàn các câu trả lời của ChatGPT. Điều này có nghĩa là một cách tiếp cận mới là cần thiết để theo dõi sự tiếp cận thương hiệu trong các kênh tìm kiếm AI.

Do đó, mục tiêu cốt lõi của truy vấn hàng loạt ChatGPT là thu thập, phân tích và tối ưu hóa một cách hệ thống sự hiện diện của thương hiệu bạn trong các câu trả lời của ChatGPT. Điều này giúp các công ty:

  • Xác định các câu hỏi tiềm năng cao đã được ChatGPT đề cập;
  • Khám phá những thiếu sót nội dung chưa được đề cập;
  • Phát triển các chiến lược tối ưu hóa GEO có mục tiêu.

Tại sao chọn Scrapeless Cloud Browser?

Nhiều người có thể cân nhắc việc gọi trực tiếp API OpenAI để thực hiện các truy vấn hàng loạt.

Tuy nhiên, trên thực tế, cách tiếp cận API có những hạn chế rõ ràng:

  • Kết quả dễ bị ảnh hưởng bởi sở thích lịch sử và ngữ cảnh, làm cho chúng mất khách quan.
  • Rất khó để nhanh chóng chuyển đổi IP để mô phỏng việc truy cập từ các vị trí địa lý khác nhau.
  • Chi phí truy vấn hàng loạt rất cao (tính theo token, trở nên đắt đỏ khi quy mô lớn).

Đây chính là lý do Scrapeless Cloud Browser xuất hiện.


Scrapeless Browser là gì?

Scrapeless Browser là một trình duyệt đám mây được thiết kế cho các tác vụ trích xuất dữ liệu và tự động hóa. Nó cho phép bạn truy cập ChatGPT từ đám mây theo cách mô phỏng hành vi của người dùng thực, cung cấp kết quả chính xác và toàn diện hơn.

So với các cuộc gọi API truyền thống, Scrapeless Cloud Browser nổi bật ở một số điểm:

  • Không có sự can thiệp từ sở thích tài khoản
    Tất cả các truy vấn đều được thực hiện trong các môi trường trình duyệt độc lập, không có đăng nhập, đảm bảo kết quả khách quan và đáng tin cậy.

  • Mô phỏng GEO đa vùng
    Các proxy dân cư tích hợp từ hơn 195 quốc gia, ISP tĩnh và IP không giới hạn cho phép dễ dàng mô phỏng người dùng từ các địa điểm khác nhau.

  • Tính tương tác cao và chi phí thấp
    Hỗ trợ hơn 1.000 phiên đồng thời cho mỗi nhiệm vụ, tính phí theo thời gian, với chi phí thấp hơn nhiều so với các API truyền thống.

  • Tương thích gốc với các framework chính
    Di chuyển các dự án Puppeteer hoặc Playwright hiện có chỉ với một dòng mã—không cần thích ứng thêm.

  • Chống phát hiện thông minh và gỡ lỗi trực quan
    Xử lý tích hợp cho Cloudflare, reCAPTCHA và các bảo vệ khác, với hỗ trợ gỡ lỗi Live View và ghi lại phiên.

Đọc thêm: Cách Bỏ Qua Bảo Vệ Cloudflare và Turnstile Sử Dụng Scrapeless | Hướng dẫn đầy đủ

Tóm lại, Scrapeless Cloud Browser cho phép bạn thực hiện các truy vấn "ChatGPT từ góc độ người dùng" hàng loạt một cách hiệu quả, tiết kiệm chi phí và chính xác—không cần đăng ký hàng trăm tài khoản ChatGPT—và tự động trích xuất kết quả có cấu trúc.


Ví dụ: Truy vấn hàng loạt ChatGPT sử dụng Scrapeless Browser

Scrapeless Browser là dịch vụ trình duyệt headless dựa trên đám mây tương thích với các framework tự động hóa lớn như Puppeteer và Playwright. Sử dụng nó, bạn không cần duy trì trình duyệt cục bộ, proxy hay nút; bạn có thể khởi động chỉ với một dòng mã kết nối.


1. Cài đặt các phụ thuộc

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

2. Cấu hình Scrapeless Browser và Kết nối

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;
}

Nhận API Key Scrapeless Browser của bạn bằng cách đăng nhập vào Scrapeless Dashboard.

Nhận API Key Scrapeless Browser của bạn

💡 Ưu điểm Scrapeless #1: Môi trường không cần cấu hình

  • Không cần duy trì các phiên trình duyệt cục bộ, tiêu thụ tài nguyên tối thiểu, và dễ dàng mở rộng.
  • Các dự án Puppeteer hiện có có thể chuyển sang Scrapeless với thay đổi tối thiểu.
  • Dễ dàng mô phỏng các môi trường người dùng thực, cải thiện sự bí mật và tỷ lệ thành công.
  • Lý tưởng cho các nhiệm vụ tự động hóa quy mô lớn, chẳng hạn như các truy vấn ChatGPT hàng loạt.

3. Tự động hóa truy cập ChatGPT và Nhập các lời nhắc

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="Ask anything"]';
  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;
}

💡 Ưu điểm Scrapeless #2: Môi trường Web Thực

  • Tự động hóa các tương tác trên các trang web thực (gõ, nhấp chuột, gửi).
  • Nắm bắt nội dung được tạo động.
  • Kết quả khớp với những gì một người dùng thực sẽ thấy khi truy cập ChatGPT.

4. Hỗ trợ nhiều loại trích xuất kết quả tìm kiếm

4.1 Trích xuất phản hồi văn bản từ ChatGPT

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

4.2 Trích xuất thẻ hình ảnh

ts Copy
let gptImageCards: ChatgptResponse['image_cards'] = [];
// Sử dụng các bộ chọn để trích xuất hình ảnh và xây dựng { url, vị trí }

4.3 Trích xuất sản phẩm được đề xuất

ts Copy
const gptRecommendProducts: ChatgptResponse['products'] = [];
// Sử dụng các bộ chọn để trích xuất liên kết sản phẩm, tiêu đề và hình ảnh

4.4 Trích xuất trích dẫn/tham khảo

ts Copy
let gptCitations: ChatgptResponse['citations'] = [];
// Sử dụng các nút chú thích để trích xuất liên kết, biểu tượng, tiêu đề và mô tả trích dẫn

4.5 Trích xuất liên kết đính kèm

ts Copy
let gptLinksAttached: ChatgptResponse['links_attached'] = [];
// Sử dụng bộ chọn liên kết Markdown để trích xuất liên kết và văn bản của chúng

4.6 Trích xuất HTML thân trang

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

Ở đây, việc làm sạch HTML được thực hiện: các thẻ <script>, <style>, <svg>, <img> bị loại bỏ để thu được nội dung cơ thể sạch sẽ.

💡 Lợi Thế Scrapeless #3: Khai Thác Kết Quả Nhiều Loại

  • Một yêu cầu duy nhất có thể lấy nhiều loại thông tin cấu trúc mà không cần nhiều cuộc gọi hoặc kết hợp các công cụ khác nhau.
  • Không chỉ văn bản, mà còn hình ảnh, sản phẩm, trích dẫn, liên kết đính kèm và HTML sạch đều có thể được khai thác.
  • Mỗi loại dữ liệu được đóng gói dưới dạng mảng đối tượng (ví dụ, ChatgptResponse['products']), giúp dễ dàng xuất trực tiếp sang JSON, CSV hoặc Webhooks, và hỗ trợ quy trình tự động hóa ở phía sau.

5. Bối Cảnh & Phiên Trình Duyệt Độc Lập

5.1 Tách Biệt Mức Phiên

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

browser = await this.connectToBrowser({
  session_name,              // Mỗi nhiệm vụ có thể chỉ định một tên phiên riêng biệt
  session_ttl: 600,          // Thời gian sống của phiên
  session_recording,
  proxy_url,
  // ...
});

Sử dụng tham số session_name, các truy vấn khác nhau có thể sử dụng các phiên trình duyệt riêng biệt, đạt được tách biệt mức phiên.

5.2 Tách Biệt Phiên Bản Trình Duyệt

ts Copy
async solver(input: QueryChatgptRequest, ...): Promise<BaseOutput> {
  let browser: Browser;
  
  try {
    // Tạo một kết nối trình duyệt mới cho mỗi lần gọi
    browser = await this.connectToBrowser(...);
    const page = await browser.newPage();
    // Thực hiện các nhiệm vụ
  } finally {
    // Đóng trình duyệt sau khi nhiệm vụ hoàn tất
    await page.close();
    await browser.close();
  }
}

Mỗi cuộc gọi solver() sẽ:

  • Tạo một phiên bản trình duyệt độc lập
  • Tự động dọn dẹp sau khi sử dụng trong khối finally

5.3 Tách Biệt Proxy

ts Copy
const { proxy_url } = input;

browser = await this.connectToBrowser({
  proxy_url,  // Mỗi nhiệm vụ có thể sử dụng một proxy khác nhau
  // ...
});

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

Các nhiệm vụ khác nhau có thể đạt được tách biệt mức mạng bằng cách sử dụng các giá trị proxy_url khác nhau.

5.4 Tách Biệt Dấu Vân Tay

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

💡 Lợi Thế Scrapeless #4: Ổn Định Nhờ Tách Biệt

  • Mỗi truy vấn ChatGPT chạy trong một phiên trình duyệt độc lập, ngăn ngừa sự can thiệp
  • Tránh ô nhiễm Cookies, LocalStorage hoặc dấu vân tay, cải thiện tỷ lệ thành công của yêu cầu
  • Có thể thực hiện một số lượng lớn các truy vấn đồng thời trên cùng một máy mà không có xung đột phiên bản Puppeteer
  • Tăng cường độ ổn định và độ tin cậy trong các kịch bản có tính đồng thời cao

6. Chuyển Đổi GEO Toàn Cầu: Nhận Phản Hồi Từ Các Khu Vực Khác Nhau

6.1 Định Vị Toàn Cầu

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

6.2 Định Vị Dấu Vân Tay

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

💡 Lợi Thế Scrapeless #5: 195+ Nút Quốc Gia, Proxy Tự Động & Mô Phỏng Địa Phương

  • Lựa Chọn IP Quốc Gia Tự Động
    Mã quốc gia được phân tích từ proxy_url (ví dụ, -country_US, -country_JP), và Scrapeless tự động định tuyến các yêu cầu đến các IP dân cư trong khu vực tương ứng.

  • Không Cần Bảo Trì Hồ Bơi Proxy
    Backend tự động quản lý các nút toàn cầu, vì vậy người dùng không cần thiết lập hoặc cập nhật danh sách proxy cho bản thân.

  • Môi Trường Trình Duyệt Địa Phương
    fingerprint.localization.timezone có thể đặt múi giờ. Kết hợp với các phiên độc lập, nó mô phỏng môi trường khu vực mục tiêu, ảnh hưởng đến cách hiển thị nội dung và kết quả tìm kiếm cụ thể theo khu vực.

  • Nhận Kết Quả Địa Phương Thực Tế
    ChatgptResponse.country_code được trả về cho biết vị trí địa lý của yêu cầu, giúp tối ưu hóa SEO quốc tế, giám sát thương hiệu, hoặc phân tích nội dung nhạy cảm theo khu vực chính xác hơn.

7. Khai Thác Kết Quả và Hỗ Trợ Nhiều Định Dạng Đầu Ra

7.1 Đầu Ra Cấu Trúc

Trong phương thức startChat, dữ liệu đã được nắm bắt được đóng gói vào một đối tượng ChatgptResponse thống nhất:

ts Copy
resolve({
  prompt,
  success: true,
  answer: answerResponse,           // văn bản / html / thô
  country_code: proxy_country,      // thông tin địa lý
  citations: gptCitations,
  links_attached: gptLinksAttached,
  image_cards: gptImageCards,
  products: gptRecommendProducts,
  url: _url,
});

💡 Lợi Thế Scrapeless #6: Đầu Ra Cấu Trúc

  • Mỗi nhiệm vụ truy vấn tạo ra một đối tượng cấu trúc.
  • Bao gồm các trường như câu trả lời văn bản, liên kết đính kèm, hình ảnh, sản phẩm được đề xuất, trích dẫn, mã quốc gia và URL.
  • Các đối tượng cấu trúc có thể được sử dụng trực tiếp cho tự động hóa, mà không cần phân tích thêm.

7.2 Nhiều Phương Pháp Đầu Ra

Trong phương thức solver, kết quả có thể được đẩy hoặc trả về:

  1. Đầu Ra Webhook
ts Copy
Copy
this.pushToMessage(payload, webhook);
  1. Trả về chức năng
ts Copy
return createResponse(JSON.stringify(payload), payload.url);

💡 Lợi ích không rác #7: Hỗ trợ tự nhiên cho tích hợp quy trình dữ liệu

  • Dữ liệu có cấu trúc có thể được đẩy trực tiếp đến các hệ thống hoặc công cụ tự động hóa bên ngoài (ví dụ: n8n, Zapier, Airtable).
  • Không cần phát triển giao diện bổ sung hoặc xử lý dữ liệu thủ công, cho phép tích hợp tự động hóa theo thời gian thực.
  • Khi kết nối với các hệ thống hoặc cơ sở dữ liệu nội bộ, không cần phân tích hoặc chuyển đổi bổ sung, hỗ trợ nhiều đầu ra quy trình dữ liệu.
  • Kết quả của mỗi tác vụ truy vấn là một đối tượng có cấu trúc, tạo điều kiện cho phân tích, thống kê hoặc xuất ra CSV/JSON.

Mã hoàn chỉnh

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(`Xử lý không thành công: ${errorMessage}`);
        throw { message: !errorMessage.includes(this.internalErrorSymbol) ? errorMessage : '' };
      }
      const errorMessage = error.message || 'Lỗi không xác định';
      const payload = handleChatResponse({
        success: false,
        error_reason: errorMessage,
      });
      this.pushToMessage(payload, webhook);
      this.logger.warn(`Xử lý không thành công: ${errorMessage}`);
      throw error;
    } finally {
      const totalDuration = getTotalDuration();
      this.logger.log(
        `Xử lý ${successful ? 'thành công' : 'đã hoàn thành'} | Thời gian tổng cộng: ${totalDuration} giây`,
      );
    }
  }
typescript Copy
async format(data: Uint8Array): Promise<QueryChatgptRequest> {
    if (!data) {
      throw new Error('Không có dữ liệu đầu vào hợp lệ');
    }
    const input = JSON.parse(data.toString()) as QueryChatgptRequest;

    if (!input.prompt) {
      this.logger.error(`prompt là bắt buộc`);
      throw new Error('prompt là bắt buộc');
    }

    return {
      ...input,
      timeout: input.timeout || this.defaultTimeout,
      web_search: input.web_search ?? true,
      session_name: input.session_name || 'Trả lời của Chatgpt',
      session_recording: input.session_recording || false,
      answer_type: input.answer_type || 'văn bản',
    };
  }

  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 = 'Đang kết nối với Trình duyệt'));

      const proxy_country = /-country_([A-Z]{2,3})/.exec(proxy_url)?.[1] || 'BẤT KỲ';
      const query = new URLSearchParams({ q: prompt });
      if (web_search) {
        query.set('hints', 'tìm kiếm');
      }
      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; // Kiểm tra mỗi 500ms

          const checkResponse = async () => {
            try {
              if (isAborted) {
                resolve('hết thời gian chờ');
                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;

                // Khi có nhiều hơn một nút sao chép xuất hiện, điều đó có nghĩa là câu trả lời của gpt đã hoàn thành
                const $answerCopyButtons = document.querySelectorAll('button[data-testid="copy-turn-action-button"]');
                if ($answerCopyButtons.length > 1) {
                  return lastMessage.textContent || 'Không có nội dung';
                } else {
                  return null;
                }
              });

              // Nếu nội dung chưa thay đổi và không rỗng
              if (currentContent) {
                retryCount++;
                // Nếu nội dung ổn định và không rỗng, phản hồi được coi là hoàn thành
                if (retryCount >= 3) {
                  // Nội dung ổn định trong 1.5 giây
                  resolve(currentContent);
                  return;
                }
              }

              // Tiếp tục kiểm tra
              setTimeout(checkResponse, CHECK_INTERVAL);
            } catch (error) {
              reject(error);
            }
          };

          // Bắt đầu kiểm tra
          checkResponse();
        });
      }

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

        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('Không thể lấy phản hồi của luồng', 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(`Thời gian chờ Chat hết sau ${timeout}ms`);
      }, timeout);

      try {
        this.logger.debug((action = 'Đăng ký CDP để ghi lại dữ liệu luồng GPT (raw_response)'));
        await receiveChatGPTStream();
        this.logger.debug((action = 'Đang điều hướng đến chatgpt.com'));

const navigateTimeout = 25_000 * this.timeoutMultiplier;
try {
await page.goto(_url, { timeout: navigateTimeout });
} catch {
throwError(Thời gian chờ khi điều hướng đến chatgpt.com (${navigateTimeout}ms));
return;
}

Copy
    // Thêm listener thay đổi 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(`Chuyển hướng đến trang đăng nhập OpenAI khi <<${_url}>> - ${action}`);
      return;
    });

    if (isAborted) return;
    await this.wait(50, 150);
    this.logger.debug((action = 'Đảm bảo rằng input tồn tại'));
    const inputs = ['#prompt-textarea', '[placeholder="Hỏi bất kỳ điều gì"]'];
    try {
      await Promise.race(
        inputs.map(async (input) => {
          await page.waitForSelector(input, {
            timeout: 20_000 * this.timeoutMultiplier,
            visible: true,
          });
          return input;
        }),
      );
    } catch {
      throwError('Khu vực hiện tại không khả dụng hoặc đã chuyển hướng đến trang đăng nhập');
      return;
    }

    if (isAborted) return;
    await this.wait(150, 250);
    this.logger.debug((action = 'Đợi phản hồi từ GPT'));
    let gptAnswer: string;
    try {
      gptAnswer = await waitForChatGPTResponse(page);
      this.logger.debug((action = 'Đã nhận phản hồi GPT'));
    } catch (error: any) {
      this.logger.error(`Không thể nhận phản hồi: ${error.message}`);
      throwError(`Lấy phản hồi từ chatgpt thất bại`);
      return;
    }

    if (isAborted) return;
    await this.wait(150, 250);
    this.logger.debug((action = 'Lấy thẻ hình ảnh của 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 = 'Không tìm thấy thẻ hình ảnh'));
      }
    } catch (error: any) {
      this.logger.debug((action = `Lấy thẻ hình ảnh chatgpt: ${error.toString()}`));
    }

    if (isAborted) return;
    await this.wait(300, 450);
    this.logger.debug((action = 'Lấy sản phẩm gợi ý của 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()) {
          // Chuyển hướng liên kết bên ngoài có thể được kích hoạt
          let newPage: Page = null as unknown as Page;
          const targetCreatedHandler = async (target: Target) => {
            this.logger.debug((action = `Lấy sản phẩm gợi ý của 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);

          // Nhấp vào mục gợi ý
          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 });

              // Chờ để thông tin thay đổi
              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 = 'Không tìm thấy sản phẩm gợi ý'));
          }
        } catch (error: any) {
          this.logger.debug((action = `Lấy sản phẩm gợi ý của chatgpt: ${error.toString()}`));
        }

        if (isAborted) return;
        await this.wait(500, 1000);
        this.logger.debug((action = 'Lấy trích dẫn của 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 = 'Không tìm thấy trích dẫn'));
          }
        } catch (error: any) {
          this.logger.debug((action = `Lấy trích dẫn của chatgpt: ${error.toString()}`));
        }

        // Trong một số trường hợp, cần thêm một phần tử cố định vào trang
        // để ngăn puppeteer nhấp vào các phần tử ngẫu nhiên
        if (isAborted) return;
        this.logger.debug((action = 'Thêm các phần tử cố định để tránh nhấp chuột không mong muốn'));
        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 = 'Lấy các liên kết đính kèm của 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 = 'Không tìm thấy liên kết đính kèm'));
}

Copy
    this.logger.debug((action = 'Đang lấy nội dung'));
    const body = await page.evaluate(() => document.body.innerHTML);
    const cleanBody = body
      .replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '') // Xóa thẻ script
      .replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '') // Xóa thẻ style
      .replace(/<svg[^>]*>[\s\S]*?<\/svg>/gi, '') // Xóa thẻ svg
      .replace(/<img[^>]*\/?>/gi, '') // Xóa thẻ img
      .replace(/style="[^"]*"/gi, '') // Xóa tất cả các thuộc tính style
      .replace(/class="[^"]*"/gi, '') // Xóa tất cả các thuộc tính class
      .replace(/<!--[\s\S]*?-->/g, '') // Xóa các bình luận
      // Chuyển đổi các thay thế liên quan
      .replace(/<span>·<\/span>/g, '') // Xóa thẻ span có ·
      .replace(/<a href="https:\/\/www\.google\.com\/maps\/[^"]*"[^>]*>[\s\S]*?<\/a>/g, '') // Xóa tất cả các liên kết google maps
      .replace(/<a href="tel:+[^"]*"[^>]*>[\s\S]*?<\/a>/g, '') // Xóa tất cả các liên kết điện thoại
      .replace(/\s+/g, ' ') // Chuẩn hóa khoảng trắng
      .trim();

    this.logger.debug((action = 'Kiểm tra phản hồi lỗi'));
    const hasError = [
      'Đã có sự cố xảy ra khi tạo phản hồi.',
      'Hoạt động bất thường đã được phát hiện từ thiết bị của bạn.',
      'Đã xảy ra lỗi. Hoặc động cơ bạn yêu cầu không tồn tại hoặc có vấn đề khác trong việc xử lý yêu cầu của bạn.',
    ].some((message) => cleanBody.includes(message));
    if (hasError) {
      throwError(`ChatGPT hiện không khả dụng`);
      return;
    }

    this.logger.log((action = 'Trò chuyện thành công'));

    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('Thời gian kết nối trình duyệt đã hết'));
}
}, 1000);
}),
]);
return browser;
} catch (error) {
this.logger.error(Kết nối trình duyệt không thành công: ${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({ content: JSON.stringify(data).slice(0, 1800) }),
// body: JSON.stringify(data),
body: JSON.stringify({
data: data,
content: data.answer_text,
}),
});
if (res.ok) {
this.logger.log('Đẩy webhook thành công');
} else {
this.logger.error('Đẩy webhook không thành công', await res.text());
}
} catch (err) {
this.logger.error('Ngoại lệ đẩy webhook', err);
}
}

private async fakePageDate(page: Page) {
await page.evaluateOnNewDocument(() => {
// Bắt ngày mới
const fixedDate = new Date();
// ngẫu nhiên đặt ngày thành 1-3 năm trước
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>) {
vi 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));
  }
}



## Sau khi truy vấn: Biến phản hồi thô thành chiến lược hoạt động GEO có thể hành động trong 30 phút

Hiểu nhanh các kết quả tìm kiếm và xác định các chủ đề nội dung:

1. Sao chép JSON kết quả → Mở [https://csvjson.com/json2csv](https://csvjson.com/json2csv) → lấy CSV → dán vào Excel.
2. Thêm hai cột mới:
   - `brandCount` = `=IF(ISNUMBER(SEARCH("YourBrand",D2)),1,0)`
   - `gap` = `F2-E2` (Cột F là số lần xuất hiện của đối thủ, Cột E là `brandCount`)



![Sau khi truy vấn: Biến phản hồi thô thành chiến lược hoạt động GEO có thể hành động trong 30 phút](https://assets.scrapeless.com/prod/posts/chatgpt-geo/e8bd8631df16a0cd711b83d26b043b9b.png)


👉 **Kết luận:** Hiện tại, không ai đã "đưa ra chủ đề này," vì vậy bạn có thể ngay lập tức viết một bài viết như *“ABCProxy là gì?”* để chiếm lĩnh không gian câu trả lời.  


**Mẹo:** Sau khi thực hiện 100 truy vấn hàng loạt lần tới, sắp xếp theo `gap` giảm dần → 20 kết quả hàng đầu trở thành ý tưởng nội dung ưu tiên.


| Trường        | Ý nghĩa kinh doanh |
|--------------|----------------|
| prompt       | Truy vấn gốc của người dùng |
| answer_text  | Phản hồi đầy đủ từ ChatGPT |
| brandCount   | Số lần thương hiệu của bạn xuất hiện trong câu trả lời |
| rivalCount   | Số lần đối thủ xuất hiện |
| gap          | `rivalCount - brandCount` → `0` = chưa được chiếm lĩnh, nội dung ưu tiên; `>0` = chọn chủ đề ngay lập tức, viết bài so sánh/xếp hạng; `<0` = duy trì cập nhật, tiếp tục tối ưu hóa |

---

## Kết luận

Với Scrapeless Cloud Browser, bạn có thể tự động hóa các truy vấn ChatGPT để đạt được tối ưu hóa GEO xuyên quốc gia, vượt thời gian và dễ dàng có được kết quả tìm kiếm chính xác, địa phương hóa. Dù cho SEO quốc tế, giám sát thương hiệu hay phân tích thông tin thị trường, Scrapeless giúp bạn nhanh chóng xây dựng một hệ thống truy vấn tự động ổn định, hiệu quả, và có thể mở rộng.

Scrapeless không chỉ cung cấp tự động hóa trình duyệt cho dữ liệu GEO mà còn cung cấp các công cụ tiên tiến và chiến lược dữ liệu để kiểm soát hoàn toàn cơ chế trích dẫn AI. Liên hệ với chúng tôi để mở khóa giải pháp dữ liệu GEO hoàn chỉnh!  

Nhìn về phía trước, Scrapeless sẽ tiếp tục tập trung vào công nghệ trình duyệt đám mây, cung cấp cho doanh nghiệp các giải pháp khai thác dữ liệu hiệu suất cao, quy trình tự động và hỗ trợ cơ sở hạ tầng đại lý AI. Phục vụ các ngành như tài chính, bán lẻ, thương mại điện tử, SEO và marketing, Scrapeless cung cấp các giải pháp tùy chỉnh, dựa trên kịch bản để giúp doanh nghiệp đi trước trong kỷ nguyên dữ liệu thông minh.

Scrapeless Browser không chỉ là một công cụ tự động hóa - nó là:  
> **Cơ sở hạ tầng thu thập dữ liệu “hệ sinh thái tìm kiếm AI” có thể mở rộng**  

Cho phép bạn thực sự định lượng khả năng hiển thị thương hiệu ChatGPT, phạm vi từ khóa và xu hướng nội dung.

---

<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;
      "
    >
      👉 Đăng nhập vào Scrapeless Browser &gt;
    </div>
  </a>
</div>

Tại Scrapless, chúng tôi chỉ truy cập dữ liệu có sẵn công khai trong khi tuân thủ nghiêm ngặt các luật, quy định và chính sách bảo mật trang web hiện hành. Nội dung trong blog này chỉ nhằm mục đích trình diễn và không liên quan đến bất kỳ hoạt động bất hợp pháp hoặc vi phạm nào. Chúng tôi không đảm bảo và từ chối mọi trách nhiệm đối với việc sử dụng thông tin từ blog này hoặc các liên kết của bên thứ ba. Trước khi tham gia vào bất kỳ hoạt động cạo nào, hãy tham khảo ý kiến ​​cố vấn pháp lý của bạn và xem xét các điều khoản dịch vụ của trang web mục tiêu hoặc có được các quyền cần thiết.

Bài viết phổ biến nhất

Danh mục