Pull to refresh

Человеческий парсер на Selenium WD

Reading time5 min
Views68K


Начало


И вот пришла моя очередь покупать автомобиль. Как это делают ребята с работы я видел. Заходят на сайт и следят за предложениями, ну кто постарше покупает газету и просматривает объявления. Все это однообразно и отвлекаться на сиденье, исследование и нажатие по ссылкам не хотелось. Хотелось просто что бы кто то делал это за меня, таких людей не нашлось. Значит надо было заставить делать все это компьютер.

Постановка задачи


Как я видел решение данной проблемы написать парсер, написать скрипт рассылки. Парсер должен собирать данные объявлений с сайта( сайтом я выбрал «из рук в руки»), а рассылка должна отправлять мне на e-mail сообщения о новом лоте. Текст сообщения должен был содержать:
  • ссылку на объявление
  • короткий текст объявления
  • цена автомобиля
  • место продажи автомобиля
Парсер должен отрабатывать каждые N-минут. А после его отработки должны приходить сообщения. Результаты парсинга записываются в базу, а после отправки сообщения на почту, помечать каждое объявления как отправленное. Не хочу же я одно и тоже тысячу раз видеть.

Парсер


Это был самый трудный шаг. Помнится мне, давным-давно, я писал парсер для одноклассников. На PHP. Сначала мне пришлось разобраться в самой соц. сети и понять каким магическим образом оно работает. Потом надо было запомнить все эти сессии, кукисы и последовательность перехода по ссылкам. А превращение всех этих мыслей в код? О ужас. Как же хотелось что бы все происходило понятно. Как хотелось не задумываться о том, что давным давно умеет делать браузер. Как хотелось, что бы моя любимая Наташа наконец-то поняла и главное увидела результаты работы, а не белый текст на черном фоне командной строки.
Вот поэтому и хотелось просто управлять браузером, что бы было понятно и видно. И тут, на сцену выходит Selenium WebDriver. С помощью которого можно управлять браузером, умея только грамотно выбрать селекторы(css, XPath). Логика работы парсера становится прозрачной. Нажать на кнопку, подождать, ввести данные, нажать на кнопку и все. И никаких кукисов. Ура! И главное я буду видеть все живьем, а не в логах.

Подготовительные работы


И так нам надо установить:
  • Java — для запуска Seleniuma.
  • Selenium — собственно сам Selenium.
  • Node.JS — все будет писать на js, поэтому без ноды никак.
  • Mongo DB — база данных.

Далее в папку с проектом надо будет установить несколько модулей для Node
  • wd — для работы с Seleium'ом.
  • async — для уменьшения вложенности нашего кода.
  • mongoose — для работы с базой.
  • swig — для формирования html файлов, которые отправляются на почту
  • emailjs — для отправки сообщений на почту
  • cron — для запуска наших скриптов каждые N-минут(секунд)

Напомню что установка модулей выглядит так:
npm install "имя модуля"

Теперь запустим наш selenium
java -jar "Путь к файлу с selenium'ом"

И сервер базы данных
mongod --dbpath "Путь к месту хранения базы"


Пишем парсер


Источник выбрал как уже сказал — «Из рук в руки». Теперь последовательность действий:
Отчищаем все куки
browser.deleteAllCookies();

Выбираем регион

image
Вот в это поле вводим регион, который нас интересует. Регион как и все другие параметры описываем в объекте, который будет указан ниже. Ввод региона и нажатие можно описать следующим псевдокодом:
               //находи инпут для ввода региона
                browser.elementByCss(LOCATOR.cssPath)
               .then(function(el){
                    //набираем регион
                    return el.type(OPTION.region);
                })
               .then(function(){
                    //меняем локатор на первый город в списке
                    LOCATOR.className = '';
                    LOCATOR.cssPath = '.b-searchRegion > ul:nth-child(1) > li:nth-child(1)';
                    return browser.elementByXPath('//span[contains(text(), "' + OPTION.region + '")]');
                })
                .then(function(el){
                    //нажимаем на найденный элемент
                    el.click();
                });


Дальше выбираем раздел(мне нужен «Легковые автомобили»)

image

