Nodejs 爬虫下载图片的问题

Nodejs 爬虫下载图片的问题

写一个爬虫,当只下载比较少的图片时候,一切正常,当下载比较多的图片的时候,会报错。我这里试了两种方式,都会出问题。 1.采用child_process,调用系统的curl下载图片。 代码: var curl=cp.spawn(‘curl’,[file_url]); curl.stdout.on(‘data’,function(data){ file.write(data); }); curl.stdout.on(‘end’,function(data){ file.end(); console.log(filename+‘download to’+DOWNLOAD_DIR); }); 这里省去了on(‘error’)和on(‘exit’) 。最终只能下载几个图片,很多都是失败的,然后报错。 这个报的错误是这样: Error: spawn EMFILE at errnoException (child_process.js:988:11) at ChildProcess.spawn (child_process.js:935:11) at Object.exports.spawn (child_process.js:723:9)

2.所以又采用第二种方式,用http来get这些图片。 var filename=url.parse(file_url).pathname.split(’/’).pop(); var file=fs.createWriteStream(DOWNLOAD_DIR+filename); http.get(file_url,function(res){ res.on(‘data’,function(data){ file.write(data); }); res.on(‘end’,function(data){ file.end(); }); res.on(‘error’,function(e){ if(e){ console.log(‘Errored:’+e); file.end(); }else{ console.log(‘Finish’); } }); }); 这样的效果更不好,也报错了,报的错误是: read econnreset 请教各位,当下载很多图片的时候,要怎么处理呢?


5 回复

写一个爬虫,当只下载比较少的图片时候,一切正常,当下载比较多的图片的时候,会报错。我这里试了两种方式,都会出问题。 1.采用child_process,调用系统的curl下载图片。 代码: var curl=cp.spawn(‘curl’,[file_url]); curl.stdout.on(‘data’,function(data){ file.write(data); }); curl.stdout.on(‘end’,function(data){ file.end(); console.log(filename+‘download to’+DOWNLOAD_DIR); }); 这里省去了on(‘error’)和on(‘exit’) 。最终只能下载几个图片,很多都是失败的,然后报错。 这个报的错误是这样: Error: spawn EMFILE at errnoException (child_process.js:988:11) at ChildProcess.spawn (child_process.js:935:11) at Object.exports.spawn (child_process.js:723:9)

2.所以又采用第二种方式,用http来get这些图片。 var filename=url.parse(file_url).pathname.split(’/’).pop(); var file=fs.createWriteStream(DOWNLOAD_DIR+filename); http.get(file_url,function(res){ res.on(‘data’,function(data){ file.write(data); }); res.on(‘end’,function(data){ file.end(); }); res.on(‘error’,function(e){ if(e){ console.log(‘Errored:’+e); file.end(); }else{ console.log(‘Finish’); } }); }); 这样的效果更不好,也报错了,报的错误是: read econnreset 请教各位,当下载很多图片的时候,要怎么处理呢?


限制并发用 async.parallelLimit https://github.com/caolan/async#parallellimittasks-limit-callback

不知道Promise有没有 parallelLimit

当你使用 Node.js 编写爬虫并下载大量图片时,可能会遇到一些常见的问题,比如 EMFILE 错误(文件描述符不足)和 ECONNRESET 错误(连接被重置)。这些问题通常是由于资源限制或网络不稳定导致的。以下是一些解决方案和优化方法:

解决方案

  1. 使用异步库:使用如 axiosnode-fetch 来管理 HTTP 请求,它们通常比原生的 http 模块有更好的错误处理机制。

  2. 限制并发请求:通过使用像 async 库中的 queue 功能或 p-limit 这样的库来限制同时进行的请求数量,以避免一次性打开太多文件描述符。

  3. 错误处理和重试机制:实现错误处理和重试逻辑,以便在网络不稳定时可以自动重试。

示例代码

const axios = require('axios');
const fs = require('fs');
const path = require('path');
const { promisify } = require('util');

// 使用 promisify 将 fs.writeFile 转换为 Promise
const writeFile = promisify(fs.writeFile);

