Использование Rust для парсинга веб-страниц

Feb 13 2023
Web Scraping — это метод извлечения информации с веб-сайтов. Это автоматизированный процесс получения больших объемов данных с веб-сайтов, позволяющий сэкономить время и силы и сделать информацию доступной для различных целей.

Web Scraping — это метод извлечения информации с веб-сайтов. Это автоматизированный процесс получения больших объемов данных с веб-сайтов, позволяющий сэкономить время и силы и сделать информацию доступной для различных целей.

В этом посте я покажу, как вы можете использовать Rust для извлечения цитат с веб-сайта.

Преимущества использования Rust для парсинга веб-страниц

  1. Производительность : Rust — это компилируемый язык со статической типизацией, обеспечивающий высокую производительность, что делает его идеальным выбором для парсинга веб-страниц.
  2. Параллелизм : Rust предоставляет встроенную поддержку параллелизма, которая упрощает одновременную обработку нескольких запросов, повышая эффективность парсинга веб-страниц.
  3. Безопасность памяти : Rust предоставляет надежные гарантии безопасности памяти, что помогает предотвратить распространенные ошибки, связанные с памятью, такие как исключения нулевого указателя и переполнение буфера, что делает его более безопасным для очистки веб-страниц.
  4. Простота в освоении : Rust имеет относительно простой синтаксис, что упрощает его изучение разработчиками по сравнению с другими языками программирования.
  1. Крутая кривая обучения : хотя синтаксис Rust относительно прост, язык может быть сложным для изучения, особенно для разработчиков, которые плохо знакомы с компилируемыми языками со статической типизацией.
  2. Ограниченное сообщество : хотя сообщество Rust растет, он все еще не так широко используется, как другие языки программирования, а это означает, что поиск ресурсов и поддержки может быть затруднен.
  3. Нет встроенной библиотеки веб-скрейпинга : в Rust нет встроенной библиотеки для веб-скрейпинга, а это означает, что разработчикам приходится использовать сторонние библиотеки, что может быть менее удобно.

Теперь мы установим Rust, проверим версию Cargoи установим три библиотеки парсинга веб-страниц, reqwest, scraperи selectчерез интерфейс командной строки.

Установить ржавчину

Чтобы установить Rust, вы можете использовать инструмент Rustup, который является рекомендуемым способом установки Rust. Вы можете запустить следующую команду в своем терминале:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Установить груз

Cargo — это менеджер пакетов для Rust, который устанавливается автоматически при установке Rust. Вы можете проверить, установлен ли Cargo, выполнив следующую команду:

cargo --version

Запустите следующую команду в своем терминале:

cargo new toscrape_quotes

Чтобы установить библиотеки парсинга веб-страниц Rust reqwest, scraperи , вы можете добавить в свой файл selectследующие зависимости :Cargo.toml

# Do not forget to update the dependencies section of your Cargo.toml file 

[dependencies]
csv = "1.1"
reqwest = "0.10.8"
scraper = "0.13.0"
select = "0.6.0"

cd toscrape_quotes && cargo install --path .

Давайте сделаем парсинг веб-страниц. А не ___ ли нам?

Toscrape.com — это песочница для веб-скрейпинга для тех, кто хочет заняться подобным делом в области разработки программного обеспечения.

Не привлекая нежелательного внимания со стороны корпоративных юристов корпоративных платформ или организаций, вы можете использовать его в качестве игровой площадки. Да вы не хотите видеть этих людей! Некоторые носят костюмы в паре с Air Jordan 1 Chicago .

Код веб-скрейпинга Rust

extern crate csv;
extern crate reqwest;
extern crate scraper;

use std::error::Error;
use std::fs::File;
use std::time::Duration;
use std::thread::sleep;

use reqwest::Client;
use reqwest::StatusCode;
use scraper::{Html, Selector};

const BASE_URL: &str = "http://quotes.toscrape.com";
const MAX_PAGE: u32 = 5;
const MAX_RETRIES: u32 = 3;
const RETRY_SLEEP_SECONDS: u64 = 2;

struct Quote {
    text: String,
    author: String,
    tags: Vec<String>,
}

fn main() -> Result<(), Box<dyn Error>> {
    let client = Client::new().timeout(Duration::from_secs(30)).build()?;
    let quotes = get_quotes(&client)?;
    write_quotes_to_csv(&quotes)?;
    Ok(())
}

