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



    Начало


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

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


    Как я видел решение данной проблемы написать парсер, написать скрипт рассылки. Парсер должен собирать данные объявлений с сайта( сайтом я выбрал «из рук в руки»), а рассылка должна отправлять мне на 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, просто узнав локаторы элемента. Весь код тут.
    А еще я хочу сказать огромное спасибо своей будущей жене Наташе, за то что она не против всех этих моих сумасшедших идей с домашним программирование, и за то что у нее такой бодрящий и милый смех.
    Share post

    Comments 39

      +5
      Нужно пришлепнуть таракана, но тапка под руками нет. Зато я знаю как работает танк и вот он рядом стоит.

      Откройте для себя python и lxml.html. Ну или что-то подобное
        +4
        вы неправы, некоторые сайты понапичканы разнообразным JS, что пока найдешь что куда отправлять уже перехочется писать. А здесь быстро и задачу решает.
          +4
          JS редко используется для основного наполнения сайта, если это не веб-приложение вроде gmail или prismatic. Сайты хотят чтоб их индексировали поисковики, а это значит, что и нам их будет не сложно разбирать.
            +3
            Ха-ха-ха.
            Попробуйте попарсить авито или авто.ру без выполнения яваскрипта, узнаете много нового =)
              0
              Я пошел смотреть, а Вы всё таки в двух словах тут расскажите, пожалуйста.
                0
                Яваскриптовые куки по которым отсекаются боты.
                Если в двух словах.
                  0
                  То есть с выключенным JS их сайт вообще не работает?
                    +3
                    Все несколько сложнее, но если коротко — то да.
                      0
                      А вы парсили их? Если не секрет, то пользовались подобным описанному автором поста способом или выполняли JS, дабы сойти за обычного пользователя?
                        +1
                        Описанный автором способ выполняет ЖС — поэтому работает очень хорошо.
                        Есть разные варианты добиться схожего варианта: phantomJS, или что совсем круто вебкит + C++ =)
                          0
                          Так PhantomJS и так использует WebKit.
          0
          Это немного другой подход. Это вам не в тапки)
          • UFO just landed and posted this here
            • UFO just landed and posted this here
              0
              А по-моему, с Selenium WD все получилось очень наглядно. Если под рукой танк, зачем каждый раз трогать тапки.

              PS. Сам решал похожую задачу на ruby + nokogiri, тоже все очень прозрачно (правда, просто данные там не «вобьешь», надо все-таки понимать как передаются какие-то параметры).;
              • UFO just landed and posted this here
                  0
                  вы думаете по ноду нет свистелок типа «lxml.html»? ох-ох-ох-ох…
                  +1
                  Хорошо бы чтобы разработчики подобных сайтов проявляли инициативу и за некоторую плату осуществляли самостоятельно рассылку новых объявлений. Это явно профит в обе стороны
                    0
                    Был подписан на auto.yandex.ru/ как раз там и есть подписка на новые объявления по нужным критериям, правда не чаще двух раз в сутки, зато это агрегатор многих досок автообъявлений.
                      0
                      Ну вот на фрилансим очень хорошо сделано: бесплатно рассылка каждый день утром, за регистрацию. За деньги — сразу после публикации.
                    0
                    А за сколько по времени это решение написали?
                      +1
                      Писал нечто подобное под irr.ru
                      Авматизация парсера сводилась к тому, что ты даешь ему ссылку на страницу с объявлениями, например irr.ru/real-estate/apartments-sale/search/rooms=2/currency=RUR/
                      В случае появления нового объявления (проверка через cron каждую минуту) — отправление СМС (через EmailToSMS) мне на телефон. Данные скрипт сохраняет в файл.

                      Итог: 1 скрипт на PHP + Cron на любом хостинге + Активация услуги EmailToSMS = PROFIT

                      P.S. Если кому интересно – могу поделиться PHP скриптом.
                        +2
                        Осталось только Selenium на PhantomJS заменить и будет совсем круто :)
                          0
                          Я на Phantom'e столкнулся с проблемой доступа к вновь отрытой вкладке. И помня это пересел на Selenium. Хотя тут можно и Phantom'ом ограничиться.
                            0
                            Вообще-то Selenium Webdriver поддерживает много разных браузеров, в том числе PhantomJS. То есть заменять ничего не надо, надо просто сказать Selenium, чтобы он в качестве браузера использовал PhantomJS.
                              0
                              Да, спасибо я в курсе. Но у меня возникали проблемы, где-то полгода назад. Поэтому думал не рисковать. Можете на гитхабе, изменить одну строчку, если по нраву PhantomJS
                                0
                                жаль пока драйвер для фантома не поддерживает скриншоты
                              0
                              Ну а лучше пойти дальше и использовать CasperJS. Особенно если необходимо несколько страниц и форма.
                              0
                              Есть неплохой сервис от Яндекса — auto.yandex.ru. Индексирует не только irr.ru, но и avito.ru, drom.ru, am.ru и т.д (не сочтите за рекламу:) ), а так же есть подписка по нужным параметрам.
                                0
                                неправда ваша.
                                он не индексирует. он агрегирует фиды от партнеров, которыми как раз являются avito, drom и.т.д.
                                но auto.ru, например, отдает яндексу только регионы, москвы там нет. наверняка и остальные отдают с задержками, ведь тот же авито живет не только за счет рекламы, но и за счет продавцов, платящих за поднятие и т.д.
                                +1
                                Грех будет не оставить здесь ссылку на Convextra. Она и данные сама выберет (не нужно писать какие-то свои XPATH просматривая верстку), и с периодичностью будет проверять новые объявления, и рассылку по ним организует и даже кукисы ваши автоматом подставит для сайта. Конечно, это не серебряная пуля, но 90% задач подобных этой она решает за 1 минуту (из которых 40 секунд уйдет на регистрацию :) )
                                  0
                                  делал то же самое на JSoup + sqlite + просмотрщик на Adobe Air.
                                    +1
                                    я тут подумал, и знаете, картинка очень в тему.

                                    Only users with full accounts can post comments. Log in, please.