Bloomberg 是互联网上最具影响力的金融新闻与市场平台之一。其版块和话题页面持续发布技术、市场、经济和政治领域的头条新闻,每条新闻都附有链接、发布时间戳和所属版块。分析师追踪市场动向,研究人员梳理报道趋势,产品团队监测哪些报道出现在哪些栏目。所有这些信号都存在于版块页面的公开列表布局中,你甚至不需要打开任何一篇文章。

本指南介绍如何用 JavaScript 和 Node.js 结合 Cheerio 抓取 Bloomberg。你将构建一个小型可运行爬虫,通过 Crawling API 获取 Bloomberg 公开版块页面,解析页面上每篇报道的标题、文章链接、发布时间戳和版块,并将结果导出为 JSON 和 CSV。整个教程仅涉及公开的标题和链接元数据,不涉及完整文章正文,靠近结尾的合法性章节也不是样板文字,因为 Bloomberg 的编辑内容受版权保护。在将此爬虫指向任何真实量级流量之前,请先阅读该章节。

你将构建的内容

一个 Node.js 脚本,接收 Bloomberg 公开版块 URL,通过 Crawling API 获取渲染后的 HTML,并为列表页上的每篇报道链接提取一条结构化记录。我们以技术版块作为贯穿全文的示例,收集每篇报道的以下公开列表字段:

  • Headline(标题)列表卡片上显示的文章标题。
  • Link(链接)指向 bloomberg.com 上该文章的 URL。
  • Published(发布时间)来自卡片 time 元素的发布时间戳,采用 ISO 日期格式。
  • Section(版块)报道所属的版块或话题,例如 "Technology"。

请注意刻意缺失的内容:文章正文、摘要和任何媒体资源。本爬虫仅收集链接和元数据,绝不采集每个标题背后受版权保护的正文内容。

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

如果你用裸 HTTP 客户端请求 Bloomberg 版块 URL,很少能拿到可用的列表。两个因素对你不利。首先,Bloomberg 在浏览器中通过 JavaScript 构建版块页面,因此初始 HTML 几乎是空壳,直到页面脚本运行后报道卡片才会填充进来。其次,Bloomberg 对自动化流量的检测极为积极:数据中心 IP 和不像真实浏览器的请求模式,在到达渲染后的标题之前就会遭到验证、限流或封锁。

因此,一个能正常工作的 Bloomberg 爬虫需要在单次请求中同时具备两点:能够实际渲染页面的浏览器,以及平台认为是真实访客的 IP。你可以自己用无头浏览器加上轮换住宅代理池来实现,但将这些拼凑在一起并保持其健康运行才是大部分工作所在。Crawling API 将这两者合并为一次调用:你发送 URL,它在可信 IP 后面渲染页面,返回可供你用 Cheerio 解析的完整 HTML。

使用 JavaScript token

Crawling API 提供两种 token:普通 token 和 JavaScript token。Bloomberg 需要在真实浏览器中渲染页面,因此本指南中的每次请求都请使用你的 JavaScript token。普通 token 返回未渲染的外壳,你的选择器将一无所获。

前置条件

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

基础 JavaScript 和 Node.js 知识。你应该熟悉编写和运行 Node 脚本,具备 DOM 概念,并能使用 npm 安装包。如果你是 Node 新手,官方文档和任何入门课程都能让你达到本教程所假设的水平。更完整的教程请参阅我们的用 Node.js 构建网络爬虫指南,涵盖基础知识。

Node.js 16 或更高版本。使用 node --version 确认你的版本。如果没有,请从 Node.js 官网安装,或通过 nvm 等版本管理器安装。

Crawlbase 账号和 token。注册后,打开控制台,从账号文档页面复制你的 JavaScript token。免费套餐最多提供 20,000 次请求,无需信用卡,且仅对成功的请求收费。请像对待密码一样保管好 token:它用于验证你的请求,不要将其提交到版本控制系统。

