价格是 Walmart 产品页面上唯一在其他一切保持原位时仍会变动的字段。标题、品牌、型号,这些都稳如磐石,但购买按钮旁那个数字会随促销、需求和季节性调整而移动。如果你想追踪这种移动、对一篮子商品比价,或盯着竞争对手的产品目录,你就需要随着时间一遍又一遍地读取同一个价格,并把每次读数写到某个你能绘制成图表的地方。

本指南将向你展示如何用 Python 抓取 Walmart 价格,并把它们变成一份你可以分析的运行日志。你会构建一个可运行的小型抓取器,它通过 Crawling API 获取渲染后的 Walmart 产品页面,用 BeautifulSoup 从 HTML 中取出标题和价格,并把每次读数连同一个时间戳追加到 JSON 和 CSV,于是你能随时间追踪价格。我们将整个演练严格限定在公开的产品和列表数据上,而临近结尾的合规性章节并非套话,所以在你把它指向任何真实流量之前,请先读一读。

你将构建什么

一个 Python 脚本,它接收一个 Walmart 产品 URL,通过 Crawling API 获取渲染后的页面,提取一条结构化记录,并把它追加到一份带时间戳的价格日志里。我们将用一个翻新 iPhone 列表作为贯穿全文的示例,并从每个产品页面取出这些字段:

  • 标题 产品名称,例如 "Restored Apple iPhone 13, Carrier Unlocked, 128GB Red"。
  • 价格 购买按钮旁显示的当前标价。
  • 折扣 节省的美元金额,当列表显示一个时。
  • 评分 平均星级评分,当产品有评论时。
  • 时间戳 抓取时加入的一个 ISO 时间,于是每次读数都是一个你可以绘制成图表的点。

为什么对 Walmart 发起普通请求会失败

如果你用一个裸 HTTP 客户端请求某个 Walmart 产品 URL,你会得到一个状态为 200 的响应,但响应体里几乎没有价格数据。有两件事在跟你作对。第一,Walmart 在客户端构建它的产品页面:初始 HTML 只是一个外壳,只有在页面 JavaScript 于浏览器里运行之后才填入标题、价格和评分。第二,Walmart 会很快标记出自动化流量。数据中心 IP 以及看起来不像真实浏览器的请求模式,远在抵达渲染后的价格之前就会被一个 CAPTCHA 挑战或封锁。

因此,一个能用的价格抓取器需要在同一个请求里满足两件事:一个真正渲染页面的浏览器,以及一个被平台读作真实购物者的 IP。你可以自己用无头浏览器加一池轮换住宅代理来拼凑这套方案,但把它们缝合起来并保持健康才是大部分工作。Crawling API 把两者折叠进一次调用:你把带 JavaScript token 的 URL 发给它,它在一个受信任的住宅 IP 之后渲染页面,并返回已完成的 HTML 供你解析。

为什么用 JS token

Crawlbase 提供两种 token 类型。普通 token 获取静态 HTML;JavaScript(JS)token 会先在一个真实浏览器里渲染页面。Walmart 在客户端把价格和评分加载进页面,所以你在这里需要 JS token。用普通 token 返回的就是普通抓取会得到的同一个空外壳,而价格元素根本不在里面。

前置条件

在写任何代码之前,你需要准备好几样东西。它们都花不了多久。

基础 Python。你应当能自如地编写和运行一个 Python 脚本,并用 pip 安装软件包。如果你刚接触这门语言,我们关于用 Python 抓取网站的演练涵盖了本教程默认你已具备的基础。

Python 3.8 或更高版本。python --version 确认你的版本。如果还没有,请从 python.org 安装,或通过 Anaconda 这类发行版安装。

一个 Crawlbase 账户和 JS token。注册、打开你的仪表盘,从账户文档页复制你的 JavaScript(JS)token。免费额度包含 1,000 次请求且无需信用卡,足够用来构建和测试这个抓取器。把 token 当作密码对待:它用于认证你的请求,所以不要把它放进版本控制。

搭建项目

创建一个虚拟环境,让项目依赖保持隔离,然后安装抓取器需要的三个库。

bash
python --version

python -m venv walmart_env
source walmart_env/bin/activate

