Прерывание `request` в цикле forEach для повышения эффективности - PullRequest
0 голосов
/ 02 мая 2018

Я создаю простой веб-сканер для автоматизации рассылки, что означает, что мне нужно ограничить количество страниц. В этом примере это не имеет большого значения, потому что скрипт будет сканировать только 3 дополнительные страницы. Но для другого случая это было бы крайне неэффективно.

Итак, мой вопрос, будет ли способ прекратить выполнение request() в этом цикле forEach?

Или мне нужно изменить подход к сканированию страниц по одной, как указано в этом руководстве.

Сценарий

'use strict';
var request = require('request');
var cheerio = require('cheerio');
var BASEURL = 'https://jobsite.procore.com';

scrape(BASEURL, getMeta);

function scrape(url, callback) {
  var pages = [];
  request(url, function(error, response, body) {
    if(!error && response.statusCode == 200) {

      var $ = cheerio.load(body);

      $('.left-sidebar .article-title').each(function(index) {
        var link = $(this).find('a').attr('href');
        pages[index] = BASEURL + link;
      });
      callback(pages, log);
    }
  });
}

function getMeta(pages, callback) {
  var meta = [];
  // using forEach's index does not work, it will loop through the array before the first request can execute
  var i = 0;
  // using a for loop does not work here
  pages.forEach(function(url) {
    request(url, function(error, response, body) {
      if(error) {
        console.log('Error: ' + error);
      }

      var $ = cheerio.load(body);

      var desc = $('meta[name="description"]').attr('content');
      meta[i] = desc.trim();

      i++;

      // Limit
      if (i == 6) callback(meta);
      console.log(i);
    });
  });
}

function log(arr) {
  console.log(arr);
}

выход

$ node crawl.js 
1
2
3
4
5
6
[ 'Find out why fall protection (or lack thereof) lands on the Occupational Safety and Health Administration (OSHA) list of top violations year after year.',
  'noneChances are you won’t be seeing any scented candles on the jobsite anytime soon, but what if it came in a different form? The allure of smell has conjured up some interesting scent technology in recent years. Take for example the Cyrano, a brushed-aluminum cylinder that fits in a cup holder. It’s Bluetooth-enabled and emits up to 12 scents or smelltracks that can be controlled using a smartphone app. Among the smelltracks: “Thai Beach Vacation.”',
  'The premise behind the hazard communication standard is that employees have a right to know the toxic substances and chemical hazards they could encounter while working. They also need to know the protective things they can do to prevent adverse effects of working with those substances. Here are the steps to comply with the standard.',
  'The Weitz Company has been using Procore on its projects for just under two years. Within that time frame, the national general contractor partnered with Procore to implement one of the largest technological advancements in its 163-year history.  Click here to learn more about their story and their journey with Procore.',
  'MGM Resorts International is now targeting Aug. 24 as the new opening date for the $960 million hotel and casino complex it has been building in downtown Springfield, Massachusetts.',
  'So, what trends are taking center stage this year? Below are six of the most prominent. Some of them are new, and some of them are continuations of current trends, but they are all having a substantial impact on construction and the structures people live and work in.' ]
7
8
9

Ответы [ 3 ]

0 голосов
/ 02 мая 2018

Я предполагаю, что вы пытаетесь прекратить выполнение после некоторого количества страниц (в вашем примере это похоже на шесть). Как уже говорилось в некоторых других ответах, вы не можете предотвратить выполнение обратного вызова из Array.prototype.forEach (), однако при каждом выполнении вы можете запретить выполнение вызова запроса.

function getMeta(pages, callback) {
    var meta = []
    var i = 0
    pages.forEach(url => {
        // MaxPages you were looking for
        if(i <= maxPages)
            request((err, res, body) => {
                // ... Request logic
            })
    })

Вы также можете использовать цикл while для переноса итерации по каждой странице, и как только я достигну значения, которое вы хотите, цикл завершится и не будет запускаться на дополнительных страницах

0 голосов
/ 02 мая 2018

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

Извините, я не мог удержаться, подумав об этом секунду.

Мы можем начать с рефакторинга:

const rp = require('request-promise-native');
const {load} = require('cheerio');

function scrape(uri, transform) {
  const options = {
    uri,
    transform: load
  };

  return rp(options).then(transform);
}

scrape(
  'https://jobsite.procore.com',
  ($) => $('.left-sidebar .article-title a').toArray().slice(0,6).map((linkEl) => linkEl.attribs.href)
).then((links) => Promise.all(
  links.map(
    (link) => scrape(
      `https://jobsite.procore.com/${link}`,
      ($) => $('meta[name="description"]').attr('content').trim()
    )
  )
)).then(console.log).catch(console.error);

Хотя это делает код немного более СУХИМЫМ и лаконичным, он указывает на одну часть, которую, возможно, необходимо улучшить: запрос ссылок.

В настоящее время он будет запускать запрос на все (или до) 6 ссылок, найденных на исходной странице, почти сразу. Это может или не может быть тем, что вы хотите, в зависимости от того, сколько ссылок будет запрашиваться в какой-то другой момент, на который вы ссылались.

Другая потенциальная проблема - управление ошибками. Поскольку рефакторинг остается в силе, в случае сбоя любого из запросов все запросы будут отклонены.

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

0 голосов
/ 02 мая 2018

Нет способа остановить forEach. Вы можете имитировать остановку, установив флажок внутри forEach, но он все равно будет проходить по всем элементам. Кстати, использование цикла для операции ввода-вывода не является оптимальным.

Как вы заявили, лучший способ обработать набор увеличивающихся данных для обработки - это сделать это один за другим, но я добавлю поворот: Threaded-one-by-one.

ПРИМЕЧАНИЕ: под нитью я не подразумеваю фактические темы. Возьми больше определение «нескольких направлений работы». Поскольку операции ввода-вывода не блокируются основной поток, пока один или несколько запросов ожидают данных, другая «линия работы» может запустить JavaScript для обработки данных получил, так как JavaScript является однопоточным WebWorkers).

Это так же просто, как наличие массива страниц, который принимает страницы для сканирования на лету, и одна функция, которая читает одну страницу этого массива, обрабатывает результат и затем возвращается к начальной точке (загрузка следующей страницы массив и обработка результата).

Теперь вы просто вызываете эту функцию количеством потоков, которые вы хотите запустить, и сделали. Псевдо-код:

var pages = [];

function loadNextPage() {
    if (pages.length == 0) {
        console.log("Thread ended");
        return;
    }
    var page = shift(); // get the first element
    loadAndProcessPage(page, loadNextPage);
}

loadAndProcessPage(page, callback) {
    requestOrWhatever(page, (error, data) => {
        if (error) {
            // retry or whatever
        } else {
            processData(data);
            callback();
        }
    });
}

function processData(data) {
    // Process the data and push new links to the pages array
    pages.push(data.link1);
    pages.push(data.link2);
    pages.push(data.link3);
}

console.log("Start new thread");
loadNextPage();

console.log("And another one");
loadNextPage();

console.log("And another one");
loadNextPage();

console.log("And another thread");
loadNextPage();

Этот код остановится, когда в массиве не будет больше страниц, и если в какой-то момент окажется, что страниц меньше, чем количество потоков, потоки закроются. Здесь и там нужны некоторые настройки, но вы понимаете, в чем дело.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...