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 返回供你解析。
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 视为密码:它用于验证你的请求,因此不要将其提交到版本控制系统中。
设置项目
创建虚拟环境以隔离项目依赖,然后安装爬虫所需的两个库。
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,然后发起请求。在解析之前检查状态码,可以让错误清晰可见而不是被静默忽略。
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 解码可能出错的字符。运行脚本后,你应该能看到真实的商品标记,而不是普通请求返回的空外壳。这证明渲染正常工作,然后你才可以编写选择器。
Walmart 需要在受信任的 IP 后端渲染页面,并在一次调用中完成。Crawling API 接受 JS token,在真实浏览器中运行页面,在服务器端轮换住宅 IP,并将完整的 HTML 返回给你,让你无需自己运行无头浏览器集群和代理池。先用免费额度测试你的搜索查询。
第二步:使用 BeautifulSoup 解析结果网格
拿到渲染后的 HTML 后,将其加载到 BeautifulSoup,找到每张商品卡片,并通过选择器提取各个字段。Walmart 将每条商品包裹在可选择的容器中,并通过 data-automation-id 属性暴露标题和价格。从卡片的锚点读取链接,并将合并的评分和评价字符串解析为两个独立字段。用 try/except 包裹每张卡片,防止一条格式错误的商品导致整次运行崩溃。
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 时,在浏览器的开发者工具中重新检查实时搜索页面,更新选择器。定期维护选择器是任何生产级爬虫的正常工作,不是出了问题的信号。
第三步:整合代码
现在将抓取和解析整合成一个可运行的脚本。获取渲染后的搜索页面,交给解析器处理,并打印结构化记录。
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 或数据库。
[ { "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= 参数进行搜索分页,通过递增该参数并在某一页没有卡片时停止来翻页。这避免了硬编码页数,也能自然处理只有少量结果的查询。
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_wait和page_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 次请求免费,无需信用卡。
