Python으로 Shopify 스크래핑하기: 시장 조사를 자동화하는 방법

최종 업데이트: June 29, 2026
Python으로 Shopify 스크래핑하기: 시장 조사를 자동화하는 방법

Shopify 스토어 URL 뒤에 /products.json만 붙여 보세요. API 키도, 로그인도, 골치 아픈 HTML 분석도 없이 정리된 JSON 데이터가 그대로 내려와요. 이커머스 데이터 쪽에서 일하는 사람이면 거의 다 아는 공공연한 비밀이에요.

저는 Thunderbit 공식 웹사이트 팀에서 사람들이 웹 데이터를 어떻게 뽑아 쓰는지 매일 들여다봐요. 그중에서도 Shopify는 단골 주제예요. 경쟁사 가격을 따라잡으려는 세일즈 팀, 카탈로그를 비교하는 운영팀, 새 공급업체를 찾는 구매 담당자까지 찾는 사람이 다양하거든요. Shopify 판매자는 482만 곳에 달하고 미국 이커머스의 약 30%를 차지하니, 건질 상품 데이터의 양도 어마어마하죠.

이 글에서는 처음부터 끝까지 다 짚어 봐요. 엔드포인트가 어떤 데이터를 주는지, 상품 수천 개를 페이지네이션으로 가져오는 법, 차단 안 당하게 속도를 조절하는 법, pandas로 중첩 JSON을 깔끔한 CSV·Excel로 펴는 법까지요. 남들이 잘 안 다루는 /collections.json·/meta.json도 소개하고, 코드 없이 바로 쓰고 싶은 분을 위한 노코드 방법도 보여 드릴게요.

Shopify의 /products.json 엔드포인트란? 왜 이렇게 긁기 쉬운가

Shopify 스토어에는 누구나 열 수 있는 {store-url}/products.json 엔드포인트가 하나씩 있어요. 여기서 구조화된 상품 데이터가 바로 나와요. API 키도 OAuth도 인증도 다 필요 없어요. 스토어 URL 끝에 /products.json만 붙이면 카탈로그 전체가 JSON 배열로 떨어져요.

지금 바로 allbirds.com/products.json이나 zoologistperfumes.com/products.json을 브라우저에 쳐보세요. 상품명, 가격, 변형, 이미지, 태그가 JSON으로 정리돼 보일 거예요.

HTML 테마를 직접 파싱하는 쪽은 어떨까요? 스토어마다 구조가 다르고, 중첩도 깊고, 판매자가 테마만 바꿔도 레이아웃이 통째로 달라져요. 둘을 나란히 놓으면 차이가 확 드러나요.

HTML 방식(번거로움):

<div class="product-card__info">
  <h3 class="product-card__title">
    <a href="/products/classic-blue-jeans">Classic Blue Jeans</a>
  </h3>
  <span class="price price--on-sale" data-product-price>$149.00</span>
</div>

JSON 방식(깔끔함):

{
  "title": "Classic Blue Jeans",
  "handle": "classic-blue-jeans",
  "vendor": "Hiut Denim",
  "variants": [{"price": "149.00", "sku": "HD-BLU-32", "available": true}]
}

일관성으로 보나 안정성으로 보나 파싱 편의로 보나 JSON이 압도적이에요. 이 엔드포인트는 핵심 쿼리 파라미터 두 개를 받아요. 페이지당 최대 250개를 정하는 ?limit=(기본값은 30개), 그리고 페이지를 넘기는 ?page=예요. 뒤에 나올 코드에서 이 둘을 적극적으로 써요.

여기서 짚고 갈 점. 이건 공개 스토어프론트 엔드포인트Shopify Admin API가 아니에요. Admin API는 스토어 주인의 액세스 토큰이 있어야 하고, 주문·재고·고객 정보를 줘요. 반대로 공개 /products.json은 누구나 읽을 수 있는 상품 데이터만 보여 줘요. 이 차이는 뒤에서 더 자세히 다룰 텐데, 실무에서 정말 자주 헷갈리는 지점이거든요.

한 가지 주의: 모든 스토어가 이 엔드포인트를 여는 건 아니에요. 테스트해 보니 약 71%에서 유효한 JSON이 나왔고(allbirds.com, gymshark.com, colourpop.com, kyliecosmetics.com 등 동작), 커스텀 설정 스토어 일부는 404를 뱉었어요(hiutdenim.co.uk, bombas.com). 확인은 간단해요. {store-url}/products.json을 브라우저에 직접 쳐서 결과만 보면 돼요.

왜 Python으로 Shopify를 긁을까? 실전 비즈니스 활용

굳이 왜 하느냐. 답은 ROI예요. 미국 소매업체의 81%가 경쟁 정보용으로 자동 가격 스크래핑을 이미 돌리고 있어요. 2020년의 34%에서 크게 뛴 수치죠. 게다가 가격 전략을 1%만 다듬어도 이익이 평균 11.1% 늘어난다는 연구도 있어요. 그냥 숫자가 아니라 통장에 찍히는 돈예요.

