Giter Club home page Giter Club logo

smlblog1's Introduction

Retrofit 2 + Jspoon = парсинг html на лету

Всем привет! Приходилось ли вам сталкиваться с задачами загрузки каких-либо данных с сайтов, у которых отсутствует API? В подобных случаях многие разработчики предпочитают использовать различные вспомогательные библиотеки для обработки html. К примеру, одним из наиболее популярных решений для парсинга html является библиотека Jsoup, содержащая довольно мощные инструменты для поиска и извлечения всех необходимых данных. Тем не менее, при использовании подобных библиотек разработчику все равно потребуется потратить определенное время на "разбор" исходного кода сайта и работу с полученным содержимым.

Сегодня мы рассмотрим Jspoon – небольшую, но довольно полезную библиотеку, которая является оберткой над Jsoup, а также попробуем что-нибудь распарсить. Мотивацей авторов библиотеки послужил пример использования Retrofit 2 и конвертеров из Moshi или Gson, когда для парсинга json-данных достаточно просто подключить соответствующую ConverterFactory. В итоге использование Jspoon ощутимо сокращает количество кода, которое необходимо написать разработчику для извлечения необходимых данных из html.

В примере с парсингом подразумевается наличие RxJava 2 и Retrofit 2, которые де-факто являются стандартными инструментами в арсенале Android-разработчика.

Установка

Для установки добавьте необходимые зависимости в файл app/build.gradle вашего проекта.

Основная библиотека:

dependencies {
    implementation 'pl.droidsonroids:jspoon:1.3.0'
}

Конвертер для Retrofit 2:

dependencies {
    implementation 'pl.droidsonroids.retrofit2:converter-jspoon:1.3.0'
}

Как это работает?

Работа Jspoon основана на использовании аннотации Selector с параметрами, на основе которых осуществляется парсинг html для получения POJO объектов. А благодаря тому, что для внутреннего парсинга html применяется уже упомянутый Jsoup, для использования с аннотацией Selector вам доступен полный синтаксис селекторов этой библиотеки.

@Selector

Аннотация может быть применена к полям следующих типов (или соответствующих им примитивов):

  • String
  • Boolean
  • Integer
  • Long
  • Float
  • Double
  • Date
  • BigDecimal
  • Element (тип данных из Jsoup)
  • Любой класс с конструктором по умолчанию
  • List и его подтипы

Параметры аннотации Selector

value

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

attr

Параметр, определяющий извлекаемый из html-тега атрибут. По умолчанию используется значение textContent, так же доступны несколько предопределенных вариантов: html (или innerHtml) и outerHtml. Кроме того, можно указать любой другой атрибут тега, который необходимо извлечь (например, для извлечения ссылки из тега <img src="..."> необходимо использовать параметр attr="src")

regex

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

defValue

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

@Format

Дополнительная аннотация для парсинга даты в поле типа Date, используется совместно с аннотацией Selector.

Параметры аннотации Format

value

Определяет формат даты для парсинга, например:

@Format(value = "HH:mm:ss dd.MM.yyyy")
@Selector(value = "#date")
Date date;
languageTag

Locale используется для парсинга типов Float, Double и Date. Вы можете указать свое значение, например:

@Format(languageTag = "ru")
@Selector(value = "div > p > span")
Double pi;

Давайте что-нибудь распарсим!

Перейдем от теории к практике и для примера распарсим раздел Разработка из SML-блога.

Для начала, определим статический вспомогательный метод для создания инстанса Retrofit, который будет использоваться для выполнения запросов к нашему блогу:

  static Retrofit createRetrofitInstance() {

    return new Retrofit.Builder()
        .baseUrl(BASE_URL)
        .client(customOkHttpClient())
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
        .addConverterFactory(JspoonConverterFactory.create())
        .build();
  }

Для использования Jspoon достаточно подключить соответствующий конвертер с помощью метода addConverterFactory, после чего в дело вступит аннотация @Selector, благодаря которой будут конструироваться объявленные нами классы.

В качестве базового примера рассмотрим получение статей из блога в виде списка объектов класса SmlArticlePreview, который содержит поля с названием статьи, ссылкой на статью, именем автора и небольшим превью-текстом. Для этого заглянем в исходники страницы с материалами блога и определимся с необходимыми нам селекторами:

...
<div class="news__item" id="blog-post-5825">
	<a href="https://ru.smedialink.com/razrabotka/change-detection-vo-frontend-frejmvorkah/">
		<img src="https://smedialink.com/wp-content/uploads/2018/02/426-248-v2.jpg" alt="Change detection во фронтенд-фреймворках">
	</a>
	<div class="description">
		<p class="author">Александр Хисматулин</p>
			<p><a class="title" href="https://ru.smedialink.com/razrabotka/change-detection-vo-frontend-frejmvorkah/">Change detection во фронтенд-фреймворках</a></p>
		<p class="preview">Мир современной фронтенд-разработки предлагает множество библиотек и фреймворков на разный...</p>
		<div class="date-tag">1 марта</div><a href="https://ru.smedialink.com/blog/razrabotka/" class="button-tag tag">Разработка</a>
	</div>
</div>
<div class="news__item" id="blog-post-5828">
	<a href="https://ru.smedialink.com/razrabotka/podhody-k-realizatsii-poiska-dokumentov-na-servere/">
		<img src="https://smedialink.com/wp-content/uploads/2018/02/426-248.jpg" alt="Подходы к реализации поиска документов на сервере">
	</a>
	<div class="description">
		<p class="author">Антон Рогазинский</p>
			<p><a class="title" href="https://ru.smedialink.com/razrabotka/podhody-k-realizatsii-poiska-dokumentov-na-servere/">Подходы к реализации поиска документов на сервере</a></p>
		<p class="preview">При разработке сайта, предоставляющего доступ к большому количеству медиа контента,...</p>
		<div class="date-tag">1 марта</div><a href="https://ru.smedialink.com/blog/razrabotka/" class="button-tag tag">Разработка</a>
	</div>