项目设置

创建项目文件夹,初始化并安装爬虫所需的两个库。

bash
node --version

mkdir bloomberg-scraper && cd bloomberg-scraper
npm init -y

npm install crawlbase cheerio

两个依赖各司其职:crawlbase 是 Crawling API 的官方 Node 客户端,cheerio 以 jQuery 风格的 API 解析返回的 HTML,让你能通过 CSS 选择器提取各个字段。在该文件夹中创建名为 scraper.js 的文件,并添加以下各步骤的代码。

第一步:获取渲染后的版块页面

从获取完整页面开始。导入 CrawlingAPI 类,用你的 JavaScript token 初始化它,并请求 Bloomberg 公开版块 URL。旧版教程使用的是技术版块,我们沿用相同选择。在解析之前检查状态码,可以让失败情况明显暴露而不是悄然无声。

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

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

const bloombergPageURL = 'https://www.bloomberg.com/technology';

api
  .get(bloombergPageURL)
  .then((response) => {
    if (response.statusCode === 200) {
      console.log(response.body.slice(0, 500));
    }
  })
  .catch((error) => console.error('API request error:', error));

node scraper.js 运行脚本,你应该在正文顶部看到真实的 Bloomberg 版块标记,而不是精简外壳。这确认了渲染在你编写任何选择器之前就已经正常工作。Crawling API 使用你提供的 JavaScript token 在真实浏览器中渲染页面,因此你拿到的 HTML 中报道卡片已经存在。

Crawlbase Bloomberg Scraper

第一次请求就返回了完整渲染的 Bloomberg 技术版块页面,你这边无需无头浏览器,也无需代理。Crawling API 在真实浏览器中运行页面,在服务端轮换住宅 IP,并处理 Bloomberg 对爬虫发出的各种验证挑战,因此你只需一次调用就能拿到完整的 HTML。先在免费套餐上指向一个公开版块页面,然后再添加解析器。

第二步:用 Cheerio 解析每个标题

拿到渲染后的 HTML,将其加载到 Cheerio 中并遍历报道链接。在 Bloomberg 版块页面上,每篇文章都通过 href 指向 /news/articles/ 路径的锚元素可以访问,版块标题位于列表上方的眉线(eyebrow)元素中。旧版教程从 .Eyebrow_sectionTitle-Wew2fboZsjA- a 读取版块,从页面 time 元素的 datetime 属性读取发布日期,我们沿用这两个方式,为每条报道链接收集一条记录。对每个字段进行防御性读取,可以防止一个缺失值导致整次运行崩溃。

javascript
const cheerio = require('cheerio');

function parseDataFromHTML(html) {
  const $ = cheerio.load(html);
  const seen = new Set();
  const results = {
    section: '',
    articles: [],
  };

  // Section / topic title from the eyebrow element
  results.section =
    $('.Eyebrow_sectionTitle-Wew2fboZsjA- a').first().text().trim() ||
    'Technology';

  // One record per public article link on the listing
  $('a[href*="/news/articles/"]').each((_, element) => {
    const anchor = $(element);
    const headline = anchor.text().replace(/\n\s+/g, ' ').trim();
    let link = anchor.attr('href');
    if (!headline || !link) return;

    if (link.startsWith('/')) {
      link = new URL(link, 'https://www.bloomberg.com').href;
    }
    if (seen.has(link)) return; // skip duplicate links
    seen.add(link);

    // Published timestamp from a nearby time element, if present
    const timeAttr = anchor
      .closest('article')
      .find('time')
      .attr('datetime');
    const published = timeAttr ? timeAttr.split('T')[0] : '';

    results.articles.push({
      headline,
      link,
      published: published || 'Date not available',
      section: results.section,
    });
  });

  return results;
}