제가 현장에서 제일 자주 보는 활용은 이런 것들이에요.

활용 사례혜택을 받는 대상얻을 수 있는 정보
경쟁사 가격 모니터링이커머스 운영팀경쟁사 카탈로그 전반의 가격 변동, 할인, 비교를 통한 가격 추적
상품 리서치 및 소싱구매 / 머천다이징상품 기능, 변형, 소재, 재고 가능 여부 비교
리드 생성세일즈 팀스토어 카탈로그에서 공급업체명, 브랜드 정보, 연락처 추출
시장 및 카테고리 분석마케팅 팀상품 구성, 태그, 컬렉션 구조, 포지셔닝 파악
재고 및 판매 가능 여부 추적공급망 팀변형 단위 재고 상태(available: true/false)의 시간별 모니터링
신규 상품 감지제품 팀created_at 타임스탬프로 경쟁사의 신규 출시 탐지

이런 일엔 Python이 딱이에요. 스크래핑 개발자의 69.6%가 주력 언어로 Python을 써요. requests(HTTP), pandas(데이터 가공), httpx(비동기) 같은 생태계 덕에, "URL 하나"에서 "완성된 스프레드시트"까지 80줄도 안 되는 코드로 끝나거든요.

products.json 전체 필드 참고표: 한눈에 보기

다른 튜토리얼은 보통 title, id, handle 정도만 보여 주고 넘어가요. 그런데 Shopify JSON 응답에는 상품·변형·이미지·옵션을 통틀어 40개가 넘는 필드가 들어 있어요. 코드를 짜기 전에 뭐가 있는지 알아 두면, 나중에 다시 긁느라 시간 버릴 일이 없어요.

아래 표는 2026년 4월 16일에 실시간 /products.json 응답을 받아 정리한 거예요. 이 구조는 엔드포인트를 여는 스토어라면 어디서나 똑같아요.

상품 수준 필드

필드데이터 유형예시 값비즈니스 활용
id정수123456789중복 제거를 위한 고유 상품 식별자
title문자열"Classic Blue Jeans"카탈로그 및 비교용 상품명
handle문자열"classic-blue-jeans"URL 슬러그—상품 페이지 링크는 {store}/products/{handle} 형식으로 구성
body_html문자열(HTML) 또는 null

Our best-selling...

콘텐츠 분석 및 SEO 리서치를 위한 상품 설명
vendor문자열"Hiut Denim"리드 생성이나 소싱을 위한 브랜드/공급업체명
product_type문자열"Jeans"시장 분석용 카테고리 분류
created_atISO 날짜/시간"2024-01-15T10:30:00-05:00"상품이 추가된 시점 추적(신규 출시 감지)
updated_atISO 날짜/시간"2025-03-01T08:00:00-05:00"최근 카탈로그 변경 감지
published_atISO 날짜/시간"2024-01-16T00:00:00-05:00"스토어프론트에 상품이 공개된 시점 확인
tags문자열 배열["organic", "women", "straight-leg"]SEO, 분류, 트렌드 파악을 위한 키워드/태그 분석
variants객체 배열(아래 변형 필드 참고)변형별 가격, SKU, 판매 가능 여부
images객체 배열(아래 이미지 필드 참고)카탈로그 및 시각 분석용 이미지 URL
options객체 배열[{"name": "Size", "values": ["S","M","L"]}]상품 구성(사이즈, 색상, 소재) 파악

변형 수준 필드(각 상품 내부 중첩)

필드데이터 유형예시활용 사례
id정수987654321고유 변형 식별자
title문자열"32 / Blue"변형 표시명
sku문자열"HD-BLU-32"재고 시스템과의 SKU 매칭
price문자열"185.00"가격 모니터링(문자열이라 계산할 땐 float 변환 필요)
compare_at_price문자열 또는 null"200.00"원래 가격—할인 추적에 필수
available불리언true재고 가능 여부(공개적으로 확인 가능한 유일한 재고 지표)
weight실수1.2배송/물류 분석
option1, option2, option3문자열"32", "Blue", null개별 옵션 값
created_at, updated_atISO 날짜/시간변형 수준 변경 추적

이미지 수준 필드

필드데이터 유형예시활용 사례
id정수111222333고유 이미지 식별자
src문자열(URL)"https://cdn.shopify.com/..."이미지 직접 다운로드 링크
alt문자열 또는 null"Front view of jeans"접근성 분석을 위한 대체 텍스트
position정수1이미지 순서
width, height정수2048, 2048이미지 크기

공개 엔드포인트에 없는 항목

미리 알아 둬야 할 함정이 하나 있어요. 공개 /products.json에는 inventory_quantity가 없어요. 보안상의 이유로 2017년 12월에 공개 JSON 엔드포인트에서 빠졌거든요. 공개로 얻을 수 있는 재고 신호는 각 변형의 불리언 available(true 또는 false)뿐이에요. 실제 재고 수량을 보려면 스토어 주인 자격으로 인증한 Admin API가 필요해요.

