Google Finance 是读取实时市场数据使用最广泛的平台之一,其行情页面包含驱动价格追踪、投资组合监控和研究所需的精确结构化数据:实时价格、当日涨跌金额和百分比、前收盘价,以及市值、市盈率、日内和年内交易区间等核心市场指标。对于监控一篮子股票的人来说,这些公开的市场数据就是原始素材,而手动跨数十只股票进行复制既费时又容易出错。

本指南将向你展示如何以可靠的方式使用 Python 抓取 Google Finance。你将构建一个小而可运行的爬虫,通过 Crawling API 获取已渲染的行情页面,用 BeautifulSoup 解析你需要的字段,循环抓取多只股票,并导出整洁的 JSON。整个演示始终聚焦于公开市场数据(属于事实性数据,而非个人信息),文末的合法性章节并非套话,在将本工具指向任何真实流量之前请务必阅读。

你将构建什么

一个 Python 脚本,接收一个 Google Finance 行情 URL 列表(每只股票一个,使用 Google 的 SYMBOL:EXCHANGE 格式),通过 Crawling API 获取每个已渲染页面,并提取每只股票的结构化记录。以下以 NASDAQ 上的 GOOGL、AAPL 和 MSFT 为运行示例,抓取这些字段:

  • 标题 行情页顶部显示的公司名称。
  • 价格 该股票的当前报价。
  • 涨跌幅 本交易日的绝对涨跌金额及百分比。
  • 前收盘价 上一交易日的收盘价。
  • 市场数据 关键指标块:市值、市盈率以及日内和年内区间(如有)。

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

用普通 HTTP 客户端请求 Google Finance 行情 URL,你会得到状态 200 的响应,但响应体中只有一小部分数据。有两个因素对你不利。第一,Google Finance 通过 JavaScript 在浏览器中填充其行情页面,因此初始 HTML 是一个薄壳。最明显的证据是价格涨跌百分比:从第一次响应中提取它,你会得到 None,因为该值是在页面加载后通过客户端渲染的。第二,Google 能迅速识别自动化流量。数据中心 IP 和不像真实浏览器的请求模式,在到达已渲染内容之前就会被限速、封禁 IP 或发起挑战。

因此,一个可用的 Google Finance 爬虫需要在一次请求中同时满足两点:一个实际渲染页面的浏览器,以及一个平台视为真实访客的 IP。你可以自行用无头浏览器加轮换住宅代理来搭建,但维护这套系统是大部分的工作量所在。Crawling API 将两者融合进一次调用:发送带 JavaScript token 的 URL,它在可信 IP 后方渲染页面,返回已完成的 HTML 供解析。关于已渲染目标的更多背景,请参阅如何爬取 JavaScript 网站

为什么需要 JS token

Crawlbase 提供两种 token 类型。普通 token 获取静态 HTML;JavaScript (JS) token 先在真实浏览器中渲染页面。Google Finance 的价格、涨跌百分比和指标均在客户端填充,因此此处需要 JS token。普通 token 返回的是与普通请求相同的薄壳,涨跌百分比缺失,几乎没有可解析的有用内容。

前提条件

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

基础 Python 知识。 你应当能够编写并运行 Python 脚本,以及使用 pip 安装包。如果你对解析部分不熟悉,BeautifulSoup 指南是本教程的好伴侣。

Python 3.8 或更高版本。 使用 python --version 确认你的版本。如果没有,请从 python.org 安装或通过 Anaconda 等发行版安装,并确保 Python 在你的 PATH 中。

Crawlbase 账户和 JS token。 注册账户,打开控制台,从账户文档页面复制你的 JavaScript (JS) token。Crawlbase 提供最多 20,000 次免费请求,足够完成本指南的学习,且仅对成功请求计费。请将 token 视为密码:它用于验证你的请求,因此不要将其提交到版本控制中。

设置项目

创建虚拟环境以隔离项目依赖,然后安装爬虫所需的库。

bash
python --version

python -m venv google_finance_env
source google_finance_env/bin/activate

pip install crawlbase beautifulsoup4

