前言
你好,我是喵喵侠。最近在找一套素材,看到了一个网站上的素材很实用,于是想要下载下来。一个个的下载属实太麻烦,毕竟图片有好几十张呢。一个个点速度慢,而且很容易遗漏。所以我写了一个Shell脚本来帮我解决这个问题。
思路
由于我是前端开发,我想尽量的使用前端相关的技术来做,一开始我只想了两个方案,分别是:
- 写一个HTML的网页进行批量下载
- 用Node.js 来实现批量下载
这两种方式都可以实现我要的功能,下面我来分享下我的实现过程。
实现过程
方案一:HTML实现
第一个方法是最简单的,只需要本地打开一个这个html页面,简单输入参数后,即可触发下载,于是我写了以下的页面:
源码如下:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>图片批量下载工具</title> <style> body { font-family: "微软雅黑", Arial, sans-serif; background: #f7f7f7; padding: 40px; } .container { background: #fff; border-radius: 8px; box-shadow: 0 2px 8px #0001; max-width: 480px; margin: auto; padding: 32px 24px; } h2 { text-align: center; color: #1684fc; } label { display: block; margin: 16px 0 8px 0; } input[type="text"], input[type="number"] { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; font-size: 16px; box-sizing: border-box; } button { width: 100%; padding: 12px; background: #1684fc; color: #fff; border: none; border-radius: 4px; font-size: 18px; cursor: pointer; margin-top: 20px; } button:active { background: #1060b0; } #downloadStatus { margin-top: 18px; text-align: center; color: #16c2fc; } </style> </head> <body> <div class="container"> <h2>图片批量下载工具</h2> <label>图片URL前缀:</label> <input id="urlPrefix" type="text" value="https://xxx.com/webp/"> <label>起始编号:</label> <input id="startNum" type="number" value="0"> <label>结束编号:</label> <input id="endNum" type="number" value="77"> <label>图片格式(如webp、jpg、png):</label> <input id="imgExt" type="text" value="webp"> <button onclick="batchDownload()">批量下载</button> <div id="downloadStatus"></div> <div style="color:#888;font-size:13px;margin-top:18px;"> <b>提示:</b>浏览器会自动下载图片到默认下载目录,无法指定文件夹。<br> 如遇浏览器拦截,请允许多文件下载。 </div> </div> <script> function batchDownload() { const prefix = document.getElementById('urlPrefix').value.trim(); const start = parseInt(document.getElementById('startNum').value, 10); const end = parseInt(document.getElementById('endNum').value, 10); const ext = document.getElementById('imgExt').value.trim().replace(/^\./, ''); const status = document.getElementById('downloadStatus'); if (isNaN(start) || isNaN(end) || start > end) { status.innerText = '编号输入有误!'; return; } status.innerText = '正在下载,请稍候...'; let count = 0; let total = end - start + 1; let fail = 0; for (let i = start; i <= end; i++) { const url = `${prefix}${i}.${ext}`; fetch(url) .then(res => { if (!res.ok) throw new Error('下载失败: ' + url); return res.blob(); }) .then(blob => { const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = `${i}.${ext}`; document.body.appendChild(a); a.click(); a.remove(); count++; if (count + fail === total) { status.innerText = fail === 0 ? '全部下载完成!' : `下载完成,${fail} 个文件失败`; } }) .catch(e => { fail++; if (count + fail === total) { status.innerText = fail === 0 ? '全部下载完成!' : `下载完成,${fail} 个文件失败`; } }); } } </script> </body> </html>
这样可能会有个问题,当本地或者自己服务器部署的这个页面,访问对方网站服务器资源时,可能会触发跨域。
所以这种方法不是很好,于是我做了新的探索。
方法二:Node.js实现
如果你的电脑安装了Node.js,就可以用这个方法,这样的好处是不存在跨域的问题,用node内置的模块就能实现功能。
源码如下:
const fs = require('fs'); const path = require('path'); const https = require('https'); const http = require('http'); const readline = require('readline'); // 获取用户输入 const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); function ask(question) { return new Promise(resolve => rl.question(question, resolve)); } (async () => { const urlPrefix = await ask('请输入图片URL前缀(如 https://xxx/images/):'); const startNum = parseInt(await ask('请输入起始编号(如 0):'), 10); const endNum = parseInt(await ask('请输入结束编号(如 77):'), 10); const ext = await ask('请输入图片格式(如 webp):'); const saveDir = './我的下载图片'; if (!fs.existsSync(saveDir)) fs.mkdirSync(saveDir); let success = 0, fail = 0; function download(url, dest) { return new Promise((resolve, reject) => { const mod = url.startsWith('https') ? https : http; mod.get(url, res => { if (res.statusCode !== 200) { reject(new Error('状态码: ' + res.statusCode)); return; } const file = fs.createWriteStream(dest); res.pipe(file); file.on('finish', () => file.close(resolve)); }).on('error', reject); }); } for (let i = startNum; i <= endNum; i++) { const url = `${urlPrefix}${i}.${ext.replace(/^\./, '')}`; const dest = path.join(saveDir, `${i}.${ext.replace(/^\./, '')}`); try { // eslint-disable-next-line no-await-in-loop await download(url, dest); console.log(`下载成功: ${i}.${ext}`); success++; } catch (e) { console.log(`下载失败: ${i}.${ext} (${e.message})`); fail++; } } console.log(`\n下载完成,成功 ${success} 个,失败 ${fail} 个。图片保存在 ${saveDir} 文件夹。`); rl.close(); })();
输入好参数后,可以看到图片下载完成。
需要注意的是,我写的这个版本都需要手动输入参数,不输入直接回车,就会下载失败。
这个方法也还不错,只是依赖Node.js环境,后面发现还有更简单的方法。
方法三:Bash + Wget实现(推荐)
我用的是macOS,自带Bash环境,如果你是Windows,也可以用Git Bash或者WSL中来执行Wget命令。
这个方法就简单多了,5 行命令搞定!
mkdir 我的下载图片 cd 我的下载图片 for i in {0..77}; do wget https://xxxx.com/images/xxx/webp/$i.webp done
总结
下载图片资源这个工具还挺实用的,可以解决批量下载的问题。如果你有多个图片的URL,也可以用类似这样的思路去实现自己的小工具。我这个图片资源正好是按照数字的索引来的,所以实现起来会更简单一些。如果你有更好的实现思路,欢迎评论区分享。