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 的反爬虫防护比普通网站更强,因此 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。
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 状态码返回挑战页面的目标来说非常重要。
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_wait 和 page_wait 选项;标准评论页面通常通过 API 的默认获取方式就足够了。
G2 对爬虫的防护力度很强,因此价值不在于解析 HTML,而在于首先获取到干净的 HTML。Crawling API 在轮换住宅 IP 后面用 G2 专属处理方式获取页面,吸收挑战和 CAPTCHA,然后将标记交给你,省去了自己运行无头浏览器队列和代理池的麻烦。先用免费套餐指向一个公开评论页面试试。
第二步:用 cheerio 解析评论
拿到可用的 HTML 后,将其加载到 cheerio 并遍历评论列表。G2 将每条评论布局为重复的卡片,因此模式是:先选取一次页面级上下文,然后迭代评论元素并从每个元素中提取相同的字段。用 try/catch 包裹提取逻辑,防止一张格式错误的卡片中止整个运行。
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,交给解析器,然后打印结构化结果。这是整个爬虫放在一个文件中的完整代码。
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 或数据库。
{ "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 获取,用同一函数解析,直到某页返回空评论为止。
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 次请求免费,无需信用卡。