以下几个细节让这段代码忠实于页面结构。版块标题来自 .Eyebrow_sectionTitle-Wew2fboZsjA- 锚元素,与旧版解析器的读取方式完全一致。每篇报道通过指向 /news/articles/ 的锚元素匹配,我们将相对路径解析为绝对的 bloomberg.com URL,使链接在页面之外也能正常使用。Set 去除了重复链接,因为同一篇文章通常会出现在版块页面的多个模块中。发布日期从附近 time 元素的 datetime 属性中读取,并通过 split('T')[0] 截取到 ISO 日期部分,这与原版处理文章时间戳的方式相同。

选择器会漂移

Bloomberg 生成的类名(如上面的 Eyebrow_* 后缀)会在无预告的情况下变化,版块页面也会频繁重新排列其模块。请将选择器视为起始模板,而非约定。当标题或版块返回空时,在浏览器开发者工具中重新检查实际页面,并更新选择器。定期维护选择器对任何生产爬虫来说都是正常的事,不代表哪里出了问题。

第三步:组装完整脚本并导出 JSON 和 CSV

现在将获取和解析两个步骤串联成一个可运行的脚本,然后将记录写入磁盘,同时保存为 JSON 和 CSV。旧版指南将原始 HTML 保存到文件后再第二次解析;将获取和解析合并到单次运行中可以减少活动部件,且效果相同。

javascript
const fs = require('fs');
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 toCsv(rows) {
  const headers = ['headline', 'link', 'published', 'section'];
  const escape = (value) =>
    `"${String(value).replace(/"/g, '""')}"`;
  const lines = [headers.join(',')];
  for (const row of rows) {
    lines.push(headers.map((h) => escape(row[h])).join(','));
  }
  return lines.join('\n');
}

async function main() {
  const url = 'https://www.bloomberg.com/technology';
  const html = await crawl(url);
  if (!html) return;

  const data = parseDataFromHTML(html);
  fs.writeFileSync('bloomberg.json', JSON.stringify(data, null, 2));
  fs.writeFileSync('bloomberg.csv', toCsv(data.articles));
  console.log(`Saved ${data.articles.length} headlines to JSON and CSV`);
}

main();

将第二步中的 parseDataFromHTML 函数粘贴到同一个文件中,使 main 可以调用它。用 node scraper.js 运行,你将得到两个文件:bloomberg.json 包含完整的结构化记录,bloomberg.csv 可直接在电子表格中打开。toCsv 辅助函数会对每个字段加引号,并将内嵌的引号双写,这一点在这里很重要,因为标题中频繁包含逗号。

输出结果示例

JSON 文件包含版块信息,以及每个标题的一个对象,各自附有标题文本、文章链接、发布日期和所属版块。下面展示的标题和链接为示例占位符,并非实时数据。

json
{
  "section": "Technology",
  "articles": [
    {
      "headline": "Chipmaker Delays Second Plant as Subsidies Stay in Flux",
      "link": "https://www.bloomberg.com/news/articles/example-chip-delay",
      "published": "2024-01-18",
      "section": "Technology"
    },
    {
      "headline": "Cloud Provider Posts Record Quarter on AI Demand",
      "link": "https://www.bloomberg.com/news/articles/example-cloud-quarter",
      "published": "2024-01-17",
      "section": "Technology"
    }
  ]
}

CSV 以相同的标题行反映了同样的数据,可直接导入 Excel、Google Sheets 或任何能读取分隔符文件的数据管道。

csv
headline,link,published,section
"Chipmaker Delays Second Plant as Subsidies Stay in Flux","https://www.bloomberg.com/news/articles/example-chip-delay","2024-01-18","Technology"
"Cloud Provider Posts Record Quarter on AI Demand","https://www.bloomberg.com/news/articles/example-cloud-quarter","2024-01-17","Technology"

跨版块和页面扩展

