Как написать парсер на java

Многие компании используют парсинг сайтов, чтобы агрегировать информацию из конкурентных ресурсов. Ее можно получить из HTML-кода веб-страниц. Но только не ту, которая находится в картинках или другом медийном контенте. В этом руководстве мы научимся парсить данные с помощью Java.

  • Обязательные условия
  • Инструменты
  • Парсер на java: пример-получим данные из интернет-магазина
  • Не останавливайтесь
  • Базовые знания Java;
  • Основы XPath.

Вам потребуется Java 8 и браузер HtmlUnit

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

Если вы используете Eclipse, советую изменить максимальную длину в окне подробностей (при нажатии на вкладку «Переменные»), чтобы можно было видеть весь HTML-код веб-страницы.

Инструменты

В примере парсинга сайта Java мы извлечем информацию с интернет-магазина. Соберем имена, цены, а также картинки и экспортируем их в формат JSON.

Сначала посмотрим, что происходит при поиске товара в магазине. Для этого откройте «Инструменты разработчика» в Chrome и перейдите на вкладку «Network»:

Парсер на java: пример-получим данные из интернет-магазина

URL поиска:

https://newyork.craigslist.org/search/moa?is_paid=all&search_distance_type=mi&query=iphone+6s

Также можно использовать

https://newyork.craigslist.org/search/sss?sort=rel&query=iphone+6s

Теперь откройте среду разработки. Пришло время писать код. Для выполнения запроса в HtmlUnit нужен WebClient. Сначала нужно отключить JavaScript, так как в нашем примере он не нужен, и без него страница будет загружаться быстрее:

String searchQuery = "Iphone 6s" ;
WebClient client = new WebClient();
client.getOptions().setCssEnabled(false);
client.getOptions().setJavaScriptEnabled(false);
try {
String searchUrl = "https://newyork.craigslist.org/search/sss?sort=rel&query=" + URLEncoder.encode(searchQuery, "UTF-8");
HtmlPage page = client.getPage(searchUrl);
}catch(Exception e){
e.printStackTrace();
}
}

В объекте HtmlPage будет HTML-код, доступ к которому можно получить с помощью метода asXml(). Вытянем с сайта названия, изображения и цены. Для этого нужно внимательно просмотреть структуру DOM:

Парсер на java: пример-получим данные из интернет-магазина - 2

Для парсинга страниц сайта есть несколько способов выбрать тег HTML, используя HtmlUnit:

  • getHtmlElementById(String id);
  • getFirstByXPath(String Xpath)-getByXPath(String XPath), который возвращает список.

Поскольку мы не можем использовать ID, чтобы выбрать теги, нужно составить выражение Xpath.

XPath — это язык запросов для выбора элементов XML (в нашем случае HTML).