在 Windows 上,请用 google_finance_env\Scripts\activate 代替 source 那行来激活环境。两个依赖项各司其职:crawlbase 是 Crawling API 的官方客户端,beautifulsoup4 解析返回的 HTML 以便按 CSS 选择器提取各字段。json 模块随标准库附带,导出步骤无需额外安装。

第一步:获取已渲染的 Google Finance 页面

首先获取一个完整页面。导入 CrawlingAPI 类,用你的 JS token 初始化它,然后请求一个行情 URL。Google Finance 是异步加载数据的,因此传入 ajax_waitpage_wait,让 API 在捕获页面前等待动态内容加载完毕。在解析前检查 Crawlbase 的 cb_status(legacy pc_status),可以让失败情况显而易见而非悄无声息。

python
from crawlbase import CrawlingAPI

api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"})

OPTIONS = {
    "ajax_wait": "true",
    "page_wait": 5000,
}

def crawl(page_url):
    response = api.get(page_url, OPTIONS)
    if response["headers"]["cb_status"] == "200":
        return response["body"].decode("utf-8")
    print(f"Request failed: {response['headers']['cb_status']}")
    return None

if __name__ == "__main__":
    quote_url = "https://www.google.com/finance/quote/GOOGL:NASDAQ"
    html = crawl(quote_url)
    print(html[:500] if html else "No HTML returned")

两个等待选项对于 Google Finance 这样的客户端渲染目标至关重要。ajax_wait 告知 API 等待异步内容加载完毕,page_wait 在页面加载后固定等待指定毫秒数,以确保延迟渲染的数值在捕获前出现。五秒是一个合理的起点;如果涨跌百分比返回为空,可适当增大。用 python google_finance_scraper.py 运行脚本,你应当看到真实的行情页标记,而非普通请求返回的薄壳。这可以在编写任何选择器之前确认渲染是否正常工作。

Crawlbase Crawling API

Google Finance 需要在一次调用中同时完成页面渲染和可信 IP 访问,而这正是上面 ajax_waitpage_wait 选项所实现的。Crawling API 接收 JS token,在真实浏览器中运行页面(确保涨跌百分比实际加载),在服务端轮换住宅 IP,并将已完成的 HTML 交给你,省去了自行运营无头浏览器集群和代理池的麻烦。先用免费额度对公开行情页面进行测试。

第二步:解析价格、涨跌幅和标题

有了已完成的 HTML,将其加载到 BeautifulSoup 中并提取核心字段。Google Finance 将公司名称、当前价格和当日涨跌幅保存在页面的 main 区域,每个字段都有一个生成的 class。以下选择器直接来自实时行情布局:标题位于 zzDege,价格位于 AHmHk,涨跌百分比位于 JwB6zf。每次查找都有防护,确保缺失字段时返回 None 而不是让运行崩溃。

python
from bs4 import BeautifulSoup

def get_price(soup):
    el = soup.find("main")
    if not el:
        return None
    price = el.find("div", "AHmHk")
    return price.get_text(strip=True) if price else None

def get_change_percentage(soup):
    main = soup.find("main")
    if not main:
        return None
    change = main.find("div", "JwB6zf")
    return change.get_text(strip=True) if change else None

def get_stock_title(soup):
    main = soup.find("main")
    if not main:
        return None
    title = main.find("div", "zzDege")
    return title.get_text(strip=True) if title else None

JwB6zf 元素是需要重点关注的。在非渲染的薄壳响应中它完全缺失,get_change_percentage 返回 None;而通过 JS token 渲染页面后,它携带当日的涨跌金额和百分比。这个单一字段是检验渲染是否正常工作的最简单证据。

选择器会漂移

Google 生成的 class 名称,如 AHmHkJwB6zfzzDege,会在没有通知的情况下发生变化。将此处的选择器视为起始模板,而非合同。当一个字段在明显已渲染的页面上返回 None 时,在浏览器开发者工具中重新检查实时行情并更新 class。定期维护选择器对于任何生产爬虫来说都是正常的,并不意味着出了什么问题。

第三步:解析市场数据块

