Walmart 的商品目录是互联网上最丰富的零售公开数据来源之一:每个搜索查询都会返回一个包含商品名称、价格、星级评分、评价数量和详情页链接的商品列表网格。这些数据可用于竞品价格追踪、市场调研、品类分析和趋势监控。问题在于,Walmart 在浏览器端渲染这些列表,并对自动化流量严加防范,因此普通的 HTTP 请求只会返回一个 JavaScript 外壳,而不是你所需要的商品信息。

本指南将向你展示如何用可靠的方式使用 Python 抓取 Walmart 搜索结果。你将构建一个小型的可运行爬虫,通过 Crawling API 获取渲染后的搜索页面,使用 BeautifulSoup 解析结果网格,并为每条商品提取结构化记录,包括商品名称、价格、评分、评价数量和商品链接。本教程仅涉及公开的搜索和列表数据,结尾的合法性部分不是套话,请在对任何真实数据量运行此爬虫之前仔细阅读。

你将构建什么

一个 Python 脚本,接受 Walmart 搜索关键词,通过 Crawling API 获取渲染后的结果页面,并为每个商品提取结构化记录。我们以 iPhone 搜索为例,从每个结果卡片中提取以下字段:

  • 名称:商品标题,例如"Apple iPhone 15, 128GB, Black"。
  • 价格:卡片上显示的商品售价。
  • 评分:平均星级评分(如有)。
  • 评价数:该评分背后的用户评价数量。
  • 链接:指向商品详情页的 URL。

为什么普通请求在 Walmart 上失效

如果你用普通 HTTP 客户端请求 Walmart 的搜索 URL,你会得到一个状态码为 200 的响应,但响应体中几乎没有任何商品数据。原因有两点:第一,Walmart 在客户端构建搜索网格,初始 HTML 只是一个外壳,只有页面的 JavaScript 在浏览器中运行后才会填充内容;第二,Walmart 会迅速识别自动化流量。来自数据中心的 IP 地址以及不像真实浏览器的请求模式,在到达渲染后的商品列表之前就会被 CAPTCHA 拦截或直接屏蔽。

因此,一个能正常工作的 Walmart 爬虫需要在单次请求中同时具备两点:一个能真正渲染页面的浏览器,以及一个让平台认为是真实购物者的 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 文档和任何入门课程都能让你达到本教程所需的水平。

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

Crawlbase 账号和 JS token。注册后打开控制台,从账号文档页面复制你的 JavaScript(JS)token。请将 token 视为密码:它用于验证你的请求,因此不要将其提交到版本控制系统中。

设置项目

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

bash
python --version

python -m venv walmart_env
source walmart_env/bin/activate

pip install crawlbase beautifulsoup4

在 Windows 上,请用 walmart_env\Scripts\activate 替代 source 那行来激活环境。两个依赖各司其职:crawlbase 是 Crawling API 的官方客户端,beautifulsoup4 用于解析返回的 HTML,让你可以通过 CSS 选择器从结果网格中提取各个字段。

了解 Walmart 搜索页面

Walmart 搜索结果页面以网格形式展示商品卡片,每个列表对应一张卡片。每张卡片包含相同的几个字段:标题、价格、带评价数的星级评分,以及指向商品详情页的链接。网格下方是翻页控件,可以浏览同一查询的更多结果页面。

在编写选择器之前,在浏览器中打开一个搜索页面,右键单击商品卡片,选择"检查"。你会看到每张卡片包裹在带有稳定数据属性的容器中,标题、价格和评分通过 data-automation-id 标记暴露。这些属性就是你的目标。Walmart 的工具类名称经常变动,但自动化 ID 更为稳定,因此尽量依赖它们。

第一步:获取渲染后的搜索页面

从获取完整页面开始。导入 CrawlingAPI 类,用你的 JS token 初始化它,根据查询关键词构建搜索 URL,然后发起请求。在解析之前检查状态码,可以让错误清晰可见而不是被静默忽略。

python
from crawlbase import CrawlingAPI

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

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

if __name__ == "__main__":
    query = "iPhone"
    search_url = f"https://www.walmart.com/search?q={query}"
    html = crawl(search_url)
    print(html[:500] if html else "No HTML returned")

