식료품 가격 스크래핑: 매장 및 우편번호 수준의 대규모 데이터
식료품 가격은 매우 지역적입니다: 같은 품목이 매장과 우편번호에 따라 다른 금액이 듭니다. 이를 포착하려면 지리적 타겟팅 요청과 UPC 매칭이 필요합니다. 견고한 매장 수준의 가격 데이터셋을 구축하는 방법을 소개합니다.
식료품 가격은 웹에서 가장 지역적인 데이터셋 중 하나입니다. 같은 시리얼 박스가 한 체인에서는 $4.29, 몇 마일 떨어진 다른 체인에서는 $5.19일 수 있으며, 배달 플랫폼은 설정한 매장과 우편번호에 따라 다른 가격을 제시합니다. 이러한 변동성은 데이터를 가치 있게 만드는 이유입니다 — CPG 브랜드가 소매 실행을 추적하거나, 비교 도구를 사용하거나, 선반에서의 인플레이션을 측정하는 사람들에게 유용합니다. 그러나 이는 전국적인 가격을 스크래핑할 수 없다는 것을 의미합니다; 매장과 우편번호별로 가격을 포착해야 하며, 이는 지리적 타겟팅과 제품 매칭 문제입니다. 이 가이드는 매장 수준의 식료품 가격을 수집하고, UPC로 제품을 매칭하며, 견고한 장바구니 지수를 구축하는 방법을 다룹니다. 이는 일반 정보이며, 법적 조언이 아닙니다 — 각 사이트의 이용 약관을 준수하세요.
왜 가격 차이가 기회인가
숫자가 그 이유를 설명합니다. Consumer Reports가 미국 6개 대표 도시의 체인 간 장바구니 비교를 의뢰했을 때, 각 도시에서 가장 저렴한 매장과 가장 비싼 매장 간의 격차는 33%를 넘었으며, 창고형 클럽과 전문 식료품점이 포함되면 더 넓어졌습니다. St. Louis Fed가 분석한 노동통계국 데이터에 따르면, 2020년 12월부터 2024년 12월까지 식료품 가격은 25.5% 상승했으며, 커피는 연간 약 20% 상승했습니다. 이를 추적하는 주간 지수는 150,000개 이상의 매장에서 수집됩니다. 누가 가장 저렴한지, 어디에서, 어떻게 주마다 변화하는지를 포착하는 데이터셋은 수작업으로 조립하기 어렵습니다 — 그래서 자동화하면 방어력이 강해지는 것입니다.
위치가 가격을 결정하므로 요청을 제어합니다
핵심 기술: 매장의 지역 가격을 보려면 해당 매장 지역의 쇼핑객으로 나타나야 합니다. 대부분의 식료품 및 배달 사이트에서는 배달 우편번호를 설정하거나 특정 매장을 선택해야 가격이 나타나며, 사이트는 이를 세션과 종종 IP의 위치에 연결합니다. 따라서 요청에는 두 가지 이동 요소가 있습니다 — 페이로드에 설정한 매장/우편번호와 사이트가 위치를 신뢰하도록 동일한 지역에 있는 종료 IP입니다. 대상 도시의 주거용 IP가 올바른 지역 가격을 해제하며, 다른 주의 데이터센터 IP는 기본 또는 차단된 보기를 제공할 수 있습니다.
import httpx
# A residential exit in the target region unlocks that region's prices
def region_proxy(state):
return f"http://USER:PASS-country-us-region-{state}@gate.quantumproxies.io:8000"
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()