在核心数据下方,Google Finance 以标签和数值行的形式列出关键指标:前收盘价、日内区间、年内区间、市值、市盈率等。每一行是一个包含 gyFHrc 的容器,其中标签位于 mfs7Fc,数值位于 P6K39c。将每一行转入字典,可以让解析器保持弹性:无论某只股票披露哪些指标,都会作为键值出现,你可以直接从该字典读取 Previous closeMarket capP/E ratioDay rangeYear range

python
def get_market_data(soup):
    rows = soup.find_all("div", {"class": "gyFHrc"})
    data = {}
    for row in rows:
        label = row.find("div", {"class": "mfs7Fc"})
        value = row.find("div", {"class": "P6K39c"})
        if label and value:
            data[label.get_text(strip=True)] = value.get_text(strip=True)
    return data

KEEP = ["Previous close", "Day range", "Year range", "Market cap", "P/E ratio"]

def select_market_fields(market_data):
    return {key: market_data[key] for key in KEEP if key in market_data}

get_market_data 辅助函数捕获页面上的所有标签和数值对,无需为每个指标硬编码一个选择器。select_market_fields 将其缩小到本指南的范围:前收盘价、日内和年内区间、市值以及市盈率。由于过滤器检查 if key in market_data,缺少某个指标的股票会直接从记录中省略该键,而不会导致失败。如果后续还需要平均成交量或主要交易所信息,保留完整的 get_market_data 输出即可。

第四步:组装完整脚本

现在将各部分串接成一个可运行的脚本:获取每个行情页面,解析核心字段和市场数据块,并将记录导出为 JSON。extract_quote 函数将各字段辅助函数整合成一条记录,main 函数遍历 SYMBOL:EXCHANGE URL 列表,让你可以在一次运行中获取多只股票的数据。

python
import json
import time
from crawlbase import CrawlingAPI
from bs4 import BeautifulSoup

api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"})

OPTIONS = {
    "ajax_wait": "true",
    "page_wait": 5000,
}

KEEP = ["Previous close", "Day range", "Year range", "Market cap", "P/E ratio"]

def crawl(page_url):
    response = api.get(page_url, OPTIONS)
    if response["headers"]["cb_status"] == "200":
        return response["body"].decode("utf-8")
    print(f"Request failed: {response['headers']['cb_status']}")
    return None

def text_in_main(soup, class_name):
    main = soup.find("main")
    if not main:
        return None
    el = main.find("div", class_name)
    return el.get_text(strip=True) if el else None

def get_market_data(soup):
    rows = soup.find_all("div", {"class": "gyFHrc"})
    data = {}
    for row in rows:
        label = row.find("div", {"class": "mfs7Fc"})
        value = row.find("div", {"class": "P6K39c"})
        if label and value:
            data[label.get_text(strip=True)] = value.get_text(strip=True)
    return data

def extract_quote(html, url):
    soup = BeautifulSoup(html, "html.parser")
    market = get_market_data(soup)
    return {
        "url": url,
        "title": text_in_main(soup, "zzDege"),
        "price": text_in_main(soup, "AHmHk"),
        "change": text_in_main(soup, "JwB6zf"),
        "previous_close": market.get("Previous close"),
        "market_data": {key: market[key] for key in KEEP if key in market},
    }

def main():
    urls = [
        "https://www.google.com/finance/quote/GOOGL:NASDAQ",
        "https://www.google.com/finance/quote/AAPL:NASDAQ",
        "https://www.google.com/finance/quote/MSFT:NASDAQ",
    ]
    records = []
    for url in urls:
        html = crawl(url)
        if html:
            records.append(extract_quote(html, url))
        time.sleep(2)

    with open("finance_data.json", "w") as f:
        json.dump(records, f, indent=2)
    print(f"Saved {len(records)} quotes")

if __name__ == "__main__":
    main()

单个 text_in_main 辅助函数取代了之前三个几乎相同的标题、价格和涨跌幅函数:它先找到 main 区域,查找一个生成的 class,并返回去除空白后的文本或 Noneextract_quote 为每只股票组装一条记录,从市场数据字典中提取前收盘价,并将其余指标嵌套在 market_data 下。两秒的延时控制运行节奏,避免过于频繁地访问站点。编辑 urls 列表可抓取任何其他股票代码。