两个等待选项对于这类客户端渲染目标非常重要。ajax_wait 告诉 API 等待异步内容加载完成,page_wait 在页面加载后再等待固定的毫秒数,确保延迟渲染的网格在页面被捕获前出现。五秒是一个合理的起点;如果商品卡片仍有缺失,可以适当增大该值。响应体以 latin1 解码,因为 Walmart 页面混用了严格 UTF-8 解码可能出错的字符。运行脚本后,你应该能看到真实的商品标记,而不是普通请求返回的空外壳。这证明渲染正常工作,然后你才可以编写选择器。

Crawlbase Walmart Scraper

Walmart 需要在受信任的 IP 后端渲染页面,并在一次调用中完成。Crawling API 接受 JS token,在真实浏览器中运行页面,在服务器端轮换住宅 IP,并将完整的 HTML 返回给你,让你无需自己运行无头浏览器集群和代理池。先用免费额度测试你的搜索查询。

第二步:使用 BeautifulSoup 解析结果网格

拿到渲染后的 HTML 后,将其加载到 BeautifulSoup,找到每张商品卡片,并通过选择器提取各个字段。Walmart 将每条商品包裹在可选择的容器中,并通过 data-automation-id 属性暴露标题和价格。从卡片的锚点读取链接,并将合并的评分和评价字符串解析为两个独立字段。用 try/except 包裹每张卡片,防止一条格式错误的商品导致整次运行崩溃。

python
from bs4 import BeautifulSoup

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

def parse_rating(card):
    el = card.select_one("span.w_iUH7")
    if not el:
        return None, None
    text = el.get_text(strip=True)
    rating = text.split(" out of")[0] if "out of" in text else None
    reviews = None
    if "reviews" in text:
        reviews = text.split("Stars.")[-1].replace("reviews", "").strip()
    return rating, reviews

def scrape_results(html):
    soup = BeautifulSoup(html, "html.parser")
    cards = soup.select("div[data-item-id]")
    results = []
    for card in cards:
        try:
            rating, reviews = parse_rating(card)
            link = card.select_one("a[href]")
            results.append({
                "name": text_of(card, 'span[data-automation-id="product-title"]'),
                "price": text_of(card, 'div[data-automation-id="product-price"] span.f2'),
                "rating": rating,
                "reviews": reviews,
                "link": link["href"] if link else None,
            })
        except Exception as e:
            print(f"Skipped a card: {e}")
    return results

text_of 辅助函数在单张卡片内查询单个元素,当元素缺失时返回 None,而不是在 .get_text() 调用时抛出异常。这使提取在字段缺失时保持健壮,而不是每条商品都有评分。价格从价格块内的整数 span 读取,商品链接来自卡片锚点的 href,而不是其文本。parse_rating 辅助函数将 Walmart 组合的"4.2 out of 5 Stars. 3244 reviews"字符串拆分为数值评分和评价数,分别存为独立字段。

选择器会漂移

Walmart 的工具类名称(价格 span 的 span.f2、评分字符串的 span.w_iUH7)随时可能变更,结果卡片容器属性偶尔也会改变。请将上面的选择器视为起始模板,而非合同。当某个字段在所有卡片中都返回 None 时,在浏览器的开发者工具中重新检查实时搜索页面,更新选择器。定期维护选择器是任何生产级爬虫的正常工作,不是出了问题的信号。

第三步:整合代码

现在将抓取和解析整合成一个可运行的脚本。获取渲染后的搜索页面,交给解析器处理,并打印结构化记录。

python
import json
from crawlbase import CrawlingAPI
from bs4 import BeautifulSoup

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

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

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

def parse_rating(card):
    el = card.select_one("span.w_iUH7")
    if not el:
        return None, None
    text = el.get_text(strip=True)
    rating = text.split(" out of")[0] if "out of" in text else None
    reviews = None
    if "reviews" in text:
        reviews = text.split("Stars.")[-1].replace("reviews", "").strip()
    return rating, reviews

