G2 是软件买家在付费前查看真实用户评价的首选平台。其评论页面承载着产品、销售和竞争情报团队大规模需要的信号:星级评分、评论标题、每篇评论的正文、评论者的职位或公司规模标签,以及评论发布时间。问题在于 G2 动态渲染这些页面,并设有强力的 Cloudflare 式反爬虫防护,因此来自 Node 的普通 HTTP 请求在看到任何评论之前就会被拦截。

本指南演示如何以可靠的方式用 JavaScript 抓取 G2 评论。你将构建一个小型 Node.js 脚本,通过 Crawling API 获取渲染后的评论页面,用 cheerio 解析,并提取每条评论的整洁记录。整个流程仅限于公开评论数据,结尾处的合法性章节并非走过场,请在大量抓取前认真阅读。

你将构建什么

一个 Node.js 脚本:接受一个公开的 G2 产品评论 URL,通过 Crawling API 获取渲染后的 HTML,并提取结构化的评论列表。我们以某个公开产品的评论页面作为示例,提取每条评论的以下字段:

  • 评论标题 评论者给出的简短标题。
  • 星级评分 数字分数,例如"4.5"。
  • 评论正文 评论的主要内容。
  • 评论者职位或标签 评论上显示的公开职位或公司规模标签。
  • 日期 评论发布的时间。

此外,我们还会获取一些页面级别的上下文信息:产品名称和总体星级平均分。这为每批评论提供了依托。

为什么普通请求在 G2 上会失败

如果用 fetch 或 axios 等裸 HTTP 客户端请求 G2 评论 URL,你不会得到评论数据。有两个原因。第一,G2 在浏览器中渲染大部分评论内容,因此你收到的原始 HTML 在页面脚本运行之前是不完整的。第二,更重要的是,G2 运行着积极的反爬虫保护:数据中心 IP、缺失的浏览器指纹以及爬虫特征的请求模式,会收到挑战页面或直接被封锁,而非内容。你会看到 403、CAPTCHA 拦截页面,或"正在验证你的浏览器"的等待页面,而非评论。

因此,一个可用的 G2 爬虫在单次请求中需要两件事:平台认为是真实访客的 IP,以及在页面需要时的渲染能力。你可以自己尝试搭建无头浏览器加上轮换住宅代理池,但面对主动对抗爬虫的目标,维护这套系统才是主要工作。Crawling API 将两者合并为一次调用:你把 URL 发给它,它在受信任的住宅 IP 后面以针对该目标的适当处理方式获取页面,并返回可供解析的 HTML。

G2 是难度较高的目标

G2 的反爬虫防护比普通网站更强,因此 Crawling API 会为其使用定制化路径,而非通用的获取方式。如果你注册后请求 G2 仍返回挑战,请联系支持以在你的账号上启用 G2 专属处理。启用后,下面的代码可以无需修改直接使用。

前提条件

在编写任何代码之前,你需要准备好以下几项。都不会花太长时间。

基础 JavaScript 和 Node.js 知识。 你应该能够编写和运行 Node.js 脚本,并使用 npm 安装包。如果你刚开始接触这一技术栈的爬虫开发,我们的用 Node.js 构建网络爬虫演练是一个温和的起点。

已安装 Node.js。node --version 确认。如果没有,从 nodejs.org 下载 LTS 版本并运行对应操作系统的安装程序。

Crawlbase 账号和 token。 注册后打开控制台,从账号文档页面复制你的请求 token。请像对待密码一样保管 token:它用于验证你的请求,不要放入版本控制或任何已提交的文件。

搭建项目

创建一个新的项目文件夹,初始化它,并安装爬虫所需的两个库:官方 Crawlbase 客户端和用于解析的 cheerio。

bash
mkdir g2-reviews-scraper
cd g2-reviews-scraper
npm init --yes

npm install crawlbase cheerio

两个依赖各司其职:crawlbase 是 Crawling API 的官方客户端,cheerio 为你提供 jQuery 风格的 API 来查询返回的 HTML,让你通过 CSS 选择器提取各个字段。提取数据不需要 Express、数据库或任何 Web 服务器;这些属于你围绕爬虫构建的东西,而不是爬虫本身。

第一步:获取渲染后的评论页面

