Instagramの公開データをスクレイピングする完全ガイド:プロキシ活用戦略

Instagramスクレイピングの技術的障壁を詳解し、レジデンシャルプロキシによるIPローテーション、Python実装例、GraphQLエンドポイントの最新動向まで網羅する実践ガイド。

How to Scrape Public Instagram Data with Residential Proxies

Instagramは世界で最もデータ豊富なソーシャルプラットフォームの一つですが、大規模なスクレイピングは非常に困難です。レート制限、ログインウォール、デバイスフィンガープリンティングなど、Metaは自動化アクセスを徹底的にブロックしています。本ガイドでは、公開データに限定した合法的なアクセス方法と、Instagramスクレイピングプロキシとしてレジデンシャルプロキシを活用した実践的なPython実装を解説します。

⚠️ 重要: 本記事は公開データへのアクセスのみを対象としています。Instagramの利用規約(ToS)を遵守してください。ログイン自動化、非公開データへのアクセス、米国CFAAやEU GDPRに違反する行為は推奨しません。常にrobots.txtを確認し、自己責任で行ってください。

なぜInstagramのスクレイピングは難しいのか

Instagramのスクレイピングが他のプラットフォームと比べて格段に難しい理由は、Metaが複数レイヤーで防御を構築しているためです。各障壁を理解することで、適切な対策を設計できます。

レート制限とスロットリング

InstagramはIPアドレスごとに厳格なレート制限を適用します。匿名アクセスの場合、同じIPから短時間に数十リクエストを送るとHTTP 429が返されます。制限はエンドポイントごとに異なり、プロフィールページよりハッシュタグページの方が厳しい傾向があります。レート制限に達すると、一時的なブロック(数時間)から恒久的なIP banまで段階的にペナルティが加重されます。

ログインウォール

2020年以降、Instagramは段階的にログインウォールを拡大しています。かつてはハッシュタグページやロケーションページもログインなしで閲覧できましたが、現在は多くのコンテンツがログイン後にしか表示されません。ただし、公開プロフィールページの基本情報は依然としてログインなしでアクセス可能です。

アンチボットとデバイスフィンガープリンティング

Instagramは以下のシグナルでボットを検出します:

  • TLSフィンガープリント — PythonのrequestsライブラリのデフォルトTLSネゴシエーションはブラウザと異なり、容易に識別されます
  • HTTP/2フィンガープリント — AKAMAIのボット検出がHTTP/2フレームの順序を分析
  • JavaScriptチャレンジ — ブラウザ環境でのJS実行結果を検証
  • マウス/スクロール動作 — ページ内インタラクションの有無
  • リクエストパターン — 人間にはありえない規則的なアクセス間隔

これらを回避するには、ヘッドレスブラウザの活用や、プロキシによるIP分散が不可欠です。

ログインなしでアクセスできる公開データ

Instagramでログインせずにアクセスできるデータは限定的ですが、ソーシャルリスニング用途には十分な場合があります。

アクセス可能なエンドポイント

  • 公開プロフィールページ — ユーザー名、bio、フォロワー数、フォロー数、投稿数、プロフィール画像、ハイライト
  • ハッシュタグページ — 一部のトップ投稿と最近の投稿(ログインウォールの影響あり)
  • ロケーションページ — 場所に紐づく投稿のサムネイル(アクセスの安定性は変動)
  • Reelsフィード — 公開設定のReelsのメタデータ
  • 埋め込み投稿/p/{shortcode}/embed/経由でアクセス可能

アクセスできないデータ

  • 非公開アカウントの投稿
  • ダイレクトメッセージ
  • ストーリーズ(ログイン必須)
  • フォロワー/フォロー中のリスト(ログイン必須)
  • 投稿の正確なタイムスタンプ(プロフィールページのHTMLからは取得困難)

スクレイピングを設計する際は、まず「ログインなしで本当にそのデータにアクセスできるか」を確認してください。ログイン自動化はToS違反であり、本ガイドでは扱いません。

Instagramスクレイピングにレジデンシャルプロキシが必須な理由

InstagramはデータセンターIPを非常にアグレッシブにフラグします。AWS、GCP、Azure、DigitalOceanなどのIPレンジは既知であり、これらからのリクエストは即座にブロックされるか、チャレンジページにリダイレクトされます。これがresidential proxies Instagramが必須とされる理由です。

レジデンシャル vs データセンタープロキシ比較

