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



В интернет опубликовано много полезной информации и умение извлекать данные поможет в жизни и работе. Научимся получать информацию с помощью webdriver API. В публикации приведу два примера, код которых доступен на github. В конце статьи скринкаст про то, как программа управляет браузером.

Программа или скрипт с помощью вебдрайвера управляет вашим браузером — выполняет ввод текста, переход по ссылкам, щелчок по элементу, извлекает данные из страницы и её элементов, делает скриншоты сайта и т.п.

Для работы webdriver нужно два компонента: браузер/сервер протокола и клиентская часть в виде библиотеки для вашего языка программирования.

Вы можете использовать webdriver API из разных языков программирования и виртуальных машин: официальные клиенты webdriver есть для C#, Ruby, Python, Javascript(Node), а так же клиенты от сообщества для Perl, Perl 6, PHP, Haskell, Objective-C, Javascript, R, Dart, Tcl.

На данный момент Webdriver является стандартом W3C, над которым все еще идет работа. Изначально Webdriver API появился в проекте selenium для целей тестирования, в результате эволюции Selenium-RC API.

В качестве сервера используется отдельный процесс «понимающий» язык протокола. Этот процесс управляет вашим браузером. Есть следующие драйвера:

  • AndroidDriver
  • ChromeDriver
  • FirefoxDriver
  • InternetExplorerDriver
  • SafariDriver

Из этого списка выделяются два драйвера:

  • HtmlUnitDriver — является оберткой для библиотеки HtmlUnit-эмулятора браузера, которая работает в том же java процессе, что и клиентская часть
  • PhantomJSDriver — это браузер на основе webkit без графической части (headless) и драйвер в одном процессе с сервером.

Суть технологии...


Минимум теории для дальнейшей работы. Последовательность действий в клиенте


Итак, наш выбор phantomjs. Это полноценный браузер, который управляется по протоколу webdriver. Можно запускать множество его процессов одновременно, не требуется графическая подсистема, внутри полноценно исполняется javascript (в контраст с ограничениями htmlunit). Если писать сценарии на javascript и передавать его как параметр при старте, то phantomJS может их выполнять и без вебдрайвер протокола и даже доступна отладка с помощью другого браузера.



Описанное ниже относится по большей части к API для java/groovy. В клиентах других языков список функций и параметров должен быть похож.

Получаем сервер с вебдрайвером.


String phantomJsPath = PhantomJsDowloader.getPhantomJsPath()

Загружает phantomjs из репозитария maven, распаковывает и возвращает путь к этому браузеру. Для использования нужно подключить к проекту библиотеку из maven: com.github.igor-suhorukov:phantomjs-runner:1.1.

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

Создаем клиент, соединяемся с сервером


WebDriver driver = new PhantomJSDriver(settings)

Конфигурирует порт для взаимодействию по webdriver протоколу, запускает процесс phantomjs и подключается к нему.

Открываем в браузере нужную страницу


driver.get(url)

Открывает в браузере страницу для заданного адреса.

Получаем интересующу нас информацию


WebElement leftmenu = driver.findElement(By.id("leftmenu"))
List<WebElement> linkList = leftmenu.findElements(By.tagName("a"))

У экземпляра драйвера driver и у элемента, полученного из него, есть два полезных метода: findElement, findElements. Первый возвращает элемент или бросает исключение NoSuchElementException, если элемент не найден. Второй — возвращает коллекцию элементов.

Элементы можно выбирать по следующим запросам org.openqa.selenium.By:

  • id
  • name
  • tagName
  • xpath
  • className
  • cssSelector
  • linkText
  • partialLinkText

Я буду активно использовать id, tagName и xpath. Для не знакомых с xpath — рекомендую разобраться на примерах или по статьям, а только потом перейти к чтению спецификации.

Выполняем действия с элементами на странице и со страницей


С элементом можно вытворять следующее:

  • menuItem.click() — посылает элементу событие клик
  • inputField.sendKeys(«blah-blah») — посылает элементу события нажатия клавиш
  • formButton.submit() — отправляет данные формы, вызывая событие submit

driver.getScreenshotAs(type)

Делает снимок окна браузера. Полезным дополнением к стандартной функции снимка рекомендую библиотеку aShot — она позволяет делать снимок только определенного элемента в окне и позволяет сравнивать элементы как изображения.

Скриншоты можно получить как:

  • OutputType.BASE64 — строку в этом формате, можно например встроить в HTML embedded картинку
  • OutputType.BYTES — массив байт и вертись с ним как умеешь
  • OutputType.FILE — временный файл, для многих инструментов самый удобный способ

Закрываем соединение с браузером


driver.quit()

Закрывает соединение по протоколу и в нашем случае останавливает процесс phantomjs.

Пример 1: Гуляем groovy скриптом по профилям социальной сети


Запустим команду:

java -jar groovy-grape-aether-2.4.5.1.jar crawler.groovy http://??.com/catalog.php

Ссылки на необходимые для запуска файлы:

Скрипт в консоле напечатает путь к html файлу, что создал на основе информации из социальной сети. В странице вы увидите имя пользователя, момент последнего посещения соцсети и скриншот всей страницы пользователя.

Зачем запускать груви скрипт с помощью groovy-grape-aether-2.4.5.1
Про сборку груви groovy-grape-aether-2.4.5.1.jar недавно рассказывал в статье «Уличная магия в скриптах или что связывает Groovy, Ivy и Maven?». Главное отличие от groovy-all-2.4.5.jar — возможность работы механизма Grape с репозитариями более корректным по сравнению с ivy способом, при помощи библиотеки aether, а так же наличие классов доступа к репозитариям в сборке.

