جمعآوری هزاران تصویر برای آموزش مدل بینایی کامپیوتری — وظیفهای است که تقریباً همیشه به دو مشکل برمیخورد: مسدودیت از طرف منبع داده (تصاویر گوگل، پینترست، بازارهای آنلاین، سایتهای استوک) و مصرف غیرمنتظره بالای ترافیک. یک IP به سرعت در لیست سیاه قرار میگیرد و هزینه ترافیک پروکسی ممکن است از هزینه خود دیتاست بیشتر شود. در این مقاله بررسی میکنیم که چگونه یک پارسر تصویر طراحی کنیم که دادهها را به دلیل مسدودیت از دست ندهد و چگونه بودجه ترافیک را از قبل محاسبه کنیم.
چرا پروکسی هنگام جمعآوری تصاویر برای CV
دیتاستهای بینایی کامپیوتری به ندرت از یک منبع جمعآوری میشوند. معمولاً این یک ترکیب از نتایج جستجوی تصاویر، کارتهای محصولات در بازارهای آنلاین، استوکهای عکس، شبکههای اجتماعی و APIهای عمومی است. تقریباً همه این منابع محدودیتهایی برای تعداد درخواستها از یک IP دارند: تصاویر گوگل پس از چند صد درخواست متوالی CAPTCHA را نشان میدهد، پینترست به طور موقت دسترسی به 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های عمومی، استوکها بدون حفاظت سخت | تصاویر گوگل، پینترست، بازارهای آنلاین | شبکههای اجتماعی با حفاظت ضد ربات سخت |
برای اکثر وظایف جمعآوری تصاویر، تعادل بهینه را پروکسیهای مسکونی ارائه میدهند — IPها به عنوان کاربران عادی به نظر میرسند، بنابراین سیستمهای ضد ربات بازارهای آنلاین و موتورهای جستجو با تصاویر کمتر CAPTCHA را فعال میکنند. اگر منبع یک استوک عکس عمومی یا 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 ثانیه فرکانس درخواستها از یک پروکسی را کاهش میدهد، که سهم CAPTCHA را در منابع با نظارت ضد تقلب سخت مانند پینترست کاهش میدهد.
چگونه مصرف ترافیک را کاهش دهیم
ترافیک از طریق پروکسی معمولاً بر اساس حجم محاسبه میشود، بنابراین هر بهینهسازی به طور مستقیم بودجه را صرفهجویی میکند. در اینجا روشهای کارآمد آورده شده است:
- پیشنمایش را درخواست کنید، نه اصل، اگر برای آموزش به وضوح 512–1024 px کافی است — اکثر منابع تصویر با اندازه مورد نیاز را از طریق URL جداگانه یا پارامتر query ارائه میدهند.
- قبل از دانلود بدنه، بر اساس Content-Length فیلتر کنید — درخواست HEAD یا خواندن هدرهای GET استریمینگ اجازه میدهد فایلهای بسیار سنگین را بدون صرف ترافیک برای بارگذاری آنها حذف کنید.
- بارگذاری منابع مربوطه را غیرفعال کنید، اگر پارسر از طریق مرورگر headless کار میکند: CSS، فونتها و اسکریپتهای ردیابی را مسدود کنید و فقط HTML و تصاویر مورد نیاز را نگه دارید.
- URLها را قبل از درخواست حذف تکراری کنید، نه بعد از دانلود — تصاویر یکسان در صفحات مختلف کارت محصول (برای مثال، در Wildberries) نیازی به دانلود مجدد ندارند.
- از کش محلی پاسخها استفاده کنید هنگام اجرای مجدد پارسر در حین اشکالزدایی، تا ترافیک را برای هر اجرای آزمایشی کد از طریق پروکسی نگردانید.
خطاهای رایج، 403 و محدودیت نرخ
هنگام جمعآوری تصاویر در حجمهای بزرگ، پارسر به ناچار با پاسخهای 403، 429 و هدایتهای موقتی به صفحات بررسی مواجه خواهد شد. پردازش صحیح این کدها هم ترافیک و هم مجموعه پروکسی را صرفهجویی میکند.
| کد/علائم | دلیل | چه کار باید کرد |
|---|---|---|
| 403 Forbidden | IP در لیست سیاه منبع | چرخش پروکسی، تغییر User-Agent |
| 429 Too Many Requests | محدودیت درخواستها در دقیقه تجاوز شده است | افزایش DOWNLOAD_DELAY، کاهش همزمانی |
| هدایت به CAPTCHA | تحلیل رفتاری ربات را مشکوک دانسته است | به IPهای مسکونی/موبایل منتقل شوید |
| Content-Length خالی | پاسخ Chunked یا گازانبری به جای تصویر | اندازه واقعی بایتهای دانلود شده را بررسی کنید |
به طور جداگانه، باید درصد درخواستهای موفق را برای هر پروکسی ثبت کنید. اگر یک IP خاص به طور مداوم 403 را بیشتر از بقیه ارائه میدهد — آن را به طور خودکار از چرخش خارج کنید، بدون اینکه منتظر بمانید تا او آمار کلی مسدودیت را در کل مجموعه تحت تأثیر قرار دهد.
محاسبه بودجه برای ترافیک پروکسی
قبل از شروع جمعآوری، مفید است که حجم ترافیک را از قبل تخمین بزنید، نه اینکه پس از آن محاسبه کنید. فرمول ساده است: تعداد تصاویر × وزن متوسط فایل × ضریب هزینههای اضافی (HTML، هدرها، درخواستهای تکراری به دلیل خطاها — معمولاً 1.3–1.6).
مثال محاسبه برای دیتاستی با 200,000 تصویر با وضوح متوسط (~150 کیلوبایت) با پارس کردن کارتهای محصول: 200,000 × 150 کیلوبایت = 30 گیگابایت وزن خالص تصاویر. با در نظر گرفتن صفحات HTML و درخواستهای تکراری به دلیل مسدودیتهای موقتی (ضریب 1.5) مصرف نهایی — حدود 45 گیگابایت خواهد بود. اگر بخشی از منابع به IPهای موبایل به دلیل حفاظت ضد ربات نیاز دارد، این را به مجموعه پروکسی به طور جداگانه در نظر بگیرید، زیرا ترافیک بر روی IPهای موبایل معمولاً گرانتر از IPهای مسکونی یا دیتاسنتری است.
یک روش خوب — جمعآوری یک دسته آزمایشی از 1,000–2,000 تصویر، اندازهگیری مصرف واقعی ترافیک و درصد خطاها، و سپس تعمیم آن به کل حجم برنامهریزی شده دیتاست است. این دقیقتر از هر محاسبه نظری است، زیرا ویژگیهای منبع خاص را در نظر میگیرد.
نتیجهگیری
جمعآوری تصاویر برای آموزش مدلهای بینایی کامپیوتری — این یک وظیفه در مرز مهندسی دادهها و مدیریت صحیح زیرساخت شبکه است. معماری صحیح پارسر (غیرهمزمانی، فیلتر کردن بر اساس Content-Length، حذف تکراری URL) ترافیک را صرفهجویی میکند، در حالی که انتخاب هوشمند پروکسی تعیین میکند که آیا شما حجم مورد نیاز دیتاست را در روزها جمعآوری میکنید یا در هفتهها با مسدودیتهای مداوم.
اگر منابع داده از حفاظت ضد ربات پیشرفته استفاده میکنند — موتورهای جستجوی تصاویر، بازارهای آنلاین، شبکههای اجتماعی — با پروکسیهای مسکونی شروع کنید: آنها بهترین تعادل بین مقاومت در برابر مسدودیتها و هزینه ترافیک را ارائه میدهند. برای APIهای عمومی و استوکها بدون محدودیتهای سخت، صرفهجویی در پروکسیهای دیتاسنتر منطقی است، و برای برنامههای موبایل با حفاظت بسیار قوی — پروکسیهای موبایل را برای بخش نهایی و دشوار جمعآوری متصل کنید.