一个公开的 Facebook 企业主页是一个有用的信号源:品牌在那里发布帖子,用户对其作出反应,而页面级的元数据(名称、简介文本、近期公开帖子,以及公开的反应数和评论数)正是为竞争对手研究、内容基准对标和品牌监测提供养料的那类数据。问题在于,Facebook 几乎所有内容都用 JavaScript 和 AJAX 在客户端渲染,并对自动化流量发起激进的挑战,所以一次普通的 HTTP 请求返回的是一个近乎空白的加载器外壳,而非你在浏览器中看到的内容。

本指南向你展示如何用 JavaScript 和 Node.js,从一个公开的 Facebook 主页,通过 Crawling API 提取数据。你将构建一个可运行的小脚本,它获取一个渲染后的公开企业主页,并拉取页面级字段:页面名称、公开帖子的文本,以及公开的互动计数。整个演练始终限定在仅公开的企业和品牌页面。它不触及个人资料、私密群组、与可识别个人相关联的评论,或任何登录之后的内容。合法性与隐私一节被放在文章靠前的位置是有原因的,所以在把它指向任何东西之前先读它。

请先读这一段

Facebook 的服务条款对自动化采集有强力限制,而平台上大部分都是个人数据。请把这当作一次教育性的、仅限公开数据的演练。对于任何真实项目,受认可的路径是官方的 Facebook Graph API,而不是抓取。下面的"抓取 Facebook 合法吗?"一节并非套话。

你将构建什么

一个 Node.js 脚本,它接受一个公开 Facebook 企业主页的 URL,通过 Crawling API 取回渲染后的 HTML,并返回一条公开页面级数据的结构化记录。我们将用一个知名品牌的主页作为贯穿示例,并拉取以下字段:

  • 页面名称该企业或品牌主页的公开显示名称,例如"Alibaba.com"。
  • 帖子文本该主页自己发布的近期帖子的公开文本正文。
  • 反应数每条帖子上显示的反应公开总数。
  • 评论数每条帖子上显示的公开评论数量,仅作为一个汇总计数。
  • 分享数每条帖子上显示的公开分享数量。

注意哪些是被刻意省略的:没有评论者姓名、没有单条评论文本、没有资料细节、没有粉丝列表。那些都是个人数据,超出范围。我们在页面和帖子层面做汇总,到此为止。

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

如果你用一个裸 HTTP 客户端请求一个 Facebook 主页 URL,你得到的响应技术上成功,实际上却是空的。有两股力量在跟你作对。

第一,Facebook 在浏览器里构建页面。页面名称、简介区块和每一条帖子,都在初始文档到达之后通过 JavaScript 和 AJAX 调用动态加载,而更多帖子只在你滚动时才出现。获取原始 URL,你拿到的多半是加载转圈的标记,而非围绕它们渲染出来的内容。捕获真实数据意味着要等那些 AJAX 调用完成解析,并模拟触发更多内容的滚动。

第二,Facebook 主动防御自动化流量。它监视 IP 地址,标记看起来不像真实浏览器的请求模式,并实施可能导致临时或永久封锁的严格限速。一个数据中心 IP 在一个紧凑循环里连发请求,正是它被造出来要拦截的形态。

所以一个可行的方法在一次请求里需要两样东西:一个能渲染页面并等待其异步内容的真实浏览器,以及一个平台读起来像普通访客的 IP。你可以用一个无头浏览器加一个轮换住宅代理池自己组装这套,但保持那个技术栈健康就是大部分工作。Crawling API 把两者折叠进一次调用:你带上一个 JavaScript token 和正确的等待选项发送 URL,它在一个受信任的 IP 背后渲染页面,并返回完成的 HTML 或已解析的 JSON。关于渲染繁重目标的背景,参见如何爬取 JavaScript 网站

前置条件

在写任何代码之前你需要准备好几样东西。没有一样耗时。

基础的 JavaScript 和 Node.js。你应当能自如地编写并运行一个 Node 脚本,并用 npm 安装包。如果你对此还较新,我们关于如何用 Node.js 构建网络抓取器的指南覆盖了本教程所假定的基础。

Node.js 16 或更高版本。node --version 确认你的版本。如果还没有,从 Node.js 网站安装,或通过一个像 nvm 这样的版本管理器安装。

一个 Crawlbase 账户和 JS token。注册一个免费账户,打开你的仪表盘,复制你的 JavaScript(JS)token。Crawlbase 给你最多 20,000 次免费请求作为起步,而且你只为成功的请求付费。Facebook 是客户端渲染的,所以这里你需要 JavaScript token,而不是普通的那个。把 token 当作密码对待,别把它放进版本控制。