코드를 짜기 전에 이 표부터 보고 필요한 필드를 추려 두세요. 가격 모니터링이 목적이면 variants[].price, variants[].compare_at_price, variants[].available이 핵심이에요. 리드 생성이 목적이면 vendor, product_type, tags에 집중하면 돼요. 용도에 맞게 걸러야 CSV도 훨씬 깔끔해져요.

products.json을 넘어서: 컬렉션, 메타, 그 밖의 엔드포인트

경쟁 글 대부분이 이쪽 엔드포인트를 빼먹어요. 진지하게 경쟁 정보를 파려면 은근히 쓸모가 많은데도요.

/collections.json — 스토어 전체 카테고리

스토어의 컬렉션(카테고리)을 제목·handle·설명·상품 수까지 묶어 돌려줘요. zoologistperfumes.com, allbirds.com, gymshark.com에서 확인했더니 다들 유효한 JSON이 나왔어요.

{
  "collections": [
    {
      "id": 308387348539,
      "title": "Attars",
      "handle": "attars",
      "published_at": "2026-03-29T12:20:32-04:00",
      "products_count": 1,
      "image": { "src": "https://cdn.shopify.com/..." }
    }
  ]
}

경쟁사가 카탈로그를 어떻게 쪼개 놨는지 궁금하면 이 엔드포인트를 보면 돼요.

/collections/{handle}/products.json — 카테고리별 상품

특정 컬렉션으로 걸러진 상품을 줘요. 구조는 /products.json과 같은데 범위가 한 카테고리로 좁혀져요. 경쟁사의 "Sale"이나 "New Arrivals" 컬렉션만 따로 지켜보고 싶을 때 아주 유용해요.

/meta.json — 스토어 수준 메타데이터

스토어 이름·설명·통화·국가, 그리고 특히 쓸 만한 published_products_count를 돌려줘요. 이 수치만 있으면 필요한 페이지 수를 미리 정확히 계산할 수 있어요. ceil(published_products_count / 250)이면 끝이죠. 빈 응답이 나올 때까지 페이지 번호를 무작정 올릴 필요가 없어져요.

어떤 엔드포인트를 써야 할까?

원하는 것엔드포인트인증 필요?
전체 상품(공개)/products.json아니요
특정 카테고리의 상품/collections/{handle}/products.json아니요
스토어 메타데이터 + 상품 수/meta.json아니요
전체 컬렉션(카테고리)/collections.json아니요
주문/매출 데이터(자신의 스토어만)Admin API /orders.json예(API 키)
재고 수량(자신의 스토어만)Admin API /inventory_levels.json

커뮤니티 단골 질문이 "경쟁사가 몇 개 팔았는지 긁을 수 있나요?"인데, 답은 짧아요. 공개 엔드포인트로는 안 돼요. 판매 데이터와 실제 재고 수량은 인증된 Admin API, 즉 스토어 주인 권한이 있어야만 보여요. 공개 엔드포인트가 주는 건 상품 카탈로그 데이터뿐예요.

shopify-data-access-methods.webp

Python으로 Shopify 긁는 법: 단계별 세팅

  • 난이도: 초급
  • 예상 소요 시간: 약 15분(세팅 + 첫 스크래핑)
  • 준비물: Python 3.11 이상, pip, 터미널, 그리고 긁을 Shopify 스토어 URL

1단계: Python과 라이브러리 설치

먼저 Python 3.11 이상이 깔려 있는지 확인하세요(pandas 3.0.x가 이걸 요구해요). 그다음 필요한 라이브러리 두 개를 설치해요.

pip install requests pandas

Excel로 내보낼 거면 이것도 추가하세요.

pip install openpyxl

스크립트 맨 위에는 이 import를 넣어요.

import requests
import pandas as pd
import time
import random
import json

실행했을 때 import 에러가 안 나야 정상이에요. pandas에서 버전 오류가 뜬다면 Python을 3.12로 올리세요.

2단계: /products.json에서 상품 데이터 가져오기

아래는 스토어 URL을 받아 엔드포인트를 호출하고 파싱된 JSON을 돌려주는 기본 함수예요.

def fetch_products_page(store_url, page=1, limit=250):
    """Shopify 스토어에서 상품 한 페이지를 가져옵니다."""
    url = f"{store_url.rstrip('/')}/products.json"
    params = {"limit": limit, "page": page}
    headers = {
        "User-Agent": "Mozilla/5.0 (compatible; ProductResearch/1.0)"
    }
    
    response = requests.get(url, params=params, headers=headers, timeout=30)
    response.raise_for_status()
    return response.json().get("products", [])

핵심만 짚으면 이래요.

  • limit=250: Shopify가 페이지당 허용하는 최대치예요. 기본값이 30이라, 250으로 명시하면 요청 횟수를 최대 8배까지 줄예요.
  • User-Agent 헤더: 꼭 현실적인 값으로 넣으세요. User-Agent 없는 요청은 Shopify 봇 방지에 더 잘 걸려요.
  • timeout=30: 요청 하나가 무한정 매달리는 일을 막아 줘요.

아는 스토어로 한번 돌려 보세요.

