Das Sammeln von Tausenden von Bildern zum Trainieren eines Computer Vision-Modells ist eine Aufgabe, die fast immer auf zwei Probleme stößt: Blockierungen von Seiten der Datenquelle (Google Bilder, Pinterest, Marktplätze, Stock-Websites) und unerwartet hohen Datenverbrauch. Eine IP-Adresse wird schnell gesperrt, und die Kosten für den Proxy-Verkehr können die Kosten für die Annotation des Datensatzes übersteigen. In diesem Artikel werden wir besprechen, wie man einen Bildparser entwirft, der keine Daten aufgrund von Blockierungen verliert, und wie man im Voraus das Budget für den Datenverkehr berechnet.
Warum Proxys beim Sammeln von Bildern für CV
Datensätze für Computer Vision werden selten aus einer einzigen Quelle gesammelt. Normalerweise handelt es sich um eine Kombination aus Bildersuchergebnissen, Produktkarten auf Marktplätzen, Stockfotos, sozialen Medien und offenen APIs. Fast alle diese Quellen setzen Einschränkungen für die Anzahl der Anfragen von einer IP-Adresse: Google Bilder zeigt bereits nach ein paar hundert aufeinanderfolgend Anfragen ein Captcha an, Pinterest blockiert vorübergehend den Zugriff auf die API, und Wildberries und Ozon liefern leere Karten oder leiten auf eine Überprüfungsseite um, wenn verdächtige Aktivitäten festgestellt werden.
Ohne Proxys stoßen Sie auf eine Grenze von mehreren Hundert oder Tausend Bildern pro Tag von einer Adresse. Wenn Sie einen Datensatz mit 50.000–500.000 Bildern benötigen (so viel wird normalerweise für Convolutional Networks und Transformer in Klassifizierungs- und Erkennungsaufgaben benötigt), ist es physisch unmöglich, dies in angemessener Zeit ohne einen verteilten Pool von IP-Adressen zu sammeln — Sie werden entweder ständig auf Blockierungen stoßen oder eine stark verzerrte Stichprobe aufgrund von Einschränkungen in Bezug auf Tageszeit und Geolokalisierung erhalten.
Ein weiterer Grund ist die geografische Vielfalt der Daten. Wenn das Modell Objekte unabhängig von der Region des Aufnahmes gleich gut erkennen soll (z.B. Verkehrsschilder in verschiedenen Ländern oder Produkte auf lokalen Marktplätzen), muss der Parser sich mit IPs aus verschiedenen Ländern verbinden. Ein Rechenzentrum mit einer einzigen ausgehenden IP wird diese Vielfalt nicht bieten.
Wie viel Daten tatsächlich für den Datensatz benötigt werden
Der Datenverbrauch beim Sammeln von Bildern wird oft unterschätzt. Es geht nicht nur um die Größe der Bilder selbst — HTML-Seiten, JS-Bundles, Vorschauen, Tracking-Pixel und CSS werden ebenfalls über Proxys übertragen, wenn der Parser nicht gezielt auf die benötigten URLs eingestellt ist.
| Inhaltstyp | Durchschnittsgewicht | Datenverkehr für 10.000 Stück |
|---|---|---|
| Vorschau/Thumbnail (150–300 px) | 15–40 KB | ~250 MB |
| Foto mittlerer Auflösung (800–1200 px) | 80–200 KB | ~1,4 GB |
| Foto hoher Auflösung (2000+ px) | 400 KB – 1,5 MB | ~9,5 GB |
| HTML-Seite der Produktkarte (ohne Bilder) | 150–400 KB | ~2,7 GB |
In der Praxis, wenn Sie 100.000 Bilder mittlerer Auflösung zusammen mit den Seiten der Produktkarten (zum Extrahieren der Annotationen — Kategorien, Boxen, Attribute) sammeln, liegt der endgültige Datenverbrauch normalerweise im Bereich von 25–45 GB. Wenn hochauflösende Bilder für die Erkennung kleiner Objekte benötigt werden, rechnen Sie mit 80–150 GB für dieselben 100.000 Stück. Diese Zahlen sollten bei der Vorauswahl des Proxy-Tarifs berücksichtigt werden, nicht nachträglich.
Datacenter, Residential und Mobile: Was wählen?
Für das Sammeln von Bildern sind drei Parameter wichtig: Geschwindigkeit, Kosten des Datenverkehrs und Widerstandsfähigkeit gegen Blockierungen. Diese Parameter unterscheiden sich erheblich zwischen den verschiedenen Proxy-Typen.
| Parameter | Datacenter | Residential | Mobile |
|---|---|---|---|
| Geschwindigkeit | Hoch | Mittel | Niedrig-mittel |
| Widerstandsfähigkeit gegen Sperren | Niedrig | Hoch | Sehr hoch |
| Kosten des Datenverkehrs | Niedrig | Mittel-hoch | Hoch |
| Geeignet für | Offene APIs, Stock-Websites ohne strenge Schutzmaßnahmen | Google Bilder, Pinterest, Marktplätze | Soziale Netzwerke mit strengen Anti-Bot-Schutzmaßnahmen |
Für die meisten Aufgaben beim Sammeln von Bildern bieten residential Proxys den optimalen Kompromiss — IPs sehen aus wie normale Benutzer, weshalb Anti-Bot-Systeme von Marktplätzen und Bildsuchmaschinen seltener ein Captcha auslösen. Wenn die Quelle ein offenes Stockfoto oder eine API ohne strenge Limits ist, ist es günstiger, Datacenter-Proxys zu verwenden — sie sind schneller und ermöglichen es, große Datenmengen zu einem niedrigeren Preis pro GB zu übertragen. Für das Sammeln von Bildern aus mobilen Anwendungen oder sozialen Netzwerken mit fortschrittlichem Anti-Bot-Schutz sind mobile Proxys am besten geeignet — sie verwenden echte IPs von Mobilfunkanbietern und werden fast nie blockiert.
Ein einfacher Parser mit Requests und Proxy-Rotation
Für den Anfang eignet sich ein einfaches synchrones Skript mit Requests und Proxy-Rotation für jede Anfrage oder nach einer bestimmten Anzahl von Anfragen. Unten finden Sie ein Beispiel zum Herunterladen von Bildern aus einer Liste von URLs mit Überprüfung des Inhaltstyps und Größenbeschränkung, um versehentlich keine großen Dateien im falschen Format herunterzuladen.
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"Fehler bei {url} über {proxy}: {e}")
return None
Ein wichtiger Punkt ist die Überprüfung von Content-Length vor dem Herunterladen des Antwortkörpers. Dies spart Daten, indem zu große Dateien bereits vor dem vollständigen Download herausgefiltert werden. Die Rotation der Proxys über itertools.cycle verteilt die Last gleichmäßig auf die IPs und verringert das Risiko, dass eine bestimmte Adresse gesperrt wird.
Asynchrones Sammeln mit aiohttp
Ein synchrones Skript mit Requests eignet sich gut für einige Tausend Bilder, aber für Datensätze ab 50.000 Dateien ist Asynchronität erforderlich — sonst zieht sich das Sammeln über Tage. aiohttp zusammen mit einem Semaphore zur Begrenzung der Parallelität ist eine praktikable Lösung für Zehntausende gleichzeitiger Aufgaben, ohne den Proxy-Pool zu überlasten.
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) # Begrenzung der parallelen Anfragen
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"Fehler bei {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))
Ein Semaphore für 50 gleichzeitige Verbindungen ist ein Ausgangspunkt. Wenn die residential Proxys die Last ohne Anstieg der Fehler 429 und Timeouts bewältigen, kann die Anzahl schrittweise auf 100–150 erhöht werden. Wichtig ist, den Anteil der fehlgeschlagenen Anfragen zu überwachen: Wenn dieser über 5–7% steigt, sollte die Parallelität verringert werden, anstatt den Proxy-Pool endlos zu vergrößern.
Scrapy + Proxy-Middleware für großflächiges Sammeln
Für das produktive Sammeln von Datensätzen mit integrierter Warteschlange, Retries und Duplikatsfilterung ist es bequemer, Scrapy mit ImagesPipeline zu verwenden. Diese lädt die Bilder selbst herunter, überprüft deren Gültigkeit über PIL und kann Vorschauen in verschiedenen Größen generieren.
# 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)
Die Parameter IMAGES_MIN_HEIGHT und IMAGES_MIN_WIDTH filtern automatisch kleine Icons und Sprites heraus, die den Datensatz verunreinigen und unnötig Daten verbrauchen. DOWNLOAD_DELAY von 0,5 Sekunden reduziert die Anfragefrequenz von einer Proxy, was den Anteil an Captchas bei Quellen mit strenger Anti-Fraud-Überwachung wie Pinterest verringert.
Wie man den Datenverbrauch senkt
Der Datenverkehr über Proxys wird normalerweise nach Volumen abgerechnet, daher spart jede Optimierung direkt Budget. Hier sind einige effektive Techniken:
- Fordern Sie Vorschauen an, nicht das Original, wenn eine Auflösung von 512–1024 px für das Training ausreicht — die meisten Quellen liefern das Bild in der benötigten Größe über eine separate URL oder einen Query-Parameter.
- Filtern Sie nach Content-Length vor dem Herunterladen des Körpers — ein HEAD-Request oder das Lesen der Header eines Streaming-GET ermöglicht es, zu große Dateien ohne Datenverbrauch für deren Download abzulehnen.
- Deaktivieren Sie das Laden von begleitenden Ressourcen, wenn der Parser über einen Headless-Browser arbeitet: Blockieren Sie CSS, Schriftarten und Tracking-Skripte und lassen Sie nur HTML und die benötigten Bilder zu.
- Deduplizieren Sie URLs vor der Anfrage, nicht nach dem Herunterladen — identische Bilder auf verschiedenen Produktkarten (z.B. auf Wildberries) müssen nicht erneut heruntergeladen werden.
- Verwenden Sie einen lokalen Cache für Antworten bei wiederholten Durchläufen des Parsers während der Fehlersuche, um nicht für jeden Testlauf des Codes Datenverkehr über Proxys zu erzeugen.
Häufige Fehler, 403 und Rate Limits
Beim Sammeln von Bildern in großen Mengen wird der Parser unvermeidlich auf Antworten 403, 429 und temporäre Weiterleitungen zu Überprüfungsseiten stoßen. Die richtige Handhabung dieser Codes spart sowohl Daten als auch den Proxy-Pool.
| Code/Symptom | Ursache | Was zu tun ist |
|---|---|---|
| 403 Forbidden | IP auf der schwarzen Liste der Quelle | Proxy-Rotation, Wechsel des User-Agent |
| 429 Too Many Requests | Anfragen pro Minute überschritten | Erhöhen Sie DOWNLOAD_DELAY, verringern Sie die Parallelität |
| Weiterleitung zu Captcha | Verhaltensanalyse hat einen Bot vermutet | Wechseln Sie zu residential/mobile IPs |
| Leerer Content-Length | Chunked-Antwort oder Platzhalter anstelle eines Bildes | Überprüfen Sie die tatsächliche Größe der heruntergeladenen Bytes |
Es ist auch sinnvoll, den Anteil erfolgreicher Anfragen für jeden Proxy zu protokollieren. Wenn eine bestimmte IP konstant häufiger 403 zurückgibt als andere, entfernen Sie sie automatisch aus der Rotation, ohne zu warten, bis sie die allgemeine Blockstatistik für den gesamten Pool beeinflusst.
Berechnung des Budgets für Proxy-Verkehr
Vor dem Start des Sammelns ist es nützlich, das Datenvolumen im Voraus abzuschätzen, anstatt nachträglich zu zählen. Die Formel ist einfach: Anzahl der Bilder × durchschnittliches Dateigewicht × Overhead-Faktor (HTML, Header, wiederholte Anfragen aufgrund von Fehlern — normalerweise 1,3–1,6).
Beispielrechnung für einen Datensatz mit 200.000 Bildern mittlerer Auflösung (~150 KB) mit Parsing von Produktkarten: 200.000 × 150 KB = 30 GB reines Gewicht der Bilder. Unter Berücksichtigung von HTML-Seiten und wiederholten Anfragen aufgrund temporärer Blockierungen (Faktor 1,5) liegt der endgültige Verbrauch bei etwa 45 GB. Wenn einige Quellen mobile IPs aufgrund von Anti-Bot-Schutzmaßnahmen erfordern, sollten Sie dies separat in den Proxy-Pool einplanen, da der Datenverkehr über mobile IPs normalerweise teurer ist als über residential oder Datacenter.
Eine gute Praxis ist es, eine Pilotcharge von 1.000–2.000 Bildern zu sammeln, den tatsächlichen Datenverbrauch und den Fehleranteil zu messen und dann auf das gesamte geplante Volumen des Datensatzes zu extrapolieren. Dies ist genauer als jede theoretische Berechnung, da es die Spezifik der jeweiligen Quelle berücksichtigt.
Fazit
Das Sammeln von Bildern zum Trainieren von Computer Vision-Modellen ist eine Aufgabe an der Schnittstelle von Datenengineering und effizientem Management der Netzwerkinfrastruktur. Die richtige Architektur des Parsers (Asynchronität, Filterung nach Content-Length, Deduplication von URLs) spart Daten, während die durchdachte Auswahl der Proxys bestimmt, ob Sie das benötigte Volumen des Datensatzes in Tagen oder Wochen mit ständigen Blockierungen sammeln.
Wenn Datenquellen fortschrittlichen Anti-Bot-Schutz verwenden — Bildsuchmaschinen, Marktplätze, soziale Netzwerke — beginnen Sie mit residential Proxys: Sie bieten die beste Balance zwischen Widerstandsfähigkeit gegen Sperren und Kosten des Datenverkehrs. Für offene APIs und Stock-Websites ohne strenge Limits macht es Sinn, bei Datacenter-Proxys zu sparen, und für die am besten geschützten mobilen Anwendungen sollten Sie mobile Proxys für den letzten, schwierigsten Teil des Sammelns verwenden.