先把页面取回来。导入 CrawlingAPI 类,用你的 token 初始化它,然后请求产品的评论 URL。在解析之前检查状态码,可以让失败信息清晰可见,这对于会以 200 状态码返回挑战页面的目标来说非常重要。

javascript
const { CrawlingAPI } = require("crawlbase");

const api = new CrawlingAPI({ token: "YOUR_CRAWLBASE_TOKEN" });

async function crawl(pageUrl) {
  const response = await api.get(pageUrl);
  if (response.statusCode === 200) {
    return response.body;
  }
  console.error(`Request failed: ${response.statusCode}`);
  return null;
}

(async () => {
  const pageUrl = "https://www.g2.com/products/xcode/reviews";
  const html = await crawl(pageUrl);
  console.log(html ? html.slice(0, 500) : "No HTML returned");
})();

将此文件保存为 scraper.js,用 node scraper.js 运行。如果一切配置正确,你会在前 500 个字符中看到真实的评论标记,而不是挑战页面。这一次检查就确认了最难的部分(突破 G2 的防护)在你编写第一个选择器之前已经能正常工作。如果 G2 某次提供了需要客户端渲染才能填充内容的页面,可在请求中添加 JavaScript token 以及 ajax_waitpage_wait 选项;标准评论页面通常通过 API 的默认获取方式就足够了。

Crawlbase Crawling API

G2 对爬虫的防护力度很强,因此价值不在于解析 HTML,而在于首先获取到干净的 HTML。Crawling API 在轮换住宅 IP 后面用 G2 专属处理方式获取页面,吸收挑战和 CAPTCHA,然后将标记交给你,省去了自己运行无头浏览器队列和代理池的麻烦。先用免费套餐指向一个公开评论页面试试。

第二步:用 cheerio 解析评论

拿到可用的 HTML 后,将其加载到 cheerio 并遍历评论列表。G2 将每条评论布局为重复的卡片,因此模式是:先选取一次页面级上下文,然后迭代评论元素并从每个元素中提取相同的字段。用 try/catch 包裹提取逻辑,防止一张格式错误的卡片中止整个运行。

javascript
const cheerio = require("cheerio");

function parseReviews(html) {
  try {
    const $ = cheerio.load(html);
    const data = {
      productName: $(".product-head [itemprop=name]").text().trim(),
      averageStars: $("#products-dropdown .fw-semibold").first().text().trim(),
      reviews: [],
    };

    $(".nested-ajax-loading > div.paper").each((_, el) => {
      const card = $(el);
      const title = card.find("[itemprop=name]").first().text().trim();
      const stars = card.find("[itemprop='ratingValue']").attr("content");
      const text = card.find(".pjax").text().trim();
      const role = card.find("[ue=tooltip]")
        .map((_, label) => $(label).text().trim())
        .get()
        .join(", ");
      const date = card.find(".x-current-review-date").text().trim();

      data.reviews.push({ title, stars, text, role, date });
    });

    return data;
  } catch (error) {
    console.error("Parse error:", error.message);
    return null;
  }
}

有几点值得说明。星级评分从 ratingValue 元素的 content 属性读取,而非其可见文本,因为 G2 在该属性中干净地暴露了数字分数。评论者职位或标签从 G2 附加到每张卡片的 tooltip 标签中提取,并拼接为一个字符串,因为一条评论可以携带多个公开标签,例如职位加公司规模。所有内容都经过 trim 处理,确保你不会存储带前后空白的评论。

选择器会发生变化

G2 的类名和标记会不定期变化。请将上述选择器视为起始模板,而非固定合约。当某个字段在所有评论中都返回空字符串时,在浏览器开发者工具中重新检查实时页面并更新选择器。定期维护选择器是任何生产爬虫的正常工作,并非方法有误的信号。

第三步:整合代码

现在将获取和解析整合为一个可运行的脚本。获取渲染后的 HTML,交给解析器,然后打印结构化结果。这是整个爬虫放在一个文件中的完整代码。

javascript
const { CrawlingAPI } = require("crawlbase");
const cheerio = require("cheerio");

const api = new CrawlingAPI({ token: "YOUR_CRAWLBASE_TOKEN" });

async function crawl(pageUrl) {
  const response = await api.get(pageUrl);
  if (response.statusCode === 200) {
    return response.body;
  }
  console.error(`Request failed: ${response.statusCode}`);
  return null;
}