products = fetch_products_page("https://allbirds.com")
print(f"Fetched {len(products)} products")
print(f"First product: {products[0]['title']}")

Fetched 250 products와 첫 상품명이 찍히면 잘 된 거예요.

3단계: 페이지네이션으로 모든 상품 긁기

요청 한 번으로는 최대 250개까지만 와요. 그런데 웬만한 스토어는 그보다 상품이 훨씬 많죠(Allbirds만 해도 1,420개 이상). 응답이 빌 때까지 페이지를 계속 돌려야 해요.

def scrape_all_products(store_url, delay=1.0):
    """페이지네이션을 처리하면서 Shopify 스토어의 모든 상품을 스크래핑합니다."""
    all_products = []
    page = 1
    
    while True:
        print(f"Fetching page {page}...")
        products = fetch_products_page(store_url, page=page, limit=250)
        
        if not products:
            print(f"No more products. Total: {len(all_products)}")
            break
        
        all_products.extend(products)
        print(f"  Got {len(products)} products (total so far: {len(all_products)})")
        page += 1
        
        # 예의를 지키기 위해 요청 사이에 잠시 대기
        time.sleep(delay + random.uniform(0, 0.5))
    
    return all_products

products가 비어서 돌아오면 끝까지 왔다는 신호예요.

time.sleep()에 랜덤 지터를 살짝 더하면, Shopify의 비공식 속도 제한(~초당 2회) 아래로 머무는 데 도움이 돼요.

: /meta.json을 먼저 받아 두면 전체 상품 수를 미리 알 수 있어요. 필요한 페이지 수도 pages = ceil(product_count / 250)으로 딱 떨어지게 계산되죠. 마지막에 빈 요청 하나를 더 보내는 낭비를 피할 수 있어요.

4단계: 필요한 필드만 추려 내기

모든 상품을 Python 딕셔너리 리스트로 받았으면, 이제 필요한 필드만 뽑으면 돼요. 아래는 가격 모니터링에 제일 흔히 쓰는 필드를 가져오는 코드예요.

def extract_product_data(products):
    """상품에서 핵심 필드를 추출하고 변형을 평탄화합니다."""
    rows = []
    for product in products:
        for variant in product.get("variants", []):
            rows.append({
                "product_id": product["id"],
                "title": product["title"],
                "handle": product["handle"],
                "vendor": product.get("vendor", ""),
                "product_type": product.get("product_type", ""),
                "tags": ", ".join(product.get("tags", [])),
                "created_at": product.get("created_at", ""),
                "variant_id": variant["id"],
                "variant_title": variant.get("title", ""),
                "sku": variant.get("sku", ""),
                "price": variant.get("price", ""),
                "compare_at_price": variant.get("compare_at_price", ""),
                "available": variant.get("available", ""),
                "image_url": product["images"][0]["src"] if product.get("images") else ""
            })
    return rows

이러면 변형마다 한 행씩 생겨요. 가격 비교엔 이 형태가 제일 편해요. 가령 "Classic Blue Jeans" 하나가 변형 12개(사이즈 6 × 색상 2)를 가질 수 있고, 변형마다 가격과 재고 상태가 다 다를 수 있으니까요.

pandas로 Shopify 데이터를 CSV·Excel로 내보내기

다른 튜토리얼은 원시 JSON을 파일에 그냥 쏟아 붓고 끝내는 경우가 많아요. 개발자한테야 괜찮지만, 금요일까지 스프레드시트가 필요한 이커머스 분석가에겐 별 쓸모가 없죠.

문제는 Shopify JSON이 중첩 구조라는 점이에요. 상품 하나에 변형이 여럿이고, 변형마다 가격·SKU·재고 상태가 제각각이거든요. 이걸 행과 열로 바꾸려면 pandas 손질이 필요해요.

중첩 JSON을 깔끔한 테이블로 펴기

쓰임새에 따라 두 가지 방식이 있어요.

옵션 A: 변형별 1행(가격 모니터링·재고 추적에 최적)

# 4단계의 extract_product_data 함수 사용
products = scrape_all_products("https://allbirds.com")
rows = extract_product_data(products)
df = pd.DataFrame(rows)

print(f"DataFrame shape: {df.shape}")
print(df.head())

이러면 각 행이 고유한 상품-변형 조합이 되는 평면 테이블이 나와요. 상품 500개에 상품당 변형이 평균 4개라면, 약 2,000행짜리 DataFrame이 만들어져요.

옵션 B: 상품 요약별 1행(카탈로그 개요에 최적)

def summarize_products(products):
    """변형 전체의 최소/최대 가격을 포함해 상품당 1행으로 요약합니다."""
    rows = []
    for product in products:
        prices = [float(v["price"]) for v in product.get("variants", []) if v.get("price")]
        rows.append({
            "product_id": product["id"],
            "title": product["title"],
            "vendor": product.get("vendor", ""),
            "product_type": product.get("product_type", ""),
            "variant_count": len(product.get("variants", [])),
            "min_price": min(prices) if prices else None,
            "max_price": max(prices) if prices else None,
            "any_available": any(v.get("available", False) for v in product.get("variants", [])),
            "tags": ", ".join(product.get("tags", []))
        })
    return rows

