Usare Rust per il Web Scraping

Feb 13 2023
Il web scraping è una tecnica per estrarre informazioni dai siti web. È un processo automatizzato di recupero di grandi quantità di dati da siti Web, risparmiando tempo e fatica e rendendo le informazioni disponibili per vari scopi.

Il web scraping è una tecnica per estrarre informazioni dai siti web. È un processo automatizzato di recupero di grandi quantità di dati da siti Web, risparmiando tempo e fatica e rendendo le informazioni disponibili per vari scopi.

In questo post, dimostrerò come utilizzare Rust per estrarre citazioni da un sito web.

Vantaggi dell'utilizzo di Rust per il web scraping

  1. Prestazioni : Rust è un linguaggio compilato con tipizzazione statica che offre prestazioni elevate, rendendolo la scelta ideale per il web scraping.
  2. Concorrenza : Rust fornisce un supporto integrato per la concorrenza, che semplifica la gestione di più richieste contemporaneamente, migliorando l'efficienza del web scraping.
  3. Sicurezza della memoria : Rust offre solide garanzie di sicurezza della memoria, che aiutano a prevenire bug comuni relativi alla memoria, come eccezioni di puntatori null e overflow del buffer, rendendolo più sicuro per il web scraping.
  4. Facile da imparare : Rust ha una sintassi relativamente semplice, che rende più facile l'apprendimento per gli sviluppatori, rispetto ad altri linguaggi di programmazione.
  1. Curva di apprendimento ripida : sebbene la sintassi di Rust sia relativamente semplice, il linguaggio può essere difficile da imparare, specialmente per gli sviluppatori che sono nuovi ai linguaggi compilati con tipizzazione statica.
  2. Comunità limitata : sebbene Rust abbia una comunità in crescita, non è ancora ampiamente utilizzato come altri linguaggi di programmazione, il che significa che trovare risorse e supporto può essere difficile.
  3. Nessuna libreria di web scraping integrata : Rust non ha una libreria integrata per il web scraping, il che significa che gli sviluppatori devono utilizzare librerie di terze parti, il che può essere meno conveniente.

Ora installeremo , Rustverificheremo la versione di Cargoe installeremo tre librerie di web scraping, reqwest, scrapere selecttramite l'interfaccia della riga di comando.

Installa ruggine

Per installare Rust, puoi utilizzare lo strumento Rustup, che è il metodo consigliato per installare Rust. Puoi eseguire il seguente comando nel tuo terminale:

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

Installa il carico

Cargo è il gestore di pacchetti per Rust e viene installato automaticamente quando installi Rust. Puoi verificare se Cargo è installato eseguendo il seguente comando:

cargo --version

Esegui il seguente comando nel tuo terminale:

cargo new toscrape_quotes

Per installare le librerie di web scraping Rust reqwest, scraper, e select, puoi aggiungere le seguenti dipendenze al tuo Cargo.tomlfile:

# 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 .

Facciamo un po' di web scraping. Lo facciamo?

Toscrape.com è una sandbox di web-scraping per coloro che vogliono fare un'incursione in un simile sforzo nell'ingegneria del software.

Senza attirare attenzioni indesiderate da parte di avvocati aziendali di piattaforme o organizzazioni aziendali, puoi usarlo come parco giochi. Sì, non vuoi vedere quelle persone! Alcuni indossano completi abbinati ad Air Jordan 1 Chicago .

Codice Rust Web Scraping

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));
    }
}

La mainfunzione

La mainfunzione è il punto di ingresso del codice Rust ed esegue i seguenti passaggi:

  1. Crea un nuovo client HTTP utilizzando reqwest::Client.
  2. Chiama la get_quotesfunzione e le passa il client, che restituisce un vettore di Quotestruct.
  3. Chiama la write_quotes_to_csvfunzione e Quotele passa il vettore di struct, che scrive le virgolette in un file .csv.
  4. Restituisce il risultato della write_quotes_to_csvfunzione, ovvero Ok(())se tutto è andato a buon fine o un errore se qualcosa è andato storto.

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(())
}

La get_quotesfunzione prende un riferimento a reqwest::Clientcome argomento e restituisce un vettore di Quotestruct che vengono prelevati dal sito web "http://quotes.toscrape.com". Ecco una ripartizione di ciò che stiamo facendo:

  1. Inizializza un vettore vuoto di Quotestruct.
  2. Scorri le pagine da raschiare, da 1 a MAX_PAGE.
  3. Per ogni pagina, effettua una richiesta GET all'URL della pagina utilizzando la retry_getfunzione e recupera l'HTML della pagina.
  4. Analizza l'HTML in un scraper::Htmlframmento.
  5. Seleziona la citazione text, authore tagsdall'HTML utilizzando i selettori CSS.
  6. Crea una nuova Quotestruttura per ogni citazione e aggiungila al vettore delle virgolette.
  7. Restituisce il vettore delle virgolette.

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)
}

La write_quotes_to_csvfunzione accetta un riferimento per suddividere le Quotestrutture e scrivere le virgolette in un .csvfile. Ecco una ripartizione di ciò che stiamo facendo:

  1. Crea un nuovo .csvfile con il nome toscrape_quotes.csv.
  2. Creane uno nuovo csv::Writerusando la csvcassa.
  3. Scrivi un nuovo record nel file .csv per ogni citazione nella sezione delle virgolette, con le virgolette text, authore tagsseparate da virgole.
  4. Svuota il writer per garantire che tutti i dati vengano scritti nel file.
  5. Restituisce il risultato dell'operazione di scrittura, ovvero Ok(())se tutto è andato a buon fine o un errore se qualcosa è andato storto.

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(())
}

La retry_getfunzione accetta un riferimento reqwest::Cliente un riferimento all'URL della stringa e invia una richiesta HTTP GET all'URL utilizzando il client. Se la richiesta ha esito negativo, la funzione riprova la richiesta un massimo di MAX_RETRIESvolte con un tempo di sospensione di RETRY_SLEEP_SECONDSsecondi tra ogni tentativo. Ecco una ripartizione di ciò che stiamo facendo:

  1. Inizializza un contatore per il numero di tentativi.
  2. Effettua una richiesta GET all'URL utilizzando il client e recupera la risposta.
  3. Se la risposta ha esito positivo (ovvero, il codice di stato della risposta è nell'intervallo 2xx), restituire la risposta.
  4. Se la risposta non va a buon fine, incrementa il contatore dei tentativi.
  5. Se il contatore dei tentativi è inferiore a MAX_RETRIES, attendere alcuni RETRY_SLEEP_SECONDSsecondi e riprovare la richiesta GET.
  6. Se il contatore dei tentativi è uguale a MAX_RETRIES, restituisce un errore che indica che la richiesta GET non è riuscita.

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));
    }
}

Tuttavia, Rust è un linguaggio relativamente nuovo rispetto ai linguaggi di programmazione di web scraping più avanzati come Python. Pertanto, potrebbe avere una comunità più piccola e meno biblioteche disponibili. La sintassi di Rust può anche essere più difficile per i principianti, specialmente quelli che hanno familiarità con linguaggi di programmazione di alto livello come Python con semantica dinamica.

Sii intelligente, divertiti a raschiare il web ed evita gli avvocati aziendali. Ci saranno avvocati, però! Hashtag, cosa? #Fatturabili