Da quando Elon Musk ha acquisito Twitter (ora X), l'accesso ai dati della piattaforma è cambiato radicalmente. L'API gratuita è stata eliminata, i costi sono aumentati drasticamente e molti team di sviluppo si trovano costretti a cercare alternative. Se stai costruendo un dashboard di monitoraggio sociale o un sistema di analisi del sentiment, sai già quanto sia frustrante questo nuovo panorama.
La buona notizia è che gran parte dei dati pubblici di X rimane accessibile tramite web scraping, ma l'azienda ha implementato misure anti-scraping aggressive. In questa guida ti mostrerò come utilizzare proxy residenziali per estrarre dati pubblici da X in modo affidabile, rispettando i limiti legali e le best practice tecniche.
Il panorama post-restrizioni API di X
Prima del 2023, l'API di Twitter offriva un tier gratuito generoso che permetteva agli sviluppatori di accedere a tweet, profili e funzionalità di ricerca senza costi significativi. Quel mondo non esiste più.
Le attuali opzioni API a pagamento
X ora offre tre livelli principali di accesso API:
| Tier | Costo mensile | Limiti tweet/mese | Limitazioni |
|---|---|---|---|
| Basic | $100 | 10.000 | Solo endpoint post/lookup, niente search |
| Pro | $5.000 | 1.000.000 | Search incluso, limiti severi |
| Enterprise | Da $42.000 | Personalizzato | Accesso completo, supporto dedicato |
Per un team che vuole monitorare trend, analizzare sentiment o tracciare menzioni di brand, questi costi sono spesso proibitivi. Un progetto che necessita di 50 milioni di tweet al mese dovrebbe spendere oltre $200.000 annualmente solo per l'accesso API.
La migrazione verso il web scraping
È comprensibile che molti sviluppatori stiano tornando al web scraping. X è una Single Page Application (SPA) che carica dinamicamente i contenuti tramite chiamate GraphQL interne. Questi endpoint non sono documentati, ma i payload JSON sono completamente accessibili se sai dove guardare.
Tuttavia, X ha implementato diverse contromisure:
- Rate limiting aggressivo basato su IP
- Verifiche browser fingerprint avanzate
- Blocchi automatici per range IP datacenter
- Challenge CAPTCHA frequenti per sessioni anonime
Ecco perché i proxy residenziali sono diventati essenziali per chiunque voglia scrapare X in modo affidabile.
Cosa è accessibile pubblicamente su X
Non tutti i dati di X sono uguali. Prima di costruire il tuo scraper, devi capire cosa puoi ottenere senza autenticazione e cosa richiede un account loggato.
Dati accessibili senza login
Tramite la versione web pubblica, puoi accedere a:
- Profili utente: nome, bio, follower count, following count, data creazione account
- Tweet pubblici: testo, timestamp, like count, retweet count, reply count, media attachments
- Thread e risposte: conversazioni complete per tweet specifici
- Trending topics: argomenti di tendenza per geolocalizzazione
- Risultati di ricerca: query per keyword, hashtag, mention (con limiti)
Dati che richiedono login
Alcune informazioni sono visibili solo agli utenti autenticati:
- Tweet da account protetti/private (che non dovresti mai tentare di scrapare)
- Like dettagliati (chi ha messo like su un tweet)
- Lista follower/following completa (X mostra solo un sottoinsieme)
- Bookmark e liste private
- Community e conversazioni in gruppi
Importante: Tentare di bypassare questi limiti con scraping viola i Termini di Servizio di X e può esporti a rischi legali. Questa guida si concentra esclusivamente sull'accesso a dati genuinamente pubblici.
Perché servono proxy residenziali per X
X ha uno dei sistemi anti-bot più sofisticati del web. Se provi a scrapare con il tuo IP domestico o, peggio, con IP datacenter, incontrerai blocchi quasi immediati.
Il problema degli IP datacenter
Gli IP datacenter sono facili da identificare perché appartengono a range ASN dedicati a provider cloud (AWS, Google Cloud, DigitalOcean). X mantiene liste di questi range e applica:
- Rate limit più severi (spesso 10-20 richieste prima del blocco)
- Challenge CAPTCHA obbligatorie
- Blocchi permanenti per pattern sospetti
Ho visto scraper basati su datacenter proxy fallire dopo appena 50 richieste, mentre setup con proxy residenziali hanno completato milioni di richieste senza interruzioni.
I vantaggi dei proxy residenziali
I proxy residenziali utilizzano IP assegnati a vere connessioni domestiche (ISP). Per X, il traffico appare come proveniente da utenti reali:
- Fingerprint naturale: ASN di provider ISP locali, non cloud provider
- Rotazione automatica: ogni richiesta può usare un IP diverso
- Distribuzione geografica: puoi simulare traffico da qualsiasi paese
- Sticky sessions: mantenere lo stesso IP per sessioni complete
Per lo scraping di X, i proxy residenziali rotanti sono la scelta ottimale. Ti permettono di distribuire le richieste su migliaia di IP diversi, rendendo quasi impossibile per X identificare pattern di scraping.
Quando usare proxy mobile
Per casi d'uso particolarmente sensibili (alto volume, target protetti), i proxy mobile offrono un livello aggiuntivo di affidabilità. X tratta il traffico mobile con meno sospetto perché:
- Gli IP mobile sono condivisi tra migliaia di utenti NAT
- Il rate limiting è meno aggressivo per reti mobile
- Le challenge CAPTCHA sono meno frequenti
Tuttavia, i proxy mobile sono più costosi. Per la maggior parte dei progetti, i residenziali offrono un ottimo bilanciamento tra costo e performance.
Esempio pratico: Python + Playwright con proxy residenziali
Vediamo come implementare uno scraper funzionale per X usando Playwright e proxy residenziali. Playwright è preferibile a Selenium perché gestisce meglio le SPA moderne e ha un fingerprint più difficile da rilevare.
Setup iniziale
Installa le dipendenze necessarie:
pip install playwright asyncio
playwright install chromiumScraper base con proxy rotante
Ecco un esempio completo che estrae tweet da un profilo pubblico:
import asyncio
from playwright.async_api import async_playwright
import json
PROXY_HOST = "gate.proxyhat.com"
PROXY_PORT = "8080"
PROXY_USER = "your_username"
PROXY_PASS = "your_password"
def get_proxy_url(country=None):
"""Costruisce URL proxy con opzionale geo-targeting"""
username = PROXY_USER
if country:
username = f"{PROXY_USER}-country-{country}"
return f"http://{username}:{PROXY_PASS}@{PROXY_HOST}:{PROXY_PORT}"
async def scrape_profile(username, max_tweets=50):
"""Scrape tweet pubblici da un profilo X"""
async with async_playwright() as p:
# Configura browser con proxy residenziale
proxy_url = get_proxy_url(country="US")
browser = await p.chromium.launch(
proxy={"server": proxy_url},
headless=True
)
context = await browser.new_context(
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
viewport={"width": 1920, "height": 1080}
)
page = await context.new_page()
# Naviga al profilo
url = f"https://x.com/{username}"
await page.goto(url, wait_until="networkidle")
# Attendi caricamento tweet
await page.wait_for_selector('[data-testid="tweet"]', timeout=15000)
tweets = []
# Scroll e estrai tweet
while len(tweets) < max_tweets:
# Estrai tweet visibili
tweet_elements = await page.query_selector_all('[data-testid="tweet"]')
for tweet_el in tweet_elements:
if len(tweets) >= max_tweets:
break
try:
tweet_data = await extract_tweet_data(tweet_el)
if tweet_data and tweet_data not in tweets:
tweets.append(tweet_data)
except Exception as e:
continue
# Scroll down per caricare altri tweet
await page.evaluate("window.scrollBy(0, 800)")
await asyncio.sleep(2)
await browser.close()
return tweets
async def extract_tweet_data(tweet_element):
"""Estrae dati strutturati da un elemento tweet"""
try:
text_el = await tweet_element.query_selector('[data-testid="tweetText"]')
text = await text_el.inner_text() if text_el else ""
time_el = await tweet_element.query_selector('time')
timestamp = await time_el.get_attribute('datetime') if time_el else None
link_el = await tweet_element.query_selector('a[href*="/status/"]')
tweet_id = None
if link_el:
href = await link_el.get_attribute('href')
tweet_id = href.split("/status/")[-1].split("?")[0]
return {
"id": tweet_id,
"text": text,
"timestamp": timestamp
}
except:
return None
# Esecuzione
async def main():
tweets = await scrape_profile("elonmusk", max_tweets=30)
print(json.dumps(tweets, indent=2, ensure_ascii=False))
asyncio.run(main())Intercettare i payload GraphQL
X carica i dati tramite chiamate GraphQL interne. Puoi intercettare questi payload per ottenere dati strutturati senza parsing HTML:
async def scrape_with_graphql_interception(username):
"""Scrape intercettando le risposte GraphQL di X"""
async with async_playwright() as p:
proxy_url = f"http://{PROXY_USER}:{PROXY_PASS}@{PROXY_HOST}:{PROXY_PORT}"
browser = await p.chromium.launch(
proxy={"server": proxy_url},
headless=True
)
context = await browser.new_context()
page = await context.new_page()
# Intercetta risposte GraphQL
graphql_data = []
async def handle_response(response):
if "graphql" in response.url and response.ok:
try:
data = await response.json()
graphql_data.append(data)
except:
pass
page.on("response", handle_response)
# Naviga e scrolla
await page.goto(f"https://x.com/{username}")
await page.wait_for_selector('[data-testid="tweet"]')
for _ in range(5):
await page.evaluate("window.scrollBy(0, 1000)")
await asyncio.sleep(2)
await browser.close()
return graphql_dataQuesto approccio è più robusto perché ottieni i dati grezzi che X usa internamente, inclusi conteggi precisi, metadati e informazioni non visibili nel rendering HTML.
Esempio con Node.js
Se preferisci JavaScript, ecco un setup equivalente:
const { chromium } = require('playwright');
const PROXY_HOST = 'gate.proxyhat.com';
const PROXY_PORT = '8080';
const PROXY_USER = 'your_username';
const PROXY_PASS = 'your_password';
async function scrapeTwitterProfile(username) {
const proxyUrl = `http://${PROXY_USER}:${PROXY_PASS}@${PROXY_HOST}:${PROXY_PORT}`;
const browser = await chromium.launch({
proxy: { server: proxyUrl },
headless: true
});
const context = await browser.newContext({
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
});
const page = await context.newPage();
const tweets = [];
page.on('response', async (response) => {
if (response.url().includes('graphql') && response.ok()) {
try {
const data = await response.json();
tweets.push(data);
} catch (e) {}
}
});
await page.goto(`https://x.com/${username}`);
await page.waitForSelector('[data-testid="tweet"]');
for (let i = 0; i < 5; i++) {
await page.evaluate(() => window.scrollBy(0, 1000));
await new Promise(r => setTimeout(r, 2000));
}
await browser.close();
return tweets;
}
scrapeTwitterProfile('elonmusk').then(console.log);Gestione dei rate limit
X applica rate limiting su diversi livelli. Capire questi meccanismi è cruciale per costruire scraper affidabili.
Tipi di rate limit su X
- IP-level throttling: limite di richieste per IP in una finestra temporale (tipicamente 15 minuti)
- Account-level throttling: limite per utente autenticato (se usi account loggati)
- Session-level detection: pattern comportamentali che identificano bot
Risposta ai codici 429
Quando X rileva troppe richieste, restituisce HTTP 429 (Too Many Requests). Ecco come gestirlo:
import asyncio
import random
from playwright.async_api import async_playwright
async def scrape_with_retry(url, max_retries=3):
"""Scrape con gestione automatica dei rate limit"""
for attempt in range(max_retries):
try:
async with async_playwright() as p:
# Usa un nuovo IP per ogni tentativo
proxy_url = f"http://{PROXY_USER}-session-{random.randint(1000,9999)}:{PROXY_PASS}@gate.proxyhat.com:8080"
browser = await p.chromium.launch(
proxy={"server": proxy_url},
headless=True
)
page = await browser.new_page()
response = await page.goto(url)
if response.status == 429:
print(f"Rate limited, tentativo {attempt + 1}")
await browser.close()
# Backoff esponenziale
wait_time = (2 ** attempt) * 30
await asyncio.sleep(wait_time)
continue
if response.ok:
content = await page.content()
await browser.close()
return content
except Exception as e:
print(f"Errore: {e}")
await asyncio.sleep(60)
return NoneStrategie per evitare il rate limiting
La prevenzione è meglio della cura. Segui queste best practice:
- Limita la velocità: non superare 1-2 richieste al secondo per IP
- Randomizza i delay: aggiungi jitter ai tempi di attesa
- Rotazione IP intelligente: cambia IP ogni 50-100 richieste
- Simula comportamento umano: scroll, pause, movimenti mouse
- Usa sticky sessions: mantieni lo stesso IP per sessioni complete
Sliding-window detection
X usa finestre temporali scorrevoli per il rate limiting. Se fai 100 richieste in 1 minuto, sarai bloccato anche se poi aspetti. La chiave è distribuire le richieste uniformemente nel tempo:
import time
import random
def rate_limited_request(url, requests_per_minute=30):
"""Esegue richieste rispettando i limiti di X"""
min_interval = 60.0 / requests_per_minute
last_request = [0]
def make_request():
elapsed = time.time() - last_request[0]
wait_time = max(0, min_interval - elapsed)
# Aggiungi jitter random (±20%)
jitter = random.uniform(-0.2, 0.2) * min_interval
time.sleep(wait_time + jitter)
last_request[0] = time.time()
# Esegui la richiesta...
return f"Request to {url}"
return make_requestAspetti legali e considerazioni etiche
Lo scraping di X solleva questioni legali significative che devi comprendere prima di procedere.
Il quadro legale
Nel 2023, X ha intentato diverse cause legali contro aziende di scraping, sostenendo violazioni del CFAA (Computer Fraud and Abuse Act) e dei Termini di Servizio. Tuttavia, le sentenze recenti hanno creato un quadro più sfumato:
- hiQ Labs v. LinkedIn: la Corte Suprema ha stabilito che lo scraping di dati pubblici non viola il CFAA
- Meta v. Bright Data: Meta ha ritirato la causa contro Bright Data per scraping di dati pubblici
- Casi X: ancora in corso, ma i precedenti favoriscono l'accesso a dati pubblici
Nota importante: Questa analisi non costituisce consulenza legale. Consulta un avvocato specializzato prima di avviare progetti di scraping su larga scala.
Termini di Servizio di X
I ToS di X proibiscono esplicitamente:
- Lo scraping senza "autorizzazione espressa scritta"
- L'accesso a dati per creare prodotti concorrenti
- La vendita di dati scrapati a terze parti
- L'uso di bot per interazioni automate (like, follow, tweet)
Tuttavia, l'applicabilità di questi termini è dibattuta legalmente, specialmente per dati genuinamente pubblici.
GDPR e protezione dati (UE)
Se operi nell'UE, devi considerare anche:
- I dati degli utenti europei sono protetti dal GDPR
- Lo scraping di dati personali richiede una base legale
- L'archiviazione e il processing devono rispettare i principi di minimizzazione
Quando usare l'API invece dello scraping
Non sempre lo scraping è la scelta giusta. Considera l'API ufficiale se:
- Hai un budget adeguato e vuoi stabilità a lungo termine
- Necessiti di dati in tempo reale con latenza minima
- Il tuo caso d'uso è supportato dall'API (post, lookup, engagement)
- Vuoi evitare rischi legali e TOS violation
Lo scraping ha senso quando:
- L'API è troppo costosa per il tuo budget
- La funzionalità che ti serve non è disponibile via API
- Stai facendo ricerca accademica o giornalistica
- Hai bisogno di dati storici non disponibili via API
Key Takeaways
- L'API di X è diventata proibitivamente costosa, spingendo molti team verso il web scraping.
- I dati pubblici (profili, tweet, search) sono accessibili senza login, ma X applica anti-bot aggressivi.
- I proxy residenziali sono essenziali: gli IP datacenter vengono bloccati quasi immediatamente.
- Intercettare i payload GraphQL è più affidabile del parsing HTML.
- La gestione dei rate limit (429) richiede backoff, rotazione IP e rate limiting proattivo.
- Il quadro legale è complesso: consulta un avvocato per progetti commerciali.
- Per volumi alti o casi d'uso business-critical, l'API a pagamento può essere più conveniente dei costi di sviluppo e manutenzione dello scraping.
Se stai costruendo un sistema di monitoraggio sociale e hai bisogno di proxy residenziali affidabili, dai un'occhiata alle soluzioni ProxyHat. Con un pool globale di IP residenziali e supporto per geo-targeting preciso, puoi scrapare X in modo stabile senza preoccuparti di blocchi IP.