CSV, Excel, Google Sheets로 내보내기

# CSV 내보내기(Excel이 특수문자를 잘 처리하도록 utf-8-sig 사용)
df.to_csv("shopify_products.csv", index=False, encoding="utf-8-sig")

# Excel 내보내기(openpyxl 필요)
df.to_excel("shopify_products.xlsx", index=False, engine="openpyxl")

print("shopify_products.csv 및 shopify_products.xlsx로 내보냈습니다")

Google Sheets엔 gspread 라이브러리를 서비스 계정과 함께 쓸 수 있어요. 다만 솔직히 대부분은 CSV로 저장한 뒤 Google Drive에 올리는 쪽이 더 빠르고 단순해요.

운영 환경용 Python 스크래핑: 속도 제한, 재시도, 차단 회피

기본 스크립트는 작은 스토어에선 잘 돌아가요. 그런데 상품이 5,000개를 넘거나 여러 스토어를 연달아 긁기 시작하면 얘기가 달라져요.

Shopify 속도 제한과 차단 동작 이해하기

Shopify 공개 JSON 엔드포인트엔 Admin API의 leaky bucket 같은 공식 속도 제한이 명시돼 있진 않아요. 다만 직접 테스트한 결과는 이래요.

  • 안전 구간: 스토어당 초당 약 2회 요청
  • 완만한 한계: 시간당이 아니라 분당 약 40회 요청부터 제한이 걸리기 시작
  • HTTP 429: "Too Many Requests"—흔한 속도 제한 응답
  • HTTP 430: Shopify 고유의 차단 코드(단순 속도 제한이 아님)
  • HTTP 403 또는 CAPTCHA 리다이렉트: 일부 스토어의 Cloudflare 추가 보호

AWS Lambda나 Google Cloud Run 같은 공유 클라우드에서 보내는 요청은 특히 잘 막혀요. 그 IP 대역의 악용 비율이 높기 때문예요.

Shopify를 안정적으로 긁는 법

내 컴퓨터에서만 되는 수준에서 실제 운영 가능한 수준까지 올리는 길을 정리하면 이래요.

수준기법신뢰성
기본requests.get() + ?page=대규모 카탈로그에서 깨질 수 있고 차단될 수 있음
중급requests.Session() + ?limit=250 + time.sleep(1) + 429 재시도대부분의 스토어에서 잘 동작
고급비동기 httpx + User-Agent 순환 + 지수 백오프운영 환경용, 1만 개 이상 상품도 대응 가능

중급 수준(대부분에게 추천):

import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

def create_session():
    """속도 제한에 대응하는 자동 재시도 로직이 있는 세션을 만듭니다."""
    session = requests.Session()
    retries = Retry(
        total=5,
        backoff_factor=1,  # 대기: 0.5초, 1초, 2초, 4초, 8초
        status_forcelist=[429, 430, 500, 502, 503, 504],
        respect_retry_after_header=True
    )
    session.mount("https://", HTTPAdapter(max_retries=retries))
    session.headers.update({
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"
    })
    return session

Retry 설정이 지수 백오프로 429 응답을 알아서 처리해 줘요. backoff_factor=1이면 재시도 사이 대기가 0.5초 → 1초 → 2초 → 4초 → 8초로 늘어나요. requests.Session()을 재사용하면 연결 풀링이 적용돼, 같은 도메인에 여러 번 요청할 때 오버헤드도 줄어들고요.

User-Agent 순환: 여러 스토어를 긁는다면 현실적인 브라우저 User-Agent 문자열 3~5개를 번갈아 쓰세요. 속이려는 게 아니라, 매 요청마다 똑같은 헤더만 보내는 봇처럼 안 보이게 하려는 거예요.

CSV 내보내기까지 다 담은 완성형 Shopify 스크래핑 스크립트

아래는 지금까지 내용을 한데 합친, 복사해서 바로 쓸 수 있는 전체 스크립트예요. 주석 빼면 실제 코드는 약 75줄 정도고, Allbirds(1,420개), ColourPop(2,000개 이상), Zoologist Perfumes(소규모 카탈로그)에서 테스트했어요.

import requests
import pandas as pd
import time
import random
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry


def create_session():
    """속도 제한 대응용 재시도 로직이 있는 세션을 만듭니다."""
    session = requests.Session()
    retries = Retry(
        total=5,
        backoff_factor=1,
        status_forcelist=[429, 430, 500, 502, 503, 504],
        respect_retry_after_header=True
    )
    session.mount("https://", HTTPAdapter(max_retries=retries))
    session.headers.update({
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                      "AppleWebKit/537.36 (KHTML, like Gecko) "
                      "Chrome/125.0.0.0 Safari/537.36"
    })
    return session


