Сбор тысяч изображений для обучения модели компьютерного зрения — задача, которая почти всегда упирается в две проблемы: блокировки со стороны источника данных (Google Images, Pinterest, маркетплейсы, стоковые сайты) и неожиданно высокий расход трафика. Один IP быстро улетает в бан, а счёт за прокси-трафик может превысить стоимость самой разметки датасета. В этой статье разберём, как спроектировать парсер изображений, который не теряет данные из-за блокировок, и как заранее посчитать бюджет на трафик.
Зачем прокси при сборе изображений для CV
Датасеты для компьютерного зрения редко собирают из одного источника. Обычно это комбинация поисковой выдачи изображений, карточек товаров на маркетплейсах, фотостоков, соцсетей и открытых API. Почти все эти источники вводят ограничения на количество запросов с одного IP: Google Images показывает капчу уже после пары сотен запросов подряд, Pinterest временно блокирует доступ к API, а Wildberries и Ozon отдают пустые карточки или редиректят на страницу проверки при подозрительной активности.
Без прокси вы упираетесь в потолок в несколько сотен или тысяч изображений в сутки с одного адреса. Если нужен датасет на 50 000–500 000 изображений (а именно столько обычно требуется для сверточных сетей и трансформеров в задачах классификации и детекции), без распределённого пула IP-адресов собрать его за разумное время физически невозможно — вы либо будете постоянно натыкаться на блокировки, либо получите сильно смещённую выборку из-за ограничения по времени суток и геолокации.
Вторая причина — географическое разнообразие данных. Если модель должна одинаково хорошо распознавать объекты независимо от региона съёмки (например, дорожные знаки в разных странах или товары на локальных маркетплейсах), парсер должен подключаться с IP разных стран. Один дата-центр с одним выходным IP такого разнообразия не даст.
Сколько трафика реально уходит на датасет
Расход трафика при сборе изображений часто недооценивают. Дело не только в весе самих картинок — HTML-страницы, JS-бандлы, превью, трекинг-пиксели и CSS тоже тянутся через прокси, если парсер не настроен точечно на нужные URL.
| Тип контента | Средний вес | Трафик на 10 000 шт. |
|---|---|---|
| Превью/thumbnail (150–300 px) | 15–40 КБ | ~250 МБ |
| Фото среднего разрешения (800–1200 px) | 80–200 КБ | ~1,4 ГБ |
| Фото высокого разрешения (2000+ px) | 400 КБ – 1,5 МБ | ~9,5 ГБ |
| HTML-страница карточки товара (без картинок) | 150–400 КБ | ~2,7 ГБ |
На практике, если вы собираете 100 000 изображений среднего разрешения вместе со страницами карточек (для извлечения разметки — категории, боксы, атрибуты), итоговый расход трафика обычно укладывается в диапазон 25–45 ГБ. Если нужны изображения высокого разрешения для задач детекции мелких объектов — рассчитывайте на 80–150 ГБ на те же 100 000 штук. Эти цифры стоит закладывать при выборе тарифа прокси заранее, а не постфактум.
Datacenter, residential и mobile: что выбрать
Для сбора изображений важны три параметра: скорость, стоимость трафика и устойчивость к блокировкам. Эти параметры у разных типов прокси сильно отличаются.
| Параметр | Datacenter | Residential | Mobile |
|---|---|---|---|
| Скорость | Высокая | Средняя | Низкая-средняя |
| Устойчивость к банам | Низкая | Высокая | Очень высокая |
| Стоимость трафика | Низкая | Средняя-высокая | Высокая |
| Подходит для | Открытые API, стоки без строгой защиты | Google Images, Pinterest, маркетплейсы | Соцсети с жёсткой антибот-защитой |
Для большинства задач сбора изображений оптимальный баланс дают резидентные прокси — IP выглядят как обычные пользователи, поэтому антибот-системы маркетплейсов и поисковых систем с изображениями реже триггерят капчу. Если источник — открытый фотосток или API без строгих лимитов, дешевле использовать прокси дата-центров — они быстрее и позволяют перегонять большие объёмы трафика по более низкой цене за ГБ. А для сбора изображений из мобильных приложений или соцсетей с продвинутой антибот-защитой лучше подходят мобильные прокси — они используют реальные IP операторов связи и почти никогда не попадают под блок.
Базовый парсер на requests с ротацией прокси
Для старта подойдёт простой синхронный скрипт на requests с ротацией прокси на каждый запрос или через заданное количество запросов. Ниже пример скачивания изображений по списку URL с проверкой типа контента и ограничением размера файла, чтобы случайно не утянуть тяжёлый файл не того формата.
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"Ошибка при {url} через {proxy}: {e}")
return None
Ключевой момент — проверка Content-Length до скачивания тела ответа. Это экономит трафик, отсеивая слишком тяжёлые файлы ещё до полной загрузки. Ротация прокси через itertools.cycle равномерно распределяет нагрузку между IP, снижая риск бана конкретного адреса.
Асинхронный сбор через aiohttp
Синхронный скрипт на requests хорошо подходит для нескольких тысяч изображений, но для датасетов от 50 000 файлов нужна асинхронность — иначе сбор растянется на дни. aiohttp вместе с семафором для ограничения параллелизма — рабочий вариант для десятков тысяч одновременных задач без перегрузки прокси-пула.
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) # ограничение параллельных запросов
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"Ошибка {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))
Семафор на 50 одновременных соединений — стартовая точка. Если резидентные прокси выдерживают нагрузку без роста ошибок 429 и таймаутов, можно постепенно увеличивать до 100–150. Главное — мониторить процент неуспешных запросов: если он растёт выше 5–7%, параллелизм стоит снизить, а не наращивать пул прокси бесконечно.
Scrapy + прокси-middleware для масштабного сбора
Для продакшен-сбора датасетов со встроенной очередью, ретраями и дедупликацией URL удобнее использовать Scrapy с ImagesPipeline. Она сама скачивает изображения, проверяет их валидность через PIL и может генерировать превью разных размеров.
# 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)
Параметры IMAGES_MIN_HEIGHT и IMAGES_MIN_WIDTH автоматически отбрасывают мелкие иконки и спрайты, которые загрязняют датасет и тратят трафик впустую. DOWNLOAD_DELAY в 0.5 секунды снижает частоту запросов с одного прокси, что уменьшает долю капч на источниках со строгим анти-фрод мониторингом вроде Pinterest.
Как снизить расход трафика
Трафик через прокси обычно тарифицируется по объёму, поэтому любая оптимизация напрямую экономит бюджет. Вот рабочие приёмы:
- Запрашивайте превью, а не оригинал, если для обучения достаточно разрешения 512–1024 px — большинство источников отдают изображение нужного размера по отдельному URL или query-параметру.
- Фильтруйте по Content-Length до скачивания тела — HEAD-запрос или чтение заголовков стримингового GET позволяет отбросить слишком тяжёлые файлы без траты трафика на их загрузку.
- Отключайте загрузку сопутствующих ресурсов, если парсер работает через headless-браузер: блокируйте CSS, шрифты и трекинг-скрипты, оставляя только HTML и нужные изображения.
- Дедуплицируйте URL до запроса, а не после скачивания — одинаковые изображения на разных страницах карточек товара (например, на Wildberries) не нужно скачивать повторно.
- Используйте локальный кэш ответов при повторных прогонах парсера во время отладки, чтобы не гонять трафик через прокси на каждый тестовый запуск кода.
Частые ошибки, 403 и rate limit
При сборе изображений в больших объёмах парсер неизбежно столкнётся с ответами 403, 429 и временными редиректами на страницы проверки. Правильная обработка этих кодов экономит и трафик, и прокси-пул.
| Код/симптом | Причина | Что делать |
|---|---|---|
| 403 Forbidden | IP в чёрном списке источника | Ротация прокси, смена User-Agent |
| 429 Too Many Requests | Превышен лимит запросов в минуту | Увеличить DOWNLOAD_DELAY, снизить параллелизм |
| Редирект на капчу | Поведенческий анализ заподозрил бота | Перейти на резидентные/мобильные IP |
| Пустой Content-Length | Chunked-ответ или заглушка вместо картинки | Проверять реальный размер скачанных байт |
Отдельно стоит логировать долю успешных запросов по каждому прокси. Если конкретный IP стабильно даёт 403 чаще остальных — выводите его из ротации автоматически, не дожидаясь, пока он потянет за собой общую статистику блока по всему пулу.
Расчёт бюджета на прокси-трафик
Перед запуском сбора полезно прикинуть объём трафика заранее, а не считать постфактум. Формула простая: количество изображений × средний вес файла × коэффициент накладных расходов (HTML, заголовки, повторные запросы из-за ошибок — обычно 1.3–1.6).
Пример расчёта для датасета в 200 000 изображений среднего разрешения (~150 КБ) с парсингом карточек товара: 200 000 × 150 КБ = 30 ГБ чистого веса изображений. С учётом HTML-страниц и повторных запросов из-за временных блокировок (коэффициент 1.5) итоговый расход — около 45 ГБ. Если часть источников требует мобильных IP из-за антибот-защиты, закладывайте это в пул прокси отдельно, так как трафик на мобильных IP обычно обходится дороже, чем на резидентных или датацентровых.
Хорошая практика — собрать пилотную партию в 1 000–2 000 изображений, замерить реальный расход трафика и процент ошибок, а затем экстраполировать на весь планируемый объём датасета. Это точнее любых теоретических расчётов, так как учитывает специфику конкретного источника.
Заключение
Сбор изображений для обучения моделей компьютерного зрения — это задача на стыке инженерии данных и грамотного управления сетевой инфраструктурой. Правильная архитектура парсера (асинхронность, фильтрация по Content-Length, дедупликация URL) экономит трафик, а продуманный выбор прокси определяет, соберёте вы нужный объём датасета за дни или за недели постоянных блокировок.
Если источники данных используют продвинутую антибот-защиту — поисковые системы изображений, маркетплейсы, соцсети — начинайте с резидентных прокси: они дают лучший баланс между устойчивостью к банам и стоимостью трафика. Для открытых API и стоков без строгих лимитов имеет смысл сэкономить на прокси дата-центров, а для самых защищённых мобильных приложений — подключить мобильные прокси для финальной, самой сложной части сбора.