Известно, что Rust — типобезопасный язык программирования, код которого проверяется пакетным менеджером cargo ещё до сборки. С одной стороны это хорошо: меньше вероятности внезапного сбоя в самый неподходящий момент. С другой стороны — код, выполняющий одну функцию, в итоге может стать очень сложным и иногда нечитаемым.
В пример привожу функцию, которая парсит названия валют с сайта floatrates и выводит их (пожалуйста, напишите в комментариях, за сколько минут вы поняли эту строку):
// main.rs
use reqwest::blocking::get
use scraper::{Html, Selector}
fn main {
let url = "http://www.floatrates.com/json-feeds.html";
let response = get(url).except("Ссылка не загружается.");
let body = response.text().unwarp();
let document = Html::parse_document(&body);
let selector = Selector::parse("div.bk-json-feeds>div.body>ul>li>a").unwarp()
.iter()
.map(|element| element.inner_html())
.collect()
.foreach(|title| println!("{title}"));
}В этой статье я хочу рассказать о своей новой библиотеке, значительно упрощающей парсинг на Rust. Приятного чтения!
Дисклеймер
Пожалуйста, не «бейте» меня за некоторые ошибки в терминах, в // и ///</p>" data-abbr="комментариях">комментариях или в синтаксисе кода: я программирую на Rust всего 2 месяца.
Однако опыт в программировании у меня есть (год в python), поэтому прекрасно понимаю, о чём здесь пишу.
Буду очень признателен, если вы напишите в комментариях, что нужно исправить в крэйте)
Благодарю reloginn за помощь в разработке версии 1.0.0
Инициализация структуры Scraper
// scr::scraping
use reqwest::blocking;
use scraper::{ElementRef, Html, Selector};
/// Простой парсер
/// // ...
pub struct Scraper {
document: Html,
}Вместо того, чтобы отдельно отправлять запрос, из его результата получать код сайта и инициализировать объект scraper::Html, можно просто вызвать команду let scraper = scr::Scraper::new("scrapeme.live/shop/") (да, «html://» вводить не надо). Структура сохраняет в себе scraper::Html для дальнейшего парсинга. Вот, что происходит «под капотом» инициализатора:
// scr::scraping
// ...
impl Scraper {
/// создание нового экземпляра парсера,
/// <i>используя код сайта **(без https://)**</i>
pub fn new(url: &str) -> Scraper {
let response = blocking::get(format!("https://{}", url))
.expect("Сайт не загружается")
.text()
.expect("Код сайта не возвращается");
Scraper {
document: Html::parse_document(&response),
}
}
// ...
}Также есть возможность инициализировать структру, используя фрагмент HTML страницы:
// scr::scraping
// ...
impl Scraper {
// ...
/// создание нового экземпляра парсера,
/// <i>используя **фрагмент** кода сайта</i>
pub fn from_fragment(fragment: &str) -> Scraper {
Scraper {
document: Html::parse_fragment(fragment),
}
}
// ...
}Получение элементов
Под командой scraper.get_els("путь#к>элементам"); скрываются выбор элементов по специальному пути c применением структуры scraper::Selector и преобразование полученного результата в Vec<scraper::ElementRef>.
// scr::scraping
// ...
impl Scraper {
// ...
/// получение элементов
pub fn get_els(&self, sel: &str) -> Vec<ElementRef> {
self.document
.select(&Selector::parse(sel).expect("Не получается парсить"))
.collect()
}
/// получение элемента
pub fn get_el(&self, sel: &str) -> ElementRef {
*self.get_els(sel).get(0).expect("Элемент не возвращается")
}
// ...
}Получение текста (inner_html) и атрибута элемента (-ов)
Можно получить текст или атрибут как одного, так и нескольких элементов, полученных с помощью scraper.get_els("path#to>els");
// scr::scraping
// ...
impl Scraper {
// ...
/// получение текста из элемента
/// ...
pub fn get_text_once(&self, sel: &str) -> String {
self.get_el(sel).inner_html()
}
/// получение текста из всех элементов
/// ...
pub fn get_all_text(&self, sel: &str) -> Vec<String> {
self.get_els(sel)
.iter()
.map(|element| element.inner_html())
.collect()
}
/// получение атрибута элемента
/// ...
pub fn get_attr_once(&self, sel: &str, attr: &str) -> &str {
self.get_el(sel).value().attr(attr).expect("Can not do")
}
/// получение атрибута всех элементов
/// ...
pub fn get_all_attr(&self, sel: &str, attr: &str) -> Vec<&str> {
self.get_els(sel)
.iter()
.map(|element| element.value().attr(attr).expect("Не получается"))
.collect()
}
}Загрузка файлов (структура FileLoader)
Это — простой загрузчик файлов. Достаточно просто ввести <code>let file_loader = scr::FileLoader::new("path/to/file")</code></p>" data-abbr="одну команду">одну команду. Исходный код структуры: *тык*
Известная проблема
К сожалению, пока нельзя вернуть экземпляр структуры scr::FileLoader (ошибка [E0515]).
Планы на версию 2.0.0
Пока что scr работает только синхронно, отчего крэйт моментами достаточно медленный. Поэтому через некоторое время я внедрю async (скорее всего отдельной фичей или модулем).
На этом пока всё. Очень надеюсь, что я заинтересовал вас. По желанию вы можете внести изменения в библиотеку, создав форк репозитория, совершив некоторые изменения и отправив их мне через pull request.
