# 杂货价格抓取：大规模的商店和邮政编码级数据

杂货价格具有高度的本地化特性：同一商品在不同商店和邮政编码的价格不同。要捕捉这些价格，需要进行地理定位请求和UPC匹配。以下是如何构建一个经得起考验的商店级价格数据集。

杂货定价是网络上最具本地化的数据集之一。同一盒麦片在一个连锁店可能是$4.29，而在几英里外的另一家则是$5.19，配送平台根据您设置的商店和邮政编码报价不同的价格。这种变化正是数据的价值所在——对于追踪零售执行的CPG品牌、比较工具以及任何测量货架通胀的人来说。但这也意味着您不能仅仅抓取国家价格；您必须按商店和邮政编码捕捉价格，这涉及到地理定位和产品匹配问题。本指南涵盖了如何收集商店级杂货价格，通过UPC匹配产品，并构建一个经得起考验的购物篮指数。这是一般信息，不是法律建议——请遵守每个网站的条款。

## 为什么差价是机会

数据说明了一切。当《消费者报告》在六个代表性美国城市的连锁店中进行购物篮比较时，每个城市中最便宜和最贵的主流商店之间的差距超过33%——一旦包括仓储俱乐部和特色杂货店，差距更大。根据圣路易斯联邦储备银行分析的劳工统计局数据，2020年12月至2024年12月间食品价格上涨了25.5%，仅咖啡每年就上涨约20%。每周指数从超过150,000家商店中提取数据。一个捕捉谁最便宜、在哪里、以及每周如何变化的数据集，手动组装起来确实很难——这就是为什么一旦自动化，它就具有防御性。

## 位置控制价格，因此它控制请求

核心技术：要查看商店的本地价格，您必须以该商店区域的购物者身份出现。在大多数杂货和配送网站上，价格只有在您设置配送邮政编码或选择特定商店后才会显示，网站将其与您的会话和通常您的IP位置相关联。因此，请求有两个动态部分——您在负载中设置的商店/邮政编码，以及一个位于同一区域的出口IP，以便网站信任该位置。目标大都市的住宅IP可以解锁正确的本地定价；另一个州的数据中心IP可能会让您看到默认或被阻止的视图。

```python
import httpx

# A residential exit in the target region unlocks that region's prices
def region_proxy(state):
    return f"http://USER-country-us-region-{state}:PASS@residentialpremium.quantumproxies.io:8080"

HEADERS = {
    "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
                  "(KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36",
    "accept": "application/json",
}

def fetch_store_price(store_api_url, zip_code, state):
    # Many stores accept the ZIP as a cookie/param that scopes pricing
    r = httpx.get(
        store_api_url,
        params={"zipCode": zip_code},
        headers={**HEADERS, "cookie": f"store_zip={zip_code}"},
        proxy=region_proxy(state),
        timeout=30,
    )
    return r.json()
```