fn get_quotes(client: &Client) -> Result<Vec<Quote>, Box<dyn Error>> {
    let mut quotes = Vec::new();
    for page in 1..=MAX_PAGE {
        let html = retry_get(client, &format!("{}/page/{}/", BASE_URL, page))?.text()?;
        let fragment = Html::parse_document(&html);
        let quote_selector = Selector::parse("span.text").unwrap();
        let author_selector = Selector::parse("span > small.author").unwrap();
        let tag_selector = Selector::parse("div.tags > a.tag").unwrap();

        for quote in fragment.select(&quote_selector) {
            let quote_text = quote.text().collect::<Vec<_>>()[0].trim().to_owned();
            let author = quote
                .parent()
                .unwrap()
                .select(&author_selector)
                .next()
                .unwrap()
                .text()
                .collect::<Vec<_>>()[0]
                .trim()
                .to_owned();

            let tags = quote
                .parent()
                .unwrap()
                .select(&tag_selector)
                .map(|tag| tag.text().collect::<Vec<_>>()[0].trim().to_owned())
                .collect::<Vec<_>>();

            quotes.push(Quote {
                text: quote_text,
                author,
                tags,
            });
        }
    }
    Ok(quotes)
}

fn write_quotes_to_csv(quotes: &[Quote]) -> Result<(), Box<dyn Error>> {
    let mut file = File::create("toscrape_quotes.csv")?;
    let mut writer = csv::Writer::from_writer(&mut file);

    for quote in quotes {
        writer.write_record(&[quote.text, quote.author, quote.tags.join(", ")])?;
    }
    writer.flush()?;
    Ok(())
}

fn retry_get(client: &Client, url: &str) -> Result<reqwest::Response, Box<dyn Error>> {
    let mut retries = 0;
    loop {
        match client.get(url).send() {
            Ok(res) => {
                if res.status().is_success() {
                    return Ok(res);
                } else {
                    retries += 1;
                    if retries >= MAX_RETRIES {
                        return Err(format!("GET request to {} failed with status code {:?}", url, res.status()).into());
                    }
                }
            }
            Err(err) => {
                retries += 1;
                if retries >= MAX_RETRIES {
                    return Err(format!("GET request to {} failed with error: {}", url, err).into());
                }
            }
        }
        sleep(Duration::from_secs(RETRY_SLEEP_SECONDS));
    }
}

Функция main_

Функция mainявляется точкой входа кода Rust и выполняет следующие шаги:

  1. Создает новый HTTP-клиент, используя reqwest::Client.
  2. Вызывает get_quotesфункцию и передает ей клиента, который возвращает вектор Quoteструктур.
  3. Вызывает write_quotes_to_csvфункцию и передает ей вектор Quoteструктур, который записывает кавычки в CSV-файл.
  4. Возвращает результат функции write_quotes_to_csv, который либо Ok(())если все удалось, либо ошибку, если что-то пошло не так.

fn main() -> Result<(), Box<dyn Error>> {
    let client = Client::new().timeout(Duration::from_secs(30)).build()?;
    let quotes = get_quotes(&client)?;
    write_quotes_to_csv(&quotes)?;
    Ok(())
}

Функция get_quotesпринимает ссылку на reqwest::Clientв качестве аргумента и возвращает вектор Quoteструктур, взятых с веб-сайта.http://quotes.toscrape.com". Вот разбивка того, что мы делаем:

  1. Инициализировать пустой вектор Quoteструктур.
  2. Прокрутите страницы, которые нужно очистить, от 1 до MAX_PAGE.
  3. Для каждой страницы сделайте запрос GET к URL-адресу страницы, используя retry_getфункцию, и извлеките HTML-код страницы.
  4. Разобрать HTML на scraper::Htmlфрагмент.
  5. Выберите цитату text, authorи tagsиз HTML с помощью селекторов CSS.
  6. Создайте новую Quoteструктуру для каждой цитаты и добавьте ее к вектору цитат.
  7. Вернуть вектор кавычек.