搭建项目

创建一个项目文件夹,初始化它,并安装 Crawlbase Node 客户端。

bash
node --version

mkdir facebook-page-scraper && cd facebook-page-scraper
npm init -y

npm install crawlbase

crawlbase 包是 Crawling API 的官方 Node 客户端。对于这个页面级演示,我们依赖 Crawlbase 内置的 Facebook Page 抓取器,它返回结构化 JSON,因此主示例不需要一个单独的 HTML 解析器。

第 1 步:获取渲染后的公开主页

先把完成的页面拿到手。导入 CrawlingAPI 类,用你的 JS token 初始化它,并请求一个公开企业主页的 URL。等待选项是让一次 Facebook 获取根本能跑起来的关键,所以它们在这里比在一个静态站点上更重要。

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

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

// A PUBLIC business/brand Page only, never a personal profile or private group
const pageUrl = 'https://www.facebook.com/Alibaba.comGlobal/';

async function fetchPage(url) {
  const options = {
    format: 'json',
    ajax_wait: 'true',
    scroll: 'true',
    scroll_interval: 30,
  };
  const response = await api.get(url, options);
  if (response.statusCode === 200) {
    return JSON.parse(response.body);
  }
  console.error(`Request failed: ${response.statusCode}`);
  return null;
}

fetchPage(pageUrl).then((data) => {
  if (data) console.log(data.body.slice(0, 500));
});

每个选项都有它存在的理由。format: 'json' 请求一个结构化响应,使渲染后的 HTML 出现在 body 字段里,而不是作为一份原始文档。ajax_wait: 'true' 告诉 API 一直等到页面的 AJAX 调用完成解析,这很关键,因为 Facebook 正是那样加载它的真实内容;跳过它你就捕获到加载器标记。scroll: 'true' 模拟一个用户滚动,使更多帖子加载,而 scroll_interval 设置以秒计的滚动时长(最大为 60)。用 node script.js 运行脚本,你应当在那段切片里看到真实的页面标记,而不是一个被削减的外壳。这在你解析任何东西之前确认了渲染是有效的。

Crawlbase Facebook Scraper

那一次 api.get 调用,做了一个无头浏览器加一个代理池本来要替你做的事。Crawling API 在一个真实浏览器中渲染主页,用 ajax_wait 等过 AJAX 调用,模拟那个加载更多帖子的滚动,并在服务器端轮换住宅 IP,于是你省去搭建并照看整套技术栈。在免费档位上起步,把它指向一个公开企业主页。

第 2 步:用内置抓取器获取结构化页面数据

原始 HTML 是可用的,但你将不得不针对 Facebook 频繁变动的标记编写并维护选择器。Crawling API 自带一个内置的 facebook-page 抓取器,它返回已经解析为 JSON 的公开页面数据,这是页面级提取的合适工具。你用 scraper 参数来启用它。

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

const api = new CrawlingAPI({ token: 'YOUR_CRAWLBASE_TOKEN' });
const pageUrl = 'https://www.facebook.com/Alibaba.comGlobal/';

async function scrapePage(url) {
  const options = {
    ajax_wait: 'true',
    scraper: 'facebook-page',
  };
  const response = await api.get(url, options);
  if (response.statusCode === 200) {
    return JSON.parse(response.body);
  }
  console.error(`Request failed: ${response.statusCode}`);
  return null;
}

scrapePage(pageUrl).then((data) => {
  if (data) console.log(JSON.stringify(data.body, null, 2));
});

抓取器响应携带页面级字段,比如页面名称和简介文本,连同该主页发布的公开帖子的一个数组,其中每条帖子都包含它的文本及其公开的反应数、评论数和分享数。因为抓取器负责解析,你无需针对每周都变的标记去追逐 CSS 选择器。Crawling API 自带数个抓取器;facebook-page 这个是为公开主页布局专门打造的。

第 3 步:仅提取公开的页面级字段

现在把抓取器输出收窄到我们想要的那些公开字段,并丢弃其余一切。这里就是我们落实范围的地方:页面名称、帖子文本,以及每条帖子的三个汇总计数。我们不读取也不存储评论者身份或单条评论正文。

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

const api = new CrawlingAPI({ token: 'YOUR_CRAWLBASE_TOKEN' });
const pageUrl = 'https://www.facebook.com/Alibaba.comGlobal/';