特徴レジデンシャルプロキシデータセンタープロキシ
IPの出自ISP実割り当て(家庭・モバイル)クラウド/ホスティング業者
Instagramでの検知リスク低(通常ユーザーと同じ)極めて高(即ブロックの可能性)
成功率85〜95%10〜30%
レスポンス速度中〜遅(500ms〜2s)高速(50〜200ms)
コスト高(GB単位)低(IP単位)
IPプールサイズ数千万数千〜数万
スティッキーセッション対応(設定可能)通常は非対応

データセンタープロキシは他のスクレイピング対象(SERPやeコマース)では有効ですが、Instagramでは実質的に使いものになりません。レジデンシャルプロキシはISPから割り当てられた本物のIPアドレスを使用するため、Instagramのトラフィックフィルターを通過できます。

モバイルプロキシも強力な選択肢です。携帯キャリアのIPからのアクセスはInstagramのモバイルアプリと同じトラフィックパターンとみなされるため、検知リスクが最も低くなります。

Pythonで実装する:ローテーションプロキシとセッション分離

ProxyHatのレジデンシャルプロキシを使った実践的なPython実装を紹介します。重要なポイントは、リクエストごとのIPローテーションリアルなブラウザヘッダーの偽装セッション分離の3点です。

基本的なプロキシ設定とIPローテーション

import requests
import time
import random

PROXY_URL = "http://user-country-US:PASSWORD@gate.proxyhat.com:8080"

# リクエストごとに異なるIPを使用するため、
# ProxyHatはユーザー名にセッションIDを含めることでIPをローテーション
def get_proxy_url(session_id):
    return f"http://user-country-US-session-{session_id}:PASSWORD@gate.proxyhat.com:8080"

def fetch_profile(username, session_id):
    url = f"https://www.instagram.com/{username}/"
    headers = {
        "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"
        ),
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
        "Accept-Language": "en-US,en;q=0.9",
        "Accept-Encoding": "gzip, deflate, br",
        "Connection": "keep-alive",
    }
    proxy = {"http": get_proxy_url(session_id), "https": get_proxy_url(session_id)}
    try:
        resp = requests.get(url, headers=headers, proxies=proxy, timeout=15)
        resp.raise_for_status()
        return resp.text
    except requests.RequestException as e:
        print(f"Error fetching {username}: {e}")
        return None

# 複数プロフィールを異なるIPで取得
usernames = ["nasa", "natgeo", "google"]
for i, username in enumerate(usernames):
    session_id = f"ig_scrape_{int(time.time())}_{i}"
    html = fetch_profile(username, session_id)
    if html:
        print(f"Fetched {username}: {len(html)} bytes")
    time.sleep(random.uniform(3, 7))  # 人間らしい間隔を確保

User-Agentローテーションとセッション分離

単一のUser-Agentを使い続けると、パターンとして検知されるリスクが高まります。複数のUAをローテーションし、各セッションで独立したCookieジャーを使用しましょう。

import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
import random
import string
import time

USER_AGENTS = [
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/125.0.0.0 Safari/537.36",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_5) AppleWebKit/605.1.15 Safari/605.1.15",
    "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/124.0.0.0 Safari/537.36",
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:126.0) Gecko/20100101 Firefox/126.0",
]

def random_session_id(length=12):
    return ''.join(random.choices(string.ascii_lowercase + string.digits, k=length))

class InstagramScraper:
    def __init__(self, proxy_user, proxy_pass):
        self.proxy_user = proxy_user
        self.proxy_pass = proxy_pass
        self._setup_session()

    def _setup_session(self):
        """新しいセッションを作成(IPとUAの両方をローテーション)"""
        sid = random_session_id()
        proxy_url = (
            f"http://{self.proxy_user}-country-US-session-{sid}"
            f":{self.proxy_pass}@gate.proxyhat.com:8080"
        )
        self.session = requests.Session()
        self.session.proxies = {"http": proxy_url, "https": proxy_url}
        self.session.headers.update({
            "User-Agent": random.choice(USER_AGENTS),
            "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
            "Accept-Language": "en-US,en;q=0.9",
            "Accept-Encoding": "gzip, deflate, br",
            "DNT": "1",
            "Upgrade-Insecure-Requests": "1",
        })
        # リトライ戦略
        retry = Retry(total=3, backoff_factor=2, status_forcelist=[429, 500, 502, 503])
        adapter = HTTPAdapter(max_retries=retry)
        self.session.mount("https://", adapter)
        self.session.mount("http://", adapter)

    def rotate(self):
        """セッションを再作成してIPとUAをローテーション"""
        self._setup_session()

    def get_profile(self, username):
        url = f"https://www.instagram.com/{username}/"
        try:
            resp = self.session.get(url, timeout=15)
            if resp.status_code == 429:
                print("Rate limited — rotating session...")
                self.rotate()
                return None
            resp.raise_for_status()
            return resp.text
        except requests.RequestException as e:
            print(f"Request failed for {username}: {e}")
            self.rotate()
            return None

