引言
训练一个图像识别模型,最耗时的工作往往不是调参,而是"凑数据"。做猫狗分类需要几千张标注清晰的图片,做商品识别需要覆盖不同角度、光照和背景的样本,做缺陷检测甚至需要专业人员逐张标注。手动一张张下载、筛选、整理,效率极低,而且很难保证类内多样性和类间平衡。
百度图片搜索是获取这类数据最直接的入口------覆盖广、关键词灵活、返回结果中同时包含缩略图和原图地址。但把"能搜到的图"变成"能用来训练的图",中间隔着一整套工程问题:接口的分页参数怎么构造、请求被限流怎么办、下载下来的图片里有多少是重复的、模糊的、甚至是无关的?本文将从接口解析出发,完整走一遍采集 → 反爬对抗 → 高并发下载 → 训练集去重与清洗的链路,并集成站大爷隧道代理IP来应对高频采集下的IP限制。
免责声明:本文仅限技术学习与交流。爬取的图片应遵守目标网站的robots.txt协议及相关版权规定,仅用于个人学习和研究,不得用于商业用途或侵犯他人权益。

一、接口解析:acjson的分页逻辑
百度图片搜索的网页端数据并非直接嵌在HTML中,而是通过一个异步JSON接口返回。核心端点如下:
https://image.baidu.com/search/acjson
关键参数中,word和queryWord是搜索关键词,pn是分页偏移量 ,rn是每页返回数量(通常为30)。第二页的pn=30,第三页pn=60,以此类推。此外,gsm参数是pn的十六进制表示,部分请求中会用到;tn和ipn用于标识请求来源与会话连续性。一个可直接使用的参数模板如下:
params = {
"tn": "resultjson_com",
"ipn": "rj",
"word": keyword,
"queryWord": keyword,
"pn": pn,
"rn": 30,
"gsm": format(pn, "x"), # pn的十六进制
"ie": "utf-8",
}
接口返回的JSON中,data是一个列表,每个元素代表一张图片。图片的真实地址优先取thumbURL ,如果为空则回退到middleURL。需要注意的是,thumbURL通常是压缩后的缩略图,分辨率对于训练集来说可能偏低;如果需要更高清的版本,可以尝试从返回结果中提取hoverURL或replaceUrl中的原图链接,但这部分字段的可用性不如thumbURL稳定。
二、反爬对抗:请求头、频率与IP三层防线
百度图片搜索的反爬是多维度叠加的,不是单一措施能解决的。
第一层:请求头完整性校验。 请求必须携带合法的User-Agent、Referer,部分场景还会检查X-Requested-With。缺少这些字段的请求会被直接拒绝或返回空数据。一个可用的基础请求头如下:
HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36",
"Referer": "https://image.baidu.com/",
"X-Requested-With": "XMLHttpRequest",
}
第二层:频率限制与行为检测。 短时间内的密集请求会触发验证码或直接封禁IP,部分情况下返回503状态码。请求间隔完全固定也会被行为模型识别,因此需要在请求之间加入随机延迟 ,而不是固定的sleep(1)。
第三层:IP层面的限制。 这是训练集采集最核心的瓶颈。一个训练集动辄需要数万张图片,按每页30张计算,仅接口请求就需要数千次;再加上每张图片的下载请求,单IP在完成采集之前几乎必然触发限流。此时需要在IP层面做轮换,这正是站大爷隧道代理IP发挥作用的地方:每次请求自动从不同出口IP发出,将单IP的请求密度摊薄到安全范围内,同时避免因某个IP被标记而导致整批采集中断。站大爷隧道代理支持HTTP/HTTPS/SOCKS全协议,覆盖全国99%地域,连接成功率在长时间压力测试中稳定在99%以上,适合训练集采集这种需要持续稳定运行的任务。
三、高并发下载:线程池与流式写入
训练集采集的图片数量通常在数千到数万张量级,同步逐张下载效率过低。使用ThreadPoolExecutor并发下载是性价比最高的方案:线程数控制在5-10之间,既能显著提速,又不至于因瞬时压力过大而触发风控。
import requests, os, random, time
from concurrent.futures import ThreadPoolExecutor, as_completed
PROXIES = {
"http": "http://用户名:密码@tps.zdaye.com:8080",
"https": "http://用户名:密码@tps.zdaye.com:8080",
}
def download_one(idx, url, save_dir):
try:
resp = requests.get(
url,
headers={"User-Agent": HEADERS["User-Agent"],
"Referer": "https://image.baidu.com/"},
proxies=PROXIES,
timeout=15,
stream=True,
)
if resp.status_code == 200:
ext = os.path.splitext(url.split("?")[0])[1] or ".jpg"
if len(ext) > 5:
ext = ".jpg"
path = os.path.join(save_dir, f"{idx:05d}{ext}")
with open(path, "wb") as f:
for chunk in resp.iter_content(8192):
f.write(chunk)
return path
except Exception:
pass
return None
def download_parallel(urls, save_dir, workers=8):
os.makedirs(save_dir, exist_ok=True)
results = []
with ThreadPoolExecutor(max_workers=workers) as executor:
futures = {
executor.submit(download_one, i, u, save_dir): i
for i, u in enumerate(urls)
}
for future in as_completed(futures):
r = future.result()
if r:
results.append(r)
return results
使用stream=True配合iter_content分块写入,避免将整张图片加载进内存;线程池的as_completed模式可以在任意一张下载完成后立即处理,而不是等待所有任务结束。
四、训练集构建:去重、清洗与组织
把图片下载到本地只是第一步。原始采集结果中通常混有大量近似重复的图片------同一张图的不同尺寸、不同压缩质量、甚至只是加了水印的变体。如果不去重直接拿去训练,模型会在这些冗余样本上过拟合,评估指标也会失真。
4.1 感知哈希去重
pHash(感知哈希) 是目前图像训练集去重最常用的方法。它通过离散余弦变换(DCT)为每张图片生成一个64位哈希指纹,然后比较两张图片哈希值的汉明距离来判断相似程度:距离为0表示几乎完全相同,距离小于5通常视为高度相似,距离大于10则可以认为是不同图片。
使用imagededup库可以快速完成批量去重:
from imagededup.methods import PHash
phasher = PHash()
encodings = phasher.encode_images(image_dir="raw_images")
duplicates = phasher.find_duplicates(
encoding_map=encodings,
max_distance_threshold=5,
)
duplicates是一个字典,键为每张图片的文件名,值为与之近重复的其他文件列表。根据这个结果删除冗余样本即可。imagededup同时支持pHash、dHash、wHash、aHash以及基于CNN的近重复检测,对于训练集场景,pHash在速度和准确率之间是较好的平衡点。
4.2 清洗与筛选
去重之后还需要做几项基础清洗:
-
尺寸过滤:低于某个分辨率阈值的图片(如宽或高小于100像素)通常信息量不足,应剔除。
-
格式统一:训练框架通常期望统一的RGB格式,可以批量转换为JPEG并去除透明通道。
-
模糊检测:使用拉普拉斯算子方差判断图片清晰度,方差过低说明图片模糊,不适合作为训练样本。
-
标签目录组织 :按关键词分目录存放,目录名即类别标签,例如
dataset/cat/、dataset/dog/。这样可以直接配合ImageFolder等数据加载器使用。
五、站大爷隧道代理的集成与调优
5.1 为什么训练集采集特别依赖代理?
训练集采集的请求量与普通爬虫完全不是一个量级。假设目标是一个包含10个类别的训练集,每个类别采集2000张,总计20000张图片。按每页30张计算,接口请求约667次;加上20000次图片下载请求,总请求数超过20000次。没有任何单个IP能够在不触发限流的情况下完成这个量级的请求。
站大爷隧道代理的每请求自动切换IP能力,将这两万多次请求分散到大量不同的出口IP上,使每个IP的请求量保持在极低水平。同时,代理层自带IP质量检测,自动淘汰低效节点,避免因个别IP失效导致下载任务反复失败。
5.2 集成方式
在requests中集成站大爷隧道代理有两种方式。直接使用URL内嵌认证:
PROXIES = {
"http": "http://用户名:密码@tps.zdaye.com:8080",
"https": "http://用户名:密码@tps.zdaye.com:8080",
}
如果服务商要求通过请求头认证,则使用Proxy-Authorization:
import base64
auth = base64.b64encode(b"用户名:密码").decode()
headers = {
"Proxy-Authorization": f"Basic {auth}",
**HEADERS,
}
# 代理地址只填 host:port,不携带认证信息
PROXY_URL = "http://tps.zdaye.com:8080"
对于需要控制轮换节奏的场景,可以在用户名后拼接period参数(如用户名-period-300),表示每300秒更换一次出口IP,而不是每个请求都换。
六、常见问题与避坑指南
接口返回空data或直接报错。 首先检查请求头是否完整,尤其是Referer和X-Requested-With。其次降低请求频率,加入1-2秒的随机延迟。如果仍然为空,可能是当前IP段被标记,切换隧道代理的出口地域后重试。
下载的图片质量偏低。 thumbURL是缩略图字段,分辨率有限。如果训练任务对清晰度要求较高,可以尝试提取replaceUrl中的原图链接,但需要对该字段为空的情况做回退处理。更稳妥的做法是在采集阶段同时保存thumbURL和hoverURL,后期按需选用。
去重阈值怎么定? pHash汉明距离阈值设为5是一个较保守的选择,会保留视觉上略有差异的图片,适合需要多样性的训练集。如果目标是剔除所有"看起来差不多"的图,可以适当降低到3甚至2,但可能丢失一些真实的不同样本。
采集规模与合规边界。 训练集采集应控制在个人学习研究所必需的范围内。百度图片搜索的结果版权归属复杂,采集的图片不得用于商业模型训练或对外分发。合理控制采集量、遵守robots.txt、不绕过登录和付费内容,是这类项目能够持续进行的前提。
七、总结
从百度图片构建图像识别训练集,核心链路可以概括为四个环节:
| 环节 | 关键做法 |
|---|---|
| 接口采集 | acjson接口,pn控制分页偏移,rn=30,解析thumbURL |
| 反爬对抗 | 完整请求头 + 随机延迟 + 站大爷隧道代理轮换IP |
| 高并发下载 | ThreadPoolExecutor(5-10线程)+ stream=True流式写入 |
| 训练集构建 | pHash去重(汉明距离阈值5)+ 尺寸/清晰度清洗 + 按类别归档 |
百度图片能解决"数据从哪来"的问题,但决定训练集质量的,是去重和清洗环节。一个经过pHash去重、尺寸过滤和清晰度筛选的5000张数据集,往往比未经处理的20000张原始采集更有训练价值。配合站大爷隧道代理的稳定IP轮换,可以把采集链路从"跑一半被封"变成可持续运行的工程流程,让数据准备不再是模型训练的前置瓶颈。