收集数千张图像以训练计算机视觉模型是一项几乎总是面临两个问题的任务:数据源的封锁(Google Images、Pinterest、市场、图库网站)和意外的高流量消耗。一个IP很快就会被封禁,而代理流量的费用可能会超过数据集标注的成本。在本文中,我们将探讨如何设计一个不会因封锁而丢失数据的图像解析器,以及如何提前计算流量预算。
收集CV图像时为什么需要代理
计算机视觉的数据集很少是从单一来源收集的。通常,这是图像搜索结果、市场产品卡片、图库、社交媒体和开放API的组合。几乎所有这些来源都对来自同一IP的请求数量施加限制:Google Images在连续几百个请求后就会显示验证码,Pinterest会暂时阻止对API的访问,而Wildberries和Ozon会在可疑活动时返回空卡片或重定向到验证页面。
如果没有代理,您每天从一个地址收集的图像数量通常会限制在几百或几千张。如果需要一个包含50,000到500,000张图像的数据集(这通常是卷积网络和变换器在分类和检测任务中所需的数量),那么在合理的时间内收集它在物理上是不可能的——您要么会不断遇到封锁,要么会因为时间和地理位置的限制而获得严重偏差的样本。
第二个原因是数据的地理多样性。如果模型必须在不同拍摄区域(例如,不同国家的交通标志或本地市场上的商品)中同样良好地识别对象,解析器必须使用来自不同国家的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、没有严格保护的图库 | Google Images、Pinterest、市场 | 具有严格反机器人保护的社交媒体 |
对于大多数图像收集任务,住宅代理提供了最佳的平衡——IP看起来像普通用户,因此市场和图像搜索引擎的反机器人系统更少触发验证码。如果数据源是开放的图库或没有严格限制的API,使用数据中心代理更便宜——它们速度更快,允许以更低的每GB价格传输大量流量。而对于来自移动应用或具有高级反机器人保护的社交媒体的图像收集,移动代理更合适——它们使用真实的运营商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 + 代理中间件进行大规模收集
对于生产环境中的数据集收集,使用Scrapy与ImagesPipeline更方便,因为它内置队列、重试和URL去重。它会自动下载图像,通过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或查询参数提供所需尺寸的图像。
- 在下载主体之前根据Content-Length进行过滤——HEAD请求或读取流式GET的头信息可以在不消耗流量的情况下排除过大的文件。
- 禁用加载附属资源,如果解析器通过无头浏览器工作:阻止CSS、字体和跟踪脚本,仅保留HTML和所需图像。
- 在请求之前去重URL,而不是在下载后——在不同商品卡页面上相同的图像(例如,在Wildberries上)不需要重复下载。
- 在调试期间使用本地响应缓存,以避免在每次测试代码时通过代理消耗流量。
常见错误、403和速率限制
在大规模收集图像时,解析器不可避免地会遇到403、429响应和临时重定向到验证页面。正确处理这些代码可以节省流量和代理池。
| 代码/症状 | 原因 | 该怎么办 |
|---|---|---|
| 403 Forbidden | IP在数据源的黑名单中 | 轮换代理,改变User-Agent |
| 429 Too Many Requests | 超过每分钟请求限制 | 增加DOWNLOAD_DELAY,降低并发性 |
| 重定向到验证码 | 行为分析怀疑是机器人 | 切换到住宅/移动IP |
| 空的Content-Length | 分块响应或占位符而不是图像 | 检查下载字节的实际大小 |
还应单独记录每个代理的成功请求比例。如果某个特定IP的403错误频率稳定高于其他IP——应自动将其从轮换中移除,而不是等到它拖累整个代理池的统计数据。
代理流量预算计算
在开始收集之前,提前估算流量是有益的,而不是事后计算。公式很简单:图像数量 × 文件平均大小 × 开销系数(HTML、头信息、因错误而重复的请求——通常为1.3–1.6)。
例如,对于一个包含200,000张中等分辨率图像(~150 KB)和商品卡解析的数据集:200,000 × 150 KB = 30 GB的图像净重。考虑到HTML页面和因临时封锁而重复请求(系数为1.5),最终消耗约为45 GB。如果某些来源由于反机器人保护需要移动IP,请单独将其纳入代理池,因为移动IP的流量通常比住宅或数据中心的流量更贵。
好的做法是收集1,000–2,000张图像的试点批次,测量实际流量消耗和错误比例,然后将其外推到计划的数据集总量。这比任何理论计算都要准确,因为它考虑了特定来源的特性。
结论
收集图像以训练计算机视觉模型是一项涉及数据工程和网络基础设施管理的任务。正确的解析器架构(异步处理、根据Content-Length过滤、URL去重)可以节省流量,而精心选择的代理决定了您是在几天内收集到所需的数据集,还是在不断的封锁中耗费数周时间。
如果数据源使用高级反机器人保护——图像搜索引擎、市场、社交媒体——请从住宅代理开始:它们在抗封锁能力和流量成本之间提供了最佳平衡。对于开放API和没有严格限制的图库,节省使用数据中心代理是有意义的,而对于最受保护的移动应用,则应连接移动代理以完成最后、最复杂的收集部分。