Scraping Dati Pubblici di Instagram con Proxy Residenziali: Guida Completa

Guida tecnica per sviluppatori Python su come accedere a dati pubblici di Instagram usando proxy residenziali, gestire rate limit e anti-bot, e scrapare in modo etico e affidabile.

How to Scrape Public Instagram Data with Residential Proxies

Perché lo Scraping di Instagram è Così Difficile

Se hai mai provato a raccogliere dati da Instagram a scala, sai già quanto possa essere frustrante. Quello che sembra un semplice GET request su una pagina profilo si trasforma rapidamente in un labirinto di redirect, challenge JavaScript e IP bannati. Instagram è una delle piattaforme più ostiche al mondo per l'estrazione automatizzata di dati, e non è un caso: Meta investe massicciamente in infrastrutture anti-bot.

Prima di addentrarci nelle soluzioni tecniche, un avvertimento fondamentale: rispetta sempre i Termini di Servizio di Instagram e le leggi applicabili (CFAA negli Stati Uniti, GDPR nell'UE, e le normative locali). Questa guida si concentra esclusivamente sull'accesso a dati pubblici, senza tentare login automation o bypass di paywall. Se un dato richiede autenticazione, non tentare di accedervi con scraping.

Le Difese di Instagram: Un Quadro Tecnico

Instagram utilizza un sistema di difesa a strati multipli:

  • Rate limiting aggressivo: Un singolo IP può fare al massimo qualche decina di richieste prima di ricevere HTTP 429 o essere redirectato alla pagina di login.
  • Login wall: Molte sezioni del sito richiedono autenticazione per essere visualizzate. Le pagine pubbliche esistono, ma l'accesso programmatico viene filtrato diversamente rispetto a un browser umano.
  • Device fingerprinting: Instagram raccoglie user-agent, risoluzione dello schermo, plugin del browser, e persino caratteristiche della connessione TLS per profilare i client.
  • Anti-bot JavaScript challenge: Le pagine HTML sono spesso shell vuote che richiedono esecuzione JavaScript per caricare il contenuto reale.
  • IP reputation: Gli IP datacenter vengono flaggati quasi immediatamente. Le richieste da ASN di hosting (AWS, DigitalOcean, Hetzner) ricevono un trattamento molto più restrittivo.

Il risultato? Un approccio naïve con requests.get() e un IP datacenter dura meno di 50 richieste prima di essere bloccato. Per costruire una pipeline affidabile, serve una strategia completamente diversa.

Che Cosa È Accessibile Senza Login

Non tutto è dietro il login wall. Instagram espone ancora alcuni dati pubblicamente, sia attraverso il sito web che tramite endpoint API interni:

  • Pagine profilo pubblico: Username, bio, numero di follower/following, post count, e i post più recenti per account pubblici.
  • Pagine hashtag: I post più recenti e più popolari associati a un hashtag, visibili senza login tramite l'URL /explore/tags/{hashtag}.
  • Pagine località: Post geotaggati associati a un luogo, accessibili tramite /explore/locations/{id}.
  • Feed Reels: Alcuni metadati dei Reels sono accessibili tramite endpoint mobili.
  • Immagini e video singoli: I media individuali di account pubblici hanno URL diretti accessibili.

Quello che non è accessibile senza login include: DM, Stories, follower list completa, commenti annidati profondi, e qualsiasi contenuto di account privati. Non tentare di bypassare queste restrizioni.

Perché i Proxy Residenziali Sono Essenziali per Instagram

Instagram classifica gli IP in base all'ASN (Autonomous System Number). Un IP proveniente da Comcast, Vodafone o Telecom Italia viene trattato come un utente domestico legittimo. Un IP da AWS o OVH viene immediatamente marcato come sospetto.

La differenza è drammatica:

CaratteristicaProxy DatacenterProxy Residenziali
ASN reputationHosting/Cloud provider — immediatamente flaggatoISP domestici — trattato come utente reale
Richieste prima del ban (IG)20–50200–500+ (con rotazione)
Rischio di CAPTCHAAltoBasso–Medio
VelocitàMolto altaMedia
CostoBassoMedio–Alto
Geo-targeting precisoLimitedPaese, città, ASN

Per Instagram, i proxy residenziali sono praticamente obbligatori. Un pool rotante di IP residenziali permette di distribuire le richieste su centinaia o migliaia di identità IP diverse, ciascuna delle quali appare come un utente domestico legittimo.

I proxy mobili (3G/4G/5G) offrono un livello di affidabilità ancora superiore per Instagram, poiché l'app mobile è il client primario della piattaforma. Tuttavia, il costo è significativamente più alto e la banda è limitata.

Regola pratica: per scraping di massa su Instagram, usa proxy residenziali con rotazione per-request. Per task ad alta criticità (come monitoraggio di account specifici), considera sessioni sticky su proxy mobili.

Implementazione Python: Requests + Proxy Residenziali Rotanti

Vediamo un'implementazione pratica usando requests con il pool di proxy residenziali di ProxyHat. L'obiettivo è scrapare pagine profilo pubbliche con rotazione IP, user-agent rotation e session isolation.

Configurazione Base

import requests
import random
import time
from urllib.parse import quote

# --- Configurazione ProxyHat ---
PROXYHAT_USER = "your_username"
PROXYHAT_PASS = "your_password"
PROXYHAT_GATE = "gate.proxyhat.com"
PROXYHAT_PORT = 8080

def get_proxy_url(country=None, session_id=None):
    """Costruisce l'URL del proxy con geo-targeting e sessione opzionali."""
    username = PROXYHAT_USER
    if country:
        username = f"{username}-country-{country}"
    if session_id:
        username = f"{username}-session-{session_id}"
    return f"http://{username}:{PROXYHAT_PASS}@{PROXYHAT_GATE}:{PROXYHAT_PORT}"

# --- User-Agent Rotation ---
USER_AGENTS = [
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
    "(KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_5) AppleWebKit/605.1.15 "
    "(KHTML, like Gecko) Version/17.4 Safari/605.1.15",
    "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 "
    "(KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:126.0) "
    "Gecko/20100101 Firefox/126.0",
]

def get_headers():
    """Headers realistici per simulare traffico browser."""
    return {
        "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",
        "Connection": "keep-alive",
        "Upgrade-Insecure-Requests": "1",
        "Sec-Fetch-Dest": "document",
        "Sec-Fetch-Mode": "navigate",
        "Sec-Fetch-Site": "none",
    }

Scraping di un Profilo Pubblico con Rotazione IP

def scrape_profile(username, max_retries=3):
    """Scrape i dati base di un profilo Instagram pubblico."""
    url = f"https://www.instagram.com/{username}/"
    
    for attempt in range(max_retries):
        # Ogni tentativo usa un IP residenziale diverso (rotazione per-request)
        proxy = {
            "http": get_proxy_url(country="US"),
            "https": get_proxy_url(country="US"),
        }
        
        try:
            # Crea una nuova sessione per ogni tentativo (session isolation)
            session = requests.Session()
            session.headers.update(get_headers())
            
            # Prima visita la homepage per ottenere cookie (simula navigazione reale)
            session.get(
                "https://www.instagram.com/",
                proxies=proxy,
                timeout=15,
            )
            time.sleep(random.uniform(1.5, 3.5))
            
            # Ora visita il profilo target
            resp = session.get(url, proxies=proxy, timeout=15)
            
            if resp.status_code == 200 and "</html>" in resp.text.lower():
                # Estrai i dati dal JSON embedded nella pagina
                import re, json
                match = re.search(
                    r'window\._sharedData\s*=\s*({.+?});</script>',
                    resp.text
                )
                if match:
                    data = json.loads(match.group(1))
                    user_data = (
                        data
                        .get("entry_data", {})
                        .get("ProfilePage", [{}])[0]
                        .get("graphql", {})
                        .get("user", {})
                    )
                    return {
                        "username": user_data.get("username"),
                        "full_name": user_data.get("full_name"),
                        "bio": user_data.get("biography"),
                        "followers": user_data.get("edge_followed_by", {}).get("count"),
                        "following": user_data.get("edge_follow", {}).get("count"),
                        "posts": user_data.get("edge_owner_to_timeline_media", {}).get("count"),
                        "is_private": user_data.get("is_private"),
                        "is_verified": user_data.get("is_verified"),
                    }
                
            elif resp.status_code == 429:
                wait = (2 ** attempt) + random.uniform(1, 5)
                print(f"Rate limited. Attesa {wait:.1f}s...")
                time.sleep(wait)
                continue
            else:
                print(f"Status {resp.status_code}, tentativo {attempt+1}")
                
        except requests.RequestException as e:
            print(f"Errore: {e}, tentativo {attempt+1}")
            time.sleep(2 ** attempt)
    
    return None

# --- Esempio di utilizzo ---
result = scrape_profile("nasa")
if result:
    print(f"Profilo: {result['username']}")
    print(f"Follower: {result['followers']:,}")
    print(f"Post: {result['posts']:,}")

Scraping Batch con Rate Limiting Autogestito

def scrape_profiles_batch(usernames, requests_per_minute=15):
    """Scrape multipli profili con rate limiting autogestito."""
    delay = 60.0 / requests_per_minute  # ~4s tra richieste
    results = []
    
    for i, username in enumerate(usernames):
        print(f"[{i+1}/{len(usernames)}] Scraping @{username}...")
        data = scrape_profile(username)
        
        if data:
            results.append(data)
            print(f"  ✓ {data['followers']:,} follower")
        else:
            print(f"  ✗ Fallito")
        
        # Rate limiting: non superare 15 req/min
        jitter = random.uniform(0.5, 2.0)
        time.sleep(delay + jitter)
    
    return results

# Esecuzione
profiles = ["nasa", "natgeo", "bbc", "cnn", "spacex"]
data = scrape_profiles_batch(profiles, requests_per_minute=12)

Peculiarità Tecniche di Instagram: Cosa Devi Sapere

L'Endpoint ?__a=1 — Storia e Declino

Per anni, aggiungere ?__a=1 a qualsiasi URL di Instagram restituiva un JSON pulito con tutti i dati della pagina. Era il metodo preferito da ogni scraper. Purtroppo, Instagram ha deprecato e poi rimosso questo endpoint nella sua forma originale. Oggi, ?__a=1 richiede autenticazione e restituisce dati parziali o reindirizza al login.

La lezione: non dipendere da endpoint non documentati. Instagram li rimuove senza preavviso.

Query GraphQL e Header Specifici

Instagram usa GraphQL internamente per caricare i dati. Le query GraphQL richiedono header specifici che fungono da autenticazione implicita:

  • x-ig-app-id: L'ID dell'app Instagram web (tipicamente 936619743392459). Senza questo header, le richieste API vengono rifiutate.
  • x-csrftoken: Un token CSRF che Instagram imposta come cookie. Deve essere incluso negli header delle richieste POST e nelle query GraphQL.
  • x-requested-with: Impostato a XMLHttpRequest per le chiamate AJAX.
# Esempio di chiamata GraphQL con header richiesti
def query_hashtag(tag_name, csrftoken, proxy_dict):
    """Query GraphQL per i post di un hashtag (dati pubblici limitati)."""
    variables = json.dumps({
        "tag_name": tag_name,
        "first": 12,
    })
    params = {
        "query_hash": "f92f56d2f3e0d7f4ec8f7c1d2c6e6b3a",  # hash soggetto a cambiamento
        "variables": variables,
    }
    headers = {
        "x-ig-app-id": "936619743392459",
        "x-csrftoken": csrftoken,
        "x-requested-with": "XMLHttpRequest",
        "User-Agent": random.choice(USER_AGENTS),
    }
    resp = requests.get(
        "https://www.instagram.com/graphql/query/",
        params=params,
        headers=headers,
        proxies=proxy_dict,
        timeout=15,
    )
    return resp.json()

Attenzione: i query hash cambiano frequentemente. Instagram aggiorna questi valori con ogni deploy del frontend. Non hardcodarli — estraili dinamicamente dal JavaScript della pagina o tieni un sistema di fallback.

HTTPS Pinning e Reverse Engineering dell'API Mobile

L'app mobile di Instagram usa SSL pinning, ovvero verifica il certificato del server contro un hash hardcoded. Questo rende il man-in-the-middle dell'API mobile molto più complesso rispetto all'intercettazione del traffico web.

La tendenza nell'ecosistema dello scraping è passata dal parsing HTML all'reverse engineering dell'API mobile. L'app mobile espone endpoint più ricchi e stabili del sito web, ma accedervi richiede:

  • Il bypass del SSL pinning (strumenti come Frida o Objection)
  • La replica esatta della firma delle richieste (header X-IG-Device-ID, X-IG-Android-ID, IG-Connection-Type, ecc.)
  • La gestione del challenge di login per molti endpoint

Questo approccio è significativamente più complesso e si avvicina al confine etico e legale. Per la maggior parte dei casi d'uso di social listening, i dati pubblici accessibili via web sono sufficienti.

Il Shift: Da HTML Scraping a Mobile API

Il panorama dello scraping di Instagram è cambiato radicalmente negli ultimi anni:

  1. 2018–2020: HTML scraping con ?__a=1, semplice e affidabile.
  2. 2020–2022: Deprecation di ?__a=1, shift verso GraphQL query scraping.
  3. 2022–2024: Endpoints GraphQL sempre più protetti, necessità di proxy residenziali obbligatoria.
  4. 2024+: Mobile API reverse engineering come approccio dominante, ma con barriere all'ingresso molto alte.

La strategia consigliata per il 2025: usa il parsing HTML del sito web per dati di base (profilo, bio, follower count) e integra con l'API ufficiale di Instagram per dati più dettagliati quando il budget lo permette.

Esempio Node.js: Profilo con Axios e Proxy Residenziali

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

const PROXYHAT_USER = 'your_username';
const PROXYHAT_PASS = 'your_password';

// Proxy residenziale HTTP con geo-targeting Italia
const proxyUrl = `http://${PROXYHAT_USER}-country-IT:${PROXYHAT_PASS}@gate.proxyhat.com:8080`;

const USER_AGENTS = [
  'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36',
  'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Safari/605.1.15',
];

async function scrapeInstagramProfile(username) {
  const url = `https://www.instagram.com/${username}/`;
  
  try {
    // Prima richiesta alla homepage per cookie
    await axios.get('https://www.instagram.com/', {
      proxy: false,
      httpsAgent: new (require('https-proxy-agent'))(proxyUrl),
      headers: {
        'User-Agent': USER_AGENTS[Math.floor(Math.random() * USER_AGENTS.length)],
        'Accept-Language': 'it-IT,it;q=0.9,en;q=0.8',
      },
      timeout: 15000,
    });
    
    await new Promise(r => setTimeout(r, 2000 + Math.random() * 2000));
    
    // Richiesta al profilo
    const resp = await axios.get(url, {
      proxy: false,
      httpsAgent: new (require('https-proxy-agent'))(proxyUrl),
      headers: {
        'User-Agent': USER_AGENTS[Math.floor(Math.random() * USER_AGENTS.length)],
        'Accept': 'text/html,application/xhtml+xml',
        'Accept-Language': 'it-IT,it;q=0.9',
      },
      timeout: 15000,
    });
    
    // Parsing base del profilo
    const followerMatch = resp.data.match(/"edge_followed_by":\{"count":(\d+)\}/);
    const postMatch = resp.data.match(/"edge_owner_to_timeline_media":\{"count":(\d+)\}/);
    
    return {
      username,
      followers: followerMatch ? parseInt(followerMatch[1]) : null,
      posts: postMatch ? parseInt(postMatch[1]) : null,
    };
  } catch (err) {
    console.error(`Errore per @${username}:`, err.message);
    return null;
  }
}

scrapeInstagramProfile('nasa').then(console.log);

Strategie Anti-Rilevamento Oltre i Proxy

I proxy residenziali risolvono il problema dell'IP reputation, ma non sono sufficienti da soli. Ecco le pratiche essenziali per evitare il rilevamento:

  • Session isolation: Non riutilizzare mai cookie o sessioni tra richieste verso target diversi. Ogni richiesta o piccolo batch deve avere la sua sessione pulita.
  • Simula il comportamento umano: Visita la homepage prima del profilo target. Aggiungi delay realistici (2–5 secondi tra richieste). Non fare burst di 50 richieste in 2 secondi.
  • User-Agent rotation: Mantieni un pool di almeno 10–20 user-agent realistici e aggiornati. Assicurati che gli altri header siano coerenti con l'UA scelto.
  • Geo-consistenza: Se scrapi profili italiani, usa proxy italiani. Un utente con UA italiano ma IP americano è sospetto.
  • Rate limiting autogestito: Limitati a 10–20 richieste per minuto per IP. È meglio andare piano e non essere bloccati che andare veloci e dover cambiare IP ogni 5 minuti.

Quando Usare le API Ufficiali Invece dello Scraping

Meta offre l'Instagram Graph API per i business account. Se il tuo caso d'uso lo permette, è sempre preferibile:

  • Stabilità: Le API ufficiali non cambiano senza preavviso (almeno, non senza un deprecation cycle).
  • Legalità: L'accesso tramite API ufficiali è esplicitamente autorizzato dai ToS.
  • Dati strutturati: JSON pulito, documentazione, pagination nativa.

I limiti principali dell'API ufficiale:

  • Richiede un Facebook Business account e un'app approvata.
  • Può accedere solo ai dati dei business/creator account (non dei profili personali).
  • Rate limits stringenti ma prevedibili (200 chiamate/ora per utente).
  • Non permette l'accesso ai dati di hashtag o location nella stessa misura dello scraping web.

La regola d'oro: se l'API ufficiale copre il tuo caso d'uso, usala. Lo scraping è giustificato solo quando l'API non offre i dati necessari (es. hashtag exploration, profili non-business, dati storici).

Scraping Etico: Linee Guida Pratiche

Lo scraping di dati pubblici è una zona grigia legale. Ecco come restare dalla parte giusta:

  1. Rispetta robots.txt: Instagram permette l'accesso limitato ai bot. Controlla sempre https://www.instagram.com/robots.txt prima di iniziare.
  2. Rate-limitati da solo: Non superare 10–15 richieste al minuto per IP. Se devi scrapare migliaia di profili, usa un pool grande di proxy e distribuisci le richieste nel tempo.
  3. Mai login automation: Non tentare di automatizzare il login, creare account finti, o usare credenziali rubate. Questo viola i ToS e potenzialmente leggi come il CFAA.
  4. Dati pubblici solo: Se un profilo è privato, non tentare di accedervi. Se un dato richiede login, non è pubblico.
  5. Non archiviare PII: Riduci al minimo i dati personali che memorizzi. Se hai bisogno di username per analytics, non hai bisogno di email o numeri di telefono.
  6. GDPR compliance: Se processi dati di utenti europei, assicurati di avere una base legale e rispetta i diritti di cancellazione.
  7. Usa i dati responsabilmente: Non costruire database per stalking, harassment, o manipolazione. Lo scraping per social listening, ricerca accademica, e analytics di mercato è generalmente accettato.

Punti Chiave

  • Instagram è una delle piattaforme più difficili da scrapare: rate limiting, login wall, device fingerprinting e IP reputation rendono l'approccio naïve impraticabile.
  • I proxy residenziali sono essenziali: gli IP datacenter vengono flaggati quasi istantaneamente da Instagram.
  • I dati accessibili senza login includono profili pubblici, hashtag, località e alcuni metadati dei Reels.
  • L'endpoint ?__a=1 è deprecato; il parsing HTML e le query GraphQL sono gli approcci attuali, ma richiedono header specifici (x-ig-app-id, x-csrftoken).
  • Session isolation, user-agent rotation, geo-consistenza e rate limiting autogestito sono indispensabili quanto i proxy stessi.
  • Se l'API ufficiale copre il tuo caso d'uso, usala. Lo scraping è l'ultima risorsa, non la prima.
  • Scraping etico: rispetta robots.txt, non fare login automation, accedi solo a dati pubblici, e conformati al GDPR.

Se stai costruendo una pipeline di social listening e hai bisogno di proxy residenziali affidabili per Instagram, dai un'occhiata alla nostra pagina prezzi o esplora le location disponibili per il geo-targeting. Per casi d'uso più generali di web scraping, consulta la nostra guida su web scraping con proxy residenziali.

Pronto per iniziare?

Accedi a oltre 50M di IP residenziali in oltre 148 paesi con filtraggio AI.

Vedi i prezziProxy residenziali
← Torna al Blog