# 使用例
scraper = InstagramScraper("your_user", "your_password")
for username in ["nasa", "natgeo", "google", "bbc"]:
    html = scraper.get_profile(username)
    if html:
        print(f"✓ {username} fetched ({len(html)} bytes)")
    time.sleep(random.uniform(4, 8))

セッション分離のポイント:

  • 各セッションは独立したrequests.Session()でCookieを分離
  • ユーザー名にsession-{id}を含めることでProxyHat側でIPを切り替え
  • 429を受け取ったら即座にセッションを再作成
  • リクエスト間に3〜8秒のランダムな間隔を設定

Instagram特有の癖と対策

Instagramのスクレイピングには、一般的なウェブスクレイピングとは異なる特有の課題があります。

?__a=1エンドポイントの現状

かつてhttps://www.instagram.com/{username}/?__a=1にアクセスすると、プロフィール情報がJSONで返されました。これは非常に便利でしたが、2024年現在、このエンドポイントはログインなしでは機能しないことが多いです。アクセスするとログインページにリダイレクトされるか、空のレスポンスが返されます。

代わりに、プロフィールページのHTMLから<script type="application/ld+json">タグ内の構造化データを抽出する方法が現在も有効です。

import json
import re

def extract_ld_json(html):
    """プロフィールページからld+jsonデータを抽出"""
    pattern = r'<script type="application/ld\+json">(.*?)</script>'
    match = re.search(pattern, html, re.DOTALL)
    if match:
        return json.loads(match.group(1))
    return None

def parse_profile(html):
    """HTMLからプロフィールメタデータを抽出"""
    data = extract_ld_json(html)
    if not data:
        return None
    return {
        "username": data.get("identifier", {}).get("value", ""),
        "name": data.get("name", ""),
        "description": data.get("description", ""),
        "follower_count": data.get("interactionStatistic", [{}])[0].get("userInteractionCount", 0),
        "profile_image": data.get("image", ""),
    }

GraphQLクエリと必須ヘッダー

InstagramのウェブフロントエンドはGraphQL APIを使用しています。このAPIに直接アクセスするには、以下のヘッダーが必要です:

  • x-ig-app-id — InstagramウェブアプリのApp ID(936619743392459が一般的)
  • x-csrftoken — CSRFトークン(Cookieから取得)
  • x-requested-withXMLHttpRequest
  • x-instagram-ajax — AJAXバージョン番号

ただし、これらのヘッダーを正しく設定しても、ログイン済みCookieがなければGraphQL APIはデータを返しません。ログインなしでGraphQLにアクセスする方法は事実上閉じられています。

HTTPS証明書ピンニング

InstagramのモバイルAPIはHTTPS証明書ピンニングを実装しています。これにより、中間者プロキシでトラフィックを傍受・解析することが困難になっています。モバイルAPIのリバースエンジニアリングには、フリップされたデバイスやFridaなどのツールが必要ですが、これらはToS違反の可能性が高く、本ガイドでは推奨しません。

HTMLスクレイピングからモバイルAPIへのシフト

Instagramスクレイピングのトレンドは、HTMLパースからモバイルAPIのリバースエンジニアリングへ移行しています。しかし、モバイルAPIへのアクセスはログインと署名付きリクエストが必須であり、ToS違反のリスクが高いです。公開データに限定するなら、HTMLスクレイピングとld+jsonの抽出が最も安全で持続可能なアプローチです。

Node.jsでの実装例

const axios = require('axios');
const { SocksProxyAgent } = require('socks-proxy-agent');

// ProxyHat SOCKS5プロキシを使用する例
const proxyAgent = new SocksProxyAgent(
  'socks5://user-country-US-session-ig_node_1:PASSWORD@gate.proxyhat.com:1080'
);