function parseReviews(html) {
  const $ = cheerio.load(html);
  const data = {
    productName: $(".product-head [itemprop=name]").text().trim(),
    averageStars: $("#products-dropdown .fw-semibold").first().text().trim(),
    reviews: [],
  };

  $(".nested-ajax-loading > div.paper").each((_, el) => {
    const card = $(el);
    data.reviews.push({
      title: card.find("[itemprop=name]").first().text().trim(),
      stars: card.find("[itemprop='ratingValue']").attr("content"),
      text: card.find(".pjax").text().trim(),
      role: card.find("[ue=tooltip]")
        .map((_, label) => $(label).text().trim()).get().join(", "),
      date: card.find(".x-current-review-date").text().trim(),
    });
  });

  return data;
}

(async () => {
  const pageUrl = "https://www.g2.com/products/xcode/reviews";
  const html = await crawl(pageUrl);
  if (!html) return;
  const data = parseReviews(html);
  console.log(JSON.stringify(data, null, 2));
})();

输出示例

node scraper.js 运行完整脚本,你会得到一个整洁的结构化对象:产品信息、其平均评分,以及一个评论数组,每条评论都可以直接写入 JSON、CSV 或数据库。

json
{
  "productName": "Xcode",
  "averageStars": "4.5",
  "reviews": [
    {
      "title": "Solid IDE for native Apple development",
      "stars": "5",
      "text": "The integration with the Apple toolchain is seamless...",
      "role": "Software Engineer, Small-Business",
      "date": "Aug 12, 2025"
    }
  ]
}

跨评论页面扩展

单页只是演示;实际任务需要遍历一个产品的所有评论页面,往往还跨越多个产品。G2 对评论进行分页,因此下一页可以通过在评论 URL 后追加页码来访问。结构保持不变:构建页面 URL,通过 Crawling API 获取,用同一函数解析,直到某页返回空评论为止。

javascript
function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

async function scrapeAllPages(productSlug, maxPages) {
  const base = `https://www.g2.com/products/${productSlug}/reviews`;
  const all = [];

  for (let page = 1; page <= maxPages; page++) {
    const url = page === 1 ? base : `${base}?page=${page}`;
    const html = await crawl(url);
    if (!html) break;

    const { reviews } = parseReviews(html);
    if (!reviews.length) break;

    all.push(...reviews);
    await sleep(2000);
  }

  return all;
}

两个细节保持了这段代码的健壮性。循环在某页返回空评论时立即停止,避免请求超出列表末尾。sleep 调用在请求之间控制节奏;即使有托管 API 在前端,在紧密循环中频繁请求 G2 仍是最快被限速的方式。两秒是合理的下限。如果你需要抓取多个产品,异步 Crawler 允许你推送 URL 并通过 webhook 收集结果,而不是阻塞每次获取。

保持不被封锁

即使 Crawling API 已吸收了 G2 的大部分防护,养成几个好习惯能让长时间运行保持健康,这些习惯同样适用于任何强防护的商业目标。

  • 控制请求频率。 在页面请求之间加入延迟,而不是以循环允许的最快速度发出请求。稳定而慢是成功之道;快而贪婪则会被挑战。
  • 依赖 IP 轮换。 住宅 IP 池将请求分散到众多真实用户地址,使单个 IP 不会触发速率限制。Crawling API 为你处理这一切;如果你自己搭建,这部分是关键。
  • 关注状态码。 运行期间开始返回 403 或挑战页面,说明当前频率过高。回退并放慢速度,而不是加大力度重试。

更广泛的策略请参阅如何绕过 Cloudflare 并避免机器人检测以及网络抓取中如何绕过 CAPTCHA的深度剖析。如果你更倾向于通过轮换池路由自己的 Node 流量而非使用托管 API,Smart AI Proxy 以直连代理端点的形式提供同等的住宅 IP 轮换能力。如果其他网站的评论是你的下一个目标,我们的抓取客户评论指南涵盖了通用模式。

抓取 G2 合法吗?

抓取 G2 是否被允许,取决于 G2 的服务条款、你所在的司法管辖区以及数据的用途。G2 条款限制自动化访问,因此无论你的工具有多谨慎,抓取行为都可能违反这些条款。这里的代码不改变这一点,只是让技术部分得以运行。请阅读 G2 的服务条款及其 robots.txt,并将两者视为你采集内容的边界。