crawler.groovy
package com.github.igorsuhorukov.phantomjs

@Grab(group='commons-io', module='commons-io', version='2.2')
import org.apache.commons.io.IOUtils
@Grab(group='com.github.detro', module='phantomjsdriver', version='1.2.0')
import org.openqa.selenium.*
import org.openqa.selenium.phantomjs.PhantomJSDriver
import org.openqa.selenium.phantomjs.PhantomJSDriverService
import org.openqa.selenium.remote.DesiredCapabilities
@Grab(group='com.github.igor-suhorukov', module='phantomjs-runner', version='1.1')
import com.github.igorsuhorukov.phantomjs.PhantomJsDowloader

public class Crawler {
    public static final java.lang.String USER_AGENT = "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36"

    public static void run(String baseUrl) {

        def phantomJsPath = PhantomJsDowloader.getPhantomJsPath()

        def DesiredCapabilities settings = new DesiredCapabilities()
        settings.setJavascriptEnabled(true)
        settings.setCapability("takesScreenshot", true)
        settings.setCapability("userAgent", com.github.igorsuhorukov.phantomjs.Crawler.USER_AGENT)
        settings.setCapability(PhantomJSDriverService.PHANTOMJS_EXECUTABLE_PATH_PROPERTY, phantomJsPath)

        def WebDriver driver = new PhantomJSDriver(settings)

        def randomUrl = null
        def lastVisited=null
        def name=null

        boolean pass=true
        while (pass){
            try {
                randomUrl = getUrl(driver, baseUrl)
                driver.get(randomUrl)
                def titleElement = driver.findElement(By.id("title"))
                lastVisited = titleElement.findElement(By.id("profile_time_lv")).getText()
                name = titleElement.findElement(By.tagName("a")).getText()
                pass=false
            } catch (NoSuchElementException e) {
                System.out.println(e.getMessage()+". Try again.")
            }
        }
        String screenshotAs = driver.getScreenshotAs(OutputType.BASE64)
        File resultFile = File.createTempFile("phantomjs", ".html")
        OutputStreamWriter streamWriter = new OutputStreamWriter(new FileOutputStream(resultFile), "UTF-8")
        IOUtils.write("""<html><head><meta http-equiv="content-type" content="text/html; charset=UTF-8"></head><body>
                                <p>${name}</p><p>${lastVisited}</p>
                                <img alt="Embedded Image" src="data:image/png;base64,${screenshotAs}"></body>
                        </html>""", streamWriter)
        IOUtils.closeQuietly(streamWriter)

        println "html ${resultFile} created"

        driver.quit();
    }

    static String getUrl(WebDriver driver, String baseUrl) {

        driver.get(baseUrl)

        def elements =  driver.findElements(By.xpath("//div[@id='content']//a"))
        def element = elements.get((int) Math.ceil(Math.random() * elements.size()))
        String randomUrl = element.getAttribute("href")
        randomUrl.contains("catalog") ? getUrl(driver, randomUrl) : randomUrl
    }
}

Crawler.run(this.args.getAt(0))

Хорошо знающие груви, заметят что Geb более подходящее решение. Но поскольку оно прячет за своим DSL всю работу с webdriver, то в наших учебных целях Geb не подходит. Из эстетических же соображений я с вами соглашусь!

Пример 2: Извлекаем данные о проектах из java-source программой на java


Пример доступен тут. Для запуска нужна java8, так как используются стримы и try-with-resources.

git clone https://github.com/igor-suhorukov/java-webdriver-example.git
mvn clean package -Dexec.args="http://java-source.net"

В этом примере использую xpath и axis для извлечения информации из страницы. Как пример, фрагмент класса Project.

WebElement main = driver.findElement(By.id("main"));
name = main.findElement(By.tagName("h3")).getText();
description = main.findElement(By.xpath("//h3/following-sibling::table/tbody/tr/td[1]")).getText();
link = main.findElement(By.xpath("//td[text()='HomePage']/following-sibling::*")).getText();
license = main.findElement(By.xpath("//td[text()='License']/following-sibling::*")).getText();

