【问题标题】:Node: Loop over lines of a file, process each asynchronously, wait for each result?节点:循环文件的行,异步处理每行,等待每个结果?
【发布时间】:2020-08-29 17:07:31
【问题描述】:

我有一个包含姓名和出生日期信息的文件。对于文件中的每一行,我需要将数据提交到 Web 表单并查看我得到的结果。我正在使用 Node 和 Puppeteer(无头)以及 readline 来读取文件。

该代码适用于小文件,但是当我在完整的 5000 个名称甚至几百个名称上运行它时,我最终会得到数百个无头的 Chromium 实例,使我的机器陷入瘫痪,并可能造成令人困惑的超时错误。

我宁愿等待每个表单提交完成,或者以其他方式限制处理,以便一次处理的名称不超过 x 个。我尝试了几种方法,但没有一个能达到我想要的效果。我根本不是 JS 高手,所以可能存在有问题的设计。

有什么想法吗?

const puppeteer = require('puppeteer');
const fs = require('fs');
const readline = require('readline');
const BALLOT_TRACK_URL = 'https://www.example.com/ballottracking.aspx';
const VOTER_FILE = 'MailBallotsTT.tab';
const VOTER_FILE_SMALL = 'MailBallotsTTSmall.tab';
const COUNTY = 'Example County';


checkBallot = (async ( fName, lName, dob, county ) => {
  /* Initiate the Puppeteer browser */
  const browser = await puppeteer.launch({headless:true });
  const page = await browser.newPage();

  await page.goto( BALLOT_TRACK_URL, { waitUntil: 'networkidle0' });


  // fill out the form
  await page.type('#ctl00_ContentPlaceHolder1_FirstNameText', fName );
  await page.type('#ctl00_ContentPlaceHolder1_LastNameText', lName );
  await page.type('#ctl00_ContentPlaceHolder1_DateOfBirthText', dob );
  await page.type('#ctl00_ContentPlaceHolder1_CountyDropDown', county );

  let pageData = await page.content();

  // Extract the results from the page
  try {
  submitSelector = 'input[name="ctl00$ContentPlaceHolder1$RetrieveButton"]';
  tableSelector  = '#ctl00_ContentPlaceHolder1_ResultPanel > div > div > div > table > tbody > tr:nth-child(3) > td:nth-child(7) > div';

  foundSubmitSelector = await page.waitForSelector(submitSelector, { timeout: 5000 } );
  clickResult = await page.click( submitSelector );
  foundTable = await page.waitForSelector(tableSelector, { timeout: 5000 } )

  let data = await page.evaluate( ( theSelector ) => {
    let text = document.querySelector( theSelector ).innerHTML.replaceAll('<br>', '').trim();
    /* Returning an object filled with the scraped data */
    return {
      text
    }
  }, tableSelector );
  return data;
} catch (error) {
  return {
    text: error.message
  }
} finally {
  browser.close();
}
});

const mainFunction =  () => {

  const readInterface = readline.createInterface({
    input: fs.createReadStream( VOTER_FILE_SMALL ),
    output: null,
    console: false
  });
  
  readInterface.on('line', async(line) => {
    split = line.split( '\t' );
    fName = split[0];
    lName = split[1];
    dob = split[2];
    checkResult = await checkBallot( fName, lName, dob, COUNTY );
    console.log( line + '\t' + checkResult.text );
    to = await new Promise(resolve => setTimeout(resolve, 5000));
  });

};

mainFunction();

【问题讨论】:

  • 如何在文件上使用同步readLine,并让main函数一次只读取和处理文件中的一行?在每行完成时,主函数再次调用自己,超时时间为 0。当主函数最终到达所有行的末尾时,它可以退出而不调用自己。如果你想重叠说 5 次调用,你可以从调用 main 函数 5 次开始。每次其中一个完成时,它会再次调用 main 函数来读取文件并处理下一行。

标签: javascript node.js asynchronous async-await


【解决方案1】:

这里有一些代码实现了我在评论中的建议。我使用 setTimeout 来表示您拥有的异步代码,但原则上该方法应该很容易适应:

// Source file testReadFileSync.js

const fs = require( "fs" );

const data = fs.readFileSync( "./inputfile.txt", { encoding: "utf-8" } );
const lines = data.split( "\n" );
console.log( `There are ${lines.length} lines to process`);

var currentLine = 0;

function main(){
    // Check if we have processed all the lines
    if (currentLine == lines.length ) return;

    // get the current line number, then increment it;
    let lineNum = currentLine++;
    
    // Process the line
    asyncProcess( lineNum,  lines[ lineNum ] )

}


function asyncProcess( lineNum, line ){
    
    console.log( `Start processing line[${lineNum}]` );

    let delayMS = getRandomInt( 100 ) * 10
    setTimeout( function(){
        

        // ------------------------------------------------
        // this function represents all the async processing 
        // for one line.
        // After async has finished, we call main again
        // ------------------------------------------------

        console.log( `Finished processing line[${lineNum}]` );
        main();

    }, delayMS )

}

function getRandomInt(max) {
    return Math.floor(Math.random() * Math.floor(max));
}

// Start four parallel async processes, each of which will process one line at a time
main();
main();
main();
main();

【讨论】:

  • 谢谢!我会试试这个。看起来这会将整个文件读入内存,但它只有大约 5K 行,所以这应该没问题。感谢您花时间提供示例代码——我会告诉您它是怎么回事。
  • 如果您也想在文件读取端保留异步操作,您可以调整代码以将行异步读取到wait-queue,并让处理函数读取下一个队列中的行而不是数组中的行。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-12-24
  • 2021-10-03
  • 2015-08-11
  • 2019-10-27
相关资源
最近更新 更多