컴퓨터 비전 모델 교육을 위한 수천 개의 이미지를 수집하는 것은 거의 항상 두 가지 문제에 직면하는 작업입니다: 데이터 소스(구글 이미지, 핀터레스트, 마켓플레이스, 스톡 사이트)로부터의 차단과 예상보다 높은 트래픽 소비입니다. 하나의 IP는 빠르게 차단되고, 프록시 트래픽 비용은 데이터 세트의 주석 비용을 초과할 수 있습니다. 이 기사에서는 차단으로 인해 데이터를 잃지 않는 이미지 파서를 설계하는 방법과 트래픽 예산을 사전에 계산하는 방법을 살펴보겠습니다.
CV용 이미지 수집 시 프록시가 필요한 이유
컴퓨터 비전 데이터 세트는 거의 항상 하나의 소스에서 수집되지 않습니다. 일반적으로 이미지 검색 결과, 마켓플레이스의 상품 카드, 스톡 사진, 소셜 미디어 및 공개 API의 조합입니다. 이러한 소스의 거의 모든 경우, 하나의 IP에서 요청 수에 제한을 두고 있습니다: 구글 이미지는 연속으로 몇 백 개의 요청 후에 CAPTCHA를 표시하고, 핀터레스트는 API 접근을 일시적으로 차단하며, Wildberries와 Ozon은 의심스러운 활동이 감지되면 빈 카드 또는 확인 페이지로 리디렉션합니다.
프록시 없이 하나의 주소에서 하루에 수백 또는 수천 개의 이미지로 한계에 부딪히게 됩니다. 50,000–500,000개의 이미지 데이터 세트가 필요한 경우(분류 및 탐지 작업에서 컨볼루션 네트워크와 변환기에 일반적으로 필요한 수량), 분산된 IP 주소 풀 없이 합리적인 시간 내에 수집하는 것은 물리적으로 불가능합니다 — 차단에 계속 부딪히거나 시간대와 지리적 위치에 따른 제한으로 인해 크게 편향된 샘플을 얻게 됩니다.
두 번째 이유는 데이터의 지리적 다양성입니다. 모델이 촬영 지역에 관계없이 객체를 동일하게 인식해야 하는 경우(예: 다양한 국가의 도로 표지판이나 지역 마켓플레이스의 상품), 파서는 다양한 국가의 IP로 연결해야 합니다. 하나의 데이터 센터에서 하나의 출구 IP로는 이러한 다양성을 제공할 수 없습니다.
데이터 세트에 실제로 소요되는 트래픽
이미지 수집 시 트래픽 소비는 종종 과소평가됩니다. 문제는 이미지 자체의 크기뿐만 아니라 HTML 페이지, JS 번들, 미리보기, 추적 픽셀 및 CSS도 프록시를 통해 전송되기 때문입니다. 파서가 필요한 URL에 맞게 정확히 설정되지 않은 경우입니다.
| 콘텐츠 유형 | 평균 크기 | 10,000개당 트래픽 |
|---|---|---|
| 미리보기/썸네일 (150–300 px) | 15–40 KB | ~250 MB |
| 중간 해상도의 사진 (800–1200 px) | 80–200 KB | ~1.4 GB |
| 고해상도의 사진 (2000+ px) | 400 KB – 1.5 MB | ~9.5 GB |
| 상품 카드의 HTML 페이지 (이미지 없음) | 150–400 KB | ~2.7 GB |
실제로, 중간 해상도의 100,000개의 이미지를 상품 카드 페이지와 함께 수집하는 경우(주석 추출을 위한 카테고리, 박스, 속성), 최종 트래픽 소비는 일반적으로 25–45 GB 범위에 포함됩니다. 작은 객체 탐지 작업을 위한 고해상도 이미지가 필요한 경우 — 같은 100,000개에 대해 80–150 GB를 예상해야 합니다. 이러한 숫자는 프록시 요금제를 선택할 때 미리 고려해야 하며, 사후에 계산해서는 안 됩니다.
데이터 센터, 주거용 및 모바일: 무엇을 선택할까
이미지를 수집할 때 중요한 세 가지 매개변수는 속도, 트래픽 비용 및 차단에 대한 저항력입니다. 이러한 매개변수는 프록시 유형에 따라 크게 다릅니다.
| 매개변수 | 데이터 센터 | 주거용 | 모바일 |
|---|---|---|---|
| 속도 | 높음 | 중간 | 낮음-중간 |
| 차단 저항력 | 낮음 | 높음 | 매우 높음 |
| 트래픽 비용 | 낮음 | 중간-높음 | 높음 |
| 적합한 용도 | 공개 API, 엄격한 보호가 없는 스톡 | 구글 이미지, 핀터레스트, 마켓플레이스 | 강력한 안티봇 보호가 있는 소셜 미디어 |
대부분의 이미지 수집 작업에 최적의 균형을 제공하는 것은 주거용 프록시입니다 — IP가 일반 사용자처럼 보이기 때문에 마켓플레이스와 이미지 검색 엔진의 안티봇 시스템이 CAPTCHA를 트리거할 가능성이 적습니다. 소스가 공개 스톡 사진이나 엄격한 제한이 없는 API인 경우, 데이터 센터 프록시를 사용하는 것이 더 저렴합니다 — 이들은 더 빠르고 더 낮은 GB당 가격으로 대량의 트래픽을 전송할 수 있습니다. 모바일 애플리케이션이나 고급 안티봇 보호가 있는 소셜 미디어에서 이미지를 수집하는 경우, 모바일 프록시가 더 적합합니다 — 이들은 실제 통신사의 IP를 사용하며 거의 차단되지 않습니다.
프록시 회전을 포함한 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 + 프록시 미들웨어를 통한 대규모 수집
내장된 큐, 재시도 및 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초로 설정하면 하나의 프록시에서 요청 빈도를 줄여, 핀터레스트와 같은 엄격한 안티 프로드 모니터링 소스에서 CAPTCHA 비율을 감소시킵니다.
트래픽 소비를 줄이는 방법
프록시를 통한 트래픽은 일반적으로 용량에 따라 과금되므로, 모든 최적화는 예산을 직접 절약합니다. 다음은 유용한 방법입니다:
- 원본이 아닌 미리보기를 요청하세요, 교육에 512–1024 px 해상도가 충분하다면 — 대부분의 소스는 별도의 URL 또는 쿼리 매개변수를 통해 필요한 크기의 이미지를 제공합니다.
- 본문 다운로드 전에 Content-Length로 필터링하세요 — HEAD 요청이나 스트리밍 GET의 헤더를 읽으면 너무 큰 파일을 다운로드하기 전에 걸러낼 수 있습니다.
- 헤드리스 브라우저를 통해 작동하는 경우, 관련 리소스의 다운로드를 비활성화하세요: CSS, 폰트 및 추적 스크립트를 차단하고 HTML 및 필요한 이미지만 남깁니다.
- 요청 전에 URL을 중복 제거하세요, 다운로드 후가 아닌 — 상품 카드의 서로 다른 페이지에서 동일한 이미지는 재다운로드할 필요가 없습니다.
- 디버깅 중 반복 실행 시 로컬 응답 캐시를 사용하세요, 매번 테스트 코드 실행 시 프록스를 통해 트래픽을 보내지 않도록 합니다.
자주 발생하는 오류, 403 및 비율 제한
대량의 이미지를 수집할 때 파서는 불가피하게 403, 429 응답 및 확인 페이지로의 임시 리디렉션에 직면하게 됩니다. 이러한 코드를 올바르게 처리하면 트래픽과 프록시 풀을 절약할 수 있습니다.
| 코드/증상 | 원인 | 대처 방법 |
|---|---|---|
| 403 Forbidden | IP가 소스의 블랙리스트에 있음 | 프록시 회전, User-Agent 변경 |
| 429 Too Many Requests | 분당 요청 수 초과 | DOWNLOAD_DELAY 증가, 병렬성 감소 |
| CAPTCHA로 리디렉션 | 행동 분석이 봇으로 의심함 | 주거용/모바일 IP로 전환 |
| 빈 Content-Length | 청크 응답 또는 이미지 대신 더미 | 다운로드된 바이트의 실제 크기 확인 |
각 프록시의 성공적인 요청 비율을 별도로 기록하는 것이 좋습니다. 특정 IP가 다른 IP보다 403을 더 자주 발생시키는 경우, 전체 풀의 차단 통계에 영향을 주기 전에 자동으로 회전에서 제외해야 합니다.
프록시 트래픽 예산 계산
수집을 시작하기 전에 미리 트래픽 양을 추정하는 것이 유용하며, 사후에 계산해서는 안 됩니다. 공식은 간단합니다: 이미지 수 × 파일의 평균 크기 × 간접비용 계수 (HTML, 헤더, 오류로 인한 재요청 — 일반적으로 1.3–1.6).
중간 해상도(약 150 KB)의 200,000개 이미지 데이터 세트와 상품 카드 파싱을 위한 계산 예시: 200,000 × 150 KB = 30 GB의 순수 이미지 크기. HTML 페이지 및 임시 차단으로 인한 재요청을 고려하면(계수 1.5) 최종 소비는 약 45 GB입니다. 일부 소스가 안티봇 보호로 인해 모바일 IP를 요구하는 경우, 프록시 풀에 별도로 고려해야 하며, 모바일 IP의 트래픽 비용은 일반적으로 주거용 또는 데이터 센터보다 비쌉니다.
좋은 방법은 1,000–2,000개의 이미지를 수집하여 실제 트래픽 소비와 오류 비율을 측정한 후, 이를 기반으로 전체 데이터 세트 계획량을 추정하는 것입니다. 이는 특정 소스의 특성을 고려하므로 이론적인 계산보다 더 정확합니다.
결론
컴퓨터 비전 모델 교육을 위한 이미지 수집은 데이터 엔지니어링과 네트워크 인프라 관리의 교차점에 있는 작업입니다. 올바른 파서 아키텍처(비동기성, Content-Length 필터링, URL 중복 제거)는 트래픽을 절약하고, 신중한 프록시 선택은 데이터 세트를 며칠 내에 수집할 수 있을지, 아니면 지속적인 차단으로 인해 몇 주가 걸릴지를 결정합니다.
데이터 소스가 고급 안티봇 보호를 사용하는 경우 — 이미지 검색 엔진, 마켓플레이스, 소셜 미디어 — 주거용 프록시로 시작하세요: 이들은 차단 저항성과 트래픽 비용 간의 최상의 균형을 제공합니다. 공개 API 및 엄격한 제한이 없는 스톡에 대해서는 데이터 센터 프록시에서 절약하는 것이 좋으며, 가장 보호된 모바일 애플리케이션의 경우 모바일 프록시를 연결하여 수집의 마지막, 가장 어려운 부분을 처리하세요.