pip install crawlbase beautifulsoup4 pandas

在 Windows 上,请用 walmart_env\Scripts\activate 替代那行 source 命令来激活环境。三个依赖各司其职:crawlbase 是 Crawling API 的官方客户端,beautifulsoup4 解析返回的 HTML,于是你能按 CSS 选择器取出各个字段,而 pandas 处理最后的 CSV 导出,于是你的价格历史能在任何电子表格里干净地打开。

理解 Walmart 产品页面

一个 Walmart 产品页面在一屏里塞了很多东西:标题、图片轮播、价格区块、带评论数的星级评分、描述以及卖家详情。对价格追踪而言,你只关心其中的几样。要紧的字段是标题(这样你就知道一次读数属于哪个商品)、当前价格、显示出来时的折扣或节省额,以及评分。

在写选择器之前,在你的浏览器里打开一个产品页面,右键点击价格,然后选择检查。你会看到价格通过一个价格包裹容器内部的 itemprop="price" 属性暴露出来,标题在一个 h1#main-title 元素里,而节省额和评分藏在 data-testid 标记之后。这些属性正是你要瞄准的。Walmart 的工具类名经常变,但语义化属性更耐用,所以在你能做到的地方就依靠它们。

第 1 步:抓取渲染后的产品页面

先从获取已完成的页面开始。导入 CrawlingAPI 类,用你的 JS token 初始化它,并请求产品 URL。在解析之前检查状态码,能让失败大声暴露而不是悄无声息。

python
from crawlbase import CrawlingAPI

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

def crawl(product_url):
    options = {"ajax_wait": "true", "page_wait": 3000}
    response = api.get(product_url, options)
    if response["status_code"] == 200:
        return response["body"].decode("latin1")
    print(f"Request failed: {response['status_code']}")
    return None

if __name__ == "__main__":
    url = "https://www.walmart.com/ip/Restored-Apple-iPhone-13-Carrier-Unlocked-128-GB-Red-Refurbished/462799546"
    html = crawl(url)
    print(html[:500] if html else "No HTML returned")

这两个等待选项对像这样的客户端渲染目标很重要。ajax_wait 告诉 API 等待异步内容加载完成,而 page_wait 在加载后再保持固定毫秒数,好让价格区块在页面被捕获之前出现。三秒是个合理的起点;如果价格返回缺失,就把它调高。响应体被解码为 latin1,因为 Walmart 页面混入了一些严格 UTF-8 解码会噎住的字符。运行脚本,你应当看到真实的产品标记,而不是普通抓取返回的那个空外壳。这在你写下第一个选择器之前就确认了渲染正常工作。

Crawlbase Walmart Scraper

那个价格区块只有在 Walmart 于一个受信任的 IP 之后渲染页面之后才存在,而这正是上面那个 crawl 函数所依靠的。Crawling API 接收一个 JS token,在一个真实浏览器里运行页面,在服务端轮换住宅 IP,并把已完成的 HTML 交给你,于是你免去了自己运行无头舰队和代理池的工作。先在免费额度上把它指向一个产品 URL。

第 2 步:用 BeautifulSoup 提取标题和价格

拿到渲染后的 HTML 后,把它载入 BeautifulSoup,并按选择器取出每个字段。价格位于价格包裹容器内部一个带 itemprop="price" 的 span 里,标题在 h1#main-title 里,而节省额和评分藏在它们各自的 data-testid 标记之后。一个小巧的辅助函数在某个元素缺失时返回空字符串,于是一个缺失的字段不会让整次运行崩溃。

python
from bs4 import BeautifulSoup
from datetime import datetime, timezone

def text_of(soup, selector):
    el = soup.select_one(selector)
    return el.get_text(strip=True) if el else ""

def parse_product(html):
    soup = BeautifulSoup(html, "html.parser")
    return {
        "title": text_of(soup, "h1#main-title"),
        "price": text_of(soup, 'span[data-testid="price-wrap"] span[itemprop="price"]'),
        "discount": text_of(soup, 'div[data-testid="dollar-saving"] span:last-child'),
        "rating": text_of(soup, 'div[data-testid="reviews-and-ratings"] span.rating-number'),
        "scraped_at": datetime.now(timezone.utc).isoformat(),
    }