Описать можно просто нажатием на ссылку содержащую определенный текст
//ждем несколько секунд
 browser.waitForVisibleByPartialLinkText(OPTION.category,OPTION.elWait) 
                .then(function(){
                    //возврат элемента содержащего текст
                    return browser.elementByPartialLinkText(OPTION.category);
                })
                .then(function(el){
                     //нажимаем на найденный элемент
                    el.click();
                })


Теперь заключительная часть установки параметров для поиска, это нажатие на кнопку «больше параметров», ввести цену, год выпуска и остальные параметры. Все это можно посмотреть в видео.
Как видно все очень просто находим элемент, узнаем его уникальный локатор и нажимаем, вводим или оставляем его в покое.
Собственно, теперь необходимо собрать данные. Нажимаем на кнопку «Показать», и парсим данные. Получение текста выглядит очень просто
               //получаем элемент содержащий текст
                browser.elementByXPathOrNull(locationXPath)
                    .then(function(el){
                        if(el) {
                            //получаем текст
                            return el.text();
                        }
                        else{
                            cb('Нет такого элемента - ' + locationXPath);
                        }
                    })

Стоп-сигналом для сбора данных служит отсутствие вот такой вот синей стрелочки «вправо» на странице результатов:
image
После сбора данных записываем их базу. И закрываем браузер.
Кстати вот объект который описывает параметры для поиска автомобиля.
OPTION = {  
        region : 'Нижний Новгород',//регион поиска
        category: 'Легковые автомобили',//категория поиска
        price : {from : 0 , to : 1800000},//цена
        cy : 'RUR',//валюта
        releaseYear : {from : 2010, to : 2013},//год выпуска
        mileage : {from : 0 , to : 99000 },//пробег
        mark : ['BMW','ВАЗ', 'Audi','Hyundai'],//марка автомобиля
        model : ['X1', 'X3', 'X5'],//модель автомобиля
        carcass : ['седан', 'хэтчбек'],//кузов автомобиля
        transmisson : ['автоматическая', 'механическая'],//трансмиссия
        motor : ['бензин'],//тип двигателя
        gear : ['задний','передний','постоянный полный','подключаемый полный'],//привод
        photo : false,//с фото
        video : false,//с видео
        district : ['Заречный','Нагорный'],//jrheuf
        area : ['Автозаводский', 'Канавинский', 'Ленинский'],//районы
        metro : {   lines : ['Автозаводская', 'Сормовская'], //линии метро
                    station : ['Горьковская м.', 'Пролетарская м.']//станции метро
                     },
        source : ['любой'],//источник объявлений
        submitted : ['вчера и сегодня'],//время подачи объявления
        ajaxWaitMilisec : 2000,//время ожидания ajax мл.сек
        elWait : 3000//время ожидания элемента мл.сек
        },


Работа с базой


Структура базы следующая
MONGODBSCHEMA : {
                    title : String, //короткое описание объявления
                    link :  {type : String , unique : true},//ссылка на объявление
                    price : String,//цена автомобиля
                    location : String,//место продажи автомобиля
                    phone : String,//номер телефона подавшего объявления*
                    text : String, //полное описание объявления*
                    images : Array,//ссылки на машину*
                    sms : {type : Boolean, default : false }, //отправилась ли мне смс с этим объявлением*
                    email : {type : Boolean, default : false }//отправилось ли мне сообщение на e-mail с этим объявлением
                }

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

Рассылаем оповещения на почту


Это самые простой момент. Используем для этого модуль emailjs. Выбираем все документы из базы у которых поле «email» установлено в «false». Отправляем на на мой почтовый ящик. Изменяем свойство«email» на «true» у отправленных. Открываем приложение телефона которое отображает наши письма и изучаем подходящие.

Выполняем все каждые N-минут


Используем для этого модуль cron. Запускаем каждые 20 минут сначала парсер, а затем рассылку писем.

Вот и все


Теперь я слежу за продажей автомобилей тогда, когда у меня под боком телефон. И нету неприятного осадка от сломанного мозга, хранящего в себе всю последовательность магических действий, как раньше с PHP(сам язык тут ни при чем). А есть чувство, что следующий парсер я напишу минут так за 60, просто узнав локаторы элемента. Весь код тут.
А еще я хочу сказать огромное спасибо своей будущей жене Наташе, за то что она не против всех этих моих сумасшедших идей с домашним программирование, и за то что у нее такой бодрящий и милый смех.
Tags:
Hubs:
Total votes 49: ↑43 and ↓6+37
Comments39

Articles