Парсинг данных о товарах электронной торговли

В этом уроке мы рассмотрим, как извлекать данные о товарах с любых сайтов электронной коммерции с помощью Java. Существует множество различных вариантов использования извлечения данных о товарах, таких как:

  • Мониторинг цен в электронной коммерции
  • Сравнение цен
  • Мониторинг доступности
  • Извлечение отзывов
  • Исследование рынка
  • Нарушение MAP

Что вам понадобится

Мы будем использовать HtmlUnit для выполнения HTTP-запроса и разбора DOM. Добавьте эту зависимость в pom.xml.

<dependency>
   <groupId>net.sourceforge.htmlunit</groupId>
   <artifactId>htmlunit</artifactId>
   <version>2.19</version>
</dependency>

Мы также будем использовать библиотеку Jackson:

<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.9.8</version>
</dependency>

Schema.org

Для того чтобы извлечь интересующие нас поля, мы будем анализировать метаданные https://schema.org из разметки Html.

Schema - это семантический словарь, который может быть добавлен к любой веб-странице. Внедрение Schema имеет множество преимуществ. Большинство поисковых систем используют ее, чтобы понять, о чем страница (продукт, статья, отзыв и многое другое).

По данным schema.org, во всем мире ее используют около 10 млн. сайтов. Это очень много! Существуют различные типы Schema, и сегодня мы рассмотрим тип Product.

Это очень удобно, потому что если вы написали парсер, который извлекает определенные данные из схемы, то он будет работать на любом другом сайте, использующем ту же схему. Больше не нужно писать специфические XPath / CSS-селекторы!

Существует три основных вида разметки schema:

JSON-LD

<script type="application/ld+json">
{
    "@context": "http://schema.org",
    "@type": "ItemList",
    "url": "http://multivarki.ru?filters%5Bprice%5D%5BLTE%5D=39600",
    "numberOfItems": "315",
    "itemListElement": [
        {
            "@type": "Product",
            "image": "http://img01.multivarki.ru.ru/c9/f1/a5fe6642-18d0-47ad-b038-6fca20f1c923.jpeg",
            "url": "http://multivarki.ru/brand_502/",
            "name": "Brand 502",
            "offers": {
                "@type": "Offer",
                "price": "4399 p."
            }
        },
        {
            "@type": "Product",
            "name": "..."
        }
    ]
}
</script>

RDF-A

<div vocab="http://schema.org/" typeof="ItemList">
    <link property="url" href="http://multivarki.ru?filters%5Bprice%5D%5BLTE%5D=39600"><span property="numberOfItems">315</span>
    <div property="itemListElement" typeof="Product">
        <img property="image" alt="Photo of product" src="http://img01.multivarki.ru.ru/c9/f1/a5fe6642-18d0-47ad-b038-6fca20f1c923.jpeg"> <a property="url" href="http://multivarki.ru/brand_502/"><span property="name">BRAND 502</span></a>
        <div property="offers" typeof="http://schema.org/Offer">
            <meta property="schema:priceCurrency" content="RUB">руб
            <meta property="schema:price" content="4399.00">4 399,00
            <link property="schema:itemCondition" href="http://schema.org/NewCondition">
        </div>...
        <div property="itemListElement" typeof="Product">
          ...
        </div>
    </div>
</div>

И тот, который используется в нашем примере, - Microdata:

<div class="schema-org">
    

<div itemscope="" itemtype="https://schema.org/Product">
    <img itemprop="image" src="https://images.asos-media.com/products/the-north-face-vault-backpack-28-litres-in-black/10253008-1-black" alt="Image 1 of The North Face Vault Backpack 28 Litres in Black">
    <link itemprop="itemCondition" href="https://schema.org/NewCondition">
    <span itemprop="productID">10253008</span>
    <span itemprop="sku">10253008</span>
    <span itemprop="brand" itemscope="" itemtype="https://schema.org/Brand">
        <span itemprop="name">The North Face</span>
    </span>
    <span itemprop="name">The North Face Vault Backpack 28 Litres in Black</span>
    <span itemprop="description">Shop The North Face Vault Backpack 28 Litres in Black at ASOS. Discover fashion online.</span>
    <span itemprop="offers" itemscope="" itemtype="https://schema.org/Offer">
        <link itemprop="availability" href="https://schema.org/InStock">
        <meta itemprop="priceCurrency" content="GBP">
        <span itemprop="price">60</span>
        <span itemprop="eligibleRegion">GB</span>
        <span itemprop="seller" itemscope="" itemtype="https://schema.org/Organization">
            <span itemprop="name">ASOS</span>
        </span>
    </span>  
</div>

  </div>

Обратите внимание, что на одной странице можно разместить несколько предложений.

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

Извлечение данных

Прежде всего, необходимо создать базовый POJO продукта:
public class Product {
 
private BigDecimal price;
private String name;
private String sku;
private URL imageUrl;
private String currency;
        // ...геттеры и сеттеры
Затем нам нужно перейти к целевому URL и создать базовый парсер микроданных для извлечения интересующих нас полей. Я использую для этого HtmlUnit, который представляет собой чистый безголовый браузер на Java. Я мог бы использовать множество различных библиотек, например Jsoup или Selenium + Headless Chrome.
 
Но в большинстве случаев HtmlUnit является хорошим решением, поскольку он легче, чем Selenium + Headless Chrome, но предлагает больше возможностей, чем сырой HTTP-клиент + JSoup (который занимается только разбором Html).
 
Для "Javascript-тяжелых" сайтов, основанных на фронтенд-фреймворках типа React / Vue.js, Headless Chrome - это то, что нужно!
WebClient client = new WebClient();
client.getOptions().setCssEnabled(false);
client.getOptions().setJavaScriptEnabled(false);
String productUrl = "https://www.asos.com/the-north-face/the-north-face-vault-backpack-28-litres-in-black/prd/10253008";
 
HtmlPage page = client.getPage(productUrl);
HtmlElement productNode = ((HtmlElement) page
.getFirstByXPath("/*[@itemtype='https://schema.org/Product']"));
URL imageUrl = new URL((((HtmlElement) productNode.getFirstByXPath("./img")))
.getAttribute("src"));
HtmlElement offers = ((HtmlElement) productNode.getFirstByXPath("./span[@itemprop='offers']"));
 
BigDecimal price = new BigDecimal(((HtmlElement) offers.getFirstByXPath("./span[@itemprop='price']")).asText());
String productName = (((HtmlElement) productNode