Рубрики
Без рубрики

Как построить простой и настраиваемый веб-скребок с использованием RXJS и узла

Джейкоб Гох Как построить простой и настраиваемый веб-скребок, используя RXJS и NodeIndRoductionAfter, чтобы узнать RXJS (благодаря углованию!) Я понял, что удивительно хорошо подходит для обработки операций Scroping. Я попробовал его в боковом проекте, и я хотел бы

Автор оригинала: FreeCodeCamp Community Member.

Джейкоб Гох

Вступление

После того, как узнал RXJS (благодаря углому!) Я понял, что удивительно хорошо подходит для обработки веб-записки.

Я попробовал его в боковом проекте, и я хотел бы поделиться своим опытом с вами. Надеюсь, это откроет ваши глаза, насколько реактивное программирование может сделать вашу жизнь проще.

Коды можно найти на https://github.com/jacobgoh101/web-scraping-with-rxjs.

Требования

  • Узел
  • RXJS и промежуточное понимание этого
  • Cheerio : Это позволяет использовать синтаксис, похожий на jQuery, чтобы извлечь информацию из HTML-кода
  • Запрос-обещание – родные : Для отправки HTTP-запроса

Гипотетическая цель

Все любят хороший комедийный фильм.

Давайте сделаем это нашей целью соскрести список хороших комедийных фильмов из IMDB.

Существует только 3 требования, которые необходимо выполнить целевые данные:

  • Это фильм (не телешоу, музыкальные клипы и т. Д.)
  • Это комедия
  • имеет рейтинг 7 или выше

Начать

Давайте установим наш базовый URL-адрес и определите поведение поведения ALLURL $ который использует базовый URL в качестве начального значения.

(Поведение …| тема с первоначальным значением.)

const { BehaviorSubject } =  require('rxjs');const  baseUrl  =  `https://imdb.com`;const  allUrl$  =  new  BehaviorSubject(baseUrl);

ALLURL $ будет отправной точкой всех вспучевых операций. Каждый URL будет передан в ALLURL $ и обрабатываться позже.

Убедившись, что мы соскресим каждый URL только один раз

С помощью отчетливый Операторы и Нормализация-URL Мы можем легко убедиться, что мы никогда не скрещиваем один и тот же URL дважды.

