用Nodejs爬文件遇到的问题,请各位大大来看看

用Nodejs爬文件遇到的问题,请各位大大来看看

我想用nodejs 的http.get爬一堆文件下来,但是这个网站做了限制每次只能同时下载2个文件,而nodejs是异步的,问题来了…例如:


var urlArr = [ /* 一堆文件的url */ ];

for(/* 遍历urlArr */ ) {
  http.get(url, ...); // http.get 下载文件 
}

怎样做到每次只爬2个文件,或者第一次爬2个文件,然后这两个文件某个爬完了,又自动去爬下一个,总之每次只能有2个下载的请求,因为多了的那些请求是没法下载文件。。

期待大家的解答~~ 灰常感谢~~


17 回复

当然可以。针对你的需求,我们可以使用 asyncawait 来控制并发请求的数量。下面是一个简单的示例代码,展示了如何实现每次只下载两个文件,并在下载完成后继续下载下一个文件。

示例代码

const https = require('https');
const fs = require('fs');
const path = require('path');

// 文件URL数组
const urlArr = [
  'https://example.com/file1.zip',
  'https://example.com/file2.zip',
  'https://example.com/file3.zip',
  // 添加更多文件URL
];

// 创建一个队列来管理请求
const downloadQueue = [];
let activeDownloads = 0;

// 控制并发数量的函数
async function downloadFile(url) {
  return new Promise((resolve, reject) => {
    const filename = path.basename(new URL(url).pathname);
    const file = fs.createWriteStream(filename);

    https.get(url, (response) => {
      response.pipe(file);
      file.on('finish', () => {
        file.close(() => resolve(`Downloaded ${filename}`));
      });
    }).on('error', (err) => {
      file.close();
      reject(err.message);
    });
  });
}

// 检查并启动新的下载任务
function startNextDownload() {
  if (downloadQueue.length > 0 && activeDownloads < 2) {
    const url = downloadQueue.shift();
    activeDownloads++;
    downloadFile(url)
      .then(() => {
        console.log(`Completed: ${url}`);
        activeDownloads--;
        startNextDownload(); // 启动下一个下载任务
      })
      .catch((err) => {
        console.error(`Error downloading ${url}: ${err}`);
        activeDownloads--;
        startNextDownload(); // 启动下一个下载任务
      });
  }
}

// 将所有URL添加到队列中
urlArr.forEach((url) => {
  downloadQueue.push(url);
});

// 启动初始的两个下载任务
startNextDownload();

解释

  1. 创建下载队列:我们首先将所有需要下载的文件URL放入一个队列中。
  2. 控制并发数量:我们定义了一个 activeDownloads 变量来跟踪当前正在执行的下载任务数量。每次开始一个新的下载任务时,该变量会增加;当下载完成时,该变量会减少。
  3. 下载文件:我们使用 https.get 方法来下载文件,并将其保存到本地。downloadFile 函数返回一个Promise,用于处理异步操作。
  4. 启动下载任务startNextDownload 函数负责检查队列并启动新的下载任务,确保每次只有两个下载任务同时进行。
  5. 初始化:我们将所有URL添加到队列中,并调用 startNextDownload 来启动初始的两个下载任务。

这样,我们就能够有效地控制并发下载的数量,并且在每次下载完成后自动启动下一个下载任务。希望这对你有所帮助!


用老赵的 jscex

正在尝试,但是不知道如何实现,求请教~

var urlArr = […]; count =0; for(…){ while(count==2){ // 休息10分钟 } // 在callback 里面 count-=1; http.get(url,…); count +=1; }

不知道可以不?

文件有大有小,时间不好设置,太长了浪费时间,太短了,那就没法控制2个请求

哈哈,async 这个不是很熟悉,我自己用EventEmitter搞定了~~


/**
 * 下载文件
 *
 * [@param](/user/param) {array} urlArr 一堆文件的url
 * [@param](/user/param) {int} threadCount 线程总数
 * [@param](/user/param) {function} callback 下载完成后的回调函数
 */
var download = function (urlArr, callback) {

  // 模拟的线程
  var thread = function () {
    var url = urlArr.shift();   // 从URL列表中取一个出来
    if (typeof url !== 'string') {
      return threadDone();
    }
    http.get(url, function () { // http.get 下载文件 
      // 处理下载回来的文件
      // ...
      thread();
    });
  };

  var finishThread = 0;     // 已完成的线程数

  var threadDone = function () {
    finishThread++;
    if (finishThread >= threadCount) {
      return callback();
    }
  };

  for (var i = 0; i < threadCount; i++) {
    thread();
  }
};

使用方法:

download(['url1', 'url2', 'url3'], 2, function () {
  // 下载完成了...
});

综合起来貌似用async的forEachLimit最简单~~

我觉得用node.io最简单, 内建一个参数就支持几个线程下载

就跟很多人说了用async.forEachLimit就行了,forEachLimit里面用Jscex写逻辑。如果你真要用Jscex——虽然我不觉得Jscex是处理这些事情的,就在循环里用whenAny吧,whenAny就是等待任意一个完成。还有真的,看看文档吧,不要搞到最后文档是白写的……

不用EventProxy, 用Bagpipe马到功成。 https://github.com/JacksonTian/bagpipe

var urlArr = [ /* 一堆文件的url */ ];

var Bagpipe = require('bagpipe');
var bagpipe = new Bagpipe(2);

for(/* 遍历urlArr */ ) {
  // http.get(url, ...); // http.get 下载文件 
  bagpipe.push(http.get, url, ..., callback);
}

代码基本不用怎么改

太复杂 有没有。

太复杂 有没有。

要实现每次只同时处理两个文件下载请求,可以使用 asyncawait 结合 Promise 来管理并发数量。这里提供一个简单的示例代码来说明如何实现这个功能:

const http = require('http');
const fs = require('fs');
const urlArr = [/* 你的文件URL数组 */];

async function downloadFile(url) {
    return new Promise((resolve, reject) => {
        const file = fs.createWriteStream(`./${Date.now()}.tmp`);
        http.get(url, response => {
            response.pipe(file);
            file.on('finish', () => {
                file.close(resolve());
            });
        }).on('error', err => {
            fs.unlink(file.path, () => { /* 文件已下载完毕,但发生了错误,删除已下载的部分文件 */ });
            reject(err.message);
        });
    });
}

async function handleDownloads(urls, limit = 2) {
    let downloading = []; // 用于存储正在下载的Promise

    async function processQueue(queue) {
        while (queue.length > 0) {
            const url = queue.shift();
            downloading.push(downloadFile(url));

            if (downloading.length >= limit) {
                const done = await Promise.race(downloading);
                downloading = downloading.filter(promise => promise !== done);
            }
        }

        await Promise.all(downloading);
    }

    await processQueue(urls);
}

handleDownloads(urlArr);

这段代码首先定义了一个 downloadFile 函数,它返回一个 Promise,表示文件下载完成或失败。接着定义了一个 handleDownloads 函数,该函数使用一个队列来管理待处理的 URL,并确保同时最多只有 limit 个文件在下载。

当你调用 handleDownloads(urlArr) 时,它将开始下载文件,每次最多同时下载两个。当一个下载完成后,下一个文件会自动加入到下载队列中。

这种模式能够有效地管理并发请求,避免超过服务器规定的最大连接数限制。

回到顶部