async function fetchInstagramProfile(username) {
  const url = `https://www.instagram.com/${username}/`;
  const headers = {
    'User-Agent':
      'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/125.0.0.0 Safari/537.36',
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
    'Accept-Language': 'en-US,en;q=0.9',
  };

  try {
    const resp = await axios.get(url, {
      headers,
      httpsAgent: proxyAgent,
      timeout: 15000,
    });
    console.log(`Fetched ${username}: ${resp.data.length} bytes`);
    return resp.data;
  } catch (err) {
    if (err.response && err.response.status === 429) {
      console.error('Rate limited — rotate proxy session and retry');
    } else {
      console.error(`Error: ${err.message}`);
    }
    return null;
  }
}

fetchInstagramProfile('nasa');

レート制限パターンとフィンガープリント対策

Instagramのアンチボットシステムを回避するには、技術的なプロキシ設定だけでなく、アクセスパターンの設計も重要です。

推奨されるレート制限パターン

  • 1IPあたり1時間で最大30〜50リクエスト — 安全側に振ることで長期的なブロックを回避
  • リクエスト間隔は3〜10秒のランダム — 固定間隔はボット検出のシグナルになる
  • バーストアクセスを避ける — 5リクエスト/秒のような急激なアクセスは即座にフラグされる
  • 時間帯を分散 — 全リクエストを深夜に集中させず、営業時間帯にも分散

フィンガープリントの最小化

  • TLSフィンガープリントをブラウザに近づけるため、curl_cffitls-clientの使用を検討
  • HTTP/2をサポートするクライアントを使用(httpxなど)
  • RefererヘッダーをInstagram内のページ遷移として設定
  • Cookieを適切に処理(Set-Cookieヘッダーを無視しない)

倫理的スクレイピングと公式APIの活用

スクレイピングは強力な手法ですが、常に倫理的・法的な境界を意識する必要があります。

守るべき原則

  • robots.txtを尊重するhttps://www.instagram.com/robots.txtを確認し、指定された制限に従う
  • 自己レート制限 — プラットフォームのインフラに負荷をかけない速度でアクセス
  • ログイン自動化は絶対にしない — クレデンシャルスタッフィングやアカウント乗っ取りとみなされるリスクがある
  • 個人情報の取り扱い — GDPR/CCPAの対象となるデータは適切に処理
  • データの再配布禁止 — スクレイピングしたデータを第三者に販売・公開しない

公式APIの代替を検討するタイミング

以下の場合は、スクレイピングではなく公式APIの使用を検討してください:

  • Instagram Graph API — ビジネスアカウントとクリエイターアカウントのデータにアクセス可能。自社のビジネスアカウントデータの取得に最適
  • Meta Marketing API — 広告キャンペーンのデータにアクセス
  • サードパーティの公式パートナー — Brandwatch、Sprout SocialなどのソーシャルリスニングツールはMetaと公式契約している

公式APIの制限(ビジネスアカウントのみ、承認プロセスが必要など)が受け入れられない場合にのみ、公開データのスクレイピングを最後の手段として検討してください。

まとめ:Key Takeaways

Key Takeaways

  • InstagramはデータセンターIPを即座にブロックするため、レジデンシャルプロキシが必須
  • ログインなしでアクセスできるのは公開プロフィールの基本情報のみ — ストーリーズやフォロワーリストは不可
  • ?__a=1は事実上終了 — HTMLからld+jsonを抽出する方が確実
  • リクエストごとにIPをローテーションし、3〜10秒のランダム間隔を確保
  • TLS/HTTP2フィンガープリントに注意 — curl_cffihttpxの使用を検討
  • ログイン自動化は絶対に避ける — CFAA/GDPR違反のリスク
  • 公式API(Graph API)で十分な場合は、そちらを優先

Instagramの公開データスクレイピングは難易度が高いものの、適切なプロキシ戦略と慎重な実装で安定したデータパイプラインを構築できます。ProxyHatのレジデンシャルプロキシプールは、ISP実割り当てのIPアドレスを提供し、Instagramの厳格なフィルタリングを回避するのに適しています。ProxyHatの料金プランを確認して、あなたのユースケースに合ったプロキシを見つけてください。プロキシロケーションの詳細は対応ロケーション一覧をご参照ください。

より広範なウェブスクレイピングのベストプラクティスについては、ウェブスクレイピングのベストプラクティススクレイピングユースケースも併せてご覧ください。

始める準備はできましたか?

AIフィルタリングで148か国以上、5,000万以上のレジデンシャルIPにアクセス。

料金を見るレジデンシャルプロキシ
← ブログに戻る