Часть извлеченных из сайта данных. Файла projects.xml - результат работы программы
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<projects source="http://java-source.net">
    <category>
        <category>Open Source Ajax Frameworks</category>
        <project>
            <name>DWR</name>
            <description>DWR is a Java open source library which allows you to write Ajax web sites. It allows code in a browser to use Java functions running on a web server just as if it was in the browser. DWR works by dynamically generating Javascript based on Java classes. The code does some Ajax magic to make it feel like the execution is happening on the browser, but in reality the server is executing the code and DWR is marshalling the data back and forwards.</description>
            <license>Apache Software License</license>
            <link>http://getahead.org/dwr</link>
        </project>
        <project>
            <name>Google Web Toolkit</name>
            <description>Google Web Toolkit (GWT) is an open source Java software development framework that makes writing AJAX applications like Google Maps and Gmail easy for developers who don't speak browser quirks as a second language. Writing dynamic web applications today is a tedious and error-prone process; you spend 90% of your time working around subtle incompatibilities between web browsers and platforms, and JavaScript's lack of modularity makes sharing, testing, and reusing AJAX components difficult and fragile. GWT lets you avoid many of these headaches while offering your users the same dynamic, standards-compliant experience. You write your front end in the Java programming language, and the GWT compiler converts your Java classes to browser-compliant JavaScript and HTML.</description>
            <license>Apache Software License</license>
            <link>http://code.google.com/webtoolkit/</link>
        </project>
        <project>
            <name>Echo2</name>
            <description>Echo2 is the next-generation of the Echo Web Framework, a platform for developing web-based applications that approach the capabilities of rich clients. The 2.0 version holds true to the core concepts of Echo while providing dramatic performance, capability, and user-experience enhancements made possible by its new Ajax-based rendering engine.</description>
            <license>Mozilla Public License</license>
            <link>http://www.nextapp.com/platform/echo2/echo/</link>
        </project>
        <project>
            <name>ICEfaces</name>
            <description>ICEfaces is an integrated Ajax application framework that enables Java EE application developers to easily create and deploy thin-client rich Internet applications (RIA) in pure Java. ICEfaces leverages the entire standards-based Java EE ecosystem of tools and execution environments. Rich enterprise application features are developed in pure Java, and in a pure thin-client model. There are no Applets or proprietary browser plug-ins required. ICEfaces applications are JavaServer Faces (JSF) applications, so Java EE application development skills apply directly and Java developers are isolated from doing any JavaScript related development.</description>
            <license>Mozilla Public License</license>
            <link>http://www.icefaces.org</link>
        </project>
        <project>
            <name>SweetDEV RIA</name>
            <description>SweetDEV RIA is a complete set of world-class Ajax tags in Java/J2EE. It helps you to design Rich GUI in a thin client. SweetDEV RIA provides you Out-Of-The-Box Ajax tags. Continue to develop your application with frameworks like Struts or JSF. SweetDEV RIA tags can be plugged into your JSP pages.</description>
            <license>Apache Software License</license>
            <link>http://sweetdev-ria.ideotechnologies.com</link>
        </project>
        <project>
            <name>ItsNat, Natural AJAX</name>
            <description>ItsNat is an open source (dual licensed GNU Affero General Public License v3/commercial license for closed source projects) Java AJAX Component based Web Framework. It offers a natural approach to the modern Web 2.0 development. ItsNat simulates a Universal W3C Java Browser in the server. The server mimics the behavior of a web browser, containing a W3C DOM Level 2 node tree and receiving W3C DOM Events using AJAX. Every DOM server change is automatically sent to the client and updated the client DOM accordingly. Consequences: pure (X)HTML templates and pure Java W3C DOM for the view logic. No JSP, no custom tags, no XML meta-programming, no expression languages, no black boxed components where the developer has absolute control of the view. ItsNat provides an, optional, event based (AJAX) Component System, inspired in Swing and reusing Swing as far as possible such as data and selection models, where every DOM element or element group can be easily a component.</description>
            <license>GNU General Public License (GPL)</license>
            <link>http://www.itsnat.org</link>
        </project>
        <project>
            <name>ThinWire</name>
            <description>ThinWire is an development framework that allows you to easily build applications for the web that have responsive, expressive and interactive user interfaces without the complexity of the alternatives. While virtually any web application can be built with ThinWire, when it comes to enterprise applications, the framework excels with its highly interactive and rich user interface components.</description>
            <license>GNU Library or Lesser General Public License (LGPL)</license>
            <link>http://www.thinwire.com/</link>
        </project>
    </category>
    <category>
        <category>Open Source Aspect-Oriented Frameworks</category>
        <project>
            <name>AspectJ</name>
            <description>AspectJ is a seamless aspect-oriented extension to the Java programming language, Java platform compatible and easy to learn and use. AspectJ enables the clean modularization of crosscutting concerns such as: error checking and handling, synchronization, context-sensitive behavior, performance optimizations, monitoring and logging, debugging support, multi-object protocols.</description>
            <license>Mozilla Public License</license>
            <link>http://eclipse.org/aspectj/</link>
        </project>
        <project>
            <name>AspectWerkz</name>
            <description>AspectWerkz is a dynamic, lightweight and high-performant AOP framework for Java. AspectWerkz offers both power and simplicity and will help you to easily integrate AOP in both new and existing projects. AspectWerkz utilizes runtime bytecode modification to weave your classes at runtime. It hooks in and weaves classes loaded by any class loader except the bootstrap class loader. It has a rich and highly orthogonal join point model. Aspects, advices and introductions are written in plain Java and your target classes can be regular POJOs. You have the possibility to add, remove and re-structure advice as well as swapping the implementation of your introductions at runtime. Your aspects can be defined using either an XML definition file or using runtime attributes.</description>
            <license>GNU Library or Lesser General Public License (LGPL)</license>
            <link>http://aspectwerkz.codehaus.org/</link>
        </project>
        <project>
            <name>Nanning</name>
            <description>Nanning Aspects is a simple yet scaleable aspect-oriented framework for Java.</description>
            <license>BSD License</license>
            <link>http://nanning.codehaus.org/</link>
        </project>
        <project>
            <name>JBossAOP</name>
            <description>JBoss-AOP allows you to apply interceptor technology and patterns to plain Java classes and Dynamic Proxies. It includes: * Java Class Interception. Field, constructor, and method interception, public, private, protected, and package protected, static and class members. * Fully compositional pointcuts caller side for methods and constructors, control flow, annotations. * Aspect classes Advices can be incapsulated in scoped Java classes * Hot-Deploy. Interceptors can be deployed, undeployed, and redeployed at runtime for both dynamic proxies and classes.(working) * Introductions. The ability to add any arbitrary interface to a Java class. Either an interceptor or a 'mixin' class can service method calls for the attached interfaces. * Dynamic Proxies. The ability to define a dynamic proxy and an interceptor chain for it. Proxies can either be created from an existing class, or from a set of interfaces ala java.lang.reflect.Proxy. * Metadata and Attribute Programming. The ability to define and attach metadata configuration to your classes or dynamic proxies. Interceptors can be triggered when metadata is added to a class. We also have Metadata Chains, the ability to define defaults at the cluster and application level, as well as the ability to override configuration at runtime for a specific method call. * Dynamic AOP. All aspected objects can be typecasted to an AOP api. You can do things like add/remove new interceptors to a specific instance or add/remove instance configuration/metadata at runtime.</description>
            <license>GNU Library or Lesser General Public License (LGPL)</license>
            <link>http://www.jboss.org/products/aop</link>
        </project>
        <project>
            <name>dynaop</name>
            <description>dynaop, a proxy-based Aspect-Oriented Programming (AOP) framework, enhances Object-Oriented (OO) design in the following areas: code reuse decomposition dependency reduction</description>
            <license>Apache Software License</license>
            <link>https://dynaop.dev.java.net/</link>
        </project>
        <project>
            <name>CAESAR</name>
            <description>CAESAR is a new aspect-oriented programming language compatible to Java, that is, all Java programs will run with CAESAR.</description>
            <license>GNU General Public License (GPL)</license>
            <link>http://caesarj.org/</link>
        </project>
        <project>
            <name>EAOP</name>
            <description>Event-based Aspect-Oriented Programming (EAOP) for Java. EAOP 1.0 realizes the EAOP model through the following characteristics: * Expressive crosscuts: execution points can be represented by events and crosscuts can be expressed which denote arbitrary relations between events. * Explicit aspect composition: Aspects may be combined using a (extensible) set of aspect composition operators. * Aspects of aspects: Aspects may be applied to other aspects. * Dynamic aspect management: Aspects may be instantiated, composed and destroyed at runtime.</description>
            <license>GNU General Public License (GPL)</license>
            <link>http://www.emn.fr/x-info/eaop/tool.html</link>
        </project>
        <project>
            <name>JAC</name>
            <description>JAC (Java Aspect Components) is a project consisting in developing an aspect-oriented middleware layer.</description>
            <license>GNU Library or Lesser General Public License (LGPL)</license>
            <link>http://jac.objectweb.org/</link>
        </project>
        <project>
            <name>Colt</name>
            <description>Open Source Libraries for High Performance Scientific and Technical Computing in Java</description>
            <license>The Artistic License</license>
            <link>http://hoschek.home.cern.ch/hoschek/colt/</link>
        </project>
        <project>
            <name>DynamicAspects</name>
            <description>DynamicAspects enables you to do aspect-oriented programming in pure Java. Using the \"instrumentation\" and \"agent\" features introduced with Sun JDK 1.5, aspects can be installed and deinstalled during runtime!</description>
            <license>BSD License</license>
            <link>http://dynamicaspects.sourceforge.net/</link>
        </project>
        <project>
            <name>PROSE</name>
            <description>The PROSE system (PROSE stands for PROgrammable extenSions of sErvices) is a dynamic weaving tool (allows inserting and withdrawing aspects to and from running applications) PROSE aspects are regular JAVA objects that can be sent to and be received from computers on the network. Signatures can be used to guarantee their integrity. Once an aspect has been inserted in a JVM, any occurrence of the events of interest results in the execution of the corresponding aspect advice. If an aspect is withdrawn from the JVM, the aspect code is discarded and the corresponding interception(s) will no longer take place.</description>
            <license>Mozilla Public License</license>
            <link>http://prose.ethz.ch/Wiki.jsp?page=AboutProse</link>
        </project>
        <project>
            <name>Azuki Framework</name>
            <description>The Azuki Framework is a java application framework, designed to reduce the development, deployment and maintenance costs of software systems. The Azuki Framework provides also an unique combination of powerful design patterns (decorator, injection, intercepter, command, proxy...). It provides a rapid application assembly from known components in order to build large systems. The software conception is split into two main stages : * Creation of independent components (technical & business service). * Definition of component dependencies (weaving)</description>
            <license>GNU Library or Lesser General Public License (LGPL)</license>
            <link>http://www.azuki-framework.org/</link>
        </project>
        <project>
            <name>CALI</name>
            <description>CALI is a framework to prototype and compose Aspect-Oriented Programming Languages on top of Java. It is based on an abstract aspect language that its extensible to implement new AOPL. As proof of approach and methodology, the following language have been implemented: -AspectJ (Dynamic part of AspectJ, where intertype declartion can be implemented using regular AspectJ); -EAOPJ : An implementation of Event-Based AOP for Java; -COOL: A DSAL for coordination; -Decorator DSAL. You can use CALI to implement your new AOPL and compose it with existing implementation or using existing implementation to write your applications with aspects form different AOPL.</description>
            <license>Other</license>
            <link>http://www.emn.fr/x-info/cali/</link>
        </project>
    </category>
