URL の末尾に /products.json を付け足す。たったそれだけで、API キーも OAuth も認証も挟まずに、整った JSON がそのまま手に入ります。EC データを扱う人の間では半ば公然の小技で、Shopify の /products.json エンドポイントはその代表格です。HTML を一行ずつたどっていく面倒も、ここでは必要ありません。
私は Thunderbit Official Website のチームに在籍していて、「ウェブからどうデータを取るか」を来る日も来る日も考えています。そのなかでも、Shopify のスクレイピングは群を抜いて相談が多いテーマです。仕入れ先を探す購買担当、商品カタログを突き合わせる EC 運用、競合の値段を追う営業――声をかけてくる人の顔ぶれは実に多彩です。それもそのはずで、Shopify には482 万のアクティブな加盟店がひしめき、米国 EC 市場のおよそ 30% を握っています。眠っている商品データの量は、想像以上に膨大なのです。
本稿では一連の流れをまとめて扱います。エンドポイントが返す中身、数千件の商品をページネーションで集めきる手順、ブロックを避けつつレート制限をいなす方法、そして入れ子になった Shopify の JSON を pandas で読みやすい CSV や Excel へ落とし込むやり方です。加えて、あまり語られない /collections.json や /meta.json といったエンドポイント、さらには Python を避けたい人向けのノーコード策にも踏み込みます。
Shopify の /products.json エンドポイントとは? なぜスクレイピングしやすいのか
どの Shopify ストアにも {store-url}/products.json という公開エンドポイントが用意されていて、ここから商品データを構造化された形で引き出せます。API キーはいりません。OAuth もいりません。認証という工程そのものが存在しないのです。ストア URL に /products.json をつなげれば、カタログ内の全商品が JSON 配列となって返ってきます。
百聞は一見にしかず、です。allbirds.com/products.json や zoologistperfumes.com/products.json をブラウザで開いてみてください。商品名、価格、バリエーション、画像、タグが、きれいに整った JSON で並んでいるのが見えるはずです。
HTML を解析するやり方と並べてみると、その差は歴然です。Shopify の 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 に軍配が上がります。しかもこのエンドポイントは便利なクエリパラメータを 2 つ備えています。1 ページの取得件数を決める ?limit=(最大 250 件、未指定だと 30 件)と、ページを送る ?page= です。後ほどのコードで両方ともフルに使い倒します。
ひとつ取り違えやすい点を先に潰しておきます。これはあくまで 公開ストアフロントのエンドポイント であって、Shopify Admin API とは別物です。Admin API のほうはストア所有者のアクセストークンを要し、注文、在庫、顧客情報まで触れます。対する公開 /products.json は、誰でも読める商品データに限った読み取り専用の窓口です。この線引きは後段で改めて掘り下げます。混同する人が、本当に後を絶たないからです。
注意点: どの Shopify ストアもこのエンドポイントを開けているわけではありません。私が実際に当たってみたところ、有効な JSON を返したのは約 71% のストアでした(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% 増えるという研究まであります。データはそのまま売上に化けるわけです。
代表的な使い道を並べます。
| 用途 | 恩恵を受ける人 | 得られる情報 |
|---|---|---|
| 競合価格の監視 | EC 運用チーム | 競合カタログ全体の価格変動、割引、比較価格を追跡 |
| 商品調査・仕入れ | 購買 / MD 部門 | 商品特性、バリエーション、素材、在庫状況を比較 |
| リード獲得 | 営業チーム | ストアカタログから仕入れ先名、ブランド情報、連絡先を抽出 |
| 市場・カテゴリ分析 | マーケティングチーム | 商品構成、タグ、コレクション構造、ポジショニングを把握 |
| 在庫・供給状況の追跡 | サプライチェーンチーム | バリエーションごとの在庫状態(available: true/false)を継続監視 |
| 新商品検知 | プロダクトチーム | created_at を追って競合の新規発売を検知 |
こうした作業と Python の相性は抜群です。スクレイピング開発者の 69.6%が主言語に Python を据えているのも納得で、HTTP 通信の requests、データ整形の pandas、非同期処理の httpx とエコシステムが厚く、URL ひとつからスプレッドシート完成までを 80 行足らずで書ききれます。
products.json の完全フィールド一覧:各項目の意味を整理
title、id、handle あたりだけ紹介して筆を置く解説が多いのですが、Shopify の JSON レスポンスは商品・バリエーション・画像・オプションをまたいで 40 項目以上を抱えています。コードを書き始める前に取れるものを把握しておけば、あとで取り直す二度手間を減らせます。
ここで載せるリファレンスは、2026 年 4 月 16 日に実際に取得した /products.json レスポンスが下敷きです。構造そのものは、このエンドポイントを開けている全ストアで共通しています。
商品レベルのフィールド
| フィールド | データ型 | 例 | 活用例 |
|---|---|---|---|
id | Integer | 123456789 | 重複排除のための一意な商品ID |
title | String | "Classic Blue Jeans" | カタログや比較用の商品名 |
handle | String | "classic-blue-jeans" | URL スラッグ。商品ページは {store}/products/{handle} で生成可能 |
body_html | String (HTML) or null | Our best-selling... | コンテンツ分析や SEO 調査向けの商品説明 |
vendor | String | "Hiut Denim" | リード獲得や仕入れ先調査に使うブランド / 供給元名 |
product_type | String | "Jeans" | 市場分析のためのカテゴリ分類 |
created_at | ISO DateTime | "2024-01-15T10:30:00-05:00" | 商品追加時期の把握(新商品検知) |
updated_at | ISO DateTime | "2025-03-01T08:00:00-05:00" | 直近のカタログ変更を検知 |
published_at | ISO DateTime | "2024-01-16T00:00:00-05:00" | ストアフロントで公開された日時を把握 |
tags | Array of Strings | ["organic", "women", "straight-leg"] | SEO、分類、トレンド把握のためのタグ分析 |
variants | Array of Objects | (see variant fields below) | バリエーションごとの価格、SKU、在庫状況 |
images | Array of Objects | (see image fields below) | カタログや視覚分析向けの商品画像 URL |
options | Array of Objects | [{"name": "Size", "values": ["S","M","L"]}] | サイズ、色、素材などの構成を把握 |
バリエーションレベルのフィールド(各商品内にネスト)
| フィールド | データ型 | 例 | 用途 |
|---|---|---|---|
id | Integer | 987654321 | バリエーションの一意識別子 |
title | String | "32 / Blue" | バリエーションの表示名 |
sku | String | "HD-BLU-32" | 在庫管理システムとの SKU 照合 |
price | String | "185.00" | 価格監視(文字列なので計算時は float に変換) |
compare_at_price | String or null | "200.00" | 元値。割引監視に必須 |
available | Boolean | true | 在庫有無の判定(公開情報で使える唯一の在庫指標) |
weight | Float | 1.2 | 配送・物流分析 |
option1, option2, option3 | String | "32", "Blue", null | 各オプション値 |
created_at, updated_at | ISO DateTime | — | バリエーション単位の変更追跡 |
画像レベルのフィールド
| フィールド | データ型 | 例 | 用途 |
|---|---|---|---|
id | Integer | 111222333 | 画像の一意識別子 |
src | String (URL) | "https://cdn.shopify.com/..." | 画像の直接ダウンロードリンク |
alt | String or null | "Front view of jeans" | アクセシビリティ分析用の alt テキスト |
position | Integer | 1 | 画像の並び順 |
width, height | Integer | 2048, 2048 | 画像サイズ |
公開エンドポイントに含まれないもの
ここは取りこぼしやすい急所です。inventory_quantity は公開 /products.json レスポンスに入っていません。 セキュリティ上の判断から、この項目は 2017 年 12 月に公開 JSON エンドポイントから外されました。公開側で読める在庫の手がかりは、各バリエーションの available(true か false)だけ。実数を知りたいなら、ストア所有者の認証情報を伴う Admin API が要ります。
スクレイピングを書く前に、この表とにらめっこして必要な項目を見定めてください。価格を監視したいなら variants[].price、variants[].compare_at_price、variants[].available が要りますし、リード獲得が狙いなら vendor、product_type、tags に目を向けます。用途に絞り込んでおくほど、出来上がる CSV はすっきりします。
products.json だけじゃない:Collections、Meta などの Shopify エンドポイント
競合記事ではあまり登場しない話ですが、腰を据えて競合分析をするなら見逃せません。
/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 と同型で、対象が 1 カテゴリに限られるだけです。競合の「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 の領分で、すなわちストア所有者としてのアクセス権が前提になります。公開側で拾えるのは、あくまで商品カタログの情報だけです。

Python で Shopify をスクレイピングする手順:セットアップから実行まで
- 難易度: 初級
- 所要時間: 約15分(セットアップ + 初回実行)
- 必要なもの: Python 3.11 以上、
pip、ターミナル、そして対象の Shopify ストア URL
ステップ1:Python と必要なライブラリをインストールする
まず Python 3.11 以上が入っているか確かめます(pandas 3.0.x ではこれが必須条件です)。確認できたら、必要なライブラリを 2 つ入れます。
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):
"""Fetch a single page of products from a Shopify store."""
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", [])
押さえどころは次の 3 点です。
limit=250は Shopify が 1 ページに許す上限です。未指定だと 30 件なので、明示すればリクエスト回数を最大 8 分の 1 に圧縮できます。User-Agentヘッダー は、なるべく自然な値にしておきましょう。空のまま投げると、Shopify のボット対策に引っかかりやすくなります。timeout=30は、1 回のリクエストが永遠に固まらないための保険です。
知っているストアで試してみます。
products = fetch_products_page("https://allbirds.com")
print(f"Fetched {len(products)} products")
print(f"First product: {products[0]['title']}")
Fetched 250 products と先頭の商品名が表示されれば成功です。
ステップ3:ページネーション対応で全商品を取得する
1 リクエストで取れるのは最大 250 件まで。実際にはそれを上回る品揃えのストアも珍しくありません(Allbirds は 1,420 件超)。だから、空のレスポンスにぶつかるまでページを送り続ける必要があります。
def scrape_all_products(store_url, delay=1.0):
"""Scrape all products from a Shopify store, handling pagination."""
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) で前もって計算できます。これで、末尾に「空の 1 回」を余分に投げる無駄が省けます。
ステップ4:必要なフィールドだけを取り出す
全商品を Python の辞書リストとして手元に集めたら、欲しい項目だけを抜き出します。ここでは、価格監視で頻出する項目を取り出す例を見せます。
def extract_product_data(products):
"""Extract key fields from products, flattening variants."""
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
これはバリエーション 1 件につき 1 行を起こす形です。たとえば「Classic Blue Jeans」のような商品なら、6 サイズ × 2 色で 12 バリエーションあり得て、それぞれ価格や在庫状態が違います。価格比較には、この形式がとてもよく馴染みます。
pandas で Shopify データを CSV と Excel に出力する
他の Shopify スクレイピング記事は、JSON をそのままファイルに保存して幕引き、というパターンが目立ちます。開発者ならそれで足りても、金曜までにスプレッドシートが欲しい EC アナリストには扱いづらいのです。
ネックは、Shopify の JSON が入れ子構造だという点です。1 商品の中に複数のバリエーションがぶら下がり、それぞれが価格、SKU、在庫を抱えています。これを行と列に開いて並べるには、pandas のひと手間が要ります。
ネストした JSON を見やすい表に整える
狙いに応じて、2 通りのアプローチがあります。
方法A:1バリエーションにつき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商品につき1行の要約(カタログ全体の俯瞰に最適)
def summarize_products(products):
"""One row per product with min/max price across variants."""
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 のような明文のレート制限仕様こそありませんが、実際に当たってみるとこんな傾向が見えます。
- 安全圏: ストアごとに約2リクエスト/秒
- ソフト上限: 約40リクエスト/分を超えると制限がかかりやすい
- HTTP 429: "Too Many Requests"。一般的なレート制限レスポンス
- HTTP 430: Shopify 独自のコードで、単なるレート制限ではなくセキュリティブロックを示す場合がある
- HTTP 403 または CAPTCHA リダイレクト: 追加の Cloudflare 保護があるストアで発生することがある
AWS Lambda や Google Cloud Run のような共有クラウド基盤から叩くと、その IP レンジには悪用の前歴が積もっているため、とりわけブロックされやすくなります。
安定して Shopify を取得するための段階的な対策
「手元の PC では動く」と「本番で回せる」の間には、こんな段差があります。
| レベル | 手法 | 信頼性 |
|---|---|---|
| 基本 | 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():
"""Create a requests session with automatic retry logic."""
session = requests.Session()
retries = Retry(
total=5,
backoff_factor=1, # sleep: 0.5s, 1s, 2s, 4s, 8s
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 出力まで含めた、完成版の Python スクリプト
ここまでの要素をひとつにまとめた、コピペで動く完全版がこちらです。コメントを除けば実コードは約 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():
"""Create a session with retry logic for rate limits."""
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):
"""Scrape all products from a Shopify store via /products.json."""
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):
"""Flatten nested product JSON into one row per variant."""
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" # Change this to your target store
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 が出来上がります(1 バリエーション 1 行)。ターミナルにはこんな出力が流れます。
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 を使わずに2クリックで Shopify を取得する:Thunderbit のノーコード代替
Python のインストール、import エラーとの格闘、スクレイピングスクリプトの保守――そこまで抱え込みたくない人もいます。明朝までに競合価格を揃えたい営業担当にとって、Python はいささか重装備かもしれません。
そんな声に応えて生まれたのが Thunderbit Official Website です。Chrome 拡張として走る AI ウェブスクレイパーで、コードも API キーも環境構築も要りません。
Thunderbit は Shopify ストアをどうスクレイピングするのか
Thunderbit には、Shopify の商品ページ向けにあらかじめ仕込まれた専用の Shopify Scraper テンプレート が備わっています。Thunderbit Chrome 拡張のダウンロードページ からインストールし、対象の Shopify ストアを開いて「Scrape」を押すだけ。商品名、説明、価格、バリエーション詳細、画像、販売元情報まで、テンプレートが自動でさらってくれます。
カスタムテーマや変則レイアウトなどで、テンプレートがストアにうまく噛み合わないこともあります。その場合は Thunderbit の AI Suggest Fields がページを読み解き、列名を自動で提案します。列名を変えたり、項目を足したり、「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 がはまります。価格比較目的のスクレイピングをもっと深掘りしたい方は、別記事の Web スクレイピングで価格比較を行う方法 もどうぞ。

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"])で簡単に整理できます。- 価格は数値に変換してから計算する: Shopify の価格は数値ではなく文字列("185.00")で返ります。
- エンドポイント変更を監視する:
/products.jsonは長年安定していますが、将来的に制限される可能性はあります。突然 404 が出たら、まず手動で確認してください。
堅牢なスクレイパーの組み立てについては、Web スクレイピングツールのベストプラクティス のガイドもあわせてご覧ください。
Shopify スクレイピング時の法的・倫理的な注意点
短いながらも、おろそかにできない項目です。
/products.json エンドポイントが返すのは公開済みの商品データで、ストアを普通にブラウズしている一般ユーザーが目にする情報とまったく同じです。Shopify の利用規約には「Services」へのアクセスに自動化手段を用いない旨の記述がありますが、これは管理画面やチェックアウトといったプラットフォーム本体を指すもので、公開ストアフロントのデータまでは通常及びません。2026 年 4 月時点で、Shopify を巡るスクレイピング訴訟は起こされていません。
公開データのスクレイピングを後押しする判例としては、hiQ v. LinkedIn が公開情報の取得は CFAA に反しないと示し、Meta v. Bright Data(2024)が利用規約上の制限はログイン中に限って効く、と判断しています。
ベストプラクティス:
- 公開されている商品データだけを取得する
- 個人情報や顧客データは取らない
robots.txtとレート制限を守る- 個人データを扱う場合は GDPR / CCPA に準拠する(商品カタログデータは非個人情報)
- 明確な User-Agent を付けて自分を名乗る
- 自社 Shopify ストアを Admin API で取得するのは常に問題ない
もっと踏み込んで知りたい方は、Web スクレイピングの法的論点 も覗いてみてください。
まとめと要点
公開 /products.json エンドポイントを足がかりにすれば、EC データの抽出は拍子抜けするほど手軽になります。手順は、/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 商品のスクレイピング や Web サイトから Excel へデータを抽出する方法 も役に立ちます。
Shopify スクレイピングに Thunderbit を試す Get Started Free
FAQ
products.json で任意の Shopify ストアをスクレイピングできますか?
多くの Shopify ストアは、初期状態でこのエンドポイントを開けています。私のテストでは約 71% が有効な JSON を返しました。ただし、カスタム構成やセキュリティ層(Cloudflare、ヘッドレス構成など)を追加しているストアでは、404 が返ったりリクエストが弾かれたりすることがあります。確かめ方は単純で、{store-url}/products.json をブラウザで開き、JSON が出れば OK です。
Shopify ストアをスクレイピングするのは合法ですか?
公開された商品データ(価格、商品名、画像、説明など)は誰でもアクセスでき、hiQ v. LinkedIn のような判例も公開情報のスクレイピングを後押ししています。とはいえ、対象ストアの利用規約と、自分の国・地域の法律は必ず確かめてください。個人情報や顧客データには手を出さず、レート制限も守りましょう。
Shopify ストアから何件まで取得できますか?
総件数にきっちりした上限はありません。?limit=250&page= を使えば、カタログ丸ごとを取得できます。2 万 5,000 件を超えるような大型ストアでは、レート制限を避けるためにセッション再利用と待機処理を仕込んでください。/meta.json を併用すれば、正確な商品数と必要ページ数を先に押さえられます。
products.json と Shopify Admin API の違いは何ですか?
/products.json は公開エンドポイントで、認証なしに誰でも読み取り専用の商品データを取れます。一方の Admin API はストア所有者のアクセストークンが要り、注文、在庫数量、顧客データ、さらには書き込み操作まで扱えます。売上や実在庫が必要なら Admin API の出番で、つまりストア所有者本人か、その許可を得た立場であることが前提になります。
Python なしで Shopify をスクレイピングできますか?
もちろんできます。Thunderbit Official Website のようなツールを使えば、Chrome 拡張からコードなしで Shopify を取得できます。ページネーションも自動でこなし、CSV、Excel、Google Sheets、Airtable、Notion へ直接書き出せます。別言語で書きたい開発者なら、同じ /products.json を JavaScript、Ruby、Go など、HTTP リクエストと JSON パースができる言語ならどれでも叩けます。
さらに詳しく