async function scrapePage(url) {
  const options = { ajax_wait: 'true', scraper: 'facebook-page' };
  const response = await api.get(url, options);
  if (response.statusCode !== 200) {
    console.error(`Request failed: ${response.statusCode}`);
    return null;
  }
  return JSON.parse(response.body).body;
}

function extractPublicData(page) {
  const posts = (page.posts || []).map((post) => ({
    text: post.text || null,
    reactionCount: post.reactionCounts || 0,
    commentCount: post.commentsCount || 0,
    shareCount: post.sharesCount || 0,
  }));

  return {
    pageName: page.pageName || page.title || null,
    postCount: posts.length,
    posts,
  };
}

async function main() {
  const page = await scrapePage(pageUrl);
  if (!page) return;
  const publicData = extractPublicData(page);
  console.log(JSON.stringify(publicData, null, 2));
}

main();

extractPublicData 函数做的是范围限定的工作。它保留页面名称、帖子文本,以及每条帖子的三个公开计数,而它不读取任何与可识别个人相关联的东西。每个字段在抓取器省略它时回退到一个安全默认值,因为并非每条帖子都显示分享数或评论数。字段名映射到抓取器的响应键(pageNamereactionCountscommentsCountsharesCount);如果某个键返回空,检查实时的抓取器输出并调整,因为主页布局会演变。

输出长什么样

node script.js 运行完整脚本,你会得到一个紧凑的 JSON 对象:页面名称,以及一个带文本和汇总计数的公开帖子列表,可随时写入文件或数据库。

json
{
  "pageName": "Alibaba.com",
  "postCount": 2,
  "posts": [
    {
      "text": "Source smarter this season with verified suppliers.",
      "reactionCount": 1280,
      "commentCount": 94,
      "shareCount": 37
    },
    {
      "text": "New buyer guide: how to vet a manufacturer in 5 steps.",
      "reactionCount": 863,
      "commentCount": 51,
      "shareCount": 22
    }
  ]
}

这里的每一个值都是公开且页面级的。没有用户名,没有单条评论文本,也没有资料数据,这正是本教程所守住的那条线。

处理滚动与 AJAX 内容

来自第 1 步的两个参数,是让 Facebook 变得可处理的关键,在你以任何体量运行它之前,值得弄懂它们。

  • ajax_wait。Facebook 在文档加载后通过 AJAX 注水它的内容。没有 ajax_wait: 'true',你在那些内容到达之前就捕获了页面,于是拿到加载器标记。有了它,API 只在异步调用完成解析后才返回 HTML。
  • scroll 与 scroll_interval。帖子随用户滚动渐进加载。scroll: 'true' 模拟那一点,而 scroll_interval 控制滚动多少秒,最多到 60。更长的间隔会浮现更多帖子,代价是更慢的请求,所以按你实际需要多少近期帖子来调它。

除了数据,Crawling API 还能用 screenshot 参数返回渲染后主页的一张截图,在响应里交回一个约一小时后过期的 screenshot_url。这对于直观确认捕获到了什么很有用,但上面那些结构化字段才是你赖以构建的东西。

守在限速之内

即便渲染已被处理好,Facebook 仍然实施严格的限速,越线会有临时或永久封锁的风险。几个习惯能让一次运行保持健康且有分寸。

  • 控制你的请求节奏。不要在一个紧凑循环里猛敲各主页。把请求间隔开,并保持总量较低;这既是一种礼貌,也是避免被标记的最快方式。
  • 依靠轮换。分散在一个住宅 IP 池上的请求,比单个数据中心地址远不容易触发限制。Crawling API 替你处理轮换;如果你构建自己的技术栈,这就是要投入的部分。完整打法参见如何在不被封锁的情况下抓取网站
  • 盯住状态码。当响应开始回来变成挑战或错误时,那是一个退避的信号,而不是要硬闯过去的噪声。

对于跨许多公开主页、更大且有排程的作业,一个异步队列比一个同步循环更合适。我们关于如何用 Crawlbase Crawler 提取数据的指南覆盖了那个模式,其中请求被排队并投递到一个 webhook,而不是阻塞你的脚本。

抓取 Facebook 合法吗?

在运行任何东西之前先读这一节。Facebook 的服务条款对自动化采集有强力限制。其条款及其自动化访问政策以宽泛的措辞禁止抓取,而无论你的工具多么谨慎,那条限制依然成立。本指南中的代码没有一行能凌驾于 Facebook 的条款之上;它只是让技术部分能跑通。在采集任何东西之前,读 Facebook 的服务条款、它的 robots.txt,以及它的开发者和平台政策,并把这三者都当作你可以触及之物的边界。