fn get_quotes(client: &Client) -> Result<Vec<Quote>, Box<dyn Error>> {
    let mut quotes = Vec::new();
    for page in 1..=MAX_PAGE {
        let html = retry_get(client, &format!("{}/page/{}/", BASE_URL, page))?.text()?;
        let fragment = Html::parse_document(&html);
        let quote_selector = Selector::parse("span.text").unwrap();
        let author_selector = Selector::parse("span > small.author").unwrap();
        let tag_selector = Selector::parse("div.tags > a.tag").unwrap();

        for quote in fragment.select(&quote_selector) {
            let quote_text = quote.text().collect::<Vec<_>>()[0].trim().to_owned();
            let author = quote
                .parent()
                .unwrap()
                .select(&author_selector)
                .next()
                .unwrap()
                .text()
                .collect::<Vec<_>>()[0]
                .trim()
                .to_owned();

            let tags = quote
                .parent()
                .unwrap()
                .select(&tag_selector)
                .map(|tag| tag.text().collect::<Vec<_>>()[0].trim().to_owned())
                .collect::<Vec<_>>();

            quotes.push(Quote {
                text: quote_text,
                author,
                tags,
            });
        }
    }
    Ok(quotes)
}

Функция write_quotes_to_csvпринимает ссылку, чтобы нарезать Quoteструктуры и записывать кавычки в .csvфайл. Вот разбивка того, что мы делаем:

  1. Создайте новый .csvфайл с именем toscrape_quotes.csv.
  2. Создайте новый csv::Writerс помощью csvящика.
  3. Запишите новую запись в файл .csv для каждой цитаты в фрагменте цитат с цитатой text, authorи tagsразделенными запятыми.
  4. Очистите модуль записи, чтобы убедиться, что все данные записаны в файл.
  5. Возвращает результат операции записи, то есть либо Ok(())если все удалось, либо ошибку, если что-то пошло не так.

fn write_quotes_to_csv(quotes: &[Quote]) -> Result<(), Box<dyn Error>> {
    let mut file = File::create("toscrape_quotes.csv")?;
    let mut writer = csv::Writer::from_writer(&mut file);

    for quote in quotes {
        writer.write_record(&[quote.text, quote.author, quote.tags.join(", ")])?;
    }
    writer.flush()?;
    Ok(())
}

Функция retry_getпринимает ссылку reqwest::Clientи ссылку на строковый URL-адрес и отправляет HTTP-запрос GET к URL-адресу с помощью клиента. Если запрос завершается неудачно, функция повторяет запрос максимальное количество MAX_RETRIESраз с интервалом ожидания в RETRY_SLEEP_SECONDSсекундах между каждой повторной попыткой. Вот разбивка того, что мы делаем:

  1. Инициализируйте счетчик количества попыток.
  2. Сделайте запрос GET к URL-адресу с помощью клиента и получите ответ.
  3. Если ответ успешен (т. е. код состояния ответа находится в диапазоне 2xx), верните ответ.
  4. Если ответ не удался, увеличьте счетчик повторных попыток.
  5. Если счетчик повторов меньше MAX_RETRIES, подождите несколько RETRY_SLEEP_SECONDSсекунд и повторите запрос GET.
  6. Если счетчик повторных попыток равен MAX_RETRIES, вернуть ошибку, указывающую, что запрос GET не выполнен.

fn retry_get(client: &Client, url: &str) -> Result<reqwest::Response, Box<dyn Error>> {
    let mut retries = 0;
    loop {
        match client.get(url).send() {
            Ok(res) => {
                if res.status().is_success() {
                    return Ok(res);
                } else {
                    retries += 1;
                    if retries >= MAX_RETRIES {
                        return Err(format!("GET request to {} failed with status code {:?}", url, res.status()).into());
                    }
                }
            }
            Err(err) => {
                retries += 1;
                if retries >= MAX_RETRIES {
                    return Err(format!("GET request to {} failed with error: {}", url, err).into());
                }
            }
        }
        sleep(Duration::from_secs(RETRY_SLEEP_SECONDS));
    }
}

Тем не менее, Rust является относительно новым языком по сравнению с более продвинутыми языками программирования веб-скрейпинга, такими как Python. Следовательно, у него может быть меньшее сообщество и меньше доступных библиотек. Синтаксис Rust также может быть более сложным для новичков, особенно для тех, кто знаком с языками программирования высокого уровня, такими как Python с динамической семантикой.

Будьте умны, получайте удовольствие от поиска в Интернете и избегайте корпоративных юристов. Зато юристы будут! Хэштег, что? #Счета