text_of 辅助函数查询单个元素,并在它缺失时返回空字符串,而不是对一个空值调用 .get_text() 而抛错。这在某个字段缺失时让提取保持健壮,而这很常见,因为并非每个列表都带着折扣或评分。价格来自 itemprop="price" 这个 span,即 Walmart 标注当前价格的规范位置,而 scraped_at 给每条记录打上它被读取那一刻的戳。正是那个时间戳把一次性的抓取变成一个你日后能绘制成图表的价格序列。

选择器会漂移

Walmart 的工具类名会不经通知地改变,而价格和节省区块上的 data-testid 值偶尔也会变动。把上面的选择器当作一个起点模板,而不是一份契约。当价格在一个你确知有价格的页面上返回为空时,在你浏览器的开发者工具里重新检查实时产品页面并更新选择器。对任何生产环境的抓取器来说,定期维护选择器都很正常,并不是出了什么问题的迹象。

第 3 步:把每次读数追加到价格日志

现在把抓取和解析串成一个可运行的脚本,并把每次读数追加到一个 JSON 文件和一个 CSV。追加而非覆盖正是关键所在:每次运行为每个产品添加一行带日期的记录,日复一日、周复一周,那些行就成了一份你可以绘图的价格历史。

python
import json
import os
import pandas as pd
from bs4 import BeautifulSoup
from datetime import datetime, timezone
from crawlbase import CrawlingAPI

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

PRODUCTS = [
    "https://www.walmart.com/ip/Restored-Apple-iPhone-13-Carrier-Unlocked-128-GB-Red-Refurbished/462799546",
]

def crawl(product_url):
    options = {"ajax_wait": "true", "page_wait": 3000}
    response = api.get(product_url, options)
    if response["status_code"] == 200:
        return response["body"].decode("latin1")
    print(f"Request failed: {response['status_code']}")
    return None

def text_of(soup, selector):
    el = soup.select_one(selector)
    return el.get_text(strip=True) if el else ""

def parse_product(html, url):
    soup = BeautifulSoup(html, "html.parser")
    return {
        "title": text_of(soup, "h1#main-title"),
        "price": text_of(soup, 'span[data-testid="price-wrap"] span[itemprop="price"]'),
        "discount": text_of(soup, 'div[data-testid="dollar-saving"] span:last-child'),
        "rating": text_of(soup, 'div[data-testid="reviews-and-ratings"] span.rating-number'),
        "url": url,
        "scraped_at": datetime.now(timezone.utc).isoformat(),
    }

def append_json(record, path="walmart_prices.json"):
    history = []
    if os.path.exists(path):
        with open(path) as f:
            history = json.load(f)
    history.append(record)
    with open(path, "w") as f:
        json.dump(history, f, indent=2)
    return history

def main():
    for url in PRODUCTS:
        html = crawl(url)
        if not html:
            continue
        record = parse_product(html, url)
        append_json(record)
        print(json.dumps(record, indent=2))

    history = json.load(open("walmart_prices.json"))
    pd.DataFrame(history).to_csv("walmart_prices.csv", index=False)

if __name__ == "__main__":
    main()

append_json 函数读取已有的历史,加入新读数,再把整个列表写回去,于是 JSON 文件每次运行都增长一条记录。循环之后,脚本用 pandas 从那份历史重建 walmart_prices.csv,这给了你一张能在任何电子表格里打开、用于绘图的扁平表。往 PRODUCTS 列表里添加一个 URL,同样的代码就能一次追踪好几个商品。要随时间收集读数,把这个脚本安排成一个每天运行一次的 cron 任务,每次运行就为每个产品追加一行新的、带日期的记录。

输出长什么样

python scraper.py 运行完整脚本,每次读数会以 JSON 打印出来并落进你的价格日志。在跨几天跑了几次之后,JSON 文件就为每个产品保存了一个小小的序列。

