You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Node.js Fetch API批量下载图片的异步问题及优化咨询

批量图片下载优化方案

先解决异步循环等待问题

你之前用async/await没成功,大概率是循环写法不对(比如用了forEach,它不支持异步等待)。改用for...of循环就能让每一步下载完成后再执行下一次循环:

const fs = require('fs').promises;
const path = require('path');
const axios = require('axios');

async function downloadSingleImage(volumeId, chapterId, pageId) {
  const url = `https://你的域名/${volumeId}/${chapterId}/${pageId}.jpg`;
  const savePath = path.join('./downloads', volumeId, chapterId);
  
  // 先创建目录
  await fs.mkdir(savePath, { recursive: true });
  
  try {
    const response = await axios.get(url, { responseType: 'stream' });
    // 检查响应状态码判断图片是否存在
    if (response.status === 200) {
      const writeStream = fs.createWriteStream(path.join(savePath, `${pageId}.jpg`));
      await new Promise((resolve, reject) => {
        response.data.pipe(writeStream);
        writeStream.on('finish', resolve);
        writeStream.on('error', reject);
      });
      console.log(`成功下载:${url}`);
      return true;
    }
  } catch (err) {
    // 404等错误直接返回false,停止当前章节的页数遍历
    if (err.response?.status === 404) {
      return false;
    }
    console.log(`下载失败:${url},错误:${err.message}`);
    // 非404错误可以考虑重试,这里简单跳过
    return true;
  }
}

async function downloadVolume(volumeId, chapterCount) {
  for (let chapterId = 1; chapterId <= chapterCount; chapterId++) {
    let pageId = 1;
    while (pageId <= 30) {
      const exists = await downloadSingleImage(volumeId, chapterId.toString(), pageId.toString());
      if (!exists) {
        // 图片不存在,当前章节结束
        break;
      }
      pageId++;
    }
  }
}

// 主函数,遍历所有卷
async function main() {
  // 假设你从数据库拿到的卷列表是这个格式:[{volumeId: 'v1', chapterCount: 5}, ...]
  const volumes = await getVolumesFromDB();
  for (const volume of volumes) {
    await downloadVolume(volume.volumeId, volume.chapterCount);
  }
}

main().catch(err => console.error('批量下载失败', err));

用流下载的优势

你提到的流方案确实更适合批量场景:

  • 内存占用低:不需要把整张图片加载到内存,边接收边写入磁盘,避免大量图片同时下载导致内存溢出
  • 稳定性高:大文件下载时,流不会因为内存不足崩溃
  • 进度可控:可以监听流的data事件获取下载进度(如果需要的话)

进阶:并发控制优化

如果想提高下载效率,又不想因为并发过高被服务器限制,可以引入并发控制,比如限制同时下载5张图:

const pLimit = require('p-limit');
const limit = pLimit(5); // 限制并发数为5
const completedChapters = new Set();

async function downloadVolumeWithConcurrency(volumeId, chapterCount) {
  const tasks = [];
  for (let chapterId = 1; chapterId <= chapterCount; chapterId++) {
    for (let pageId = 1; pageId <= 30; pageId++) {
      tasks.push(limit(async () => {
        const key = `${volumeId}-${chapterId}`;
        // 如果章节已标记完成,直接跳过
        if (completedChapters.has(key)) return false;
        
        const exists = await downloadSingleImage(volumeId, chapterId.toString(), pageId.toString());
        if (!exists) {
          completedChapters.add(key);
        }
        return exists;
      }));
    }
  }
  await Promise.all(tasks);
}

注:p-limit是一个轻量的并发控制包,也可以自己实现简单的并发队列逻辑。

其他注意事项

  • 重试机制:针对网络波动导致的失败,可以给下载函数加重试逻辑(比如失败后延迟1秒重试2次)
  • 日志记录:把失败的URL记录到文件,方便后续补下
  • 请求头模拟:加上浏览器的User-Agent等请求头,避免被服务器识别为异常请求(虽然你有授权,友好请求更稳妥)

内容的提问来源于stack exchange,提问作者Nicolò Rancan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 23:30:27