输出结果示例

python google_finance_scraper.py 运行完整脚本,你将获得每只股票的整洁结构化记录,可直接用于分析、入库或制作电子表格。以下数值仅为示例;你的运行结果会显示实时行情。

json
[
  {
    "url": "https://www.google.com/finance/quote/GOOGL:NASDAQ",
    "title": "Alphabet Inc Class A",
    "price": "$163.79",
    "change": "+1.01 (0.62%)",
    "previous_close": "$162.78",
    "market_data": {
      "Previous close": "$162.78",
      "Day range": "$163.09 - $167.12",
      "Year range": "$103.71 - $174.71",
      "Market cap": "2.04T USD",
      "P/E ratio": "25.54"
    }
  },
  {
    "url": "https://www.google.com/finance/quote/AAPL:NASDAQ",
    "title": "Apple Inc",
    "price": "$169.30",
    "change": "-0.61 (0.36%)",
    "previous_close": "$170.33",
    "market_data": {
      "Previous close": "$170.33",
      "Day range": "$169.11 - $172.71",
      "Year range": "$164.08 - $199.62",
      "Market cap": "2.61T USD",
      "P/E ratio": "26.34"
    }
  }
]

注意 change 字段现在携带了真实的值,而非 null。在非渲染的普通请求中,它返回为空,因为 Google 是通过客户端渲染来填充它的;通过 JS token 路由请求才能让它出现。从这里,记录可以直接导入 pandas 或任何电子表格,用于追踪价格走势、比较观察列表中各股的市盈率,或输入价格情报工作流

扩展到观察列表并保持不被封锁

上面的脚本只抓取三只股票,但通过扩展 urls 列表,结构可以扩展到完整的观察列表。随着运行规模增大,以下几个习惯有助于保持其健康运行,这些习惯适用于任何高强度目标。

  • 控制请求节奏。 在紧密循环中大量请求行情是被限速或发起挑战的最快方式。上面的两秒延时是下限,而非上限。对于较大的观察列表适当加宽,并分散股票代码而非以全速重复请求同一路径。
  • 依赖轮换。 住宅 IP 池将请求分散到众多真实用户地址,使单个地址不会触发速率限制。Crawling API 会替你处理这个问题;如果你自行搭建,这是需要着重处理的部分。
  • 关注状态码。 运行开始返回非 200 的 cb_status 值,说明当前的速率或 IP 级别已经不够用了。将此视为退让的信号,而不是可以忽略的噪音。

对于较大规模的任务,异步 Crawler 可以将请求排入队列并通过 webhook 返回结果,适合在不保持长连接的情况下按计划刷新多只股票的行情。关于更广泛的操作指南,请参阅如何不被封锁地抓取网站。同样的渲染加可信 IP 方法同样适用于其他市场数据来源,例如从 CoinMarketCap 抓取加密货币价格

抓取 Google Finance 是否合法?

抓取 Google Finance 是否被允许,取决于 Google 的服务条款、你所在的司法管辖区以及你对数据的使用方式。Google 的条款限制了对其服务的自动访问,因此无论你的工具多么谨慎,抓取行为都可能与这些条款相抵触。此处的代码并不改变这一点,只是让技术层面运作起来。请阅读 Google 的服务条款以及 Google Finance 路径的 robots.txt,并将两者视为你所收集内容和请求频率的边界。

本指南所针对的数据明确处于公开非个人的范畴:股票价格、当日涨跌幅、前收盘价、市值、市盈率以及日内和年内区间都是客观的市场数字,而非任何人的个人信息。这使工作范围比抓取包含用户内容的网站更为狭窄,但并不意味着你凌驾于 Google 的条款之上。请只访问任何访客无需账户即可加载的行情页面,将请求量控制在不给 Google 服务器造成负担的范围内,不要大量转发底层市场数据源,因为交易所和数据供应商对这些数据进行了授权,而授权会随数据一起转移。

