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

使用Node.js + Axios批量下载图片时无报错突然停止

问题分析与解决方案

核心问题原因

  1. 无限制并发请求
    一次性发起4500个HTTP请求,远超服务器和本地系统的资源承载上限:目标服务器会触发限流(IP临时封禁、请求丢弃),本地网络连接数、文件描述符也会被耗尽,直接导致下载中途停滞,部分请求无响应、流写入中断,产生残缺或空文件。

  2. 错误处理缺失
    当前代码仅监听了文件流的写入错误,未捕获axios请求本身的异常(如网络断开、HTTP 4xx/5xx状态码);加上Promise.allSettled会自动忽略失败的Promise,导致你看不到任何报错,无法感知请求失败。

  3. 流写入无异常兜底
    当请求中途失败时,已创建的空WriteStream未被清理,残留空文件;部分中断的流也会生成残缺文件。


具体修复方案

1. 控制并发请求数

用工具(如p-limit)将同时发起的请求数控制在10-20之间,避免资源耗尽和被限流。

2. 完善全链路错误处理

捕获axios请求、流写入的所有异常,记录失败任务;请求失败时自动清理空文件。

3. 添加重试机制

对失败的请求进行有限次数重试,应对临时网络波动或服务器限流。

4. 校验文件完整性

下载完成后对比文件大小(通过响应头Content-Length),确保文件完整,残缺文件自动删除。


修改后的代码示例

const fs = require("fs");
const path = require("path");
const axios = require("axios");
const pLimit = require("p-limit"); // 先安装依赖:npm install p-limit

// 配置项
const collection = "rebels";
const folder = `${collection}_json`;
const outputDir = `${collection}_images2`;
const concurrencyLimit = 15; // 并发数可根据网络调整
const retryTimes = 2; // 重试次数

// 创建输出目录(不存在则自动创建)
if (!fs.existsSync(outputDir)) {
  fs.mkdirSync(outputDir, { recursive: true });
}

// 获取目录下所有JSON文件
const getFiles = (folder) => {
  const directoryPath = path.join(__dirname, folder);
  return fs.readdirSync(directoryPath);
};

// 解析JSON提取图片URL
const urlsArray = getFiles(folder).map(file => {
  const filePath = path.join(__dirname, folder, file);
  const fileContent = fs.readFileSync(filePath, "utf8");
  const parsedJson = JSON.parse(fileContent);
  const imageFromMetadata = parsedJson.image;
  const url = imageFromMetadata.replace("ipfs://", "https://ipfs.io/ipfs/");
  const nr = file.replace(".json", "");
  return { url, nr };
});

// 带重试的下载函数
const downloadImageWithRetry = async (url, nr, retryCount = 0) => {
  const outputPath = path.join(outputDir, `${nr}.png`);
  
  // 清理之前残留的空文件
  if (fs.existsSync(outputPath) && fs.statSync(outputPath).size === 0) {
    fs.unlinkSync(outputPath);
  }

  try {
    const response = await axios({
      url,
      method: "GET",
      responseType: "stream",
      validateStatus: status => status === 200 // 仅接受200状态码的响应
    });

    // 获取响应头中的文件大小
    const contentLength = parseInt(response.headers["content-length"], 10);
    if (!contentLength) throw new Error("无法获取文件大小");

    return new Promise((resolve, reject) => {
      const writer = fs.createWriteStream(outputPath);
      let receivedSize = 0;

      // 统计已接收数据大小
      response.data.on("data", chunk => {
        receivedSize += chunk.length;
      });

      response.data.pipe(writer);

      writer.on("finish", () => {
        // 校验文件完整性
        if (receivedSize !== contentLength) {
          fs.unlinkSync(outputPath);
          reject(new Error(`文件大小不匹配,已删除残缺文件: ${nr}.png`));
        } else {
          console.log(`下载完成: ${nr}.png`);
          resolve();
        }
      });

      // 捕获文件写入错误
      writer.on("error", err => {
        fs.unlinkSync(outputPath);
        reject(new Error(`写入文件失败: ${err.message}`));
      });

      // 捕获数据流错误
      response.data.on("error", err => {
        writer.destroy();
        fs.unlinkSync(outputPath);
        reject(new Error(`数据流错误: ${err.message}`));
      });
    });
  } catch (err) {
    if (retryCount < retryTimes) {
      console.log(`下载失败,正在重试(${retryCount+1}/${retryTimes}): ${nr}.png,原因: ${err.message}`);
      return downloadImageWithRetry(url, nr, retryCount + 1);
    } else {
      console.error(`最终下载失败: ${nr}.png,原因: ${err.message}`);
      throw err;
    }
  }
};

// 并发控制执行下载
const limit = pLimit(concurrencyLimit);

const downloadAll = async () => {
  const promises = urlsArray.map(data => {
    return limit(() => downloadImageWithRetry(data.url, data.nr));
  });

  const results = await Promise.allSettled(promises);
  
  // 统计任务结果
  const failedTasks = results.filter(res => res.status === "rejected").length;
  console.log(`全部任务完成,成功: ${results.length - failedTasks},失败: ${failedTasks}`);
};

downloadAll();

额外建议

  • 若当前IPFS网关限制严格,可更换为https://cloudflare-ipfs.com/ipfs/这类备用网关。
  • 运行前执行npm install axios p-limit安装依赖。
  • 可将失败任务记录到本地文件,方便后续单独处理。

内容的提问来源于stack exchange,提问作者ThomasDEV

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 00:15:46