#javascript #node.js #asynchronous #async-await
#javascript #node.js #асинхронный #async-ожидание
Вопрос:
У меня есть файл с именем и информацией о дате рождения. Для каждой строки в файле мне нужно отправить данные в веб-форму и посмотреть, какой результат я получу. Я использую 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();
Комментарии:
1. Как использовать синхронную строку чтения в файле и заставить основную функцию читать и обрабатывать только одну строку из файла за раз? По завершении каждой строки основная функция снова вызывает себя с таймаутом 0. Когда основная функция, наконец, достигает конца всех строк, она может завершиться без вызова самой себя. Если вы хотите перекрыть, скажем, 5 вызовов, вы могли бы начать с вызова основной функции 5 раз. Каждый раз, когда одна из них завершается, она снова вызывает функцию main для чтения файла и обработки следующей строки.
Ответ №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();
Комментарии:
1. Спасибо! Я попробую. Похоже, что это приведет к считыванию всего файла в память, но это всего лишь около 5 Тыс. строк, так что все должно быть в порядке. Спасибо, что нашли время предложить пример кода — я дам вам знать, как это происходит.
2. Если вы хотите сохранить асинхронную операцию и на стороне чтения файла, вы могли бы адаптировать свой код для чтения строк асинхронно в очередь ожидания , и заставить функции обработки считывать следующую строку из очереди, а не из массива.