</projects>


Вот так этот же пример работает с драйвером ChromeDriver (org.seleniumhq.selenium:selenium-chrome-driver:2.48.2). В отличии от PhantomJS в этом случае видно что происходит во время запуска программы: переход по ссылкам, отрисовка страницы.



Вывод


Webdriver API можно использовать из разных языков программирования. Написать скрипт или программу для управления браузером и извлечения информации из страниц достаточно просто: данные из страницы удобно получать по Id тега, CSS селектору или XPath выражению. Возможно делать снимки страницы и отдельных элементов на ней. На основе примеров и документации можно разработать сценарии почти любой сложности для работы с сайтом. Для разработки и отладки лучше использовать обычный браузер и вебдрайвер для него. Для полностью автоматической работы лучше подходит PhantomJS.

Удачи в извлечении открытой и полезной информации из веб!

Комментарии (42)


  1. alekciy
    08.12.2015 14:55
    +4

    Хочется обратить внимание, что PhantomJS можно запускать из без java (т.е. Selenium Standalone Server не нужен). Если при этом используется версия ниже 2, то фаерволом нужно закрыть 8910 порт (по умолчанию) или тот, на котором вы его стартанули. Дело в баге ghostdriver в результате чего он слушает 0.0.0.0 адрес, т.е. становиться доступен всем и если сервер имеет выход наружу, то очень быстро находятся желающие порулить вашим фантомом.


    1. igor_suhorukov
      08.12.2015 17:56
      +1

      Сейчас проверили старый вебдрайвер эксплорера, тоже биндится на 0.0.0.0. Хорошее замечание, спасибо вам!


    1. auine
      08.12.2015 20:09

      Можно запускать и силениум с ноды. Вообщем предпочитаю силениум. Фантом слишком лоу лвл, каспер и прочии надстройки над фантомом часто огорчают тем, что там далеко не последние движки, что может повлиять как и на тесты так и на парсинг.


  1. SOLON7
    08.12.2015 17:32
    -6

    конечно меня захейтят, но гоу имеет куда более лучшею динамику чем тот же груви!


    1. igor_suhorukov
      08.12.2015 17:55
      +1

      Дело предпочтений. Кому на чем удобнее писать. Если нужны библиотеки, доступные в JVM, то груви удобнее


  1. Demogor
    08.12.2015 17:59

    Спасибо, познавательно.
    Но вообще, из того, что пробовал использовать для написания парсеров, лучше nw.js+jQuery не видел.


    1. igor_suhorukov
      08.12.2015 18:09

      Пожалуйста!
      К сожалению не сталкивался с nodejs, так что пока поверю на слово


    1. alekciy
      09.12.2015 11:03

      Это с полным построением DOM и возможность рендеринга в скриншот? Если да, то какая скорость и сколько под себя требует ресурсов?


      1. igor_suhorukov
        09.12.2015 11:07

        В примере собственно и есть скриншот, если вы про графический


        1. alekciy
          09.12.2015 11:18

          Не, я про nw.js+jQuery уточняю.


          1. Demogor
            09.12.2015 22:31

            Node-webkit(nw.js) это гибрид ноды и webkit — движка того же хрома, к примеру. Так что да, он умеет большую часть из того, что умеет нормальный браузер, соответственно, у него и запросы по ресурсам приблизительно те же(запустил сейчас просто окно с гуглом, отожрало около 140МБ). Впрочем, большую часть отъедает, скорее всего, рендер.
            По скорости работы не замерял, основным критерием в большинстве случаев служило удобство. Если не хватает клиентского/нодовского js'а, всегда можно дописать либо использовать код на плюсах при помощи аддонов либо через node-ffi, либо код на C# через edge.js. Главный же профит для парсеров это, конечно же, использование jQuery. К примеру, это может выглядеть как-то так:

            //функция для построения DOM
            var _html=function(html){ 
              return $('<div></div>').html(html);
            };
            
            //К примеру, скомбинируем node'овский request и jQuery
            request.get(url, function(err,res,html){
                var container=_html(html);
                var name=container.find('.name span').text().trim();
            }); 
            
            //Или то же самое на чистом jQuery
            $.get(url, function(html){
                var container=_html(html);
                var name=container.find('.name span').text().trim();
            });
            
            


            1. alekciy
              10.12.2015 14:24

              Понятно. Как вижу по ресурсам примерно тот же фантом. На парсинге считаю все же XPath более удобным и мощным. В общем профита по ресурсам не даст. Я просто подумал было, а вдруг…


              1. Demogor
                11.12.2015 18:22

                Ну, как говорится — не проверишь, не узнаешь)


  1. anjiJa
    08.12.2015 18:54
    +1

    Не столь функционален, но для интереса, можно глянуть и сюда: jsoup.org


    1. igor_suhorukov
      08.12.2015 19:21

      Спасибо за ссылку. В похожем контексте использовал библиотеку HtmlUnit, которая с ограничениями, но поддерживает и javascript клиентский


    1. igor_suhorukov
      11.12.2015 14:57

      Попробовал использовать jsoup. Огорчает отсутствие возможности выборки по xpath. В итоге воспользовался библиотекой htmlunit. Они в одной категории по сути


  1. nikitasius
    08.12.2015 19:33

    Вот кстати, на счет агента лучше маскироваться под google bot'а.


    1. igor_suhorukov
      08.12.2015 19:40
      +1

      А IP клиента как на гугловые заменит? ;-)


      1. nikitasius
        08.12.2015 19:49
        +1

        А их почти никто и не проверяет. IPB к примеру хватает только агента, и он считает вас гуглом.


        1. alekciy
          09.12.2015 11:06
          -2

          «Почти» не знает ни кто. Поэтому эти «никто» просто кучка школьников, не более. Которые не знают, что есть PTR.
          Лично для меня кейс «в юзерагенте поисковый сервис, а PTR не сервиса» четкий триггер добавления такого адреса в черный список фаервола.


  1. gurinderu
    09.12.2015 10:22

    А как fantom driver по производительности себя ведет? Хотя бы в сравнении с apache httpclient+htmlcleaner или jsoup (естественно речь идет о парсинге чистого html, без js)


    1. igor_suhorukov
      09.12.2015 10:36

      Ну мы сейчас будем сравнивать сферического коня в вакууме. Конечно jsoup быстрее, но он и десятой части не делает того, что фантом


      1. gurinderu
        10.12.2015 10:26

        На самом деле мне и интересен этот конь. Мне это интересно, чтобы прикинуть примерное время парсинга.


        1. igor_suhorukov
          10.12.2015 10:37

          Надо писать бенчмарк и измерять


        1. alekciy
          10.12.2015 14:31
          +1

          От долго до очень долго. Поэтому если есть возможность не использовать полный DOM с рендирингом, нужно не использовать их. А на сколько долго в каждом случае будет по разному.

          Клинический пример из личной практики. Парсер на PHP+SpiderMonkey+curl написанный кастомно под некий шаблон страницы отрабатывает не дольше чем 0,5 сек. Эта же страница отработанная через парсер на базе фантома отрабатывает что-то около минуты. Но фантом на супорте именно кода парсера сильно проще и подвержен меньшему числу поломок из-за изменения страницы. Но когда основным критерием была именно скорость работы (страниц было очень много), то он был заменен на означенную связку на базе SpiderMonkey. Поддерживать стало сложнее, но получать данные стали с требуемой скоростью.


    1. igor_suhorukov
      09.12.2015 11:04

      Причем фантом вебдрайвер добавляет задержку по сравнению с решением внутри JVM, так как взаимодействие между фантомом и джава процессом с веб драйвером на основе REST API и в любом случае это межпроцессное взаимодействие


      1. alekciy
        09.12.2015 11:08

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


    1. alekciy
      09.12.2015 11:15
      +1

      Нельзя сравнивать решения без js и решения с js с полным построение DOM. Конечно же первый рвут последних в клочья. Поэтому если есть возможность не использовать PhantomJS и подобные решения, нужно не использовать его. Ибо да, долго, дорого (по ресурсам). Но есть кейсы в которых заменить его нечем ибо несмотря на все проблемы целесообразнее использовать именно его (т.е. легче на сапорте).

      Там где его скорости не хватает, но все же нужен JS, то можно использовать любой JS движок (v8 к примеру). Сапорт приложения усложняется, скорость чуть хуже, чем у простых текстовых парсеров на регулярках. Я, к примеру, использую SpiderMonkey из PHP там, где требуется поддежка JS, но где скорости фантома не хватает или нет под него железа (держать отдельный сервер с ХХ ГБ озу готов не каждый проект несмотря на относительную дешевизну соверменных серверов).


  1. funnybanana
    11.12.2015 08:17

    <?php
    $pos2 = 0;
    for ($i=0; $i<substr_count($где_искать, "что_искать"); $i++) {
    	$pos1 = stripos($где_искать, "<открывающий_тег", $pos2);
    	$pos2 = stripos($где_искать, "</закрывающий_тег>", $pos1);
    	$temp = substr($где_искать, $pos1 + strlen(открывающий_тег), $pos2 - $pos1 - strlen(открывающий_тег));
    }
    ?>
    


    а я до сих пор древним методом страницы парсирую =(


    1. alekciy
      11.12.2015 11:06
      +1

      Значит просто не приходилось парсить сайты с обилием AJAX или построенных на JS. Но даже для сайтов выдающих статический HTML использовать XPath удобнее, чем регулярки. На супорте проще. Потому что у меня были кейсы когда нужно получить содержимое тега который не уникален (допустим тупо span без каких либо классов) и который находится где-то в разметке причем имеет смысл именно контексте этой разметки. Утрированно "достать содержимое span который находится Х-ым потомком Y тега который в свою очередь является братом картинки Z которая с свою очередь находится где-то среди потомков div-а с классом header". Регулярка под такой контекст получалась бы совершенно дикой и трудночитаемой. Работая с регулярками мы описываем не логическую структуру данных которые хотим получить, а по сути физическую. Регулярки более низкоуровневы.

      Ну и каждый инструмент должен использоваться в задачах под которые он заточен. Регулярки — работа с символами текста, XPath — навигация по логической структуру дерева без углубления в низкоуровневые аспекты.


  1. Dominis
    15.12.2015 11:13

    Хочу поделиться опытом использования old-school -ного парсера вместе с PhantomJS, думаю опыт может быть полезен, плюс тут есть цифры на которых можно «пощупать» скорость работы.
    Не так давно был у меня проект под который надо было парсить сайт с недвижимостью, python + requests + beatyful soup и вопрос решен довольно быстро. Навигация по html-дереву через xpath, регулярки только для анализа данных, но никак не структуры. Задача имела временные ограничения — работать парсер должен был не более 6ти часов в день. Среднее количество записей которые собирались с сервера — 67.000. Парсер был разбит на задачи, т.е. есть головной скрипт который производит общий аудит и создает отдельные задачи на парсинг, 8 воркеров отведено было для решения этих задач, каждый воркер мог еще создать отдельные задачи, на каждую из задач опять же отводилось 8 воркеров. Чтобы было понятно — сбор данных одна очередь, сбор изображений другая очередь, анализ урлов уводящих со страницы — третья. Такой вот парсер в работе был совершенно не заметен по нагрузке, и в свои 6 часов укладывался с лихвой. Потом на сайте-доноре произошли изменения которые без исполнения js никак не обработать. И тут я подключил к этому делу фантом. 12воркеров запускавших фантомы отжирали несколько гигов памяти, периодически они (экземпляры фантомов) не могли нормально «умереть» и не освобождали ресурсы, что в конечном итоге приводило к отказу сервера, т.к. в особо плохих случаях, когда проблема возникала очень часто — память на сервере просто заканчивалась физически. А это было 16 гигов оперативы и 16 гигов свапа. Данный вопрос решился относительно быстро, пайтон воркеры запускали phantomjs как обычную консольную команду и слушали output, это не было использование какого-то драйвера. В итоге, при плохом коннекте к сайту (думаю на стороне сайта-донора была защита по кол-ву обращений с одного IP) Phantomjs порой не мог загрузить страницу целиком, происходил обрыв связи, или получал ответ слишком поздно. Опять же, все мои скрипты для phantomjs-а исполнялись после того как DOM полностью построен, и задержка/обрыв связи при загрузке какого-нить скрипта блокировали исполнение задач моего phantom-а, соответственно как только я поставил таймеры на самоубийство фантома — проблема ушла. Однако пару раз сервер ночью у нас таки лёг. Но баги-то починить можно, а вот со скоростью работы сделать ничего нельзя :( Скорость сбора данных упала до 6-10 тысяч записей за отведенные 6 часов. Что любопытно, увеличение кол-ва воркеров выше 12 в моем случае приводило только к замедлению работы. Кол-во отказов в доступе/обрывов связи с сайтом-донором возрастало жутко, но это всё-таки не проблема решения, а проблема иной плоскости.


    1. Demogor
      15.12.2015 11:41

      Небольшой вопрос из разряда «здрасте, я Капитан Очевидность»: какого рода произошли изменения в js? Перенесли часть логики на ajax? Если да-то знатно проще самому выполнять ajax запрос и получать готовый результат в виде json или чего они там юзают. Если нет — то какого рода были вообще изменения?

      А вообще для вещей подобного плана кроме nw.js или питона сокомпани неплохо работает связка awesomium+C#+CSQuery(ну или любой другой хороший парсер). Если нужна огромная скорость парсинга — то code.google.com/p/majestic13 (крайне неудобная гадость, но по скорости работы уделывает большинство из парсеров, которые я вообще пробовал(давал прирост где-то на 40мс со страницы, что при большом кол-ве страниц довольно-таки заметный прирост). Парсил когда-то магазинчик, разгонял где-то под 40-50 страниц в секунду за счет многопоточности, правда ЦПУ жрало неимоверно).


      1. Dominis
        15.12.2015 12:03

        Часть нужных ссылок была переделана из обычного тега «a» в js реализацию по onClick, и, к сожалению, место откуда берутся данные весьма не очевидно, а дебагать обфусцированный js код очень сомнительное удовольствие.

        неплохо работает связка awesomium+C#+CSQuery

        К счастью/сожалению (по желанию) я не знаю C# и .net платформу. Знаю Python и js, на них и писал :)

        Что касается скорости парсинга, как я писал выше наибольшая проблема была в качестве связи с сайтом-донором. Сбросы соединений, 502-ые и т.д. не починить ничем, кроме разнесения процесса парсинга на разные сервера чтобы стучать с разных IP. Это при условии что я прав и такое поведение — это защита от слишком большого количества запросов от одного хоста, а не просто дохлый хостинг.


        1. Demogor
          15.12.2015 14:44

          В общем-то, с учетом того, что эффект один и тот же(что так, что так дохнет немилосердно), разница невелика)


    1. alekciy
      15.12.2015 17:33

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

      Оценка прироста от такого решения не проводилась? И я не очень уловил, как тогда оправляются в него команды?

      Я к примеру делал так, из php запускал пачку фантомов в фоновом режиме (т.е. после завершения работы php продолжали работать). Каждый фантом на своем локальном порту. Дальше воркеры пускаемые по крону ходили на них через webdriver с использованием библиотечки от фейсбука. По ощущениям фантом занимался каким-то соплежуйством где-то глубоко внутри (во 2-ой версии просто беда) и оверхед на коннекты был небольшой.

      думаю на стороне сайта-донора была защита по кол-ву обращений с одного IP

      Фантомы можно запускать с использованием проксей. Я вот запускал количество фантомов равным количеству проксей. Это частично решает вопрос со скоростью обхода (воркеры тупо берут активные прокси по round-robin).

      упала до 6-10 тысяч записей за отведенные 6 часов

      Думаю проксями решилось бы. На одной крайне кривом сайте на базе phpBB отслеживали онлайнеров. Удавалось за 15-20 минут обходить около 1500 страниц. Крутилось что-то около 30 Phantom2 с расходом ~20ГБ ОЗУ и большим расходом ЦПУ хоть и не в полку. LA был высокий, но в целом сервер работал нормально обслуживая внутренние веб сервисы.

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


      1. Dominis
        15.12.2015 18:31

        Оценка прироста от такого решения не проводилась? И я не очень уловил, как тогда оправляются в него команды?

        Нет, не производилось. Да и это решение было выбрано не совсем осознанно, надо было использовать какой-то headless браузер. Быстрый гуглинг для поиска решения подходящего под пайтон не принесли адекватных плодов, а с PhantomJs опыт уже был. Потому быстренько его и использовал. В тот момент когда возникла необходимость в парсинге с учетом js, было слишком мало времени на анализ, планирование, прототипирование… надо было сделать еще вчера. Потому получилось так, как я описал.
        Как фантом получает команды? Через консоль. /usr/bin/phantomjs /my/path/to/script.js params а в самом script.js есть всё, что надо для работы. Результат отдаем обратно в output и слушаем пайтоном. Т.е. там нет полноценного общения, запуск и получение результата, всё. Вся логика внутри скрипта который скармливается фантому.

        Фантомы можно запускать с использованием проксей

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

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

        Очень сомневаюсь в действительной пользе такого решения. Зачем внутри фантома решать задачи парсинга большие, чем получение нужных данных? Думается мне что оверхед на передачу данных в сторонний скрипт (Python, php, C#, nodejs, anything_you_like) будет гораздо ниже, чем анализ данных внутри виртуального браузера. Особенно если данных какой-то относительно большой объем. Я думал избавиться от воркера который собирает картинки и делать это в фантоме, всё равно ведь страница отрисовывается. Но способа лучше чем отрисовать картинку в канвасе и сохранить данные из канваса — не нашел. А данный способ всё равно порождает дополнительный запрос за контентом, потому смысла не имеет.
        Хотя в случае с использованием API для общения с браузером, возможно, всё иначе.


        1. alekciy
          15.12.2015 19:42

          Очень сомневаюсь в действительной пользе такого решения.

          Но именно он, как я понимаю, и был использован. "а в самом script.js есть всё, что надо для работы. Результат отдаем обратно в output"

          чем анализ данных внутри виртуального браузера

          У меня на одной из собираемых страниц таблица. Строится динамически аяксом. Колонок что-то около десятка. При количестве строк под сотню разбор данных из этой таблицы занимал где-то минуту из PHP по webdriver с использованием достаточно простого XPath. На сколько я знаю реализация да и сам протокол, скорость не самая это сильная сторона. Поэтому подумалось, что парсер этой таблицы через JS загруженный с фантомом должен работать быстрее. Все равно ведь сам фантом базируется на ghostdriver по сути своей наборе JS скриптов. Я надеялся, что спициализированный JS вся быстрее отработает, чем пачка JS общего назначение вынужденная к тому же поддерживать протокол. Но реализации не дошло. Пришлось срочно переписать на SpiderMonkey. Но я все думаю, помогло бы… Вот и спрашиваю у всех кто использовал фантом, был ли у них подобный опыт.


          1. Dominis
            15.12.2015 20:22

            Но именно он, как я понимаю, и был использован.

            Не совсем, я не отказался от всей той логики что была до появления фантома, всё что делал фантом в данном случае — кликал на элемент который якобы ссылка, отрабатывал все внутренние редиректы (от 2 до примерно 7) и возвращал итоговый url в output.

            Колонок что-то около десятка. При количестве строк под сотню разбор данных из этой таблицы занимал где-то минуту из PHP по webdriver с использованием достаточно простого XPath

            Звучит жутко, как я понимаю каждый xpath запрос отдавался в браузер и основная проблема была именно в этом? В случае с пайтоном и адекватным способом общения его и браузера, я бы стащил готовый html в пайтон и там уже разбирал. Может быть так и следовало поступить? Маленький html жрется очень быстро. Если надо распарсить какие-то огромные данные, то есть event-based парсинг. Доводилось мне парсить xml в 60гигов, примерно за 4 часа он был пережеван, при этом в базу ушло около 5,5 миллионов записей. В самом файле их было намного больше. Хотя таких данных в веб-парсинге пожалуй что нет.


            1. alekciy
              15.12.2015 23:28
              +1

              Не, XPath там один. Хотя сейчас сходил в репозиторий уточнить, все же уже запамятовал. Два, один на отслеживание появления таблицы на странице, второй вытаскивание данных:

              	/**
              	 * С заданного $url загрузит информацию о заказах закупки. Вернет массив в котором элементы это заказы УЗов.
              	 */
              	public function loadOrders($url)
              	{
              		$result = array();
              
              		$this->autoLogin();
              		$this->_account->driver->get($url);
              		$this->_account->driver->wait(20)->until(\WebDriverExpectedCondition::presenceOfElementLocated(
              			\WebDriverBy::id('page-footer')
              		));
              
              		echo '[' . date('d/M/Y:H:i:s') . "] Load purchase orders from URL: «{$url}»\n";
              		$orders_table = $this->_account->driver->wait(10)->until(\WebDriverExpectedCondition::presenceOfElementLocated(
              			\WebDriverBy::xpath('//div[@id="page-body"]//div[contains(@class,"post")]//div[@class="postbody"]/div[@class="content"]/table[1]')
              		));
              
              		// Бежим по строкам таблицы с заказами
              		$orders = $orders_table->findElements(\WebDriverBy::xpath('tbody/tr[position()>1]'));
              		foreach ($orders as $order)
              		{
              			$order_prop_list = $order->findElements(\WebDriverBy::xpath('td'));
              			// [NOTE] 2015-02-21 alekciy: При изменении формата выходного массива скорректировать атрибуты модели MonitPurchaseOrder
              			// т.к. в OrdersMonitCommand::_load() идет массовое присвоение
              			$result[] = array(
              				'member'           => mb_trim($order_prop_list[0]->getText()),
              				'item_name'        => mb_trim($order_prop_list[1]->getText()),
              				'item_price'       => mb_trim($order_prop_list[2]->getText()),
              				'item_props'       => mb_trim($order_prop_list[3]->getText()),
              				'item_status'      => mb_trim($order_prop_list[4]->getText()),
              				'item_status_norm' => $this->normalizeStatus($order_prop_list[4]->getText()),
              			);
              		}
              
              		return $result;
              	}
              

              Причем сначала я думал в сторону аякса и проксей. Там конечно все тоже не быстро, но не на целую минуту. Вариант работы с исходником да, отличный, нужно было опробывать. Но там был сильно кривой html, а в php в плане работы с xml все немного хуже, чем в питоне.


              1. Dominis
                16.12.2015 00:19

                Я тут вижу дважды вызов метода wait(X), с 20 и 10 в качестве параметра. Я правильно понимаю что это время отведенное на ожидание драйверу, прежде чем вернуть ответ? Т.е. грубо говоря ждем 20 секунд чтобы всё загрузилось потом тащим таблицу? Тогда получается что полминуты это только ожидания…


                1. alekciy
                  16.12.2015 08:26

                  Нет, не так. Это таймаут сколько мы ждём появление элемента. Внутри ясное дело libevent и не пахнет, но отрабатывает быстрее заданного таймаута.


  1. alekciy
    15.12.2015 19:41

    delete, ошибся кнопкой