json
[
  {
    "title": "Restored Apple iPhone 13, Carrier Unlocked, 128 GB Red (Refurbished)",
    "price": "$449.00",
    "discount": "$200.00",
    "rating": "(4.4)",
    "url": "https://www.walmart.com/ip/...462799546",
    "scraped_at": "2026-06-09T14:05:33+00:00"
  },
  {
    "title": "Restored Apple iPhone 13, Carrier Unlocked, 128 GB Red (Refurbished)",
    "price": "$429.00",
    "discount": "$220.00",
    "rating": "(4.4)",
    "url": "https://www.walmart.com/ip/...462799546",
    "scraped_at": "2026-06-10T14:04:11+00:00"
  }
]

这两条记录共享一个标题和 URL,但在价格和日期上不同,这正是价格追踪需要的形态。从这里出发,把 walmart_prices.csv 载入 pandas,能让你计算每个产品的最低、最高和平均价格,标记跌破某个阈值的情况,或绘制这个序列。如果你想把这份日志变成一个跨零售商的并排视图,配套指南构建一个比价工具会从这里接着讲下去。

跨多个产品抓取价格

追踪一个列表只是演示;一项真实的工作会盯着一篮子产品,而你往往事先并没有这些产品的 URL。要构建那个篮子,你先抓取一个搜索结果页,收集产品链接,然后对每一个运行上面的价格抓取器。从一个 Walmart 搜索页面提取那些链接本身就是一项任务,在我们关于用 Python 抓取 Walmart 搜索页面的指南里有详细介绍。一旦你有了 URL,把它们喂进 PRODUCTS 列表,循环就会为每一个读取一个价格。

为循环控速,这样你就不会在一个紧凑的爆发里猛攻 Walmart。在产品请求之间加一段短暂的延迟,能让你的请求速率保持得低、让你的运行保持健康,这正是任何难啃的商业目标会回报的那种自律。

python
import time

def track_prices(urls):
    readings = []
    for url in urls:
        html = crawl(url)
        if not html:
            continue
        record = parse_product(html, url)
        append_json(record)
        readings.append(record)
        print(f"{record['title'][:40]}: {record['price']}")
        time.sleep(2)
    return readings

产品之间的 time.sleep(2) 是下限,而非上限:篮子越大就把它调高。因为 append_json 在每个产品之后都写盘,一次中途失败的运行仍会保住它已经收集到的读数,于是你不会因为一个坏页面而丢掉一项长时间的工作。

保持不被封锁

即便渲染已经被处理好,Walmart 仍会监视带抓取器特征的流量。有几个习惯能让一次运行保持健康,它们适用于任何难啃的商业目标。

  • 为你的请求控速。在产品之间加一段延迟把请求摊开,并避免比你需要的更频繁地重复抓取同一个列表。对价格追踪而言,每个产品每天一两次通常就足以捕捉到每一处有意义的变化。
  • 依靠轮换。一池住宅 IP 把请求分散到许多真实用户的地址上,于是没有任何单个地址会触发速率限制。Crawling API 替你处理这一点;如果你自建技术栈,这就是需要做对的部分。
  • 读取状态码。一次开始返回挑战或错误的运行,是在告诉你当前的速率或 IP 层级已经不够了。把它当作退避的信号,而不是可以无视的噪音。

关于更广的套路,参见我们关于如何抓取网站而不被封锁的指南。如果你想深入抓取单个列表,包括变体和规格数据,配套指南用 Selenium 抓取一个 Walmart 产品页面讲得比单纯价格更深,而追踪竞争对手价格的更宏观理由,则在运用网页抓取做价格情报里有所阐述。

抓取 Walmart 合法吗?

抓取 Walmart 是否被允许,取决于 Walmart 的服务条款、你所在的司法管辖区,以及你拿这些数据做什么。Walmart 的条款对自动化访问设有限制,所以无论你的工具多么谨慎,抓取都可能与那些条款相抵触。这里的任何代码都不会改变这一点;它只是让技术部分能跑通。请阅读 Walmart 的使用条款及其 robots.txt,并把两者都当作你采集范围的边界。

有几条值得坚守的准则。只采集公开数据:任何人无需账户就能在产品页或搜索页上看到的产品标题、价格、折扣和评分。尊重 Walmart 所声明的速率预期,并把你的请求量保持得足够低,以免给它的服务器造成压力,而对价格追踪来说这很容易,因为每个产品每天一次读数就绰绰有余。避开个人数据,包括任何与可识别的购物者、评论者或卖家相关、超出公开列出范围的东西。如果你打算把价格用于商业用途,请取得许可或一份官方协议,而不是把沉默当作同意。