def scrape_shopify(store_url, delay=1.0):
    """/products.json을 통해 Shopify 스토어의 모든 상품을 스크래핑합니다."""
    session = create_session()
    all_products = []
    page = 1
    base_url = f"{store_url.rstrip('/')}/products.json"

    while True:
        print(f"  Page {page}...", end=" ")
        resp = session.get(base_url, params={"limit": 250, "page": page}, timeout=30)
        resp.raise_for_status()
        products = resp.json().get("products", [])

        if not products:
            break

        all_products.extend(products)
        print(f"{len(products)} products (total: {len(all_products)})")
        page += 1
        time.sleep(delay + random.uniform(0, 0.5))

    return all_products


def flatten_to_variants(products):
    """중첩된 상품 JSON을 변형별 1행 구조로 평탄화합니다."""
    rows = []
    for p in products:
        base = {
            "product_id": p["id"],
            "title": p["title"],
            "handle": p["handle"],
            "vendor": p.get("vendor", ""),
            "product_type": p.get("product_type", ""),
            "tags": ", ".join(p.get("tags", [])),
            "created_at": p.get("created_at", ""),
            "updated_at": p.get("updated_at", ""),
            "image_url": p["images"][0]["src"] if p.get("images") else "",
        }
        for v in p.get("variants", []):
            row = {**base}
            row["variant_id"] = v["id"]
            row["variant_title"] = v.get("title", "")
            row["sku"] = v.get("sku", "")
            row["price"] = v.get("price", "")
            row["compare_at_price"] = v.get("compare_at_price", "")
            row["available"] = v.get("available", "")
            rows.append(row)
    return rows


if __name__ == "__main__":
    STORE_URL = "https://allbirds.com"  # 대상 스토어로 바꾸세요
    OUTPUT_CSV = "shopify_products.csv"
    OUTPUT_EXCEL = "shopify_products.xlsx"

    print(f"Scraping {STORE_URL}...")
    products = scrape_shopify(STORE_URL)
    print(f"\nTotal products scraped: {len(products)}")

    print("Flattening to variant-level rows...")
    rows = flatten_to_variants(products)
    df = pd.DataFrame(rows)
    print(f"DataFrame: {df.shape[0]} rows x {df.shape[1]} columns")

    df.to_csv(OUTPUT_CSV, index=False, encoding="utf-8-sig")
    df.to_excel(OUTPUT_EXCEL, index=False, engine="openpyxl")
    print(f"\nExported to {OUTPUT_CSV} and {OUTPUT_EXCEL}")

실행은 python scrape_shopify.py로 하면 돼요. Allbirds 기준 약 45초쯤 걸리고, 변형 단위로 5,000행 이상이 담긴 CSV가 생겨요. 터미널 출력은 대략 이런 모양이에요.

Scraping https://allbirds.com...
  Page 1... 250 products (total: 250)
  Page 2... 250 products (total: 500)
  ...
  Page 6... 170 products (total: 1420)

Total products scraped: 1420
Flattening to variant-level rows...
DataFrame: 5680 rows x 14 columns

Exported to shopify_products.csv and shopify_products.xlsx

Python 없이 클릭 두 번으로 Shopify 긁기: Thunderbit 노코드 방법

누구나 Python 설치하고, import 오류 잡고, 스크래핑 스크립트를 관리하고 싶어 하진 않아요. 내일 아침까지 경쟁사 가격이 필요한 세일즈 담당자라면 Python은 좀 과하죠.

그래서 저희가 만든 게 Chrome 확장으로 돌아가는 AI 웹 스크래퍼, Thunderbit 공식 웹사이트예요. 코드도 API 키도 환경 세팅도 필요 없어요.

Thunderbit Chrome 확장 프로그램 사용해보기

Thunderbit가 Shopify를 긁는 방식

Thunderbit엔 Shopify 상품 페이지에 맞춰 미리 세팅된 전용 Shopify Scraper 템플릿이 있어요. Thunderbit Chrome 확장 프로그램 다운로드 페이지에서 설치한 뒤 Shopify 스토어로 가서 "Scrape"만 누르면 돼요. 템플릿이 상품명·설명·가격·변형·이미지·공급업체 정보를 알아서 뽑아 줘요.

템플릿이 딱 안 맞는 스토어(커스텀 테마, 특이한 레이아웃 등)라도 괜찮아요. Thunderbit의 AI 필드 추천이 페이지를 읽고 열 이름을 자동으로 만들어 주거든요. 이 열은 직접 고칠 수도 있고, "compare_at_price가 설정된 상품만 추출"처럼 조건을 줄 수도 있어요.

Python 스크립트가 하던 일을 그대로 대신하는 기능은 이래요.

  • 서브페이지 스크래핑: 상품 상세 페이지를 하나씩 자동 방문해 전체 설명·리뷰·변형 정보를 표에 채워 줘요. Python으로 여러 페이지를 도는 것과 같은 일이지만 코드는 안 짜도 돼요.
  • 자동 페이지네이션: 클릭형 페이지네이션도, 무한 스크롤도 별도 설정 없이 처리해요.
  • 예약 스크래핑: 정기 가격 모니터링용으로 "매주 월요일 오전 9시" 같은 반복 작업을 걸어 둘 수 있어요. cron도 서버도 필요 없어요.
  • 무료 내보내기: 모든 플랜에서 CSV·Excel·Google Sheets·Airtable·Notion으로 내보내요.

