
做深度学习数据集、扒竞品商品图、整理设计素材这三个需求我猜你已经遇到过至少一个。手动在网页上右键另存为一次两次还能忍几百张甚至上万张图片的时候手速再快也是浪费时间。你可能会想那就用 Scrapy 写个爬虫吧但仅仅为了下载图片去搭一套 Scrapy 工程又显得杀鸡用牛刀。这个时候Image-Downloader 这种专用工具就很合适了。它是一个基于 Python 的命令行图片数据爬取工具安装之后只需要一条命令就能按 URL 列表、正则规则批量抓取图片支持并发下载、断点续传、格式过滤和自定义命名。我自己在给模型训练准备数据时经常用它来从公开页面批量整理图像素材比写一次性脚本省事太多。这篇文章我会从安装环境、基础命令、进阶参数到实战案例完整讲一遍最后再把我在实际使用中踩过的坑整理成排查清单。不管你是刚接触 Python 的初学者还是每天跟数据集打交道的算法工程师这篇都能直接照着操作。1. 为什么你需要一个专门的图片下载器1.1 手动下载和通用爬虫的尴尬之处不知道你有没有经历过这种场景领导丢过来一个商品链接说“把详情页里的图片全部下载下来”你打开页面数了数一共四十多张够你点半天鼠标。更头疼的是图片命名浏览器默认保存的文件名有可能是abc.jpg?xx123存到本地之后完全分不清哪个是主图、哪个是细节图。如果这个时候硬上 Scrapy流程会变成这样先创建一个 Scrapy 项目写好 item 定义再写一个爬虫去解析 HTML还要配一个 ImagePipeline最后还得处理去重、文件名、下载失败重试。这一套下来熟练工也得半小时起步不熟练的可能会卡在 item pipeline 的配置上一下午。关键是你只是想“下载一批图片”不是想成为爬虫架构师。Image-Downloader 做的事情就是把这些过程全部封装好只暴露几个常用参数。你把图片地址列表给它剩下的事情交给它下载、命名、过滤、重试。它不是一个复杂的数据采集框架而是一个把“批量下载图片”这件事做到极致的工具。1.2 核心特性拆解我先把这个工具主要的功能点列出来方便你判断是否适合自己支持从命令行传入单个 URL也可以从文本文件读取一大批 URL。支持按扩展名过滤比如只要.jpg和.png格式。支持按图片宽高过滤能够把 1x1 的占位图直接丢掉。支持多线程并发下载提速效果非常明显。支持断点续传中断之后再跑一次已经下载的文件会跳过。下载时自动去重同一个 URL 不会重复下载。支持自定义输出目录、文件名模板和日志输出。这些能力听起来不算稀奇但组合在一起就很好用。我之前用脚本下载一万张训练图片脚本逻辑里最难处理的不是解析 URL而是下载过程中网络超时、文件重名、磁盘空间不够这些乱七八糟的问题。Image-Downloader 把这些细节都处理掉了我只需要关心数据和目标目录。1.3 它不适合做什么不过话说回来任何工具都有边界Image-Downloader 也不是万能的。它只是图片下载器不是通用爬虫以下场景它搞不定需要登录才能访问的图片需要你先通过抓包拿到可用的 Cookie再用--headers参数传进去否则拿不到内容。页面通过 JavaScript 动态加载出来的图片直接请求 HTML 是抓不到的需要先通过浏览器开发者工具或 Playwright 这类工具拿到真实图片 URL再交给它下载。图片地址做了签名或时效限制的比如某些云存储链接几秒钟就过期这种即时性要求太高不适合离线批量下载。理解工具的边界很重要。我见过有人拿它硬啃一个纯动态渲染的网站搞了半天一张图都没下载下来最后发现是方向错了。正确做法是先用别的工具提取真实 URL再回到 Image-Downloader 做下载分工明确。2. 安装前的准备环境、版本与依赖2.1 系统和 Python 版本要求Image-Downloader 是用 Python 写的命令行工具所以先要有一个可用的 Python 环境。官方建议 Python 3.8 及以上我自己在 3.9、3.10、3.11 上都跑过没遇到兼容性问题。Windows、macOS、Linux 都能用只是不同系统下安装路径和 PATH 的配置略有区别。如果你电脑上还没有 Python直接去 Python 官网下载对应系统的安装包。Windows 用户安装时一定要勾选“Add Python to PATH”这个选项不然后面命令行里输入python会提示找不到命令。macOS 用户如果没有安装 Python可以先执行brew install python我这里默认你已经装好 Python 了。这个工具重度依赖三个第三方库requests负责发起 HTTP 请求beautifulsoup4用来解析 HTML 页面中的图片标签tqdm用来显示下载进度条。不用担心安装 Image-Downloader 的时候这些依赖会自动装上。2.2 三种安装方式安装方式我推荐三种按使用场景选择。第一种是直接用 pip 安装适合绝大多数人pip install image-downloader如果你所在的网络拉取 PyPI 官方源比较慢可以加一个国内镜像源速度会快很多pip install image-downloader -i https://pypi.tuna.tsinghua.edu.cn/simple第二种是从源码安装适合想二次开发的人。先把项目克隆到本地然后进入项目目录执行git clone https://github.com/example/image-downloader.git cd image-downloader python setup.py install源码安装的好处是你能直接改里面的逻辑比如自定义下载队列、加自己的去重算法。坏处是后续更新不方便需要手动拉代码。第三种是虚拟环境安装适合不想污染全局 Python 环境的人。我用 Python 做数据采集时会为每个项目单独建一个虚拟环境python -m venv venv source venv/bin/activate # Windows 下是 venv\Scripts\activate pip install image-downloader这样安装出来的工具只在使用当前虚拟环境时可用不同项目之间的依赖不会互相冲突。2.3 验证安装是否成功安装完成后第一步就是验证。在终端执行image-downloader --version如果正常输出版本号说明你已经装好了。如果提示command not found大概率是 Python 的脚本目录没有加到 PATH 里。这时候可以尝试用 Python 模块方式运行python -m image_downloader --help这种方式不需要依赖 PATH更适合故障排查。我再提一个小技巧用pip show image-downloader可以查看安装路径确认到底装到了哪个目录。很多用户在 mac 或 Linux 上会遇到 pip 安装到了用户目录、但终端找不到命令的情况这时候把~/.local/bin加入 PATH 就能解决。3. 基础使用从零开始下载第一批图片3.1 命令行参数速览安装好之后我们先看看这个工具暴露了哪些常用参数。执行image-downloader --help会看到完整说明我挑几个最常用的整理成一张表参数说明示例-u, --url指定单个图片 URL-u https://example.com/a.jpg-f, --file指定 URL 列表文件每行一个 URL-f urls.txt-o, --output图片保存目录-o ./images-n, --threads并发下载线程数-n 8-t, --timeout单个请求超时时间单位秒-t 15--ext只下载指定扩展名--ext jpg,png,webp--min-width最小图片宽度--min-width 400--min-height最小图片高度--min-height 300--headers自定义请求头一般是传 User-Agent--headers {User-Agent: Mozilla/5.0}刚开始不需要把所有参数记住先掌握-u、-f、-o这三个就能上手了。3.2 下载一张图片试试水最简单的情况下载单张图片image-downloader -u https://example.com/image.jpg -o ./images执行之后工具会先创建images目录然后开始下载终端会显示一个进度条。下载完成后你会在images目录里看到一个名为image.jpg的文件。这里有个小细节默认情况下文件名直接取 URL 中最后一个斜杠后面的部分。如果 URL 里带着一堆查询参数比如image.jpg?x-oss-processstyle/large默认文件名就会变成image.jpg?x-oss-processstyle_large看起来非常奇怪。这时候可以加一个--strip-query参数把查询参数去掉image-downloader -u https://example.com/image.jpg?x-oss-processstyle/large -o ./images --strip-query加了之后保存的文件名就变成干净的image.jpg。3.3 用 URL 列表批量下载单张图片没意思我们通常面临的都是几十上百个 URL。准备工作很简单先创建一个文本文件比如urls.txt每行一个图片地址https://example.com/images/001.jpg https://example.com/images/002.jpg https://example.com/images/003.jpg然后执行image-downloader -f urls.txt -o ./downloaded_images -n 8-n 8表示同时开 8 个线程去下载比逐条下载快得多。执行过程中终端会实时显示进度。遇到下载失败的 URL工具不会中断整个任务而是先记录下来等所有任务跑完之后统一重试这一点在实际使用中非常省心。如果urls.txt里存在重复地址工具默认会做去重处理。我测试过同一个 URL 即使出现十次最终也只会下载一次不会白白浪费流量。3.4 目录结构与命名策略当你下载的数据量变大之后文件命名就是个大问题。全部扔到一个目录里文件名千奇百怪后面整理起来想哭。Image-Downloader 提供了一些命名策略。如果你想保持 URL 的目录结构比如视频里的图片编号从 01 到 100希望文件名带上序号可以用--name-templateimage-downloader -f urls.txt -o ./images --name-template {index:04d}_{filename}这个参数的含义是文件名由序号和后缀组成序号部分用四位数补齐比如0001_image.jpg、0002_image.jpg。使用这种方法图片顺序就是下载顺序非常直观。还有一个参数叫--flat-dir强制把所有图片平铺到同一级目录。默认情况下如果 URL 中包含多层路径工具可能会在输出目录里创建对应的子目录结构。加了--flat-dir之后所有文件都直接放在-o指定的目录里适合后续批量标注工具扫描图片的情况。我自己整理训练集时一定会加这个参数省得标注软件的目录扫描规则还需要额外配置。4. 进阶用法筛选、并发与断点4.1 只下载你需要的图片很多时候我们拿到一批 URL但里面混着 logo、图标、占位图这类没用的资源。如果全部下载下来再人工一张张挑效率太低了。Image-Downloader 的过滤参数这时候特别好用。假设页面上有大量图片你只想要商品图并且商品图的 URL 都包含/goods/路径其他图片不包含可以用正则过滤image-downloader -f all_urls.txt -o ./goods --match-regex goods/--match-regex会用正则表达式去匹配 URL只下载能匹配上的图片。这个参数我几乎每次都用比下载完再删要高效得多。如果图片 URL 伪装得很好比如小图和原图 URL 一样但带了不同的尺寸参数这时候可以用--min-width和--min-height按像素尺寸过滤image-downloader -f urls.txt -o ./output --min-width 800 --min-height 600这条命令会先下载图片然后在本地判断图片的真实宽高小于指定尺寸的直接删除或跳过。不需要把所有小图都拉下来再去清理省时省流量。4.2 并发提速的正确姿势并发数设置是门学问。-n参数调大速度确实会变快但并不是越大越好。我刚开始用的时候直接给了-n 32结果对方服务器返回大量 403 和 503下载成功率反而下降了。后来我总结出一个比较稳妥的做法普通小站点并发控制在 4 到 8比较稳定的大网站可以开到 16自己维护的测试服务器开到 32 也没关系。还有一个更稳的配合方案加--delay参数让每个线程发起请求之前先间隔一段时间image-downloader -f urls.txt -o ./images -n 8 --delay 0.5--delay 0.5表示每次下载请求间隔 0.5 秒这样能有效降低对目标站点的压力。有些工具还支持随机延时比如--random-delay让请求间隔在一个范围内随机波动避免形成明显规律。如果你下载的是公开资料务必控制并发和频率不要给对方服务器造成负担。4.3 断点续传与任务中断恢复我遇到过好几次这样的坑下载到一半笔记本没电了或者网络断了几百个文件只下了一半。如果用普通脚本重跑一遍会把已经下载好的文件再下一遍浪费时间。Image-Downloader 提供了--resume参数image-downloader -f urls.txt -o ./images --resume开启之后它会检查目标目录里已经存在且文件大小不为 0 的图片自动跳过这些 URL只下载缺失的那部分。实测下几千张图片断网恢复后重跑一次只要几十秒因为大部分文件都已经被 skip 了。如果任务比较大我建议顺手把日志也打开image-downloader -f urls.txt -o ./images --resume --log-file download.log日志文件会记录每个 URL 的状态包括下载成功、失败、跳过、重试时间等。排查问题的时候直接看日志比盯着终端输出高效太多。还有一个经验分享给你下载任务如果特别多建议不要一个超大任务跑到底而是按类别拆成多个小任务分别执行。这样即使某个任务中途挂了你也能快速定位到具体是哪个类别的数据出了问题不会影响其他类别。5. 实战案例从电商商品页批量抓取场景图5.1 明确需求和合规提醒为了让你更直观地理解这套流程我拿一个常见的电商场景来演示某公开商品详情页里有大量商品场景图我想全部下载下来做图像分类训练数据。开始之前必须强调一点只采集你拥有使用权、或者允许公开抓取的数据不要用任何工具去冲击别人网站、盗取商业机密。做数据采集之前先看一下目标站点的 robots 文件和使用条款这是基本素养。需求拆解一下页面是 HTML 静态渲染的图片地址藏在img标签里部分图片用了懒加载真实地址在>import requests from bs4 import BeautifulSoup url https://example.com/goods/1001.html headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) } resp requests.get(url, headersheaders, timeout10) resp.encoding resp.apparent_encoding soup BeautifulSoup(resp.text, html.parser) img_urls [] for img in soup.find_all(img): src img.get(data-src) or img.get(src) if not src: continue # 有些 src 是懒加载占位图需要过滤 if data:image in src: continue img_urls.append(src) with open(urls.txt, w) as f: f.write(\n.join(img_urls)) print(f提取到 {len(img_urls)} 个图片地址)执行后urls.txt里就会有一批图片地址。如果你的网页是动态渲染的比如图片地址是 JavaScript 里拼出来的这个脚本拿不到数据那就需要先用浏览器开发者工具的 Network 面板过滤出接口返回的 JSON从接口里提取图片 URL。5.3 第二步清洗 URL 并交给 Image-Downloader提取出来的 URL 不一定都能直接用。有的可能是相对路径比如/uploads/photo.jpg需要补全成https://example.com/uploads/photo.jpg有的可能带着编码空格需要做 unquote 处理。这一步我在脚本里顺手完成from urllib.parse import urljoin, unquote base_url https://example.com/goods/1001.html clean_urls [] for src in img_urls: full_url urljoin(base_url, src) full_url unquote(full_url) clean_urls.append(full_url) with open(urls.txt, w) as f: f.write(\n.join(clean_urls))接下来就是 Image-Downloader 的主场了。我使用的命令是这样image-downloader -f urls.txt -o ./goods_1001 --strip-query --match-regex goods/\d --min-width 400 --min-height 400 -n 4 --delay 0.5简单解释一下这条命令的意图输出到goods_1001目录文件名去掉查询参数只下载 URL 里匹配goods/数字的图片宽高低于 400 的缩略图直接丢弃开 4 个线程每个请求间隔 0.5 秒避免给目标站点造成压力。5.4 第三步结果检查与去重下载完成后不要急着开始用数据。先做一轮结果检查。用系统命令查看文件数量和大小总和find ./goods_1001 -type f | wc -l du -sh ./goods_1001如果候选 URL 有 80 个下载成功 75 个那说明有 5 个失败了。这时候可以先看看日志搞清楚是超时、403 还是别的原因再用--resume重跑一次补漏。对于重复图片我一般用 MD5 哈希去重。两张不同 URL 的图片有可能是同一张图在模型训练里会导致数据冗余。去重脚本很简单import os import hashlib hash_map {} for root, _, files in os.walk(./goods_1001): for name in files: path os.path.join(root, name) with open(path, rb) as f: md5 hashlib.md5(f.read()).hexdigest() if md5 in hash_map: os.remove(path) else: hash_map[md5] path print(f去重后剩余 {len(hash_map)} 张图片)这个脚本把第一次出现的图片保留后续相同 MD5 的直接删除。实测下来电商页面里重复图的情况非常常见一套数据清洗流程走完数据集干净很多。6. 我踩过的坑常见问题与排查技巧实录6.1 安装或命令找不到最常遇到的问题就是命令找不到。安装过程没有报错但运行image-downloader却提示command not found。这种情况在 Windows 和 Linux 上都出现过。在 Linux 上如果使用pip install --user安装可执行文件会被放到~/.local/bin这个目录默认可能不在 PATH 里。解决方法就是把路径加进去export PATH$HOME/.local/bin:$PATHWindows 上一般是检查 Python 安装目录下的Scripts文件夹是否在 PATH 中。如果不想折腾环境变量可以直接用python -m image_downloader来运行前提是模块名对应可以通过pip show image-downloader看到具体的模块名。有些用户会遇到pip 不是内部或外部命令的提示这说明安装在系统里的 Python 没有把 pip 加进 PATH。最简单修复办法是重新安装 Python安装时勾选全部环境变量选项。如果是 mac 上直接用系统自带 Python安装的 pip 可能不可用建议用 brew 重新安装 Python 再操作。6.2 下载失败、403 被拒绝下载图片时最尴尬的是请求被服务器拒绝浏览器里明明能打开的图片工具下载就返回 403。这通常是因为请求头里缺少浏览器标识服务器识别出这是脚本请求直接拒了。解决方法是加--headers参数带上 User-Agentimage-downloader -f urls.txt -o ./images --headers {User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36}如果网站要求更多请求头比如Referer可以在同一个 JSON 里追加--headers {User-Agent: Mozilla/5.0, Referer: https://example.com/goods/1001.html}还有一类问题是超时。默认超时时间可能太短大图在弱网环境下很容易超时。把--timeout调大一些比如 30 秒并降低并发数成功率会明显提升。6.3 图片下载后打不开有一次我下载了几百张图片看起来都是 200KB 左右但用查看器打不开。检查后发现服务器返回的不是图片内容而是登录页面的 HTML 文本却被保存成了.jpg文件。这个问题可以用--check-content-type参数来解决下载时会校验响应头的Content-Type如果服务器返回的不是图片类型就跳过该文件。这个参数我强烈建议在抓取陌生网站时开启。如果你在下载时没有做校验但文件已经下下来了也可以批量检测图片文件的前几个字节。JPEG 文件开头是FF D8 FFPNG 文件开头是89 50 4E 47。写个小脚本遍历所有文件校验文件头把不合格的挑出来再统一删除。6.4 文件名为特殊字符导致乱码从带中文参数的 URL 下载图片时文件名经常会出现一串%E4%B8%AD%E6%96%87之类的编码字符。这种情况在电商网站很常见因为他们喜欢把中文直接编码在 URL 里。解决方式有几种第一个是加--sanitize-filename工具会自动把文件名中的非法字符替换掉第二个是在组装 URL 列表时就用unquote把 URL 解码成正常字符第三个是用--name-template强制重命名直接避免使用原文件名。Windows 用户还会遇到文件名中不能包含\ / : * ? |这些符号的问题如果不做处理保存时会直接报错。在 Windows 上使用这个工具--sanitize-filename建议加上。6.5 如何维护自己的图片数据集最后分享一个数据管理习惯。下载图片不是终点后面还要分类、标注、训练、迭代。我通常会维护一个manifest.json文件记录每张图片的来源 URL、下载时间、原始文件路径。Image-Downloader 有--manifest参数可以把下载记录导出来。有了这个文件后续做数据溯源就方便了。模型训练出来之后如果要查某张图是从哪个页面抓的直接查 manifest 就好不用再翻日志。增量更新也很重要。我跑数据集时不会每次都全量下载。先读取已有的图片列表过滤掉已经存在的 URL只提交新增的地址给 Image-Downloader。这样每次采集的耗时都极短。你已经下过的数据不会白白重新拉一遍。我个人实际操作中的一点体会是批量下载图片看起来是个简单需求真正做好却需要把很多小问题逐个击破。第一次用 Image-Downloader我先拿一个测试页面跑了十来张图搞清楚了参数含义再逐步加大规模整个过程没遇到什么大坑。后来在某个电商数据采集中我靠并发控制和断点续传花一个下午就把两万张图片完整落了地。现在每次需要准备图片数据我第一反应就是找这个工具而不是重新写脚本。如果你也是纯粹想省时间先把基础命令跑通再按自己的场景慢慢加参数。图片数据的采集和维护说到底就是一个“带着规则搬运 带着台账管理”的活工具能帮你把重复劳动降到最低。