Просто спарсь что угодно с помощью языка Rust (ну… или просто скачай файл)

от автора

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

В пример привожу функцию, которая парсит названия валют с сайта floatrates и выводит их (пожалуйста, напишите в комментариях, за сколько минут вы поняли эту строку):

// main.rs use anyhow::anyhow; use reqwest::blocking::get; use scraper::{Html, Selector};  fn main() -> anyhow::Result<()> {     let body = get("http://www.floatrates.com/json-feeds.html")?.text()?;     let document = Html::parse_document(&body);     let selector = Selector::parse("div.bk-json-feeds>div.body>ul>li>a")         .map_err(|err| anyhow!("{err}"))?;      for element in document.select(&selector) {         println!("{}", element.inner_html());     }     Ok(()) }

В этой статье я хочу рассказать о своей новой библиотеке, значительно упрощающей парсинг на Rust. Приятного чтения!

Дисклеймер

Пожалуйста, не «бейте» меня за некоторые ошибки в терминах, в reloginn и Black Soul за помощь в разработке версии 1.0

Инициализация структуры Scraper

// src/error.rs #[derive(Debug)] pub enum ScrapingErrorKind {     NotFound,     InvalidSelector }  #[derive(Debug)] pub enum ScrapingHttpErrorKind {     GettingDataError }  // src/scraping.rs use reqwest::blocking; use scraper::{ElementRef, Html, Selector, error::SelectorErrorKind}; use crate::error::{ScrapingErrorKind, ScrapingHttpErrorKind};  /// Создание нового экземпляра fn instance(url: &str) -> Result<Scraper, ScrapingHttpErrorKind> {     let document = blocking::get(url)         .map_err(|_| ScrapingHttpErrorKind::GettingDataError)?         .text()         .map_err(|_| ScrapingHttpErrorKind::GettingDataError)?;      Ok(Scraper { document: Html::parse_document(&document) }) }  /// Простой парсер /// // ... pub struct Scraper {     document: Html, }

Вместо того, чтобы отдельно отправлять запрос, из его результата получать код сайта и инициализировать объект scraper::Html, можно просто вызвать команду let scraper = scr::Scraper::new("scrapeme.live/shop/").unwrap() (да, «https://» и «http://» вводить не надо). Структура сохраняет в себе scraper::Html для дальнейшего парсинга. Вот, что происходит «под капотом» инициализатора:

// scr::scraping // ... impl Scraper {     /// создание нового экземпляра парсера,     /// <i>используя код сайта **(без https://)**</i>     pub fn new(url: &str) -> Result<Scraper, ScrapingHttpErrorKind> {         instance(format!("https://{url}").as_str())     }      /// создание нового экземпляра парсера,     /// <i>используя код сайта **(без http://)**</i>     pub fn from_http(url: &str) -> Result<Scraper, ScrapingHttpErrorKind> {         instance(format!("http://{url}").as_str())     }     // ... }

Также есть возможность инициализировать структру, используя фрагмент HTML страницы:

// scr::scraping // ... impl Scraper {     // ...     /// создание нового экземпляра парсера,     /// <i>используя **фрагмент** кода сайта</i>     pub fn from_fragment(fragment: &str) -> Result<Scraper, SelectorErrorKind> {         Ok(Scraper { document: Html::parse_fragment(fragment) })     }     // ... }

Получение элементов

Под командой scraper.get_els("путь#к>элементам").unwrap(); скрываются выбор элементов по специальному пути c применением структуры scraper::Selector и преобразование полученного результата в Vec<scraper::ElementRef>.

// scr::scraping // ... impl Scraper {     // ...     /// получение элементов     pub fn get_els<'a>(&'a self, sel: &'a str) -> Result<Vec<ElementRef>, ScrapingErrorKind> {         let parsed_selector = Selector::parse(sel).map_err(|_| ScrapingErrorKind::InvalidSelector)?;          let elements = self.document             .select(&parsed_selector)             .collect::<Vec<ElementRef>>();          Ok(elements)     }      /// получение элемента     pub fn get_el<'a>(&'a self, sel: &'a str) -> Result<ElementRef, ScrapingErrorKind> {         let element = *self.get_els(sel)?             .get(0)             .ok_or(ScrapingErrorKind::NotFound)?;          Ok(element)     }     // ... }

Получение текста (inner_html) и атрибута элемента (-ов)

Можно получить текст или атрибут как одного, так и нескольких элементов, полученных с помощью scraper.get_els("путь#к>элементам").unwrap();

// scr::scraping // ... impl Scraper {     // ...     /// получение текста из элемента     /// ...     pub fn get_text_once(&self, sel: &str) -> Result<String, ScrapingErrorKind> {         let text = self.get_el(sel)?             .inner_html();          Ok(text)     }      /// получение текста из всех элементов     /// ...     pub fn get_all_text(self, sel: &str) -> Result<Vec<String>, ScrapingErrorKind> {         let text = self.get_els(sel)?             .iter()             .map(|element| element.inner_html())             .collect();          Ok(text)     }      /// получение атрибута элемента     /// ...     pub fn get_attr_once<'a>(&'a self, sel: &'a str, attr: &'a str) -> Result<&str, ScrapingErrorKind> {         let attr = self.get_el(sel)?             .value()             .attr(attr)             .ok_or(ScrapingErrorKind::NotFound)?;          Ok(attr)     }      /// получение атрибута всех элементов     /// ...     pub fn get_all_attr<'a>(&'a self, sel: &'a str, attr: &str) -> Result<Vec<&str>, SelectorErrorKind> {         let attrs = self.get_els(sel)             .unwrap()             .iter()             .map(|element| element.value().attr(attr).expect("Some elements do not contain the desired attribute"))             .collect();          Ok(attrs)     } }

Загрузка файлов (структура FileLoader)

Это — простой загрузчик файлов. Достаточно просто ввести *тык*

Известная проблема

К сожалению, пока нельзя вернуть экземпляр структуры scr::FileLoader (ошибка [E0515]).

Планы на версию 2.0.0

Пока что scr работает только синхронно, отчего крэйт моментами достаточно медленный. Поэтому через некоторое время я внедрю async (скорее всего отдельной фичей или модулем).

На этом пока всё. Очень надеюсь, что я заинтересовал вас. По желанию вы можете внести изменения в библиотеку, создав форк репозитория, совершив некоторые изменения и отправив их мне через pull request.


ссылка на оригинал статьи https://habr.com/ru/articles/755728/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *