Raccogliere migliaia di immagini per addestrare un modello di visione artificiale è un compito che quasi sempre si scontra con due problemi: i blocchi da parte della fonte dei dati (Google Immagini, Pinterest, marketplace, siti di stock) e un consumo di traffico inaspettatamente elevato. Un singolo IP viene rapidamente bloccato, e il costo del traffico proxy può superare il costo stesso della marcatura del dataset. In questo articolo analizzeremo come progettare un parser di immagini che non perda dati a causa dei blocchi e come calcolare in anticipo il budget per il traffico.
Perché usare proxy nella raccolta di immagini per CV
I dataset per la visione artificiale raramente vengono raccolti da una sola fonte. Di solito si tratta di una combinazione di risultati di ricerca di immagini, schede prodotto sui marketplace, stock fotografici, social media e API aperti. Quasi tutte queste fonti impongono limiti sul numero di richieste da un singolo IP: Google Immagini mostra un captcha già dopo un paio di centinaia di richieste consecutive, Pinterest blocca temporaneamente l'accesso all'API, e Wildberries e Ozon restituiscono schede vuote o reindirizzano a una pagina di verifica in caso di attività sospette.
Senza proxy, si raggiunge un limite di alcune centinaia o migliaia di immagini al giorno da un solo indirizzo. Se hai bisogno di un dataset di 50.000–500.000 immagini (e questo è quanto di solito è richiesto per le reti convoluzionali e i trasformatori in compiti di classificazione e rilevamento), senza un pool distribuito di indirizzi IP, raccoglierlo in un tempo ragionevole è fisicamente impossibile: o ti imbatterai costantemente in blocchi, o otterrai un campione fortemente distorto a causa delle limitazioni di tempo e geolocalizzazione.
La seconda ragione è la diversità geografica dei dati. Se il modello deve riconoscere oggetti in modo uniforme indipendentemente dalla regione di ripresa (ad esempio, segnali stradali in diversi paesi o prodotti su marketplace locali), il parser deve connettersi con IP di diversi paesi. Un singolo data center con un solo IP di uscita non fornirà tale varietà.
Quanto traffico viene realmente speso per il dataset
Il consumo di traffico nella raccolta di immagini è spesso sottovalutato. Non si tratta solo del peso delle immagini stesse: le pagine HTML, i bundle JS, le anteprime, i pixel di tracciamento e i CSS passano anch'essi attraverso i proxy, se il parser non è configurato in modo specifico per gli URL necessari.
| Tipo di contenuto | Peso medio | Traffico per 10.000 unità |
|---|---|---|
| Anteprima/thumbnail (150–300 px) | 15–40 KB | ~250 MB |
| Foto di media risoluzione (800–1200 px) | 80–200 KB | ~1,4 GB |
| Foto ad alta risoluzione (2000+ px) | 400 KB – 1,5 MB | ~9,5 GB |
| Pagina HTML della scheda prodotto (senza immagini) | 150–400 KB | ~2,7 GB |
In pratica, se raccogli 100.000 immagini di media risoluzione insieme alle pagine delle schede (per estrarre la marcatura — categorie, box, attributi), il consumo totale di traffico di solito rientra nell'intervallo di 25–45 GB. Se hai bisogno di immagini ad alta risoluzione per compiti di rilevamento di piccoli oggetti, calcola tra 80–150 GB per le stesse 100.000 unità. Questi numeri dovrebbero essere considerati nella scelta del piano proxy in anticipo, e non a posteriori.
Datacenter, residenziali e mobili: cosa scegliere
Per la raccolta di immagini, sono importanti tre parametri: velocità, costo del traffico e resistenza ai blocchi. Questi parametri variano notevolmente tra i diversi tipi di proxy.
| Parametro | Datacenter | Residenziale | Mobile |
|---|---|---|---|
| Velocità | Alta | Media | Bassa-media |
| Resistenza ai ban | Bassa | Alta | Molto alta |
| Costo del traffico | Basso | Media-alta | Alta |
| Adatto per | API aperti, stock senza protezione rigorosa | Google Immagini, Pinterest, marketplace | Social media con protezione anti-bot rigorosa |
Per la maggior parte delle attività di raccolta di immagini, il miglior equilibrio è fornito dai proxy residenziali — gli IP appaiono come utenti normali, quindi i sistemi anti-bot dei marketplace e dei motori di ricerca di immagini attivano meno frequentemente il captcha. Se la fonte è un stock fotografico aperto o un'API senza limiti rigorosi, è più conveniente utilizzare proxy di datacenter — sono più veloci e consentono di gestire grandi volumi di traffico a un costo inferiore per GB. E per la raccolta di immagini da applicazioni mobili o social media con protezione anti-bot avanzata, i proxy mobili sono più adatti — utilizzano IP reali degli operatori di telecomunicazioni e raramente vengono bloccati.
Parser di base su requests con rotazione dei proxy
Per iniziare, un semplice script sincrono su requests con rotazione dei proxy per ogni richiesta o dopo un numero prestabilito di richieste andrà bene. Di seguito è riportato un esempio di download di immagini da un elenco di URL con verifica del tipo di contenuto e limitazione della dimensione del file, per evitare di scaricare accidentalmente un file pesante di un formato non corretto.
import requests
import itertools
import hashlib
import os
PROXIES = [
"http://user:[email protected]:10000",
"http://user:[email protected]:10000",
"http://user:[email protected]:10000",
]
proxy_cycle = itertools.cycle(PROXIES)
HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"Accept": "image/avif,image/webp,image/*,*/*;q=0.8",
}
def download_image(url, save_dir="dataset", max_size_mb=10, timeout=15):
proxy = next(proxy_cycle)
proxies = {"http": proxy, "https": proxy}
try:
resp = requests.get(url, headers=HEADERS, proxies=proxies, timeout=timeout, stream=True)
resp.raise_for_status()
content_type = resp.headers.get("Content-Type", "")
if "image" not in content_type:
return None
content_length = int(resp.headers.get("Content-Length", 0))
if content_length > max_size_mb * 1024 * 1024:
return None
file_hash = hashlib.md5(url.encode()).hexdigest()
ext = content_type.split("/")[-1].split(";")[0]
filepath = os.path.join(save_dir, f"{file_hash}.{ext}")
os.makedirs(save_dir, exist_ok=True)
with open(filepath, "wb") as f:
for chunk in resp.iter_content(chunk_size=8192):
f.write(chunk)
return filepath
except requests.exceptions.RequestException as e:
print(f"Errore durante {url} tramite {proxy}: {e}")
return None
Il punto chiave è controllare Content-Length prima di scaricare il corpo della risposta. Questo risparmia traffico, escludendo file troppo pesanti prima del caricamento completo. La rotazione dei proxy tramite itertools.cycle distribuisce uniformemente il carico tra gli IP, riducendo il rischio di ban di un indirizzo specifico.
Raccolta asincrona tramite aiohttp
Uno script sincrono su requests è adatto per alcune migliaia di immagini, ma per dataset a partire da 50.000 file è necessaria l'asincronia — altrimenti la raccolta si allungherà per giorni. aiohttp insieme a un semaforo per limitare il parallelismo è una soluzione efficace per decine di migliaia di attività simultanee senza sovraccaricare il pool di proxy.
import asyncio
import aiohttp
import itertools
PROXIES = [
"http://user:[email protected]:10000",
"http://user:[email protected]:10000",
]
proxy_cycle = itertools.cycle(PROXIES)
sem = asyncio.Semaphore(50) # limitazione delle richieste parallele
async def fetch_image(session, url, idx):
proxy = next(proxy_cycle)
async with sem:
try:
async with session.get(url, proxy=proxy, timeout=15) as resp:
if resp.status != 200:
return
content_type = resp.headers.get("Content-Type", "")
if "image" not in content_type:
return
data = await resp.read()
with open(f"dataset/{idx}.jpg", "wb") as f:
f.write(data)
except Exception as e:
print(f"Errore {url}: {e}")
async def main(urls):
async with aiohttp.ClientSession() as session:
tasks = [fetch_image(session, url, i) for i, url in enumerate(urls)]
await asyncio.gather(*tasks)
# asyncio.run(main(url_list))
Un semaforo con 50 connessioni simultanee è un punto di partenza. Se i proxy residenziali reggono il carico senza un aumento degli errori 429 e dei timeout, puoi aumentare gradualmente fino a 100–150. L'importante è monitorare la percentuale di richieste non riuscite: se cresce oltre il 5–7%, è meglio ridurre il parallelismo piuttosto che aumentare indefinitamente il pool di proxy.
Scrapy + middleware proxy per raccolta su larga scala
Per la raccolta di dataset in produzione con coda integrata, retry e deduplicazione degli URL, è più comodo utilizzare Scrapy con ImagesPipeline. Essa scarica automaticamente le immagini, verifica la loro validità tramite PIL e può generare anteprime di diverse dimensioni.
# settings.py
ITEM_PIPELINES = {
'scrapy.pipelines.images.ImagesPipeline': 1,
}
IMAGES_STORE = 'dataset_images'
IMAGES_MIN_HEIGHT = 200
IMAGES_MIN_WIDTH = 200
DOWNLOADER_MIDDLEWARES = {
'myproject.middlewares.ProxyRotationMiddleware': 350,
}
CONCURRENT_REQUESTS = 32
DOWNLOAD_DELAY = 0.5
RETRY_TIMES = 3
# middlewares.py
import random
class ProxyRotationMiddleware:
PROXIES = [
"http://user:[email protected]:10000",
"http://user:[email protected]:10000",
"http://user:[email protected]:10000",
]
def process_request(self, request, spider):
request.meta['proxy'] = random.choice(self.PROXIES)
I parametri IMAGES_MIN_HEIGHT e IMAGES_MIN_WIDTH escludono automaticamente icone piccole e sprite che inquinano il dataset e sprecano traffico. DOWNLOAD_DELAY di 0.5 secondi riduce la frequenza delle richieste da un singolo proxy, diminuendo la percentuale di captcha su fonti con monitoraggio anti-frode rigoroso come Pinterest.
Come ridurre il consumo di traffico
Il traffico tramite proxy è solitamente tariffato in base al volume, quindi qualsiasi ottimizzazione risparmia direttamente il budget. Ecco alcune tecniche efficaci:
- Richiedi anteprime, non originali, se per l'addestramento è sufficiente una risoluzione di 512–1024 px — la maggior parte delle fonti restituisce l'immagine della dimensione richiesta tramite un URL separato o un parametro di query.
- Filtra per Content-Length prima di scaricare il corpo — una richiesta HEAD o la lettura delle intestazioni di un GET in streaming consente di escludere file troppo pesanti senza sprecare traffico per il loro caricamento.
- Disabilita il caricamento delle risorse accessorie, se il parser funziona tramite un browser headless: blocca CSS, font e script di tracciamento, lasciando solo HTML e le immagini necessarie.
- Deduplica gli URL prima della richiesta, e non dopo il download — immagini identiche su diverse schede prodotto (ad esempio, su Wildberries) non devono essere scaricate nuovamente.
- Utilizza una cache locale delle risposte durante i passaggi ripetuti del parser durante il debug, per non sprecare traffico tramite proxy ad ogni esecuzione di test del codice.
Errori comuni, 403 e rate limit
Durante la raccolta di immagini in grandi volumi, il parser si scontrerà inevitabilmente con risposte 403, 429 e reindirizzamenti temporanei a pagine di verifica. Una corretta gestione di questi codici risparmia sia traffico che il pool di proxy.
| Codice/sintomo | Causa | Cosa fare |
|---|---|---|
| 403 Forbidden | IP nella blacklist della fonte | Rotazione dei proxy, cambio User-Agent |
| 429 Too Many Requests | Superato il limite di richieste al minuto | Aumentare DOWNLOAD_DELAY, ridurre il parallelismo |
| Reindirizzamento a captcha | L'analisi comportamentale ha sospettato un bot | Passare a IP residenziali/mobili |
| Content-Length vuoto | Risposta chunked o placeholder invece dell'immagine | Controllare la dimensione reale dei byte scaricati |
È utile registrare separatamente la percentuale di richieste riuscite per ogni proxy. Se un IP specifico continua a restituire 403 più frequentemente degli altri, escludilo automaticamente dalla rotazione, senza aspettare che influenzi negativamente le statistiche complessive del blocco su tutto il pool.
Calcolo del budget per il traffico proxy
Prima di avviare la raccolta, è utile stimare il volume di traffico in anticipo, piuttosto che calcolarlo a posteriori. La formula è semplice: numero di immagini × peso medio del file × coefficiente di spese generali (HTML, intestazioni, richieste ripetute a causa di errori — di solito 1.3–1.6).
Esempio di calcolo per un dataset di 200.000 immagini di media risoluzione (~150 KB) con parsing delle schede prodotto: 200.000 × 150 KB = 30 GB di peso netto delle immagini. Tenendo conto delle pagine HTML e delle richieste ripetute a causa di blocchi temporanei (coefficiente 1.5), il consumo totale è di circa 45 GB. Se alcune fonti richiedono IP mobili a causa della protezione anti-bot, considera questo nel pool di proxy separatamente, poiché il traffico su IP mobili di solito costa di più rispetto a quello residenziale o di datacenter.
Una buona pratica è raccogliere un lotto pilota di 1.000–2.000 immagini, misurare il reale consumo di traffico e la percentuale di errori, e poi estrapolare sul volume totale pianificato del dataset. Questo è più preciso di qualsiasi calcolo teorico, poiché tiene conto delle specificità della fonte specifica.
Conclusione
Raccogliere immagini per addestrare modelli di visione artificiale è un compito che si colloca all'incrocio tra ingegneria dei dati e gestione efficace dell'infrastruttura di rete. Una corretta architettura del parser (asincronia, filtraggio per Content-Length, deduplicazione degli URL) risparmia traffico, mentre una scelta ponderata dei proxy determina se raccoglierai il volume necessario del dataset in giorni o in settimane di blocchi continui.
Se le fonti di dati utilizzano protezioni anti-bot avanzate — motori di ricerca di immagini, marketplace, social media — inizia con proxy residenziali: offrono il miglior equilibrio tra resistenza ai ban e costo del traffico. Per API aperte e stock senza limiti rigorosi, ha senso risparmiare su proxy di datacenter, mentre per le applicazioni mobili più protette, è consigliabile utilizzare proxy mobili per la parte finale, la più complessa della raccolta.