</div>
...

Каждая новость в блоге заключена в тег div с классом news__item, используем это для создания класса BlogPage, в котором будет содержаться список статей:

public class BlogPage {

  @Selector("div.news__item")
  public List<SmlArticlePreview> articles;
}

Аналогичным образом используем аннотации для получения данных об отдельно взятой статье. Название статьи заключено в тег с классом title, теги с автором и превью имеют классы author и preview соответственно, а для получения ссылки используем параметр attr, описанный выше. В итоге объявление класса SmlArticlePreview имеет следующий вид:

public class SmlArticlePreview {

  @Selector(value = "a.title")
  public String title;

  @Selector(value = "a.title", attr = "href")
  public String link;

  @Selector(value = "p.author")
  public String author;

  @Selector(value = "p.preview")
  public String preview;
}

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

public interface BlogLoader {

  @GET("/blog/razrabotka/")
  Single<BlogPage> loadBlogPage();

  @GET("/blog/razrabotka/")
  Single<BlogPageAdvanced> loadBlogPageAdvanced();
}

Для проверки результатов используем простую программу на Java:

  public static void main(String[] args) {

    RetrofitHelper
        .createRetrofitInstance()
        .create(BlogLoader.class)
        .loadBlogPage()
        .subscribe(
            blogPage ->
                blogPage.articles.forEach(SmlDisplayArticles::prettyPrintArticle),
            throwable ->
                System.out.println("Ошибка загрузки: " + throwable.getMessage()));
  }

И распечатаем результаты в консоли:

  private static void prettyPrintArticle(SmlArticlePreview article) {
    System.out.println("Заголовок: " + article.title);
    System.out.println("Ссылка на статью: " + article.link);
    System.out.println("Автор: " + article.author);
    System.out.println("Превью-текст: " + article.preview);
    System.out.println("");
  }

Дополнительный пример

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

Еще раз заглянем в исходники страницы:

<div class="footer__social">
  <ul class="social__list">
    <li class="social-list__item"><a href="https://vk.com/smedialink" id="id_vk_social"></a></li>
    <li class="social-list__item"><a href="https://www.facebook.com/smedialink/" id="id_fb_social"></a></li>
    <li class="social-list__item"><a href="https://www.linkedin.com/company/s-media-link-россия" id="id_in_social"></a></li>
    <li class="social-list__item"><a href="https://twitter.com/smedialink" id="id_tw_social"></a></li>
  </ul>
</div>

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

public class BlogPageAdvanced {

  @Selector(value = "a[id=id_vk_social]", attr = "href")
  public String vkLink;

  @Selector(value = "a[id=id_fb_social]", attr = "href")
  public String facebookLink;

  @Selector(value = "a[id=id_tw_social]", attr = "href")
  public String twitterLink;

  @Selector("div.news__item")
  public List<SmlArticlePreviewAdvanced> articles;
}

Для извлечения картинки на превью статьи воспользуемся синтаксисом селекторов, позводяющим искать теги в определенной последовательности. Т.к. поиск будет осуществляться внутри тега div имеющего класс news__item, то нам остается лишь найти тег img внутри тега a, получить его аттрибут src и извлечь имя файла из ссылки с помощью простого регулярного выражения. Обновленная версия класса, представляющего статью, имеет следующий вид:

public class SmlArticlePreviewAdvanced {

  @Selector(value = "a.title")
  public String title;

  @Selector(value = "a.title", attr = "href")
  public String link;

  @Selector(value = ".author")
  public String author;

  @Selector(value = "p.preview")
  public String preview;

  @Selector(value = "a > img", attr = "src", regex = ".*/(.*)", defValue = "")
  public String previewImage;
}

Обратите внимание на параметр defValue, определяющий значение по умолчанию для поля previewImage в случаях неудачного парсинга (когда статья не содержит картинку-превью).

Обновим основной блок тестовой программы:

  public static void main(String[] args) {

    RetrofitHelper
        .createRetrofitInstance()
        .create(BlogLoader.class)
        .loadBlogPageAdvanced()
        .subscribe(
            blogPage -> {
              System.out.println("VK: " + blogPage.vkLink);
              System.out.println("Facebook: " + blogPage.facebookLink);
              System.out.println("Twitter: " + blogPage.twitterLink);
              System.out.println("");
              blogPage.articles.forEach(SmlDisplayArticlesAdvanced::prettyPrintArticle);
            },
            throwable ->
                System.out.println("Ошибка загрузки: " + throwable.getMessage()));
  }

  private static void prettyPrintArticle(SmlArticlePreviewAdvanced article) {
    System.out.println("Заголовок: " + article.title);
    System.out.println("Ссылка на статью: " + article.link);
    System.out.println("Автор: " + article.author);
    System.out.println("Превью-текст: " + article.preview);
    System.out.println("Файл превью: " + article.previewImage);
    System.out.println("");
  }

И проверим результат:

Итоги

Использование jspoon значительно сокращает объем кода, требуемый для парсинга необходимых вам данных, а богатый возможностями синтаксис селекторов и возможность использования регулярных выражений позволят извлекать данные из html-кода практически любой сложности.

smlblog1's People

Contributors

djkovrik avatar

Watchers

James Cloos avatar Andrey Myrzin avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.