当前位置: 首页 > 知识库问答 >
问题:

我如何处理一个有30,000个URL的文件而不发生内存泄漏?

尉迟晔
2023-03-14

在。txt文件中有30.000个url要刮,当我制作程序时,我用10个url测试它,一切都很好,当我制作30K url文件时。txt几分钟后崩溃,我想它开始读取。txt文件,然后由于内存问题崩溃,这里是控制台输出和我的代码。处理这样的文件最好的方法是什么?

致命错误:无效标记-压缩接近堆限制分配失败-JavaScript堆内存不足1:0x100BA0C4A节点::abort()(.cold.1)[/usr/local/bin/node]2:0x100084961节点::fatalerror(char const*,char const*)[/usr/local/bin/node]3:0x100084A89节点::onfatalerror(char const*,char const*)[/usr/local/bin/node]4:0x10017Fa4d V8::Utils::ReportoomFailure(V8::internal::isolate*10017f9f7 v8::internal::v8::fatalprocessoutofmemory(V8::internal::isolate*,char const*,bool)[/usr/local/bin/node]6:0x100299BAF v8::internal::heap::fatalprocessoutofmemory(char const*)[/usr/local/bin/node]7:0x10029AF4c v8::internal::heap::markcompactPrologue()/usr/local/bin/node]9:0x1002975AB V8::internal::heap::collectgarbage(V8::internal::AllocationSpace,V8::internal::GarbageCollectionReason,V8::GcCallback标志)[/usr/local/bin/node]10:0x100296A2A V8::internal::heap::handlegcrequest()[/usr/local/bin/node]11:0x10026D9A5 V8::internal::stackguard::handleinterrupts()[/usr/local/bin/node]12:0x1004E1383 V8::internal::runtime_stackguard(int,无符号long*,V8::internal::isolate*)[/usr/local/bin/node]bin/node]14:0x10073c5fb Builtins_StringPrototypeMatch[/usr/local/bin/node]15:0x267b75f209cb zsh:abort node scrape.js

let cheerio = require('cheerio');
let request = require('request');
let UserAgent = require('user-agents');
let axios = require('axios');
const fileUrlErrors = "UrlsWithErrors.txt";
const async = require('async')
let Promise = require("bluebird");

let userAgent = new UserAgent({ deviceCategory: 'desktop' });
let options = {
  headers: { userAgent }
};
let exec = require('child_process').exec;

const mysql = require('mysql2/promise');
let con = mysql.createPool({
      host: "xxx.xxx.xxx.xxx",
      user: "xxx",
      password: "xxxx",
      database: "xxx"
    });


async function run() {

    let file = fs.readFileSync('urls.txt');
    let urls = file.toString().split('\r\n');

    console.log(urls);

    const numeroUrl = urls.length;
    let urlsArray = [];
    console.log("numeroUrl : " + numeroUrl);
    for (let i = 1; i < numeroUrl; i++) {
      for (let y = 1; y < 6; y++) {
        urlsArray.push(urls[y-1] + '&page=' + y);
      }
    }
    Promise.map(urlsArray, parseUrl, {concurrency: 10}).then(function(data) {
        // all done here
        console.log("Done!!!");
    });
}


async function parseUrl(url) {
  try {
    let response = await axios.get(url, {
      headers: { 
        'User-Agent': new UserAgent() 
      }  
    });
    console.log(url + " " + response.status);
    if (response.status >= 201) {

      fs.appendFile(fileUrlErrors, '\n' + url + ' - ' + response.status, (error) => {
        if (error) {
            console.error(`Could not save the url status error to a file: ${error}`);
            return;
        }

        console.log('Saved Url error to ' + fileUrlErrors);
        });  
      
    } else if (response.status == 200) {

      let $ = cheerio.load(response.data);

      prodotti = $(".item");

      let items = $(prodotti).get();

      for (let item of items) {

        let title = $(".title", item).text();
        if (!title) {
          title = $(".title2", item).text();
        }
        
        let price = $(".price", item).text();
        if (!price) {
          price = $(".price2", item).text();
        }
        

        if (title) {
          const prodotto = [
          [
          title,
          price]
          ];
          let result = await con.query("INSERT INTO Items (title, price) VALUES ? ON DUPLICATE KEY UPDATE price=VALUES(price)", [prodotto]);
          console.log('Prodotto ' + title + ' inserito nel DB.');
          console.log(prodotto);
        }

        }
    } 

    } catch (error) {
        //console.error(error);
        if (error.response) {
          // Request made and server responded
            await fs.appendFile(fileUrlErrors, '\n' + url + " - " + error.response.status, (error) => {
            if (error) {
                console.error(`Could not save the url status error to a file: ${error}`);
                return;
            }
            console.log('Saved Url error to ' + fileUrlErrors);
            });  
        }

    }
}

run().then(() => {
    console.log("Done!");
}).catch(err => {
    console.log(err);
});

共有1个答案

柯阳曦
2023-03-14

正如在注释中所讨论的,parseurl()函数混合了promise和普通的异步回调,这是一个灾难。你真的不能把它们混在一起。最好的解决方案是使用promise执行所有的异步控制流,如果您有一些非promise返回异步回调,那么可以使用util.promisify()手动promise它们,使用API的正确promise版本,或者获得包含promise支持的库的正确版本。