// ...const { map, distinct, filter } =  require('rxjs/operators');const  normalizeUrl  =  require('normalize-url');
// ...
const  uniqueUrl$  =  allUrl$.pipe(    // only crawl IMDB url    filter(url  =>  url.includes(baseUrl)),    // normalize url for comparison    map(url  =>  normalizeUrl(url, { removeQueryParameters: ['ref', 'ref_']     })),    // distinct is a RxJS operator that filters out duplicated values    distinct());

Пришло время начать соскоб

Мы собираемся сделать запрос на каждый уникальный URL-адрес и сопоставьте содержимое каждого URL в другой наблюдаемый.

Для этого мы используем Mergemap Чтобы сопоставить результат запроса на другой наблюдаемый.

const { BehaviorSubject, from } =  require('rxjs');const { map, distinct, filter, mergeMap } = require('rxjs/operators');const rp = require('request-promise-native');const  cheerio  =  require('cheerio');
//...const urlAndDOM$ = uniqueUrl$.pipe(  mergeMap(url => {    return from(rp(url)).pipe(      // get the cheerio function $      map(html => cheerio.load(html)),      // add URL to the result. It will be used later for crawling      map($ => ({        $,        url      }))    );  }));

URLANDD $ выделяет объект, состоящий из 2 свойств, которые являются $ и урл . $ Является ли функция Cheerio, где вы можете использовать что-то вроде $ ('div'). Текст () Извлечь информацию из необработанного HTML-кода.

Ползать все URL

const { resolve } =  require('url');//...
// get all the next crawlable URLsurlAndDOM$.subscribe(({ url, $ }) => {  $('a').each(function(i, elem) {    const href = $(this).attr('href');    if (!href) return;
// build the absolute url    const absoluteUrl = resolve(url, href);    allUrl$.next(absoluteUrl);  });});

В вышеупомянутом коде мы соскрегаем все ссылки внутри страницы и отправьте их на ALLURL $ быть ползным позже.

Scrape и сохранить фильмы, которые мы хотим!

const  fs  =  require('fs');//...
const isMovie = $ =>  $(`[property='og:type']`).attr('content') === 'video.movie';const isComedy = $ =>  $(`.title_wrapper .subtext`)    .text()    .includes('Comedy');const isHighlyRated = $ => +$(`[itemprop="ratingValue"]`).text() > 7;
urlAndDOM$  .pipe(    filter(({ $ }) => isMovie($)),    filter(({ $ }) => isComedy($)),    filter(({ $ }) => isHighlyRated($))  )  .subscribe(({ url, $ }) => {    // append the data we want to a file named "comedy.txt"    fs.appendFile('comedy.txt', `${url}, ${$('title').text()}\n`);  });

Да, мы только что создали веб-скребок

Около 70 строк кода мы создали веб-скребок что

  • Автоматически выполнять URL без ненужных дубликатов
  • автоматически соскрести и сохраняет информацию, которую мы хотим в текстовом файле

Вы можете увидеть код до этого момента https://github.com/jacobgoh101/web-scraping-with-rxjs/blob/86ff05e893dec5f1b39647350cb0f74efe258c86/index.js.

Если вы когда-либо пытались написать веб-скребок с нуля, вы сможете увидеть теперь, как элегантно написать один с RXJS.

Но мы еще не сделаны …

В идеальном мире код выше должен работать навсегда без проблем.

Но на самом деле случаются глупые ошибки.

Обработка ошибок

Ограничить количество активных одновременных соединений

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

Это также считается грубый/неэтичный Чтобы отправить так много запросов одновременно, потому что он создаст более тяжелую нагрузку на сервер и в некоторых случаях Сбой сервера Отказ

Mergemap Встроенная функциональность для контроля параллелизма. Просто добавьте номер к аргументу 3-й функции, и он автоматически ограничит активное одновременное соединение. Гравно!

const maxConcurrentReq = 10;//...const urlAndDOM$ = uniqueUrl$.pipe(  mergeMap(    //...    null,    maxConcurrentReq  ));

Code diff: https://github.com/jacobgoh101/web-scraping-with-rxjs/commit/6aaeed6dae230d2dde1493f1b6d78282ce2e8f316.

Ручка и повторный неудачный запрос

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

Мы можем использовать Caveroror , Повторите попытку операторы для обработки этого.

const { BehaviorSubject, from, of } =  require('rxjs');const {  // ...  retry,  catchError} = require('rxjs/operators');//...
const maxRetries = 5;// ...
const urlAndDOM$ = uniqueUrl$.pipe(  mergeMap(    url => {      return from(rp(url)).pipe(        retry(maxRetries),        catchError(error => {          const { uri } = error.options;          console.log(`Error requesting ${uri} after ${maxRetries} retries.`);          // return null on error          return of(null);        }),        // filter out errors        filter(v => v),        // ...      );    },

Code diff: https://github.com/jacobgoh101/web-scraping-with-rxjs/commit/3098b48ca91a59aa5171bc2aa9c17801e769fcbb.

Улучшенная повторная попытка повторной попытки

Использование оператора Retry, повторение произойдет сразу после неудачного запроса. Это не идеально.

Лучше повторить попытку после определенного периода задержки.

Мы можем использовать GenericRetryStrategy предложил в judsrxjs для достижения этой цели.

Code diff: https://github.com/jacobgoh101/web-scraping-with-rxjs/commit/e194f4ff128a573241055FFC0D1969D54CA8C270.

Заключение

Чтобы повторить, в этом посте мы обсудили:

  • Как сканировать веб-страницу с помощью Cheerio
  • Как избежать дублированного ползания с использованием операторов RXJS, как фильтр, отличный
  • Как использовать Mergemap для создания наблюдаемого ответа запроса
  • Как ограничить параллелизм в Mergemap
  • Как обрабатывать ошибку
  • Как обращаться с повторением

Я надеюсь, что это было полезно для вас и углубило ваше понимание RXJS и Web Scraping.

Первоначально опубликовано dev.to .

Оригинал: “https://www.freecodecamp.org/news/how-to-build-a-simple-customizable-web-scraper-using-rxjs-and-node-6858cfe82a39/”