async function downloadImage(url, directory) {
    try {
        const response = await axios({
            url,
            responseType: 'stream'
        });

        const filename = path.basename(new URL(url).pathname);
        const filePath = path.join(directory, filename);

        // 创建可写流
        const writer = fs.createWriteStream(filePath);

        // 管道响应数据到文件
        response.data.pipe(writer);

        return new Promise((resolve, reject) => {
            writer.on('finish', resolve);
            writer.on('error', reject);
        });
    } catch (error) {
        console.error(`Error downloading ${url}:`, error.message);
        throw error;
    }
}

async function downloadImages(urls, directory) {
    for (const url of urls) {
        await downloadImage(url, directory);
    }
}

// 示例:下载多个图片
const urls = [
    'https://example.com/image1.jpg',
    'https://example.com/image2.jpg',
    // 添加更多 URL
];

const DOWNLOAD_DIR = './downloads';
if (!fs.existsSync(DOWNLOAD_DIR)) {
    fs.mkdirSync(DOWNLOAD_DIR);
}

downloadImages(urls, DOWNLOAD_DIR)
    .then(() => console.log('All images downloaded successfully'))
    .catch(error => console.error('Failed to download images:', error));

解释

  1. 使用 axiosaxios 是一个流行的 HTTP 客户端,它支持 promise 并且有更好的错误处理能力。

  2. 创建可写流:使用 fs.createWriteStream 创建一个可写流,并将 HTTP 响应的数据管道到该流中。

  3. 错误处理和重试:在 downloadImage 函数中捕获错误,并在主函数 downloadImages 中处理错误,确保所有错误都被记录。

  4. 目录检查:在下载之前检查目标目录是否存在,如果不存在则创建目录。

通过这种方式,你可以有效地管理和下载大量图片,同时避免常见的错误。

[@magicdawn](/user/magicdawn) 的确应该是这个并发的原因 我代码里面是没一点限制的

当你需要下载大量图片时,使用 child_processhttp.get 都可能遇到资源限制或并发问题。一种更好的方法是使用一个专门设计用于处理 HTTP 请求的库,比如 axios 或者 node-fetch,并且结合 async/await 来更好地管理异步操作。

以下是使用 axios 下载大量图片的一个简单示例:

示例代码

首先,你需要安装 axios 库:

npm install axios

然后你可以编写如下的代码来下载图片:

const fs = require('fs');
const axios = require('axios');

async function downloadImages(urls, downloadDir) {
    const downloadPromises = urls.map(async (url) => {
        try {
            const response = await axios({
                url,
                responseType: 'stream'
            });

            const filename = url.split('/').pop();
            const filePath = `${downloadDir}/${filename}`;

            return new Promise((resolve, reject) => {
                response.data.pipe(fs.createWriteStream(filePath))
                    .on('finish', () => resolve(`Successfully downloaded ${filename}`))
                    .on('error', (err) => reject(err));
            });
        } catch (err) {
            console.error(`Failed to download ${url}:`, err.message);
        }
    });

    try {
        await Promise.all(downloadPromises);
        console.log('All images have been successfully downloaded.');
    } catch (err) {
        console.error('One or more images failed to download:', err);
    }
}

// Example usage:
const urls = [
    'https://example.com/image1.jpg',
    'https://example.com/image2.jpg',
    // Add more URLs here
];

const DOWNLOAD_DIR = './downloads';
downloadImages(urls, DOWNLOAD_DIR);

解释

  1. 安装库:使用 axios 库来进行 HTTP 请求,它比原生的 http 模块更容易使用且功能更强大。
  2. 异步函数:使用 async/await 来处理异步操作,使代码更易读。
  3. Promise 管理:将每个图片的下载封装在一个 Promise 中,并使用 Promise.all 等待所有下载完成。
  4. 并发控制:通过合理设置并发请求的数量(例如使用 p-limit 库),可以避免一次性发起过多请求导致的资源耗尽问题。

这种方法可以有效地解决下载大量图片时出现的问题。如果你仍然遇到问题,建议检查网络连接、服务器限制以及本地磁盘空间等。

回到顶部