Многоуровневая очистка с Node.js, request-promises и cheerio: как заставить функцию записи файла ждать, пока все запросы не будут выполнены? - PullRequest
0 голосов
/ 21 мая 2019

Я пытаюсь очистить три уровня веб-страницы, которые ссылаются друг на друга, например, Домой -> Работа -> Открытые позиции. Затем я хочу записать очищенные данные в файл output.json. Очистка работает просто отлично, но запись файла завершена до того, как запросы будут выполнены из-за их асинхронной природы.

Приведенный ниже код с использованием обычных запросов очищает все данные, но слишком «запаздывает», и поэтому информация не записывается в файл.

request(url, function(error, response, html){
        var $ = cheerio.load(html);
        $("tr").each(function(i, elem){
            var club_url = $(this).children().first().children().attr("href");
            club_url = url.substring(0,25) + club_url;
            request(club_url, function(error, response, html){
                if(!error){
                    var $ = cheerio.load(html);
                    var club_name = $("h1.masthead-title").first().text().trim();
                    console.log(club_name);
                    clubs[i] = club_name;
                    var teams = {};
                    $("tr").each(function(i,elem){
                        var team_url = $(this).children().first().children().attr("href");
                        team_url = url.substring(0,25) + team_url;
                        request(team_url, function(error,response,html){
                            if(!error){
                                var $ = cheerio.load(html);
                                var team = $(".team-name").text().trim();
                                console.log(team);
                                teams[i] = team;
                            }
                        });
                    }); 
                }
            }); 
        });
        fs.writeFile('output.json', JSON.stringify(clubs, null, 4), function(err){
            console.log('File successfully written! - Check your project directory for the output.json file');
        });

Поэтому я попытался использовать запрос-обещание и переписать код с ним, чтобы запись выполнялась после разрешения обещаний запроса.

app.get('/scrape', function(req, res){

    var clubs = {};
    url = 'https://norcalpremier.com/clubs/';

    var options = {
        uri: 'https://norcalpremier.com/clubs/',
        transform: function (body) {
            return cheerio.load(body);
        }
    };

    rp(options).then(($) => {
        var ps = [];
        $("tbody tr").each(function(i, elem){
            var club_url = $(this).children().first().children().attr("href");
            club_url = url.substring(0,25) + club_url;
            console.log(club_url);
            var club_options = {
                uri: club_url,
                transform: function (body) {
                    return cheerio.load(body);
                }
            };
            ps.push(rp(club_options));
        });
        Promise.all(ps).then((results) =>{
                results.forEach((club)=>{
                    var $ = cheerio.load(club);
                    var club_name = $("h1.masthead-title").first().text().trim();
                    console.log(club_name);
                    clubs[i] = club_name;
                })
        }).then(()=>{
            fs.writeFile('output.json', JSON.stringify(clubs, null, 4), function(err){
                console.log('File successfully written! - Check your project directory for the output.json file');
            });
            res.send('Scraping is done, check the output.json file!');
        }).catch(err => console.log(err));

    })

})

Тем не менее, я просто не заставляю его работать и получаю ошибку шлюза, как ни странно, после того, как консоль зарегистрировала, что файл был записан. Некоторые из них, я полагаю, не работают сейчас и не ожидают завершения запросов. Примечание: третий запрос вырезан в этой версии, потому что мне нужно запустить второй уровень первым.

Чего я хочу добиться, так это получить информацию с каждого из сайтов на уровнях 2 и 3, в основном имя, поместить ее в объект JSON, а затем записать ее в файл. Как было сказано ранее, очистка соответствующих данных на уровне 2 и 3 работала в предыдущей версии, но не записывала в файл.

Спасибо, ваша помощь очень ценится!

1 Ответ

0 голосов
/ 21 мая 2019

Вот что я хотел бы сделать, сделать функцию асинхронной, а затем сделать:

url = 'https://norcalpremier.com/clubs/'

// put the request code in a function so we don't repeat it
let $ = await get(url)

// get the club urls
let club_urls = $('td:nth-child(1) a[href*="/club/"]').map((i, a) => new URL($(a).attr('href'), url).href).get()

// await the responses. I used slice because I think this much concurrency will cause problems
let resolved = await Promise.all(club_urls.slice(0,2).map(club_url => get(club_url)))

// get the club names
let club_names = resolved.map($ => $("h1.masthead-title").first().text().trim())

// write the file, I think synchronously is a good idea here.
fs.writeFileSync('output.json', JSON.stringify(club_names))

Я позволю вам выяснить функцию get, поскольку я не люблю использовать запрос-обещание

...