本指南刻意被限定在公开的产品和列表页面上,因为那正是让这项工作站得住脚的边界。它不涉及任何登录墙之后的内容、账户或订单数据、个人信息、支付或结账流程,或任何绕过认证的尝试。要获得授权或大批量访问,Walmart 提供官方 API 和合作伙伴计划,当你需要大批量、有保证的结构或商业权利时,那才是正确的工具。如果你的项目需要的不止于公开价格,那么一个官方 API 或一份数据协议才是正确的路径,而不是一个更聪明的抓取器。

回顾

核心要点

  • Walmart 价格是客户端渲染的。一次普通抓取返回一个空外壳,所以你必须先用 JS token 渲染页面,价格元素才会存在以供解析。
  • 瞄准耐用的属性。itemprop="price" 这个 span 取价格,从 h1#main-title 取标题;预期周围的类名会漂移,并在某个字段返回为空时重新检查。
  • 一个时间戳把一次抓取变成一个序列。scraped_at 给每次读数打戳并追加而非覆盖,你的 JSON 和 CSV 就成了一份你能绘图的价格历史。
  • 从搜索扩展到篮子。从一个搜索页收集产品 URL,把它们喂进循环,用一段延迟为请求控速,并按每日计划运行以随时间追踪。
  • 只停留在公开数据上。尊重 Walmart 的服务条款和 robots.txt,对授权或大批量数据优先用官方 Walmart API,绝不触碰账户、订单或个人信息。

常见问题

为什么一次普通请求返回不到 Walmart 的任何价格?

因为 Walmart 用 JavaScript 在客户端把价格加载进页面。初始 HTML 只是一个外壳,价格元素只有在页面脚本于浏览器里运行之后才出现,所以一次裸 HTTP 请求返回状态 200,但价格是空的。要读取它,你必须先渲染页面,而这正是 Crawling API 的 JS token 替你处理的。

我如何随时间追踪一个 Walmart 价格?

按计划运行抓取器,每天一两次,并把每次读数连同一个时间戳追加到同一个 JSON 和 CSV 文件。本指南里的 append_json 辅助函数每次运行添加一条带日期的记录而不是覆盖,于是文件成了一个价格序列。把那个 CSV 载入 pandas 来计算最低、最高和平均值,或标记跌破某个阈值的情况。

我能一次为许多产品抓取价格吗?

能。把每个产品 URL 添加到 PRODUCTS 列表,或把一个 URL 列表传给 track_prices 函数,循环就会为每一个读取一个价格。如果你事先没有这些 URL,先抓取一个 Walmart 搜索页面来收集产品链接,然后把那些链接喂进循环。在请求之间保持一段短暂的延迟,这样你就能为运行控速。

我的价格选择器返回一个空字符串。变了什么?

几乎可以肯定是 Walmart 的标记。它的工具类名会不经通知地改变,而价格和节省区块周围的 data-testid 值偶尔也会变动。在你浏览器的开发者工具里重新检查一个实时产品页面,在你能做到的地方优先选用耐用的 itemprop="price"h1#main-title 目标,并更新选择器。对任何生产环境的抓取器来说,定期维护都很正常。

我应该把价格存在 CSV、JSON 还是数据库里?

对于跨数周追踪的少数几个产品,本指南里的 JSON 和 CSV 文件就足够,而且在任何地方都能打开。随着历史增长,或你追踪成百上千个商品,就转向像 SQLite 或 PostgreSQL 这样的数据库,于是你能按日期范围和产品查询,而不必加载整个文件。记录的形态保持不变;只有存储层改变。

在抓取 Walmart 价格时,我如何避免被封锁?

把你每个 IP 的请求速率保持得低,在产品之间加一段延迟,不要比你需要的更频繁地重复抓取同一个列表,并通过轮换住宅 IP 路由,这样就没有任何单个地址会触发速率限制。Crawling API 替你管理轮换和一个受信任的 IP 池;如果你自建技术栈,那就是需要投入的部分。盯着状态码,在你开始看到挑战时退避。

开始构建

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

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

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