单个版块页面只是演示;真正的任务会随时间追踪多个栏目。Bloomberg 公开了一系列版块 URL(technology、markets、economics、politics 等),你可以遍历它们,通过 Crawling API 分别获取,用同一个函数解析,再合并结果。由于每个版块页面共享相同的列表结构,你已经写好的解析器无需修改就能跨所有版块使用。

javascript
async function scrapeSections(sections) {
  const all = [];

  for (const path of sections) {
    const url = `https://www.bloomberg.com/${path}`;
    const html = await crawl(url);
    if (!html) continue;

    const { articles } = parseDataFromHTML(html);
    all.push(...articles);
    console.log(`${path}: ${articles.length} headlines`);

    // Pace requests so you stay under the rate limit
    await new Promise((r) => setTimeout(r, 2000));
  }

  return all;
}

// scrapeSections(['technology', 'markets', 'economics']);

对于大量或重复运行,同样的获取再解析模式可以直接迁移到异步 Crawler,它可以将多个 URL 加入队列,并将结果推送回来,而无需阻塞在每个请求上。关于像这样的渲染密集型 JavaScript 页面的更多信息,请参阅我们的抓取 JavaScript 网站指南,以及有关大规模金融数据抓取的背景介绍。

保持不被封锁

即使渲染问题已经解决,Bloomberg 仍会监测爬虫特征的流量。以下几个习惯能让运行保持健康,适用于任何难度较高的商业目标。

  • 控制请求节奏。在版块请求之间引入延迟,而不是在紧密循环中高频访问。分散请求是保持在 Bloomberg 速率限制之下最重要的单一因素。
  • 依赖 IP 轮换。住宅 IP 池将请求分散到众多真实用户地址,确保没有单一地址触发限制或验证挑战。Crawling API 为你处理这一切;如果你自己搭建方案,这是最需要做好的部分。
  • 读懂状态码。当运行开始返回验证挑战或非 200 响应时,说明当前速率或 IP 层级已不再足够。将其视为需要回退的信号,而不是可以忽略的噪声。

更宏观的操作手册请参阅如何抓取网站而不被封锁

抓取 Bloomberg 是否合法?

抓取 Bloomberg 是否被允许,取决于 Bloomberg 的服务条款、你所在的司法管辖区,以及你对数据的使用方式。Bloomberg 的条款限制了自动化访问和对其内容的再利用,因此无论你的技术手段多么谨慎,抓取行为都可能与这些条款相抵触。这里的任何代码都不会改变这一点,它只是让技术层面的事情能够运作。请阅读 Bloomberg 的服务条款及其 robots.txt,遵守其中说明的任何速率预期,并将两者视为你采集内容的边界。关键是,绝不抓取登录墙或付费墙后面的任何内容:Bloomberg 的大量报道都是付费内容,绕过付费墙在条款上违规,在许多地区也构成法律违规。

本指南刻意将范围限定在公开的标题和链接元数据:文章标题、文章 URL、发布时间戳,以及报道所属版块,这些信息无需登录即可在版块页面上看到。这与文章本身截然不同。Bloomberg 的报道、分析和媒体资源是受版权保护的作品。切勿抓取、存储或再分发完整文章正文、摘要或图片,也不要组装一个复现 Bloomberg 编辑内容的衍生档案库。收集标题和链接供读者点击跳转至 bloomberg.com 原文,是一种正常的、以链接为导向的用途;复制该链接背后的正文则不然。如果你触及的任何字段涉及可识别的个人,隐私法(如 GDPR 和 CCPA)将在版权之上额外适用,这是坚守事实性列表元数据的又一理由。

如果你的项目需要超出公开标题范围的内容,正确的途径是合规渠道,而不是更聪明的爬虫。Bloomberg 提供官方和授权数据产品,包括 Bloomberg Terminal 和企业数据 feed,在明确的商业条款、归因规则和再利用权利下提供市场数据和内容。当你需要完整内容、保证结构、大量数据或再分发权利时,这些才是正确的工具。当你不确定某种用途是否被允许时,请获取许可或签署数据协议,而不是假设沉默即默许。关于合规选项的更广泛调查,请参阅我们对全球最佳金融数据提供商的综述。