如果你出于研究或教育目的继续,请严格待在一条狭窄的公开车道内。只从公开的企业或品牌主页采集公开数据:页面名称、该主页自己发布的帖子的文本,以及汇总的公开互动计数。不要采集个人数据。这意味着没有个人资料、没有私密群组、没有粉丝或成员列表、没有私信,也没有与可识别个人相关联的单条评论。用户名、账号标识、资料细节和用户撰写的评论都是个人数据,而从中构建一个可识别个人的画像,正是要避免的事。在页面和帖子层面做汇总,就像上面的代码那样,然后到此为止。

只要涉及个人数据,隐私法就适用。在 GDPR 和 CCPA 之下,你需要一个处理个人数据的合法依据,并且必须履行删除和退出请求,这是一项沉重的义务,而公开主页的汇总计数正是被专门设计来绕开它的。对于任何生产用途,受认可且安全得多的路线是官方的 Facebook Graph API,它提供对一个主页所有者或应用被许可看到的数据的授权、有限速的访问,并附带清晰的条款。强烈优先 Graph API。仅把本指南中的公开数据方法用于小型的、教育性的、公开主页的工作,并且绝不把它作为绕过登录、隐私设置或平台条款的途径。

回顾

核心要点

  • Facebook 在客户端渲染。一次普通请求返回加载器标记,所以在任何内容出现之前,你必须渲染页面、等待 AJAX 并模拟滚动。
  • 渲染与一个受信任的 IP,在一次调用里。带 JS token 的 Crawling API 两者都做;ajax_waitscrollscroll_interval 控制页面如何被捕获。
  • 使用内置的 facebook-page 抓取器。它把公开页面数据作为 JSON 返回,于是你避免了针对不断变化的标记维护选择器。
  • 把范围限定在公开的页面级数据。仅页面名称、帖子文本,以及汇总的反应、评论和分享计数;绝不涉及评论者身份、个人资料、私密群组或单条评论。
  • 优先官方 API。Facebook 的服务条款强力限制抓取,且 GDPR/CCPA 适用于个人数据,所以对于小型教育用途以外的任何事情,Facebook Graph API 是受认可的路径。

常见问题

为什么普通请求从一个 Facebook 主页返回不了真实内容?

因为 Facebook 在客户端加载它的内容。页面名称、简介区块和帖子,都在初始文档之后通过 JavaScript 和 AJAX 到达,而更多帖子只在滚动时出现。一次裸 HTTP 请求在那之前就捕获了页面,所以你拿到的是加载器标记而非数据。用 Crawling API 的 JS token 渲染页面并等待 AJAX,才能返回真正的内容。

对于 Facebook 我需要普通 token 还是 JS token?

使用 JavaScript(JS)token。Facebook 用客户端渲染构建它的页面,所以那个获取静态 HTML 的普通 token,回来时带的是加载器标记,没有有意义的内容。JS token 先在一个真实浏览器中渲染页面,这正是让数据出现的原因。

我能从一个 Facebook 企业主页安全提取哪些公开数据?

坚持页面级的公开字段:页面名称、该主页自己发布的帖子的文本,以及作为数字的汇总互动计数(反应、评论、分享)。避免任何个人的东西,包括评论者姓名、单条评论文本、资料细节、粉丝列表、私密群组,以及任何登录之后的内容。在页面和帖子层面的汇总计数是站得住脚的范围。

我能抓取个人资料或私密群组吗?

不能,本指南也不涉及它。个人资料、私密群组、成员列表和私信都是个人且非公开的数据,采集它们违反 Facebook 的条款和隐私法。本演练特意限定在公开的企业和品牌主页。要获得对更多内容的受认可访问,请带着适当的授权使用 Facebook Graph API。

我应该改用 Facebook Graph API 吗?

对于任何生产或商业用途,是的。Facebook Graph API 是官方、授权的路径,带有限速和清晰条款,当一个主页所有者或应用需要可靠访问时,它就是合适的工具。这里的公开数据抓取方法只适合小型的、教育性的、没有 API 访问的公开主页工作,而且它仍然必须尊重 Facebook 的条款。

我如何避免被封锁或被限速?

保持请求量较低,控制请求节奏而非紧凑循环,并经由轮换住宅 IP 路由,使任何单个地址都不触发 Facebook 的限制。Crawling API 替你管理轮换和一个受信任的 IP 池。盯住状态码,并在你开始看到挑战或错误时立刻退避,而不是硬闯过去。

开始构建

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

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

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