您已经将所有内容转换为promise控制流,然后可以使用Asyncawait和其他promise控制流工具,只有这样,parseurl()函数才会返回一个promise,该promise只有在所有底层异步操作完成后才会解析,并且只有这样,您才会有正确的错误传播。

下面的示例修复了parseurl(),以便对所有异步操作正确使用promise,然后在for循环中使用await一次处理一个URL,这样就不会同时占用太多内存:

const fs = require('fs');

async function run() {
    const file = fs.readFileSync('urls.txt');
    const urls = file.toString().split('\r\n');

    // count the number of urls inside .txt file
    const numberOfUrls = urls.length;
    console.log("There are : " + numberOfUrls + " urls");

    // Add page to url and use the scrape function
    for (let i = 1; i < numberOfUrls; i++) {
        for (let y = 1; y < 6; y++) {
            let url = urls[y - 1] + '&page=' + y;
            await parseUrl(url);
        }
    }

    async function parseUrl(url) {
        try {
            const response = await axios.get(url, {
                headers: {
                    'User-Agent': new UserAgent()
                }
            });
            if (response.status >= 201) {
                await fs.promises.appendFile(fileUrlErrors, '\n' + url + ' - ' + response.status);
            }  else if (response.status == 200) {
                const $ = cheerio.load(response.data);
                const prodotti = $(".result");

                // get items into a normal array so we can use a normal for loop
                const items = $(prodotti).get();
                for (let item of items) {
                    const title = $("title", item).text();
                    const code = $(".code", item).text();
                    if (asin[1]) {
                        const prodotto = [
                            [title, code]
                        ];
                        // promise support in your mysql database requires the mysql2 module
                        const result = await con.query("INSERT INTO Items (title, code) VALUES ? ON DUPLICATE KEY UPDATE code=VALUES(code)", [prodotto]);
                        console.log('Prodotto ' + code + ' inserito nel DB.');
                        console.log(prodotto);
                    }

                }
            }
        } catch (error) {
            console.error(error);
            throw error;          // propagate error back to caller
        }
    }
}

run().then(() => {
    console.log("all done");
}).catch(err => {
    console.log(err);
});

注意,NPM上的mysql2模块是获得对mysql数据库(我猜这就是您正在使用的数据库)promise支持的最佳位置。您可能还必须更改设置数据库连接的其他代码以使用promise(此处未显示的代码)。

变更摘要:

  1. 切换到fs.promises.appendfile()以获得promise支持。
  2. 将所有变量声明更改为letconst。不使用var.
  3. 正确声明所有变量,以便没有意外的全局值。
  4. 切换到使用mysql2,以便可以使用对con.query()
  5. 的promise支持
  6. ifif更改为ifelse if,因为这正是您的逻辑。
  7. file.tostring().split('\r\n')已经是一个数组,所以不需要在它上使用object.keys()来获取长度。您可以直接引用数组上的.length属性。
 类似资料:
  • 我有一个小型java应用程序,运行一组计算量大的任务。为了处理这些任务,我使用了一个外部库,它通过本机方法和一些C代码完成大部分计算。不幸的是,在解决一个任务后,库会遭受严重的内存泄漏,因此每次应用程序执行只能解决一个任务。 库中的编码人员知道内存问题,但尚未修复,可能永远也不会修复(这与java垃圾收集器不能正确使用本机inferface有关)。由于这个特定的库没有其他选择,我正在寻找通过顺序执

  • 本文向大家介绍内存泄漏和内存溢出是什么?一般怎么处理内存泄漏?相关面试题,主要包含被问及内存泄漏和内存溢出是什么?一般怎么处理内存泄漏?时的应答技巧和注意事项,需要的朋友参考一下 (1)内存溢出(OOM)和内存泄露(对象无法被回收)的区别。 (2)引起内存泄露的原因 (3)内存泄露检测工具 ------>LeakCanary 内存溢出 out of memory:是指程序在申请内存时,没有足够的内

  • 问题内容: 我多次问到这个问题。有什么好的答案 问题答案: Java中会不会发生内存泄漏? 答案是,这取决于您正在谈论的是哪种内存泄漏。 经典C / C ++内存泄漏是在应用程序或对象完成使用后忽略时发生的,并且会泄漏。循环引用是这种情况的一个子案例,其中应用程序很难知道何时使用/ ,因此忽略了这样做。相关问题是应用程序在释放对象后使用对象,或尝试释放对象两次。(您可以将后者称为内存泄漏,或者仅是

  • 本文向大家介绍请你说说C++如何处理内存泄漏?相关面试题,主要包含被问及请你说说C++如何处理内存泄漏?时的应答技巧和注意事项,需要的朋友参考一下 使用varglind,mtrace检测

  • 我有一个关于接口的问题,比如: 现在,我需要这只猫去抓一只老鼠void catchMouse(){std::cout 有几种可能的解决方案,但看起来都不好。 > 在IAnim中添加一个方法,然后使用AnimalFactory创建猫后,我可以从IAnimal调用catchMouse()方法。但是catchMouse并不适用于所有动物,狗不catchMouse。将方法添加到IEM会污染界面,嗅到代码。

  • 我试图在一个特定文件夹中保存多个图像第一个图像保存正确,但下一个图像只是替换第一个图像。如何保存多个图像?如何动态命名并使用相同的名称保存图像,但使用不同的扩展名,如image、image1、image2。。。下面是我的代码