Python 스크립트 vs. Thunderbit: 솔직한 비교

항목Python 스크립트Thunderbit(노코드)
설정 시간15~60분(환경 + 코드)약 2분(Chrome 확장 설치)
코딩 필요 여부예(Python)아니요
커스터마이징무제한AI 추천 필드 + 사용자 프롬프트
페이지네이션 처리직접 코딩 필요자동
내보내기 형식직접 구현(CSV/Excel)CSV, Excel, Google Sheets, Airtable, Notion(무료)
예약 실행cron 작업 + 호스팅내장 스케줄러
속도 제한 대응재시도/백오프 직접 구현자동 처리
적합한 대상개발자, 대규모 데이터 파이프라인비즈니스 사용자, 빠른 추출, 반복 모니터링

완전한 제어가 필요하거나 큰 데이터 파이프라인에 끼워 넣어야 한다면 Python이에요. 빠르게 뽑고 코드 관리는 하기 싫다면 Thunderbit가 맞고요. 가격 비교용 웹 스크래핑을 더 파고들고 싶으면 관련 가이드를 따로 참고하세요.

python-vs-thunderbit-comparison.webp

Shopify 스토어 스크래핑 팁과 모범 사례

도구가 뭐든 아래 원칙은 그대로예요.

  • 무조건 ?limit=250을 쓰세요. 페이지당 기본 30개로 두면 같은 데이터를 받는데 요청만 8배로 늘어나요.
  • 스토어를 존중하세요: 요청 사이에 1~2초 지연을 넣으세요. 서버를 너무 빠르게 두드리면 매너에도 안 맞고 차단 위험도 커져요.
  • robots.txt부터 확인하세요: Shopify 기본 robots.txt/products.json을 막지 않아요. 그래도 스토어에 따라 커스텀 규칙이 붙을 수 있으니, 대규모로 긁기 전엔 확인하는 게 좋아요.
  • 원본 JSON을 먼저 로컬에 저장하고 처리하세요. 나중에 파싱 로직이 바뀌어도 다시 긁을 필요가 없어요. 펴기 전에 json.dump(all_products, open("raw_data.json", "w"))처럼 저장해 두면 두고두고 편해요.
  • product.id로 중복을 제거하세요. 페이지 경계에서 같은 상품이 두 번 나오는 일이 있어요. df.drop_duplicates(subset=["product_id", "variant_id"])로 금방 정리돼요.
  • 가격은 계산 전에 float로 바꾸세요. Shopify는 가격을 숫자가 아니라 문자열("185.00")로 줘요.
  • 엔드포인트 변경을 지켜보세요: /products.json은 오랫동안 안정적이었지만, 이론상 Shopify가 제한할 가능성은 있어요. 스크래퍼가 갑자기 404를 뱉으면 먼저 스토어부터 직접 확인하세요.

더 탄탄한 스크래퍼를 만드는 법은 웹 스크래핑 도구 모범 사례 가이드를 참고하세요.

Shopify 스크래핑의 법적·윤리적 고려사항

짧지만 챙겨야 할 부분이에요.

/products.json 엔드포인트가 주는 건 누구나 볼 수 있는 상품 데이터예요. 방문자가 스토어를 둘러볼 때 보는 정보와 똑같죠. Shopify 이용약관에는 "서비스"에 접근할 때 "자동화된 수단"을 쓰지 말라는 문구가 있는데, 이건 플랫폼 자체(관리 대시보드, 결제)를 가리키는 것이지 공개 스토어프론트 데이터까지 포함한다고 보긴 어려워요. 2026년 4월 기준 Shopify 스크래핑 관련 소송은 아직 한 건도 없어요.

공개 데이터 스크래핑을 뒷받침하는 주요 판례도 있어요. hiQ v. LinkedIn 사건은 공개 접근 가능한 데이터의 스크래핑이 CFAA 위반이 아니라고 봤고, Meta v. Bright Data(2024)는 약관 제한이 로그인 상태에서만 적용된다고 판단했어요.

모범 사례는 이래요.

  • 공개 접근 가능한 상품 데이터만 긁기
  • 개인 정보나 고객 데이터는 긁지 않기
  • robots.txt와 속도 제한 지키기
  • 개인 정보를 다룬다면 개인정보보호법(미국이면 CCPA, EU면 GDPR) 준수하기(상품 카탈로그 데이터는 비개인 정보)
  • 명확한 User-Agent 문자열로 자신을 밝히기
  • 자기 Shopify 스토어를 Admin API로 긁는 건 언제나 문제없음

더 자세한 내용은 웹 스크래핑의 법적 쟁점 글에서 확인하세요.

결론 및 핵심 요약

Shopify 공개 /products.json은 이커머스 데이터 추출을 놀랄 만큼 쉽게 만들어 줘요. 흐름은 단순해요. /products.json을 붙이고 → Python으로 가져오고 → ?limit=250&page=로 페이지를 넘기고 → pandas로 펴고 → CSV나 Excel로 내보내면 끝이에요.