本指南故意将范围限定在公开行情页面,因为这是保持工作可辩护的边界。它不涵盖任何需要登录的内容、付费或高级数据,或任何绕过身份验证或速率限制的尝试。如果你的项目需要有保障的正常运行时间、再分发权利或超出礼貌抓取所允许的更大流量,正确的路径是获取授权数据源:使用官方的市场数据 API 或授权的交易所数据源。这才是商业或批量使用的正确途径,而不是更激进的爬虫。

回顾

核心要点

  • Google Finance 是客户端渲染的。 普通请求返回的薄壳缺少涨跌百分比,因此必须先渲染页面再解析。
  • 你需要渲染和可信 IP 的结合。 带 JS token 的 Crawling API 可在一次调用中完成两者;ajax_waitpage_wait 控制等待数值加载的时长。
  • 将字段映射到真实的 class 名称。 标题位于 zzDege,价格位于 AHmHk,当日涨跌幅位于 JwB6zf;指标块是一组 gyFHrc 行,将 mfs7Fc 标签与 P6K39c 数值配对。
  • 循环并导出。 传入 SYMBOL:EXCHANGE 行情 URL 列表,用短暂延时控制运行节奏,并将每只股票的记录写入 JSON,市场数据嵌套其中。
  • 仅针对公开市场数据。 遵守 Google 的服务条款和 robots.txt,坚持客观公开的行情数据,生产或再分发场景请使用官方市场数据 API 或授权数据源。

常见问题

为什么普通请求中涨跌百分比为 null?

因为 Google Finance 是通过 JavaScript 在客户端渲染该值的。初始 HTML 是一个薄壳,持有当日涨跌幅的 JwB6zf 元素只有在浏览器中运行页面脚本后才会出现。原始 HTTP 请求返回的是状态 200 加上缺失涨跌幅的响应,这就是为什么该字段返回 None。通过 Crawling API 的 JS token 先渲染页面,可以让数值呈现,BeautifulSoup 再读取它。

抓取 Google Finance 需要普通 token 还是 JS token?

JS token。普通 token 获取的是静态 HTML,对于 Google Finance 来说,这与普通请求返回的薄壳相同,涨跌百分比同样缺失。JS token 先在真实浏览器中渲染页面,然后再返回 HTML,确保价格、涨跌幅和指标块在解析时都已完整呈现。

我可以从 Google Finance 行情页抓取哪些数据?

公开的市场字段:公司标题、当前价格、当日涨跌金额和百分比、前收盘价,以及指标块(包括市值、市盈率,以及日内和年内区间,如有)。这些都是任何访客无需账户即可加载的客观公开行情数据,而非个人信息。请坚守这一范围,如需再分发权利,请使用授权数据源。

我的选择器返回 None。发生了什么?

几乎可以肯定是 Google 的标记发生了变化。其生成的 class 名称(价格为 AHmHk,涨跌幅为 JwB6zf,标题为 zzDege,指标行为 gyFHrcmfs7FcP6K39c)会在没有通知的情况下变化,所以上个月有效的选择器可能已经失效。在浏览器开发者工具中重新检查实时行情并更新 class 字符串。定期维护选择器对于任何生产爬虫来说都是正常的。

如何同时抓取多只股票?

Google Finance 以 SYMBOL:EXCHANGE 格式在行情 URL 中标识每只股票,例如 GOOGL:NASDAQ。构建这些 URL 的列表并循环遍历,在每个已渲染页面上调用相同的 extract_quote 函数,如上面的 main 函数所示。在请求之间保持短暂延时,并将列表扩展到覆盖你的完整观察列表。

生产使用有官方的 Google Finance API 吗?

Google 并没有提供公开的、受支持的 Google Finance 市场数据 API,因此对于生产或商业使用,可靠的路径是授权的市场数据提供商或官方交易所数据源,而非抓取网页。这些数据源附带正常运行时间保证和再分发条款,这是抓取公开页面所无法提供的,当数据准确性或使用权利成为需求的一部分时,这一点尤为重要。

开始构建

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

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

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