![同一城市的两个杂货购物篮：最便宜的主流超市与最贵的一家相比，后者在 Consumer Reports 对美国六个城市的比较中贵 33%；卡片还给出了更宏观的情况：从 2020年12月到 2024年12月食品价格上涨 25.5%，咖啡上涨约 20%，每周价格指数覆盖 150,000+ 家门店。](https://res.cloudinary.com/dmqliab0t/image/upload/f_auto,q_auto,w_1200/v1785335218/blog/generated/qp-b9-092-diag1.png)
*在同一城市的一个连锁店，购物篮的价格可能比另一个高出三分之一——捕捉这种差距就是关键所在。*

## 读取水合JSON，而不是渲染的价格

现代杂货网站大量使用客户端渲染，并且——像大多数大型电子商务应用一样——它们将产品数据嵌入到页面中的JSON中，而不仅仅是呈现在屏幕上。寻找一个水合负载（通常在`<script>`标签中，如`__NEXT_DATA__`或商店特定的状态对象）携带价格、库存状态以及关键的UPC或GTIN。读取该JSON比抓取渲染的价格元素要稳健得多，并为您提供匹配所需的条形码。我们关于[为什么抓取器返回空页面](https://quantumproxies.io/blog/scraper-returns-empty-page-javascript)的说明涵盖了在可见HTML看起来空白时如何找到这些负载。

```python
import re, json

def extract_hydration(html: str) -> dict:
    m = re.search(r'<script id="__NEXT_DATA__"[^>]*>(\{.+?\})</script>',
                  html, re.DOTALL)
    if not m:
        raise ValueError("hydration payload not found")
    return json.loads(m.group(1))

def parse_product(state_json: dict) -> dict:
    p = state_json["props"]["pageProps"]["product"]
    return {
        "upc": p.get("upc") or p.get("gtin"),
        "name": p.get("name"),
        "price": p["price"]["current"],
        "in_stock": p.get("availabilityStatus") == "IN_STOCK",
    }
```

## 通过UPC匹配，而不是通过名称

杂货价格数据中最大的错误是通过名称匹配产品。“Cheerios家庭装”、“Cheerios家庭装18盎司”和“通用磨坊Cheerios（家庭装）”是同一商品，在三家商店中有三个标签——通过名称匹配，您的比较就是噪音。相反，通过UPC/GTIN条形码匹配，无论每个商店如何命名产品，它都是相同的。每个严肃的杂货比较工具都这样做；这使得跨商店的购物篮有意义。

```python
from collections import defaultdict

# Group scraped rows by barcode so each item lines up across stores
def compare_by_upc(rows: list[dict]) -> dict:
    by_upc = defaultdict(dict)
    for r in rows:                      # r = {store, upc, price, ...}
        if r.get("upc"):
            by_upc[r["upc"]][r["store"]] = r["price"]
    # cheapest store per item
    return {upc: min(prices, key=prices.get) for upc, prices in by_upc.items()}
```

从条形码匹配的行中，您可以构建重要的指标：一个购物篮指数。定义一个固定的常用商品篮，在每个商店按邮政编码定价，并总计——这个单一数字，随着时间的推移进行跟踪，揭示了33%的差距和每周的通胀。因为您在多个位置和商店运行相同的收集，通过[住宅代理](https://quantumproxies.io/residential-proxies)池进行每次请求轮换，保持任何一个出口看起来不正常，同时保留您设置的地理位置。

[在地理定位的IP上收集本地杂货价格](https://quantumproxies.io/residential-proxies)

## 扩展到真实数据集

生产级的杂货价格源是一个矩阵：商店×邮政编码×产品×时间。这需要大量请求，也是DIY抓取变得繁重的地方——您在处理地理绑定的会话、在部署之间变化的水合负载，以及大连锁店的反机器人层。一个[Scraper API](https://quantumproxies.io/scraper-api)在需要时渲染，轮换地理定位的IP并返回干净的JSON，将大部分内容简化为一个调用，因此您可以将精力放在购物篮逻辑上，而不是管道上。有关同一数据的定价策略方面，请参阅我们的[竞争对手价格监控](https://quantumproxies.io/blog/competitor-price-monitoring-with-proxies)和[构建价格比较数据层](https://quantumproxies.io/blog/build-price-comparison-website-data)指南。

![五步杂货价格流水线：设置门店和邮政编码（ZIP），从同一地区的住宅出口发送请求，读取页面自带的 hydration JSON，按 UPC 而非名称匹配商品，并按门店和 ZIP 长期汇总一个固定购物篮的总价；卡片展示了用到的 JSON 字段：UPC、价格、库存。](https://res.cloudinary.com/dmqliab0t/image/upload/f_auto,q_auto,w_1200/v1785335218/blog/generated/qp-b9-092-diag2.png)
*设置位置，读取页面传递的JSON，通过条形码匹配，然后随着时间的推移按邮政编码总计购物篮。*

## 常见问题解答

### 如何按位置抓取杂货价格？

在请求中设置商店或配送邮政编码（通常是参数或cookie），并通过出口IP位于该地区的住宅代理路由，这样网站就会信任该位置并返回本地定价。然后从页面的水合JSON中读取价格和UPC，而不是渲染的元素。按邮政编码重复以构建地理矩阵。

### 为什么通过UPC而不是名称匹配杂货产品？

因为商店对同一商品的命名不同——尺寸、品牌排序和缩写都不同——所以名称匹配会产生错误匹配和噪音比较。UPC或GTIN条形码在零售商之间是相同的，因此通过它匹配可以让您对比每个商店抓取的完全相同商品的价格。

### 杂货配送价格真的因邮政编码而异吗？

是的。杂货和配送平台将定价、促销和可用性范围限定在商店内，而商店与您的配送邮政编码相关联，因此同一产品在相邻地区的价格可能不同。《消费者报告》发现，在同一城市中，最便宜和最贵的连锁店之间的购物篮差距超过33%，这就是为什么位置特定的收集很重要。

### 抓取杂货价格合法吗？

收集公开可见的价格很常见——新闻媒体和价格指数每周都这样做——但这取决于网站的条款和您的司法管辖区。坚持公共产品页面，避免个人数据和登录区域，控制请求频率，并为商业用途获得法律建议。这是一般信息，不是法律建议。

杂货价格数据的生死取决于位置和身份。设置商店和邮政编码，从该地区的住宅IP呈现，读取水合JSON，并通过UPC匹配——然后总计购物篮并随着时间跟踪。这样做跨越足够多的商店和邮政编码，您就拥有了一个手动比较无法企及的数据集。

[使用Scraper API构建您的杂货价格源](https://quantumproxies.io/scraper-api)
