ShopifyストアをScrapeする理由
小規模な独立したブランドから大手小売店まで、世界中で4万以上のオンラインストアを販売しています。 これは、eコマースインテリジェンスの最も豊富なソースの1つです。 Shopifyストアをスクレイピングすることで、競合他社の価格設定を追跡し、製品の発売を監視し、市場動向を分析し、包括的な製品データベースを構築することができます。
グッドニュースは、Shopifyは、ほとんどの電子商取引プラットフォームよりも、より体系的にスクレイピングする予測可能な構造を持っていることです。 Shopifyストアは、標準化されたエンドポイントを介して特定のデータを露出します。つまり、単一のスクレーパーアーキテクチャは、数千の異なる店舗で作業することができます。 eコマーススクレイピング戦略の広範な概要については、当社のを参照してください。 eコマースデータスクレイピングガイド. .
Shopifyのストア構造を理解する
Shopifyストアは、テーマやカスタマイズに関係なく、同じURLとデータパターンをフォローします。
パブリックJSONエンドポイント
Shopifyは認証を必要としないJSONエンドポイントで製品データを露出します。 これらは、HTMLパーシングなしで構造化されたデータを取得しているため、Shopifyストアをスクレイピングするための最も効率的な方法です。
| エンドポイント | 返されるデータ | パジネーション |
|---|---|---|
/products.json | 品種、価格、画像のすべての製品 | ?page=N&limit=250 |
/products/{handle}.json | 単一プロダクト細部 | N・A |
/collections.json | すべてのコレクション | ?page=N |
/collections/{handle}/products.json | コレクション内の製品 | ?page=N&limit=250 |
/meta.json | メタデータを保存(名前、説明) | N・A |
製品データ構造
JSON API の各製品オブジェクトには、以下が含まれます。
- 基本情報: title, handle (slug), body html (description), ベンダー, product type, タグ
- バリアント: 各 variant には独自の価格、Compared at price、SKU、在庫状況、オプション値(サイズ、色など)があります。
- イメージ: altテキストですべての製品イメージのURL
- 日付: create at, updated at, 公開 at
レート制限
Shopifyは、店舗のパフォーマンスを保護するために、レート制限が適用されます。 一般的なJSONエンドポイントは通常、スロットリングキックの前に、IPごとに2-4リクエストを割り当てます。 これは、 住宅のプロキシ 複数の IP 間でリクエストを広めることで、単一の IP でレート制限を打つことなくスループットを維持できます。
Shopifyのプロキシ構成
Shopifyのレート制限はIPベースで、プロキシの回転をスケールでスクレイピングするための主な戦略です。
ProxyHat セットアップ
# Rotating residential proxy (new IP per request)
http://USERNAME:PASSWORD@gate.proxyhat.com:8080
# Geo-targeted for region-specific stores
http://USERNAME-country-US:PASSWORD@gate.proxyhat.com:8080
# Sticky session for paginated scraping of one store
http://USERNAME-session-shopify001:PASSWORD@gate.proxyhat.com:8080Shopifyのスクレイピングのために、異なる店舗をスクレイピングするとき、および1つのストアの製品カタログをパギンするときに粘着セッションをスクレイピング使用してください。 このパターンは自然な閲覧行動を模倣します。
Pythonの実装
ここでは、生産準備のShopifyスクレーパーを使用して ProxyHatのPython SDK. .
JSON API スクレーパー
import requests
import json
import time
import random
from dataclasses import dataclass, field
from typing import Optional
PROXY_URL = "http://USERNAME:PASSWORD@gate.proxyhat.com:8080"
USER_AGENTS = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/124.0.0.0 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 Chrome/124.0.0.0 Safari/537.36",
]
@dataclass
class ShopifyProduct:
id: int
title: str
handle: str
vendor: str
product_type: str
tags: list[str]
variants: list[dict]
images: list[str]
min_price: float
max_price: float
created_at: str
updated_at: str
def get_session(store_domain: str) -> requests.Session:
"""Create a session with proxy and headers configured."""
session = requests.Session()
session.proxies = {"http": PROXY_URL, "https": PROXY_URL}
session.headers.update({
"User-Agent": random.choice(USER_AGENTS),
"Accept": "application/json",
"Accept-Language": "en-US,en;q=0.9",
})
return session
def scrape_all_products(store_domain: str) -> list[ShopifyProduct]:
"""Scrape all products from a Shopify store via JSON API."""
products = []
page = 1
session = get_session(store_domain)
while True:
url = f"https://{store_domain}/products.json?page={page}&limit=250"
try:
response = session.get(url, timeout=30)
response.raise_for_status()
except requests.RequestException as e:
print(f"Error on page {page}: {e}")
break
data = response.json()
page_products = data.get("products", [])
if not page_products:
break
for p in page_products:
prices = [float(v["price"]) for v in p.get("variants", [])
if v.get("price")]
product = ShopifyProduct(
id=p["id"],
title=p["title"],
handle=p["handle"],
vendor=p.get("vendor", ""),
product_type=p.get("product_type", ""),
tags=p.get("tags", "").split(", ") if p.get("tags") else [],
variants=[{
"id": v["id"],
"title": v["title"],
"price": v["price"],
"compare_at_price": v.get("compare_at_price"),
"sku": v.get("sku"),
"available": v.get("available", False),
} for v in p.get("variants", [])],
images=[img["src"] for img in p.get("images", [])],
min_price=min(prices) if prices else 0,
max_price=max(prices) if prices else 0,
created_at=p.get("created_at", ""),
updated_at=p.get("updated_at", ""),
)
products.append(product)
print(f"Page {page}: {len(page_products)} products (total: {len(products)})")
page += 1
time.sleep(random.uniform(1, 3))
return products
def scrape_collections(store_domain: str) -> list[dict]:
"""Scrape all collections from a Shopify store."""
collections = []
page = 1
session = get_session(store_domain)
while True:
url = f"https://{store_domain}/collections.json?page={page}"
try:
response = session.get(url, timeout=30)
response.raise_for_status()
except requests.RequestException:
break
data = response.json()
page_collections = data.get("collections", [])
if not page_collections:
break
collections.extend(page_collections)
page += 1
time.sleep(random.uniform(1, 2))
return collections
# Example: Scrape multiple Shopify stores
if __name__ == "__main__":
stores = [
"example-store-1.myshopify.com",
"example-store-2.com",
"example-store-3.com",
]
for store in stores:
print(f"\nScraping: {store}")
products = scrape_all_products(store)
print(f"Found {len(products)} products")
# Save to JSON
with open(f"{store.replace('.', '_')}_products.json", "w") as f:
json.dump([vars(p) for p in products], f, indent=2)
time.sleep(random.uniform(3, 7))店舗間での価格変更の監視
def compare_prices(store_domain: str, previous_data: dict) -> list[dict]:
"""Compare current prices with previously stored data."""
changes = []
products = scrape_all_products(store_domain)
for product in products:
prev = previous_data.get(product.handle)
if not prev:
changes.append({
"type": "new_product",
"handle": product.handle,
"title": product.title,
"price": product.min_price,
})
continue
if product.min_price != prev.get("min_price"):
changes.append({
"type": "price_change",
"handle": product.handle,
"title": product.title,
"old_price": prev["min_price"],
"new_price": product.min_price,
"change_pct": ((product.min_price - prev["min_price"])
/ prev["min_price"] * 100)
if prev["min_price"] else 0,
})
return changesNode.js 実装
Node.js バージョン ProxyHat ノード SDK. .
const axios = require("axios");
const { HttpsProxyAgent } = require("https-proxy-agent");
const fs = require("fs");
const PROXY_URL = "http://USERNAME:PASSWORD@gate.proxyhat.com:8080";
const agent = new HttpsProxyAgent(PROXY_URL);
async function scrapeShopifyProducts(storeDomain) {
const products = [];
let page = 1;
while (true) {
const url = `https://${storeDomain}/products.json?page=${page}&limit=250`;
try {
const { data } = await axios.get(url, {
httpsAgent: agent,
headers: {
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/124.0.0.0 Safari/537.36",
Accept: "application/json",
},
timeout: 30000,
});
const pageProducts = data.products || [];
if (pageProducts.length === 0) break;
for (const p of pageProducts) {
const prices = p.variants.map((v) => parseFloat(v.price)).filter(Boolean);
products.push({
id: p.id,
title: p.title,
handle: p.handle,
vendor: p.vendor,
productType: p.product_type,
tags: p.tags ? p.tags.split(", ") : [],
minPrice: Math.min(...prices),
maxPrice: Math.max(...prices),
variants: p.variants.map((v) => ({
id: v.id,
title: v.title,
price: v.price,
compareAtPrice: v.compare_at_price,
sku: v.sku,
available: v.available,
})),
images: p.images.map((img) => img.src),
updatedAt: p.updated_at,
});
}
console.log(`Page ${page}: ${pageProducts.length} products (total: ${products.length})`);
page++;
// Random delay 1-3 seconds
await new Promise((r) => setTimeout(r, 1000 + Math.random() * 2000));
} catch (err) {
console.error(`Error on page ${page}: ${err.message}`);
break;
}
}
return products;
}
async function scrapeMultipleStores(stores) {
const results = {};
for (const store of stores) {
console.log(`\nScraping: ${store}`);
const products = await scrapeShopifyProducts(store);
results[store] = products;
console.log(`Found ${products.length} products`);
// Delay between stores
await new Promise((r) => setTimeout(r, 3000 + Math.random() * 4000));
}
return results;
}
// Usage
scrapeMultipleStores([
"example-store-1.myshopify.com",
"example-store-2.com",
]).then((results) => {
fs.writeFileSync("shopify_data.json", JSON.stringify(results, null, 2));
console.log("Data saved to shopify_data.json");
});Shopify-Specificスクレイピング戦略
Shopifyストアを発見
スクレイピングする前に、どの競合サイトがShopifyで実行されているかを識別する必要があります。 共通の表示器は下記のものを含んでいます:
- ザ・オブ・ザ・
/products.jsonendpoint は有効な JSON を返します - HTML ソースが含まれています
Shopify.themeまたはcdn.shopify.com - ザ・オブ・ザ・
x-shopify-stageヘッダはレスポンスに存在する
パスワードストアの取り扱い
Shopifyストアにはパスワードが必要です。 これらは通常、プレランチまたは卸売店です。 JSON エンドポイントは、パスワードページのリダイレクトを返します。 許可されたアクセスがない限り、これらのストアをスクレイピングパイプラインにスキップします。
カスタムドメインを扱う
Shopifyストアは、代わりにカスタムドメインを使用することが多い .myshopify.com. JSON API はカスタムドメインと同じ方法で動作します。 リクエストでストアのパブリックフェーシングドメインを使用するだけです。
在庫追跡
製品の種類には、 available 在庫状況を示すフィールド。 時間の経過とともにこのフィールドを追跡することで、競合他社の在庫レベルを監視し、製品が在庫切れるときに識別することができます。価格設定と再入荷決定のための有用な知能。
ブロックやレート制限を回避
ShopifyはAmazonよりもスクレーパーフレンドリーですが、保護を強化しています。
| ソリューション | ニュース | マイティグレーション |
|---|---|---|
| IP率制限 | ~2-4 JSON エンドポイントの IP ごとの req/sec | リクエスト間で住宅のプロキシを回転 |
| クラウドフレア保護 | Cloudflareを使用している店舗 | ブラウザのようなヘッダーを持つ住宅IP |
| ボット検出 | 行動パターンを監視 | 遅延とユーザーエージェントをランダム化 |
| パスワードページ | プレランチ/卸売店ロック | 許可されたアクセスをスキップまたは使用 |
アンチボットシステムの取り扱いについて詳しくは、ガイドをご覧ください ブロックせずにウェブサイトをスクレイピングする方法. .
キーテイクアウト: ShopifyのJSON APIは、最も効率的なスクレイピングアプローチです。これにより、HTMLパーシングなしで構造化されたデータが得られます。 HTMLのスクレイピングに戻る前に使用してください。
データユースケース
Shopify製品データを収集したら、最も価値のあるアプリケーションは次のとおりです。
- 競争価格: 競合他社の価格を追跡し、価格設定戦略をリアルタイムで調整します。
- プロダクト研究: 複数の店舗を監視することで、トレンド商品、新規発売、市場ギャップを特定します。
- 市場分析: マーケットトレンド、価格設定分布、カテゴリの成長を理解するために、Shopifyストアの何百ものデータを集計します。
- カタログの濃縮: 競合製品の説明、画像、仕様を使用して、独自のリストを改善します。
- ブランド監視: 製品の無許可の売り手を追跡し、ShopifyストアフロントにMAPコンプライアンスを監視します。
キーテイクアウト
- Shopifyの
/products.jsonエンドポイントは最も効率的なスクレイピング方法です。HTML 解析の前に使用します。 - シングルスクレーパーアーキテクチャは、標準化された構造により、すべてのShopifyストアで動作します。
- ShopifyのIPベースのレート制限を克服した住宅のプロキシ。
- シングルストアのカタログを通した際の粘液セッションを使用してください。
- 包括的な競争力のあるインテリジェンスのために、バリアントレベルの価格設定と可用性を追跡します。
- まずは ProxyHatの住宅用プロキシ 確実にスクレイピングをスケールアップ
Shopifyストアをスクラップする準備はできましたか? 詳しくはこちら eコマースデータスクレイピングガイド 完全な戦略のために、そして私達の点検して下さい Pythonプロキシガイド そして、 Node.jsプロキシガイド 実装の詳細 訪問する プライシングページ はじめに。