Сначала нужно выбрать все теги <p> с классом `result-info. Затем выполнить итерацию в списке, и для каждого предмета выбрать название, цену и URL, а также вывести на экран.

List<HtmlElement> items = (List<HtmlElement>) page.getByXPath("//p[@class='result-info']" ;
if(items.isEmpty()){
System.out.println("No items found !");
}else{
for(HtmlElement item : items){
HtmlAnchor itemAnchor =  ((HtmlAnchor) htmlItem.getFirstByXPath(".//a"));
String itemName = itemAnchor.asText();
String itemUrl = itemAnchor.getHrefAttribute() ;
HtmlElement spanPrice =((HtmlElement) htmlItem.getFirstByXPath(".//span[@class='result-price']")) ;
// Возможно, что для товара не установлена цена
String itemPrice = spanPrice == null ? "no price" : spanPrice.asText() ;
System.out.println( String.format("Name : %s Url : %s Price : %s", itemName, itemPrice, itemUrl));
}
}

Затем сохраним данные в формате JSON, используя библиотеку Jackson. Для представления элементов, полученных при парсинге email адресов с сайта, нам понадобится POJO (объект языка Java).

Item.java

public class Item {
private String title ;
private BigDecimal price ;
private String url ;
// геттеры и сеттеры
}

Затем добавим это в файл pom.xml:

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

Теперь нужно создать элемент, задать атрибуты, конвертировать в строку или файл JSON и немного адаптировать предыдущий код парсинга данных с сайта:

for(HtmlElement htmlItem : items){
HtmlAnchor itemAnchor = ((HtmlAnchor) htmlItem.getFirstByXPath(".//span[@class='txt']/span[@class='pl']/a"));
HtmlElement spanPrice = ((HtmlElement) htmlItem.getFirstByXPath(".//span[@class='txt']/span[@class='l2']/span[@class='price']")) ;
// Возможно, для товара не установлена цена, в этом случае мы обозначаем ее как 0.0
String itemPrice = spanPrice == null ? "0.0" : spanPrice.asText() ;
Item item = new Item();
item.setTitle(itemAnchor.asText());
item.setUrl( baseUrl +
itemAnchor.getHrefAttribute());
item.setPrice(new BigDecimal(itemPrice.replace("$", "")));
ObjectMapper mapper = new ObjectMapper();
String jsonString = mapper.writeValueAsString(item) ;
System.out.println(jsonString);
}

Этот пример парсинга другого сайта не идеален, многое можно улучшить:

  • Поиск по городам;
  • Обработка пагинации;
  • Поиск по нескольким критериям.

Код примера находится здесь.

Overview

Parsers are an essential part of any programming language. Although there are many open-source parsers easily available in Java and developers can select the right one as per the requirement, sometimes the one you need is not available. In that case, developers often have to create custom parsers in Java. Along with unavailability, another primary reason behind developing custom parsers includes performance issues in available parsers, flaws in them, or their inability to fulfill all the requirements.

Further, in this article, we will discover various ways to generate parsers in Java yourself using available tools and Java libraries.

new java job roles

Getting started

Before diving into details, let’s first understand what the terms ‘parsing’ and ‘parser’ mean.

Parser and parsing

Generally, parsing can be defined as the process of breaking down a block of data into smaller parts based on certain pre-defined rules. These parts are then interpreted, modified, or managed as per the requirement of the developers.

And, a parser is a program written to perform the breaking down of data into smaller parts.  A parser is usually composed of two parts: a lexer, also called scanner or tokenizer, and the parser itself.  A lexer and a parser work in sequence, the lexer scans the input data and produces a list of tokens, the parser then scans the tokens and produces the parsing result.

Rules and grammars

The definitions used by lexers or parsers are called rules or productions.  And all these rules make up grammar. In simple terms, grammar is the list of rules that define how each construct or line of code should be composed. For example, a rule for an if statement must specify that it must starts with the “IF” keyword, followed by a left parenthesis, an expression, a right parenthesis, and a statement.

If the code or text is not according to the rule, the parser will identify it as incorrect which results in a syntax error.

Ways to parse in Java

If you need to parse a document in Java there are primarily three ways to do it.

  • As mentioned earlier, use an existing library that supports that specific language you want to parse: for instance, a library to parse XML.
  • If you cannot find an existing parser, use a tool or library to develop a parser: for example, ANTLR, which is used to build parsers for Java or any language.
  • Lastly, writing your custom parser from scratch as per your requirement.

Using an Existing Library for parsing

It is a good option for parsing well-known and supported languages, like XML or HTML. A good library usually also includes API to create and modify documents in that language. This is an additional feature that will not come with a basic parser. The limitation is that such libraries are not that common and they support only the most common languages.

Building your own custom Java parser

You may need to go with this option if you have extremely particular needs. In the sense that the language you need to parse cannot be parsed with available parsers and you can’t get the required features in parser generating tools. There could be several reasons behind it like because you need the best possible performance or a deep integration between different components but it is surely the most difficult and time-consuming option out of three.

You would also be needing to have some exceptional skills for writing a parser in Java or a developer who could write that for you.

Using existing tools to write a Parser

Out of all three cases, it is the most used one and is considered the best way to generate parsers in Java. As the most flexible and least time-consuming option to develop a Java parser, we will be mostly concentrating on the tools and libraries.

Libraries and tools to build parsers in Java

For starters, Tools that are used to write the code for a parser are known as parser generators and libraries that create parsers are called parser combinators. Along with that, another tool that is needed is a lexical analyzer (lexers) that analyzes regular languages while writing parsers in Java.

Following is the list of different types of tools required to write a Java parser:

· JFlex

JFlex is a lexer generator based on deterministic finite automata (DFA). It matches the input according to the defined grammar known as spec and executes the corresponding action. It can also be used as a standalone tool, but being a lexer generator, it is designed to work with parser generators: it is typically used with CUP or ANTLR (we will be discussing them later)

The spec (grammar) is divided into three parts, separated using a ‘%%’ symbol:

  • usercode, that will be included in the generated class,
  • options/macros,
  • lastly, the lexer rules.

· ANTLR

ANTLR is one of the most used generators for context-free parsers in Java. Along with Java, ANTLR can be used to write parsers in numerous other languages.

Its typical grammar is divided into two parts: lexer rules and parser rules. The division is implicit as all the rules starting with an uppercase letter are lexer rules whereas the ones starting with a lowercase letter are parser rules. Lexer and parser grammars can also be defined in separate files.

· APG

APG is another tool for generating parsers in Java but unlike other examples, it is a recursive-descent parser using a variation of Augmented BNF. APG also supports additional operators, like custom user-defined matching functions and syntactic predicates. An APG grammar is also very clean and easy to understand. It can generate parsers in Java, C/C++, and JavaScript.

· Coco/R

Coco/R is a compiler generator that generates a scanner and a recursive descent parser after taking an attributed grammar. Attributed grammar is the one where rules are written in an EBNF variant and can be annotated in multiple ways to change the methods of a generated parser.

Coco/R has fine documentation, with numerous examples of grammar available for better understandings. Along with Java, it also supports C# and C++.

· CUP

CUP stands for Construction of Useful Parsers. It is LALR (look-Ahead LR) parser generator for Java. It just generates proper parts of a parser and is well suited to be used with JFlex.  It also comes with an Eclipse plugin to help developers in creating grammar.

· JavaCC

JavaCC is the most widely used parser generator for Java. The grammar that comes with it contains all the actions and the custom code needed to build parsers in Java. Compared to ANTLR, the grammar file is not that clean and includes a significant part of Java source code.

Thanks to some of its prominent usage in very important projects, like JavaParser, it led to some good content in its documentation.  It offers a grammar repository, but it does not contain that many grammars.

· Java libraries that parse Java: JavaParser

In one special case where you want to parse a Java code in Java, a library named JavaParser is the best option available. It supports lexical preservation as well as pretty printing which means you can parse a Java code, modify it and print it back either with the original formatting or pretty printed. It can be used with JavaSymbolSolver as well. It supports all versions of Java from 1 to 9 so you do not have to worry about which version you should use.

Pros and cons of using parsing tools

Many of these tools and libraries were started as a thesis or a research project. The bright side to this is these tools tend to be easily and freely available by their makers. However, on the flip side, there is often a lack of good documentation on how to use them. It is obvious as they are not primarily made to be used by the mass audience. Also, some tools are abandoned without any new updates as the original authors are done with their master’s or Ph.D. degrees so they stop maintaining them.

Keeping these points in mind, you should select the right tool based on their available information and go through all the documentation before using any of these tools for parsing.

See Also: Understanding Memory Management In Java

Wrapping it up

Parsers in Java is a very vast topic and it is different from the usual world of java development. Due to this, it would be way too much to expect a developer to be able to write parsers in Java. This is where these tools shine as it is now way easier for developers to generate a parser in Java.

new Java jobs

Достать данные можно разными способами и, конечно, зависит от задач. Попробую рассмотреть некоторые варианты разбора Json.

Заметка: для каждого из примеров для парсинга будет взят Json из вопроса, чтобы зря не копировать в ответ.


Simple Json

Где взять: здесь / репозиторий на github / или через Maven и пр.

Это самый примитивный способ. По сути, всё, что тут есть — это JSONObject и JSONArray.

  • JSONArray может включать в себя несколько объектов JSONObject, его можно обходить циклом на каждой итерации получая объект JSONObject.
  • JSONObject — объект, из которого можно доставать его отдельные свойства.

Я бы использовал его для небольших Json строк, где не надо сильно заморачиваться или если не лень писать свой класс-обработчик на основе кода, который продемонстрирован ниже:

// Считываем json
Object obj = new JSONParser().parse(jsonString); // Object obj = new JSONParser().parse(new FileReader("JSONExample.json"));
// Кастим obj в JSONObject
JSONObject jo = (JSONObject) obj;
// Достаём firstName and lastName
String firstName = (String) jo.get("firstName"); 
String lastName = (String) jo.get("lastName");
System.out.println("fio: " + firstName + " " + lastName);
// Достаем массив номеров
JSONArray phoneNumbersArr = (JSONArray) jo.get("phoneNumbers");
Iterator phonesItr = phoneNumbersArr.iterator();
System.out.println("phoneNumbers:");
// Выводим в цикле данные массива
while (phonesItr.hasNext()) {
    JSONObject test = (JSONObject) phonesItr.next();
    System.out.println("- type: " + test.get("type") + ", phone: " + test.get("number"));
}

Остальная работа с вложенными массивами аналогична. Можно складывать в List, Map и пр.


GSON

Где взять: здесь / репозиторий на github / или через Maven и пр.

Документация: http://www.studytrails.com/java/json/java-google-json-introduction/

Позволяет парсить Json также, как и Json-simple, т.е. используя JSONObject и JSONArray (см. документацию), но имеет более мощный инструмент парсинга.
Достаточно создать классы, которые повторяют структуру Json‘а. Для парсинга Json из вопроса создадим классы:

class Person {
    public String firstName;
    public String lastName;
    public int age;
    public Address address;
    public List<Phones> phoneNumbers;
    public List<Person> friends;
}

class Address {
    public String streetAddress;
    public String city;
    public String state;
    public int postalCode;
}

class Phones {
    public String type;
    public String number;
}

Теперь достаточно написать:

Gson g = new Gson();
Person person = g.fromJson(jsonString, Person.class);

Всё! Магия! Чудо! Теперь в person лежит объект с типом Person, в котором находятся данные именно с теми типами, которые были указаны в созданных классах!
Теперь можно работать с любым типом, как это привыкли всегда делать: String, Integer, List, Map и всё остальное.

// Выведет фамилии всех друзей с их телефонами
for (Person friend : person.friends) {
    System.out.print(friend.lastName);
    for (Phones phone : friend.phoneNumbers) {
        System.out.println(" - phone type: " + phone.type + ", phone number : " + phone.number);
    }
}

// output:
// Snow - phone type: home, phone number : 141 111-1234
// Tompson - phone type: home, phone number : 999 111-1234

Пример парсинга в Map:

…… JSON для разбора:

…… Классы (POJO):

class Seanse {
    public String name;
    public String locate
    public String metro;
    public List<Sessions> sessions;
}

class Sessions {
    public String time;
    public double price;
}   

…… Сам разбор выглядит так:

Gson g = new Gson();
Type type = new TypeToken<Map<String, Seanse>>(){}.getType();
Map<String, Seanse> myMap = g.fromJson(json, type);

Всё.

Дополнительно в GSON можно использовать аннотации, например: исключить указанные поля при парсинге, поменять имя переменной (например не personFirstName, а fName) и многое другое. Подробнее см. в документации.


Jackson

Где взять: здесь / репозиторий на github / или через Maven и пр.

Документация и примеры: https://github.com/FasterXML/jackson-docs

Как и GSON он также позволяет работать используя JSONObject и JSONArray если это требуется, и тоже умеет парсить на основе предоставленных классов (см. пример ниже).

Аналогично в нем можно указывать дополнительные требования за счет аннотаций, например: не парсить указанные поля, использовать кастомный конструктор класса, поменять имя переменной (например не firstName, а fName) и многое другое.
Подробнее см. в документации.

ObjectMapper mapper = new ObjectMapper();
Person person = mapper.readValue(jsonString, Person.class);

System.out.println("My fio: " + person.firstName + " " + person.lastName + " and my friends are: ");
for (Person friend : person.friends) {
    System.out.print(friend.lastName);
    for (Phones phone : friend.phoneNumbers) {
        System.out.println(" - phone type: " + phone.type + ", phone number : " + phone.number);
    }
}

// output:
// My fio: Json Smith and my friends are: 
// Snow - phone type: home, phone number : 141 111-1234
// Tompson - phone type: home, phone number : 999 111-1234

JsonPath

Где взять: через Maven и другие сборщики / репозиторий на github

Относится к так называемым XPath библиотекам. Её суть аналогична xpath в xml, то есть легко получать часть информации из json‘а, по указанному пути. А также позволяет фильтровать по условию.

// Выведет все фамилии друзей
List<String> friendsLastnames = JsonPath.read(jsonString, "$.friends[*].lastName");
for (String lastname : friendsLastnames) {
    System.out.println(lastname);
}

// output:
// Snow
// Tompson

Пример с выборкой по условию:

// Поиск друга, которому больше 22 лет
List<String> friendsWithAges = JsonPath
.using(Configuration.defaultConfiguration())
.parse(jsonString)
.read("$.friends[?(@.age > 22)].lastName", List.class);

for (String lastname : friendsWithAges) {
    System.out.println(lastname);
}

// output:  
// Tompson

Парсим на Java -23

Программирование, JAVA, HTML, jQuery


Рекомендация: подборка платных и бесплатных курсов создания сайтов — https://katalog-kursov.ru/

Решил я как-то перенести своего Телеграм-бота с Python на Java, дабы подтянуть свой скилл. Все перенес, осталось только самое сложное — HTML-парсинг. На Хабре постов про парсинг на Java не нашел, так почему бы не написать свой?

Выбираем парсер

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

import org.jsoup.Jsoup;

Парсим

В jsoup можно загрузить HTML-код 3 разными способами:

  • Из строки: str = Jsoup.parse(HTMLSTring);
  • С веб-сайта: web = Jsoup.connect("http://google.com/").get();
  • Из файла: htmlFile = Jsoup.parse(new File("login.html"), "ISO-8859-1");

Мне нужен был парсинг с веба, сейчас для примера будет парсить гугл.

Document doc = Jsoup.connect("https://google.com").get();

Поиск элементов в DOM происходит следующими способами:

  • С помощью стандартных методов getElementById(String id), getElementsByTag(String tag) и getElementsByClass(String className)
  • Элементы, имеющие указанный аттрибут getElementsByAttribute(String key)
  • Элементы, имеющие аттрибут с значением getElementsByAttributeValue(String key, String value)
  • С помощью CSS-подобного селектора (aka jQuery) select("a[href]")

Например, хотим найти текст кнопки с аттрибутом jsaction=sf.chk

Document doc = Jsoup.connect("https://www.google.com").get();
Element button = doc.select("input[jsaction="sf.chk"]").first();
System.out.print(button.val());

Или:

Element button = doc.body().getElementsByAttributeValue("jsaction", "sf.chk").first();

Тут кому как удобнее. А теперь найдем главное поле ввода и выведем значение его аттрибута title:

Element input = doc.body().getElementById("lst-ib");
System.out.print(input.attr("title"));

А вот текст ссылки с почтой:

doc.select("a.gb_P[data-pid="23"]").first().text();

Выводы

Как видите, парсинг HTML на Java — не такая и страшная штука как казалось. Надеюсь с моей помощью вы без труда разберете на тэги любой сайт. Удачного парсинга!

Содержание

  1. Подключаем Jsoup и парсим HTML
  2. Выбор элементов и их атрибутов
  3. Выводы

Если вы пишете робота на Java для разбора контента с каких-либо сайтов (т.н. «краулер»), то вы можете встретиться с некоторыми сложностями. Язык HTML хоть и формализован, однако допускает ошибки в разметке без нарушения отображения, в отличие от более строгого XML. Самой частой ошибкой является незакрытый тэг.

Страница в браузере может выглядеть корректно, но при попытке разобрать вёрстку вы потерпите неудачу. Кроме html 5-ой версии, существует ещё несколько стандартов вёрстки.

Чтобы не изобретать велосипед, можно воспользоваться готовой библиотекой Jsoup, которая позволяет легко парсить исходный html и выбирать оттуда отдельные элементы в простом декларативном синтаксисе. Библиотека поддерживает выбор как в формате CSS (более привычный на frontend), так и в XPath.

Для подключения достаточно добавить в maven следующую зависимость:

<dependency>
    <groupId>org.jsoup</groupId>
    <artifactId>jsoup</artifactId>
    <version>1.14.3</version>
</dependency>

Скачивать html содержимое можно силами самой библиотеки:

try {
    var document = Jsoup.connect(«https://devmark.ru/»).get();
    // бизнес-логика
} catch (Exception e) {
    // обработка ошибок парсинга
}

Если же вы используете асинхронную модель, когда робот сначала скачивает содержимое множества страниц, а затем в отдельном процессе выполняет обработку, то библиотека поддерживает также парсинг строки и чтение из файла:

var htmlFile = new File(«/home/user/Documents/index.html»);
var document = Jsoup.parse(htmlFile, StandardCharsets.UTF_8.name());

После получения объекта Document, вы можете запрашивать у него различные элементы и их атрибуты. Далее будем запрашивать элементы в формате CSS. Если известно, что элемент должен быть на странице в единственном экземпляре (например, тэг title), то используйте метод selectFirst():

var titleElement = document.selectFirst(«title»);
System.out.printf(«Title: %s%n», titleElement.text());

Но более безопасно использовать общий подход, когда элементов может быть несколько. Например, для получения всех гиперссылок на странице, используйте метод select():

var anchors = document.body().select(«a»);
System.out.printf(«Anchor count: %s%n», anchors.size());

Мы получаем список объектов Element, у каждого из которых нужно взять значение атрибута href с помощью метода attr():

var links = anchors.stream()
        .map(anchor ->
                anchor.attr(«href»)
        ).toList();
System.out.println(links);

В результате в консоль будут выведены все гиперссылки, которые есть на странице.

Как видите, Jsoup предоставляет простое и понятное API для выбора элементов и их атрибутов, которые содержатся в скачанном html-документе. Обладая этими знаниями, вы уже можете написать собственный краулер, а то и новый поисковик.

Понравилась статья? Поделить с друзьями:
  • Как написать пароль на питоне
  • Как написать пароль на латинице
  • Как написать пароль на компьютере
  • Как написать пароль на английском языке на ноутбуке
  • Как написать пароль на английской раскладке русскими буквами на телефоне