安装httpx 以及selectolax,发送一个带有真实 User-Agent 头信息的 GET 请求,并使用单个 CSS 选择器锁定价格。这样,大约 15 行代码就能实现一个可运行的爬虫。一旦开始被封禁(在真实的零售网站上,这种情况往往很快就会发生),就添加代理并设置重试机制。
如何使用 Python 抓取价格:分步指南 (2026)
使用 Python 进行价格抓取,是指编写一个脚本,该脚本用于获取商品页面,在 HTML 或渲染后的输出中查找价格,并将其保存为结构化数据,以便您进行比较或长期追踪。 最简化的实现只需一次 HTTP 请求加上一个 HTML 解析器。难点在于反机器人防护机制以及由 JavaScript 渲染的价格,而本指南的大部分内容也正是围绕这些方面展开的。
这是我们关于……的综合指南中的一篇技术操作指南,竞争对手价格监控. 如果您想了解策略和工具的概述,请从这里开始。如果您想要可运行的代码,请继续阅读。
要点总结
- 一个简易的价格抓取工具是一个 HTTP 客户端(
请求或httpx) 以及一个解析器 (selectolax或 BeautifulSoup),用于定位一个 CSS 选择器。 - 最脆弱的部分并非解析,而是访问:速率限制、用户代理过滤、地理位置隐藏以及直接封禁IP地址。
- 对于价格页面而言,轮换住宅代理至关重要,因为您看到的价格取决于请求所在的国家/地区以及IP的信誉状况。
- 由 JavaScript 渲染的价格需要使用无头浏览器、反向工程的 API 调用,或者渲染为 Markdown 的服务。
- 将每条观测数据作为包含时间戳和货币单位的一行数据进行存储,以便您能够追踪变化,而不仅仅是获取快照。请遵守各网站的服务条款和 robots.txt 文件的规定。
什么是价格抓取?
价格抓取是指从网页中自动提取产品价格(以及通常相关的字段,如标题、货币、库存状况和SKU)的过程。零售商这样做是为了监控竞争对手;品牌方这样做是为了执行最低广告价;聚合平台这样做则是为了支持网页抓取价格对比 网站。
价格是高价值且受到积极防御的目标。2025年《Imperva恶意机器人报告》(Thales旗下公司)发现,自动化流量十年来首次超过了人类活动,在2024年占所有网络流量的51%,其中恶意机器人占比达37%。 电子商务是受攻击最严重的行业之一,因此价格页面背后部署着与阻挡凭证填充和囤积库存机器人相同的防御措施。您的爬虫必须伪装成普通访客。
步骤 1:一个简易的 Python 价格抓取工具
从最简单且能正常运行的方案开始:一个 GET 请求和一个解析器。我们使用httpx (一种现代的请求-兼容客户端)以及selectolax (一个基于 lexbor 构建的高速 HTML 解析器)。
pip install httpx selectolax
import httpx
from selectolax.parser import HTMLParser
HEADERS = {
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/126.0.0.0 Safari/537.36"
),
"Accept-Language": "en-US,en;q=0.9",
}
def scrape_price(url: str, price_selector: str) -> str | None:
resp = httpx.get(url, headers=HEADERS, timeout=20.0, follow_redirects=True)
resp.raise_for_status()
tree = HTMLParser(resp.text)
node = tree.css_first(price_selector)
return node.text(strip=True) if node else None
if __name__ == "__main__":
price = scrape_price("https://example.com/product/123", "span.price")
print(price)
唯一与具体地点相关的部分是price_selector. 在浏览器中打开产品页面,检查价格元素,并复制一个稳定的选择器(一个类或data- 属性,而不是深层div > div > div (在下次部署时会导致链断裂)。
返回的结果是类似于以下形式的杂乱字符串:"$1,299.00" 或“1.299,00 €”. 存储前进行归一化:
import re
from decimal import Decimal
def parse_amount(raw: str) -> Decimal | None:
if not raw:
return None
# 去除除数字和分隔符以外的所有内容
cleaned = re.sub(r"[^\d.,]", "", raw)
# 启发式处理:如果逗号是小数分隔符(例如 "1.299,00")
if cleaned.count(",") == 1 and cleaned.rfind(",") > cleaned.rfind("."):
cleaned = cleaned.replace(".", "").replace(",", ".")
else:
cleaned = cleaned.replace(",", "")
try:
return Decimal(cleaned)
except Exception:
return None
请务必将原始货币符号单独保留。不要默认是美元(USD)。
步骤 2:应对反机器人防御措施
上面的示例在测试网站上可以正常运行,但在大多数真实的零售商网站上会失败。以下是您可能会遇到的情况以及相应的处理方法。
速率限制与重试
对某个网站进行疯狂请求是被封禁的最快途径,也是你能做的最无礼的行为。在响应中加入延迟、抖动和指数退避机制,这些都意味着“放慢速度”(429) 或“暂时不可用”(503).
import random
import time
import httpx
def get_with_backoff(client: httpx.Client, url: str, max_tries: int = 4):
for attempt in range(max_tries):
resp = client.get(url, timeout=20.0)
if resp.status_code not in (429, 503):
return resp
wait = (2 ** attempt) + random.uniform(0, 1.0)
time.sleep(wait)
resp.raise_for_status()
return resp
用户代理和标头
一个没有请求头的请求简直就是在昭告“这是脚本”。请发送一个真实、最新的浏览器 User-Agent 以及真实浏览器会发送的请求头(接受,Accept-Language,Accept-Encoding). 在一小部分真实的、最近使用的用户代理字符串中轮换使用确实有帮助,但如果将一个可信的用户代理与已被标记的IP地址搭配使用,仍然会被拦截。
IP地址段、地理位置隐藏,以及住宅代理为何重要
这是大多数教程都会跳过的一部分。对同一商品网址发出的两次请求可能会返回不同的价格、不同的货币,或者显示一个被屏蔽页面,这完全取决于请求的IP地址。
- 数据中心 IP 地址 (大多数云服务器使用的范围)很容易识别,且在价格页面上通常会被限速或直接屏蔽。
- 地理隐藏 这意味着某个网站会向美国访客显示美元价格,向德国访客显示欧元价格。如果你从单一数据中心区域抓取数据,你看到的永远只有一个市场的价格,有时还会遇到“该商品在您所在地区不可用”的提示。
轮换式住宅代理会通过真实的用户连接转发请求,因此它们具有普通访问者的IP信誉,并允许您选择请求看似来自的国家(有时甚至包括城市)。 对于价格抓取而言,这意味着您可以查看每个市场的真实本地化价格,并避开那些限制这些页面访问的数据中心IP地址段。Massive运营着覆盖195多个国家/地区的住宅网络(支持HTTP/HTTPS/SOCKS5),提供国家/城市地理定位以及轮换或固定会话功能。
在客户端中配置代理只需修改一行代码:
import os
import httpx
# Massive 住宅代理。凭据以 user:pass 的形式包含在代理 URL 中。
PROXY = (
f"https://{os.environ['MASSIVE_PROXY_USERNAME']}:"
f"{os.environ['MASSIVE_API_KEY']}@network.joinmassive.com:65535"
)
with httpx.Client(proxy=PROXY, headers=HEADERS, timeout=20.0) as client:
resp = client.get("https://example.com/product/123")
print(resp.status_code)
使用一个旋转 当您希望每个请求都使用一个新的 IP 地址(有助于分散负载)时,请使用会话,以及一个置顶 当一个会话中的多个请求需要使用相同的 IP 地址时(例如,设置区域 Cookie,然后加载价格)。有关零售商专用的操作指南,请参阅抓取亚马逊价格而不被封禁.
步骤 3:处理通过 JavaScript 渲染的价格
许多网店会提供一个几乎空白的 HTML 框架,并通过 JavaScript 在客户端渲染价格。如果resp.text 如果其中不包含价格,则步骤 1 中的解析器会返回无 无论你的选择器有多好,你有三种选择。
选项 A:查找底层 API。 打开开发者工具,切换到“网络”选项卡,筛选为 XHR/Fetch,然后刷新页面。价格几乎总是以 JSON 响应的形式返回。如果 API 本身未受限制,直接调用该接口比通过渲染页面获取更快、更稳定。
方案 B:驱动无头浏览器。 Playwright 使用真正的 Chromium 渲染页面,因此当您阅读时,价格信息已存在于 DOM 中。
from playwright.sync_api import sync_playwright
def scrape_rendered_price(url: str, selector: str) -> str | None:
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
page.goto(url, wait_until="networkidle")
node = page.query_selector(selector)
value = node.inner_text() if node else None
browser.close()
return value
无头浏览器很耗资源:它们消耗的 CPU 和内存远多于一个 HTTP 请求,且扩展速度较慢。
选项 C:渲染为 Markdown(简便方法)。 这是阻力最小的路径。Web Render API 会在他人的基础设施上运行无头渲染:由真实浏览器执行页面的 JavaScript,并将最终结果转换为干净的 Markdown 格式,然后返回给您的代码。Massive 的 Web Render API 提供了一个名为“Browsing”的端点,其功能正是如此。 您既省去了无头浏览器的开销,又避免了大部分 HTML 解析工作——因为您只需在可读文本中搜索价格行,而非遍历易出错的 DOM 结构;而且,如果您想通过这种方式提取价格,生成的 Markdown 内容可以直接输入到 AI 客户端或 LLM 提示中。
import os
import re
import httpx
# Massive Web Render API,浏览端点:它在
# Massive 的网络上渲染页面,并返回干净的 Markdown 代码。
def render_markdown(url: str, country: str = "US") -> str:
resp = httpx.get(
"https://render.joinmassive.com/browser",
params={"url": url, "format": "markdown", "country": country},
headers={"Authorization": f"Bearer {os.environ['MASSIVE_API_TOKEN']}"},
timeout=60.0, # 渲染操作比普通 GET 请求更耗时,需预留足够时间
)
resp.raise_for_status()
return resp.text
# Markdown 比原始 HTML 更容易解析。传入国家参数,以
# 当地购物者所见的方式读取价格(美元价格、欧元价格等)。
markdown = render_markdown("https://example.com/product/123")
match = re.search(r"\$[\d,]+\.\d{2}", markdown)
price = match.group(0) if match else None
第 4 步:对数据进行结构化处理并存储
价格只有作为时间序列才有意义。将每条观测值作为独立的一行数据存储,并附上时间戳,这样您就可以发现价格下跌、追踪竞争对手,并构建价格监控系统 在顶部。
import sqlite3
from datetime import datetime, timezone
from decimal import Decimal
def init_db(path: str = "prices.db") -> sqlite3.Connection:
conn = sqlite3.connect(path)
conn.execute(
"""
CREATE TABLE IF NOT EXISTS observations (
id INTEGER PRIMARY KEY,
sku TEXT NOT NULL,
url TEXT NOT NULL,
amount TEXT NOT NULL, -- 将 Decimal 作为文本存储,绝不使用 float
currency TEXT NOT NULL,
country TEXT, -- 该价格来自哪个市场
scraped_at TEXT NOT NULL
)
"""
)
return conn
def record(conn, sku: str, url: str, amount: Decimal, currency: str, country: str):
conn.execute(
"INSERT INTO observations (sku, url, amount, currency, country, scraped_at) "
"VALUES (?, ?, ?, ?, ?, ?)",
(sku, url, str(amount), currency, country,
datetime.now(timezone.utc).isoformat()),
)
conn.commit()
两条规则能让你日后省去不少麻烦:将金额以文本形式或小单位数量的整数形式存储(切勿使用二进制浮点数,否则会丢失分),并且务必记录货币 以及国家. 在同一列中同时出现“$50”和“50 EUR”且未标注货币单位,这属于隐性数据错误。对于 CSV 或数据仓库管道而言,数据结构是相同的;带有时间戳和市场标签的行才是关键单位。如果您更倾向于“买现成”而非“自己开发”,请比较竞争对手价格跟踪工具.
关于合法性和服务条款的说明
这并非法律建议,但了解实际情况还是很有必要的。抓取公开可见的数据的行为,在美国根据《计算机欺诈与滥用法案》提起的多次诉讼中均被认定为合法:在持续已久的hiQ Labs 诉 LinkedIn 在该诉讼中,第九巡回上诉法院于2022年重申,访问公开可用的网站数据很可能不构成《计算机欺诈与滥用法案》(CFAA)所指的“未经授权”访问。该裁决专门针对《计算机欺诈与滥用法案》,并非一概放行。 其他法律理论(如违反网站服务条款、侵犯版权、侵犯动产所有权以及违反隐私法等)仍可能适用。
实用的防护措施:请阅读并遵守各网站的服务条款,并robots.txt,仅抓取公开的定价数据(切勿抓取您已同意不抓取的、需登录才能访问的内容),设置速率限制以避免影响网站性能,并避免抓取个人数据。如有疑问,请咨询法律顾问。
在能够看清真实价格的网络上构建它
代码部分其实很简单。 困难之处在于能否持续获取准确且本地化的价格,而这关键取决于请求看似来自何处。Massive的住宅网络(覆盖195多个国家,支持国家/城市地理定位,提供轮换或固定会话)及其Web Render API(将渲染后的页面转换为纯Markdown格式)可同时解决这两个问题: 一方面通过 IP 信誉评估和地理定位帮助您绕过封锁;另一方面通过渲染功能,让您无需自行运行浏览器即可获取由 JavaScript 加载的商品价格。探索 Massive 的 Web Render API 和住宅代理.
来源
- Imperva(泰雷兹旗下公司),《2025年Imperva恶意机器人报告:人工智能如何助长机器人威胁》,https://www.imperva.com/blog/2025-imperva-bad-bot-report-how-ai-is-supercharging-the-bot-threat/ (检索于 2026-06-15)
- hiQ Labs, Inc. 诉 LinkedIn Corp. 一案,31 F.4th 1180(第九巡回上诉法院,2022年)(经发回重审后,重申抓取公开可用的网站数据很可能不构成《计算机欺诈与滥用法》(CFAA)所指的“未经授权”访问)。
- Jenner & Block LLP,《数据抓取:在hiQ诉LinkedIn案中,第九巡回上诉法院重申了对《计算机欺诈与滥用法案》(CFAA)的狭义解释》,https://www.jenner.com/en/news-insights/publications/client-alert-data-scraping-in-hiq-v-linkedin-the-ninth-circuit-reaffirms-narrow-interpretation-of-cfaa (检索于 2026-06-15)
常见问题解答
有两个常见原因。首先,价格可能是通过 JavaScript 渲染生成的,因此并不包含在 HTTP 客户端接收的原始 HTML 中;你需要使用无头浏览器或 Markdown 渲染服务。其次,该网站可能采用了地理位置隐藏机制,根据国家显示不同的价格,或者屏蔽了你的 IP 地址。 通过当地的住宅 IP 地址进行数据抓取,既能显示正确的本地化价格,又能避免因数据中心 IP 地址而被封禁。
如果只是几页的话,不需要。但如果请求量较大,那就必须了。价格页面受到严格保护,如果单个数据中心IP地址频繁发送请求,很快就会被限流或封禁。轮换使用家用代理可以将请求分散到真实的用户IP地址上,并让你针对特定国家进行请求,当不同市场的价格存在差异时,这一点尤为必要。
有三种方案,按优先级排序:找出该页面调用的 JSON API(查看开发者工具的“网络”选项卡)、驱动 Playwright 之类的无头浏览器,或者使用能返回完全渲染后页面的 Web Render API(Markdown 格式输出最易于解析)。纯文本请求/httpx 仅凭它本身无法执行 JavaScript。
抓取公开的定价数据在面对美国《计算机欺诈与滥用法案》(CFAA)的法律挑战时经受住了考验(值得注意的是hiQ 诉 LinkedIn),但这并不涵盖服务条款、版权或隐私方面的主张,且各司法管辖区的法律规定各不相同。请仅使用公开数据,并尊重robots.txt 并遵守服务条款(ToS),礼貌地进行速率限制,如有任何商业用途或大规模操作,请咨询律师。