def scrape_results(html):
    soup = BeautifulSoup(html, "html.parser")
    cards = soup.select("div[data-item-id]")
    results = []
    for card in cards:
        try:
            rating, reviews = parse_rating(card)
            link = card.select_one("a[href]")
            results.append({
                "name": text_of(card, 'span[data-automation-id="product-title"]'),
                "price": text_of(card, 'div[data-automation-id="product-price"] span.f2'),
                "rating": rating,
                "reviews": reviews,
                "link": link["href"] if link else None,
            })
        except Exception as e:
            print(f"Skipped a card: {e}")
    return results

def main():
    query = "iPhone"
    search_url = f"https://www.walmart.com/search?q={query}"
    html = crawl(search_url)
    if not html:
        return
    data = scrape_results(html)
    print(json.dumps(data, indent=2))

if __name__ == "__main__":
    main()

输出结果示例

python scraper.py 运行完整脚本,你将得到一个干净的记录列表,每条对应一个结果,可直接写入 JSON、CSV 或数据库。

json
[
  {
    "name": "AT&T Apple iPhone 11, 64GB, Black - Prepaid Smartphone",
    "price": "399",
    "rating": "3.8",
    "reviews": "202",
    "link": "https://www.walmart.com/ip/..."
  },
  {
    "name": "Straight Talk Apple iPhone 11, 64GB, Black",
    "price": "249",
    "rating": "4.2",
    "reviews": "3244",
    "link": "https://www.walmart.com/ip/..."
  }
]

处理跨结果页的翻页

抓取一页只是演示;真实任务需要遍历一个查询的所有结果页。Walmart 使用 &page= 参数进行搜索分页,通过递增该参数并在某一页没有卡片时停止来翻页。这避免了硬编码页数,也能自然处理只有少量结果的查询。

python
import time

def scrape_all_pages(query, max_pages=5):
    base = f"https://www.walmart.com/search?q={query}"
    all_results = []
    for page in range(1, max_pages + 1):
        page_url = f"{base}&page={page}"
        html = crawl(page_url)
        if not html:
            break
        results = scrape_results(html)
        if not results:
            break
        all_results.extend(results)
        print(f"Page {page}: {len(results)} products")
        time.sleep(2)
    return all_results

max_pages 上限确保宽泛查询不会无限运行,空结果的中断在 Walmart 没有更多页面时提前停止。页面之间的 time.sleep(2) 对请求进行节流,避免在紧密循环中高频请求搜索,这是被限速最快的方式。根据你的数据量和下方的速率限制调整这两个值。

保持不被封锁

即使渲染问题已解决,Walmart 仍会监控具有爬虫特征的流量。以下几个习惯有助于保持运行顺畅,适用于任何难度较高的商业目标。

  • 控制请求频率。在页面之间添加延迟,并变换查询关键词,不要以全速爬取单一关键词。翻页循环中的 time.sleep 是下限,而非上限。
  • 依赖 IP 轮换。住宅 IP 池将请求分散到众多真实用户地址,确保没有单一地址触发速率限制。Crawling API 为你处理这一切;如果你自建技术栈,这是最需要做好的部分。
  • 关注状态码。如果运行中开始出现挑战或错误,说明当前的请求频率或 IP 层级已经不够用了。将其视为回退的信号,而不是可以忽略的噪声。

更完整的方法论请参见如何在网页抓取时绕过 CAPTCHA。如果你想在收集搜索链接后深入抓取单个商品,配套指南使用 Selenium 抓取 Walmart 商品页面将从这里继续。关于管理型 API 与原始代理池在该目标上的对比数据,Walmart 抓取代理基准测试值得一读。如果你希望通过轮换池路由自己的流量而不使用管理型 API,Smart AI Proxy(也称 AI Proxy)可作为直接替换的代理端点提供同样的住宅 IP 轮换功能。

抓取 Walmart 是否合法?

抓取 Walmart 是否被允许,取决于 Walmart 的服务条款、你所在的司法管辖区以及你对数据的用途。Walmart 的条款限制自动化访问,因此无论你的工具多么谨慎,抓取行为都可能违反这些条款。本文中的代码不会改变这一点;它只是让技术部分得以实现。请阅读 Walmart 的使用条款及其 robots.txt,并将两者视为数据收集的边界。