렌더링된 가격이 아닌 하이드레이션 JSON을 읽으세요
현대 식료품 사이트는 클라이언트 렌더링이 많이 되어 있으며, 대부분의 대형 전자상거래 앱처럼 제품 데이터를 페이지에 JSON으로 포함시킵니다. 하이드레이션 페이로드를 찾으세요 (종종 <script> 태그 안에 있는 __NEXT_DATA__ 또는 매장별 상태 객체) 가격, 재고 상태 및 중요한 UPC 또는 GTIN을 포함합니다. 이 JSON을 읽는 것이 렌더링된 가격 요소를 스크래핑하는 것보다 훨씬 견고하며, 매칭에 필요한 바코드를 제공합니다. 스크래퍼가 빈 페이지를 반환하는 이유에 대한 우리의 노트는 보이는 HTML이 빈약해 보일 때 이러한 페이로드를 찾는 방법을 다룹니다.
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 Family Size", "Cheerios Family Size 18oz" 및 "General Mills Cheerios (Family Size)"는 세 매장에서 세 가지 라벨로 동일한 품목입니다 — 이름으로 매칭하면 비교가 소음이 됩니다. 대신 UPC/GTIN 바코드로 매칭하세요, 이는 각 매장이 제품을 어떻게 제목을 붙이든 상관없이 동일합니다. 모든 진지한 식료품 비교 도구는 이렇게 합니다; 이것이 교차 매장 장바구니를 의미 있게 만드는 것입니다.
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% 격차와 주간 인플레이션을 드러냅니다. 많은 위치와 매장에서 동일한 수집을 실행하기 때문에, 주거용 프록시 풀을 통해 요청당 회전이 설정한 지리를 유지하면서도 비정상적으로 보이지 않도록 합니다.
실제 데이터셋으로 확장하기
생산용 식료품 가격 피드는 행렬입니다: 매장 × 우편번호 × 제품 × 시간. 이는 많은 요청이며, DIY 스크래핑이 무거워지는 부분입니다 — 지리적으로 제한된 세션을 조율하고, 배포 간에 이동하는 하이드레이션 페이로드, 더 큰 체인에서의 안티봇 레이어를 다룹니다. 필요할 때 렌더링하고, 지리적 타겟팅 IP를 회전하며, 깨끗한 JSON을 반환하는 Scraper API는 대부분을 한 번의 호출로 축소하여, 배관 대신 장바구니 논리에 노력을 집중할 수 있게 합니다. 동일한 데이터의 가격 전략 측면에 대해서는 경쟁자 가격 모니터링 및 가격 비교 데이터 레이어 구축에 대한 가이드를 참조하세요.

자주 묻는 질문
위치별로 식료품 가격을 어떻게 스크래핑하나요?
요청에 매장 또는 배달 우편번호를 설정하고 (보통 매개변수 또는 쿠키), 해당 지역에 있는 주거용 프록시를 통해 라우팅하여 사이트가 위치를 신뢰하고 지역 가격을 반환하도록 합니다. 그런 다음 페이지의 하이드레이션 JSON에서 가격과 UPC를 읽고, 렌더링된 요소가 아닌 것을 읽습니다. 우편번호별로 반복하여 지리적 행렬을 구축하세요.
왜 식료품 제품을 이름이 아닌 UPC로 매칭하나요?
매장이 동일한 품목에 대해 다른 제목을 붙이기 때문입니다 — 크기, 브랜드 순서 및 약어가 모두 다르므로 이름 매칭은 잘못된 불일치와 소음이 많은 비교를 초래합니다. 동일한 제품에 대해 소매업체 간에 UPC 또는 GTIN 바코드는 동일하므로, 이를 기준으로 매칭하면 스크래핑한 모든 매장에서 동일한 품목의 가격을 정렬할 수 있습니다.
식료품 배달 가격이 정말로 우편번호에 따라 다른가요?
네. 식료품 및 배달 플랫폼은 가격, 프로모션 및 가용성을 매장에 맞추며, 매장은 배달 우편번호에 연결되어 있으므로 동일한 제품이 인접 지역에서 다른 가격을 보일 수 있습니다. Consumer Reports는 한 도시 내에서 가장 저렴한 체인과 가장 비싼 체인 간의 장바구니 격차가 33% 이상임을 발견했으며, 이는 위치별 수집이 중요한 이유입니다.
식료품 가격 스크래핑이 합법인가요?
공개적으로 보이는 가격을 수집하는 것은 일반적입니다 — 뉴스 매체와 가격 지수는 이를 주간으로 수행합니다 — 그러나 이는 사이트의 이용 약관과 귀하의 관할권에 따라 다릅니다. 공개 제품 페이지에만 집중하고, 개인 데이터 및 로그인 영역을 피하며, 요청을 조절하고, 상업적 사용을 위해 법적 조언을 받으세요. 이는 일반 정보이며, 법적 조언이 아닙니다.
식료품 가격 데이터는 위치와 신원에 따라 좌우됩니다. 매장과 우편번호를 설정하고, 해당 지역의 주거용 IP에서 나타나고, 하이드레이션 JSON을 읽고, UPC로 매칭하세요 — 그런 다음 장바구니를 합산하고 시간에 따라 추적하세요. 충분한 매장과 우편번호에서 그렇게 하면 수작업 비교가 따라올 수 없는 데이터셋을 소유하게 됩니다.