이 가이드가 다른 글보다 깊게 들어간 부분은 이래요.

  • 완전한 필드 참고표: 코드 한 줄 짜기 전에 어떤 데이터가 있는지 정확히 파악(상품·변형·이미지 전반 40개+ 필드)
  • 추가 엔드포인트: /collections.json/meta.json으로 카테고리 수준 인사이트와 스토어 메타데이터 확보
  • 운영 가능한 기법: 세션 재사용, 지수 백오프, User-Agent 헤더, ?limit=250으로 실제 환경의 속도 제한 대응
  • 제대로 된 CSV/Excel 내보내기: 원시 JSON 덤프가 아니라 pandas로 변형 단위 데이터를 펴기
  • 노코드 방법: 코드보다 속도가 먼저라면 Thunderbit

단발성이든 반복이든 Shopify 데이터를 코드 없이 뽑고 싶다면 Thunderbit Chrome 확장 프로그램 다운로드 페이지를 써보세요. Shopify Scraper 템플릿이 페이지네이션부터 내보내기까지 다 처리해요. 여러 스토어를 대규모로 묶어 커스텀 파이프라인을 짜고 싶다면, 이 글의 Python 스크립트가 완전한 제어권을 줄 거예요.

영상 튜토리얼은 Thunderbit YouTube 채널에서 볼 수 있고, 관련 기술로는 Amazon 상품 스크래핑웹사이트 데이터를 Excel로 추출하기 가이드를 참고하세요.

Shopify 스크래핑을 위해 Thunderbit 사용해보기 Get Started Free

자주 묻는 질문

products.json으로 어떤 Shopify 스토어든 긁을 수 있나요?

대부분의 스토어는 기본적으로 이 엔드포인트를 열어요. 테스트해 보니 약 71%에서 유효한 JSON이 나왔어요. 다만 커스텀 설정이나 Cloudflare, 헤드리스 구조 같은 추가 보안층이 있는 스토어는 404를 뱉거나 요청을 막을 수 있어요. 빠른 확인법은 {store-url}/products.json을 브라우저에 직접 쳐보는 거예요. JSON이 보이면 정상이에요.

Shopify 스토어를 긁는 건 합법인가요?

가격·상품명·이미지·설명 같은 공개 상품 데이터는 보통 접근 가능하고, hiQ v. LinkedIn 같은 판례도 공개 정보 스크래핑을 뒷받침해요. 다만 각 스토어의 이용약관과 현지 법률은 꼭 확인하세요. 개인 정보나 고객 데이터는 모으지 말고, 속도 제한도 지켜야 해요.

Shopify 스토어에서 상품을 몇 개까지 긁을 수 있나요?

총개수에 하드 리미트는 없어요. ?limit=250&page=로 페이지를 넘기면 카탈로그 전체를 다 가져와요. 상품이 25,000개 넘는 대형 스토어라면 세션 재사용과 지연으로 속도 제한을 피하세요. /meta.json이 정확한 상품 수를 미리 알려 주니 페이지 수를 예측하는 데 도움이 돼요.

products.json과 Shopify Admin API의 차이는 뭔가요?

/products.json은 공개 엔드포인트라 인증 없이 누구나 읽기 전용 상품 데이터를 봐요. 반면 Admin API는 스토어 주인의 액세스 토큰이 있어야 하고, 주문·재고 수량·고객 데이터·쓰기 권한까지 줘요. 판매 데이터나 실제 재고 수량이 필요하면 Admin API 접근이 있어야 해요(즉 스토어 주인이거나 허가를 받아야 해요).

Python 없이 Shopify를 긁을 수 있나요?

물론이에요. Thunderbit 공식 웹사이트 같은 도구를 쓰면 Chrome 확장으로 코딩 없이 Shopify 스토어를 긁어요. 페이지네이션도 알아서 처리하고, CSV·Excel·Google Sheets·Airtable·Notion으로 바로 내보내요. 다른 언어가 편한 개발자라면 JavaScript·Ruby·Go 등 HTTP 요청과 JSON 파싱이 되는 언어 무엇으로든 같은 /products.json 엔드포인트를 쓸 수 있어요.

더 알아보기

Ke
Ke
Thunderbit CTO | 시니어 데이터 사이언티스트 & ML 전문가 머신러닝과 데이터 과학 분야에서 약 10년에 가까운 경험을 쌓아온 Ke Shen은 컬럼비아 대학교 출신이며, 전 Walmart Labs의 시니어 데이터 사이언티스트였습니다. Python, R, Java, 통계 분야에서 동료들에게도 인정받는 깊은 전문성을 바탕으로, 복잡한 AI 알고리즘을 이론에서 실제 운영 수준의 아키텍처로 전환하는 데 필요한 실전 인사이트를 공유합니다.

Thunderbit 사용해보기

단 2번 클릭으로 리드와 기타 데이터를 추출하세요. AI로 구동됩니다.

Thunderbit 받기 무료입니다
AI로 데이터 추출
Google Sheets, Airtable, Notion으로 데이터를 쉽게 전송하세요
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week