以下几条原则值得坚守。只收集公开数据:任何人在搜索结果页面上无需登录即可看到的商品名称、价格、评分、评价数和列表链接。尊重 Walmart 规定的请求频率预期,将请求量控制在不会给其服务器造成压力的范围内。避免个人数据,包括与结果页面上公开列出内容之外的可识别购物者、评价者或卖家相关的任何信息。如果你计划商业化使用这些数据,请获得许可或正式协议,而不要假设沉默即是同意。

本指南特意限定在公开搜索和列表页面,因为这是使工作具有可辩护性的边界。它不涵盖登录后的内容、账户或订单数据、个人信息、支付或结账流程,以及任何绕过身份验证的尝试。如需许可或批量访问,Walmart 提供官方 API 和合作伙伴计划,当你需要大规模访问、有保证的结构或商业权利时,这才是正确的途径。如果你的项目需要的不止是公开商品列表,官方 API 或数据协议才是正确的路径,而不是更复杂的爬虫。

回顾

核心要点

  • Walmart 搜索是客户端渲染的。普通请求返回空外壳,因此必须先渲染页面再解析。
  • 渲染和受信任的 IP 缺一不可。带 JS token 的 Crawling API 在一次调用中完成两者;ajax_waitpage_wait 控制等待网格的时长。
  • BeautifulSoup 负责提取。遍历结果卡片,将名称、价格、评分、评价数和链接映射到当前选择器,并预期这些选择器会漂移。
  • 通过 page 参数翻页。递增 &page= 直到某页没有卡片,控制请求间隔,并设置页数上限。
  • 坚守公开数据。尊重 Walmart 的服务条款和 robots.txt,如需许可或批量数据请使用官方 Walmart API,绝不触碰账户、订单或个人信息。

常见问题

为什么普通请求从 Walmart 返回不了商品?

因为 Walmart 用 JavaScript 在客户端构建搜索网格。初始 HTML 只是一个外壳,只有页面的脚本在浏览器中运行后才会填充内容,因此原始 HTTP 请求返回状态码 200,但商品列表是空的。要获取真实数据,必须先渲染页面,这正是 Crawling API 的 JS token 为你处理的事情。

抓取 Walmart 需要普通 token 还是 JS token?

需要 JS token。普通 token 获取静态 HTML,在 Walmart 上与普通请求返回的空外壳相同。JS token 在将 HTML 返回之前先在真实浏览器中渲染页面,因此 BeautifulSoup 解析时商品卡片是存在的。

如何抓取 Walmart 搜索的每一页?

Walmart 通过搜索 URL 上的 &page= 参数分页。在循环中递增它,用相同的解析器抓取每一页,当某页没有卡片时停止。设置页数上限并在请求之间添加短暂延迟,以控制运行节奏,避免被限速。

我的选择器返回 None。是什么变了?

几乎可以肯定是 Walmart 的标记发生了变化。其工具类名称(如价格的 span.f2 和评分字符串的 span.w_iUH7)随时可能变更,卡片容器属性偶尔也会改变。在浏览器的开发者工具中重新检查实时搜索页面,尽量使用更稳定的 data-automation-id 标记,并更新选择器。定期维护是任何生产级爬虫的正常工作。

我能从 Walmart 抓取订单、账户或结账数据吗?

不能,本指南也不涵盖这些内容。订单历史、账户详情和结账流程都在登录后,属于非公开数据。抓取需要登录才能访问的内容,或绕过身份验证来访问它,超出了本文范围,且违反 Walmart 的条款。如需经过授权地访问更丰富的数据,正确的途径是官方 Walmart API 或合作协议。

抓取 Walmart 时如何避免被封锁?

保持每个 IP 的请求率较低,在页面之间添加延迟,变换查询而不是循环单一关键词,并通过轮换住宅 IP 路由,确保没有单一地址触发速率限制。Crawling API 为你管理轮换和受信任的 IP 池;如果你自建技术栈,这是最值得投入的部分。关注状态码,当开始出现挑战时及时退让。

开始构建

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

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

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