回顾

核心要点

  • Bloomberg 在客户端渲染列表,且封锁力度很强。普通请求返回空外壳或验证挑战,因此你必须使用 JavaScript token 在可信 IP 后面渲染页面,才能解析。
  • Crawling API 一次调用完成所有工作。它在真实浏览器中渲染页面,轮换住宅 IP,处理验证挑战,返回可用 Cheerio 解析的完整 HTML。
  • Cheerio 提取公开字段。匹配每个 /news/articles/ 锚元素,读取标题、链接、发布时间戳和版块,按链接去重,并预期生成的类名会发生漂移。
  • 扩展和导出。遍历 Bloomberg 的公开版块 URL,控制请求节奏,将结构化记录写入 JSON 和 CSV;当数量增大时改用异步 Crawler。
  • 仅限标题和链接。Bloomberg 的文章正文和媒体受版权保护,因此切勿抓取或再分发正文,不要触碰登录或付费墙后面的任何内容,遵守服务条款和 robots.txt,并优先使用 Bloomberg 的官方或授权 feed 用于生产用途。

常见问题

用这个爬虫可以从 Bloomberg 收集哪些数据?

本指南仅收集公开列表元数据:文章标题、文章链接、发布时间戳,以及报道所属版块,这些信息无需登录即可在 Bloomberg 版块页面上看到。它不收集文章正文、摘要或媒体资源,因为这些内容受版权保护。输出的是一组链接和元数据,你可以用它来监测报道动态并点击跳转至原文。

为什么普通请求从 Bloomberg 返回不完整的数据?

因为 Bloomberg 通过 JavaScript 在客户端构建版块页面,并对自动化流量发起验证挑战。来自数据中心 IP 的原始 HTTP 请求通常返回空外壳或封锁页面,而不是报道卡片。要获取完整页面,你必须在可信 IP 后面渲染它,这正是当你使用 JavaScript token 时 Crawling API 为你处理的事情。

我可以抓取付费墙后面的 Bloomberg 文章吗?

不可以。本指南在这一点上有明确要求:绝不抓取登录墙或付费墙后面的任何内容。付费内容受 Bloomberg 条款约束,绕过付费墙可能还会带来法律风险。请坚守任何人无需账号即可在版块页面上看到的公开标题和链接,如需完整的付费内容,请使用 Bloomberg 的官方或授权产品。

我的选择器返回空值。发生了什么变化?

几乎可以肯定是 Bloomberg 的标记发生了变化。像 Eyebrow_sectionTitle-Wew2fboZsjA- 这样的生成类名会在无预告的情况下改变,版块页面也会频繁重新排列其模块,因此上个月还能用的选择器可能就此失效。在浏览器开发者工具中重新检查实际页面,更新 parseDataFromHTML 中的选择器,即可恢复正常。定期维护选择器对任何生产爬虫来说都是正常的事。

我可以用 JavaScript 以外的语言构建 Bloomberg 爬虫吗?

可以。本指南使用 JavaScript 和 Cheerio,但同样的方法适用于任何语言。Crawling API 为多种语言提供了库和 SDK,因此你以同样的方式获取渲染后的 HTML,再用你的技术栈偏好的 HTML 解析器(如 Python 中的 BeautifulSoup)解析。选择器和字段保持不变,只有解析语法会变。

Bloomberg 是否提供官方数据 feed?

是的。Bloomberg 提供官方和授权数据产品,包括 Bloomberg Terminal 和企业数据 feed,在明确的商业条款和再利用权利下提供市场数据和内容。如果你需要完整内容、大量数据、保证结构或再分发权利,合规渠道才是正确选择。这个公开元数据爬虫最适合研究、监测和链接收集场景,即不需要官方协议的用途。

开始构建

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

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

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