1. Вступайте в наш Telegram чат: https://t.me/a_parser Нас уже 250 и мы растем!
    Скрыть объявление

Создание JS парсеров. Реализация подстановки запросов и их многопоточной обработки.

Парсим топ10 и содержимое тегов title и description в многопоточном режиме

Метки:
  1. Support Денис
    В прошлой статье мы рассмотрели способ разработки парсера, который собирает ТОП 10 в выдаче одной из поисковых систем, а затем по очереди парсит сайты по полученным ссылкам и собирает нужные данные. Вроде все неплохо, но если у вас не 10 запросов, несколько тысяч? Задание будет выполняться очень долго, а время это самый драгоценный и не восполняемый ресурс.
    К счастью в A-Parser есть такая замечательная вещь, как многоуровневый парсинг, который позволяет многократно увеличить скорость парсинга, и в этой статье мы рассмотрим как этой возможностью пользоваться.

    Общий алгоритм такой:
    1. Реализуем метод для сбора содержимого тегов.
    2. Реализуем метод для сбора ссылок и отправки их на следующий уровень парсинга.
    3. Опишем в главном методе parse работу этих методов по разным уровням.
    Все опции как дефолтные так и изменяемые остаются такими же.
    Код:
    class Parser {
        constructor() {
            this.defaultConf = {
                results: {
                    arrays: {
                        /*Хеш serp в котором будут переменные
                         domain, title и description*/
                        serp: ['Top 10 domains and title', [
                            ['domain', 'domain name'],
                            ['title', 'the contents of the title tag'],
                            ['description', 'the contents ot the description tag'],
                        ]],
                    }
                },
                /*Формат результата*/
                results_format: "$serp.format('$domain - $title - $description\\n')",
                /*Код ответа*/
                parsecodes: {
                    200: 1,
                },
                /*Максимальный размер ответа*/
                max_size: 200 * 1024,
                search_engine: 'SE::Google::Modern', //Парсер поисковика по умолчанию.
                SE_Google_Modern_preset: 'default', //Пресеты для парсеров по умолчанию
                SE_Yandex_preset: 'default',
                SE_Bing_preset: 'default',
            };
            this.editableConf = [
                ['SE_Google_Modern_preset', ['combobox', 'SE::Google::Modern preset']],
                ['SE_Yandex_preset', ['combobox', 'SE::Yandex preset']],
                ['SE_Bing_preset', ['combobox', 'SE::Bing preset']],
                ['search_engine', ['combobox', 'Search engine', ['SE::Google::Modern', 'Google'],
                    ['SE::Yandex', 'Yandex'],
                    ['SE::Bing', 'Bing'],
                ]],
            ];
        }
    
    Так как парсить мы теперь будем в два уровня, то сбор ссылок с выдачи поисковой системы вынесем для удобства в отдельный метод. Как видно из кода, разница со сбором ссылок из предыдущей статьи, лишь в том что мы не вызываем для каждого витка цикла метод, который собирает содержимое тегов title и description, а отправляем на следующий уровень.
    Для того чтобы запросы не уходили в неудачные если ответ был успешен определяем
    Код:
       results.success = 1;
    и возвращаем массив results.
    Код:
    /*Метод который принимает в качестве параметра запрос по которому будеть парсить
         один из парсеров поисковых систем*/
        * parseLinks(key,results) {
            let response = yield this.parser.request(
                this.conf.search_engine, //Парсить будем выбранным парсером.
                this.conf[this.conf.search_engine.replace(/::/g, '_') + '_preset'], { //Указываем пресет
                    pagecount: 1, //задаем через override одну страницу
                    linksperpage: 10, //задаем через override 10 результатов на страницу
                }, key);
    
            if (response.info.success) {
                let step;
                /*Устанавливаем шаг прохода по циклу в зависимости от парсера.
                Для SE::Google::Modern и SE::Bing 3, а для SE::Yandex 6*/
                if (this.conf.search_engine == 'SE::Google::Modern' || this.conf.search_engine == 'SE::Bing') {
                    step = 3;
                } else {
                    step = 6;
                }
                /*Отправляем запросы на уровень парсинга 1*/
                for (let i = 0; i < response.serp.length; i += step) {
                      this.query.add(response.serp[i],1)
                }
                /*Пищем в результат что запрос успешный*/
                results.success = 1;
                return results;
            }
              else {
                this.logger.put(`Error scraping ${this.conf.search_engine}`);
                /*Пищем в результат что запрос неудачный*/
                results.success = 0;
                return results;
            }
        }
    Теперь все запросы из следующего уровня будут работать почти одновременно, то есть не в 1 поток перебираются 10 запросов, а 10 запросов сразу. И как результат прирост в скорости очевиден.

    Далее опишем метод который будет работать на следующем уровне. Он будет переходить по ссылке и собирать содержимое тегов title и description.
    Код:
     /*Метод записывающий домен сайта и содержимое тегов title и descritpion
           в массив results и возвращающий его*/
        * parseTitleDescription(set,results) {
            let response = yield this.request('GET', set.query, {}, {
                decode: 'auto-html',
            });
            if (response.success) {
                let title = response.data.match(/<title>([\s\S]+?)<\/title>/);
                let description = response.data.match(/meta.+?=".*?description"\s+content="(.*?)"/);
                if(!title){
                    title = [];
                    title[1] = 'none';
                    }
                if(!description){
                    description = [];
                    description[1] = 'none';
                }
                 /*Записываем в массив результатов. С помощью this.utils.url.extractDomain достаем из ссылки только домен*/
                    results.serp.push(this.utils.url.extractDomain(set.query),title[1],description[1]);
                  results.success = 1;
            }
            return results;
        }
    Теперь давайте в методе parse реализуем парсинг по нескольким уровням. Метод parse является обязательным методом класса Parser, своего рода функция main в языке С.
    Код:
     * parse(set, results) {
            /*Если уровень парсинга нулевой*/
            if (set.lvl == 0) {
                /*Записываем в переменную key запрос*/
                results = yield* this.parseLinks(set.query,results);
                return results;
           }
           /*Если уровень парсинга равен 1*/
           if (set.lvl == 1){
               results = yield* this.parseTitleDescription(set,results);
               return results;
           }
        }
    C помощью условного оператора if мы проверяем на каком уровне парсинга мы находимся. Если уровень равен 0 (отсчет уровней начинается с нуля) то вызываем метод parseLinks, который собирает ссылки с выдачи и отправляет их на следующий уровень. Если же уровень парсинга равен единице то вызываем метод parseTitleDescription.

    Данный пресет вы можете скачать по этой ссылке.