几条值得遵守的底线。只采集公开显示的评论数据:任何人无需登录即可在公开评论页面上看到的评论标题、星级评分、评论正文、公开职位或标签,以及日期。尊重 G2 声明的预期,保持请求量足够低,不给其服务器造成压力。不要收集超出页面公开显示的评论者个人或联系数据,也不要尝试将公开评论与私人身份相关联。

本指南有意将范围限定在无需登录的公开评论页面,因为这是让工作具有可辩护性的边界。本指南不涵盖任何需要登录的内容、需要 G2 账号或付费套餐才能访问的数据、评论者个人或联系信息(超出公开显示的部分),或任何绕过身份验证或 G2 保护措施以访问受限内容的尝试。G2 设置强力反爬虫防护是有原因的;正确的姿态是以礼貌的频率只读取其向公众展示的内容。如果你的项目需要超出公开评论数据的内容,官方数据协议才是正确的途径,而不是更聪明的爬虫。

回顾

核心要点

  • G2 会拦截普通请求。 其 Cloudflare 式防护会向裸 HTTP 客户端发出挑战和 403,因此获取干净的 HTML 才是难点,而非解析它。
  • Crawling API 承担繁重工作。 它在一次调用中用 G2 专属处理方式通过轮换住宅 IP 获取页面,省去了自己运行无头浏览器队列和代理池的麻烦。
  • cheerio 负责提取。 遍历评论卡片,将标题、评分、正文、公开职位或标签,以及日期映射到当前选择器,并预期这些选择器会发生变化。
  • 通过循环页面实现扩展。 追加页码,获取,解析,并对每次请求添加延迟以防止长时间运行触发限速。
  • 坚守公开数据。 尊重 G2 的服务条款和 robots.txt,只采集公开显示的评论字段,绝不触碰登录、受限数据或评论者个人信息。

常见问题

为什么普通请求从 G2 返回不到评论?

因为 G2 运行着积极的反爬虫保护,并在客户端渲染部分评论内容。来自 Node 的裸 HTTP 请求在到达任何评论标记之前就会碰到挑战页面、CAPTCHA 或 403。要获取真实数据,必须在 G2 认为是真实访客的 IP 后面获取页面,并在页面需要时进行渲染,这正是 Crawling API 为你处理的事情。

G2 是否需要特殊设置?

是的。G2 的防护比普通网站更强,因此 Crawling API 会为其使用定制化路径。如果你注册后请求 G2 仍返回挑战,请联系支持以在你的账号上启用 G2 专属处理。启用后,本指南中的代码无需修改即可正常工作。

如何处理 G2 评论页面的分页?

G2 对评论进行分页,下一页可以通过在评论 URL 后追加页码来访问,例如 ?page=2。从第一页向上循环,用同一函数获取并解析每页,在某页返回空评论时停止。在请求之间添加延迟,防止运行被限速。

我的选择器返回空字符串。是什么变了?

几乎可以肯定是 G2 的标记发生了变化。其类名和卡片结构会在不通知的情况下变化,因此上个月有效的选择器可能会失效。在浏览器开发者工具中重新检查实时评论页面并更新选择器。定期维护选择器是任何生产爬虫的正常工作,并非方法有误的信号。

我可以从 G2 抓取评论者姓名、电子邮件或其他个人数据吗?

不,本指南也不涵盖这部分内容。将你的采集范围限定在公开显示的评论字段:标题、星级评分、评论正文、公开职位或标签,以及日期。评论者个人或联系数据(超出公开显示的部分)、任何需要登录才能访问的内容,以及任何绕过身份验证的尝试,都超出本文范围并违反 G2 的条款。需要超出公开评论数据的内容,正确的途径是官方数据协议。

应该将评论存储在哪个数据库?

使用适合你技术栈的任何数据库。爬虫返回的是普通 JSON 对象,因此可以直接放入 PostgreSQL、MySQL、MongoDB、云存储,或者对于小型运行来说,甚至可以是扁平的 JSON 或 CSV 文件。提取逻辑有意与存储解耦,让你可以在不修改解析器的情况下稍后选择存储方案。

开始构建

大规模爬取任何站点,无需与基础设施对抗。

Crawlbase 负责处理代理、指纹和 CAPTCHA,让你的团队专注于交付数据流水线,而非维护爬取管道。1,000 次请求免费,无需信用卡。

自助开通 · 无需销售通话 · 提供企业级爬取量