Verificador de Links Multi-Threads

Vamos usar nosso novo conhecimento para criar um verificador de links multi-threads. Comece em uma página da web e verifique se os links na página são válidos. Verifique recursivamente outras páginas no mesmo domínio e continue fazendo isso até que todas as páginas tenham sido validadas.

Para isso, você precisará de um cliente HTTP como reqwest. Você também precisará de uma maneira de encontrar links, podemos usar scraper. Por fim, precisaremos de alguma maneira de lidar com erros, usaremos thiserror.

Crie um novo projeto Cargo e adicione reqwest como uma dependência com:

cargo new link-checker
cd link-checker
cargo add --features blocking,rustls-tls reqwest
cargo add scraper
cargo add thiserror

Se cargo add falhar com error: no such subcommand, edite o arquivo Cargo.toml à mão. Adicione as dependências listadas abaixo.

As chamadas cargo add irão atualizar o arquivo Cargo.toml para ficar assim:

[package]
name = "link-checker"
version = "0.1.0"
edition = "2021"
publish = false

[dependencies]
reqwest = { version = "0.11.12", features = ["blocking", "rustls-tls"] }
scraper = "0.13.0"
thiserror = "1.0.37"

Agora você pode baixar a página inicial. Tente com um pequeno site como https://www.google.org/.

Seu arquivo src/main.rs deve se parecer com isto:

use reqwest::blocking::Client;
use reqwest::Url;
use scraper::{Html, Selector};
use thiserror::Error;

#[derive(Error, Debug)]
enum Error {
    #[error("request error: {0}")]
    ReqwestError(#[from] reqwest::Error),
    #[error("bad http response: {0}")]
    BadResponse(String),
}

#[derive(Debug)]
struct CrawlCommand {
    url: Url,
    extract_links: bool,
}

fn visit_page(client: &Client, command: &CrawlCommand) -> Result<Vec<Url>, Error> {
    println!("Verificando {:#}", command.url);
    let response = client.get(command.url.clone()).send()?;
    if !response.status().is_success() {
        return Err(Error::BadResponse(response.status().to_string()));
    }

    let mut link_urls = Vec::new();
    if !command.extract_links {
        return Ok(link_urls);
    }

    let base_url = response.url().to_owned();
    let body_text = response.text()?;
    let document = Html::parse_document(&body_text);

    let selector = Selector::parse("a").unwrap();
    let href_values = document
        .select(&selector)
        .filter_map(|element| element.value().attr("href"));
    for href in href_values {
        match base_url.join(href) {
            Ok(link_url) => {
                link_urls.push(link_url);
            }
            Err(err) => {
                println!("Em {base_url:#}: ignorado não analisável {href:?}: {err}");
            }
        }
    }
    Ok(link_urls)
}

fn main() {
    let client = Client::new();
    let start_url = Url::parse("https://www.google.org").unwrap();
    let crawl_command = CrawlCommand{ url: start_url, extract_links: true };
    match visit_page(&client, &crawl_command) {
        Ok(links) => println!("Links: {links:#?}"),
        Err(err) => println!("Não foi possível extrair links: {err:#}"),
    }
}

Execute o código em src/main.rs com

cargo run

Tarefas

  • Use threads para verificar os links em paralelo: envie as URLs a serem verificadas para um channel e deixe alguns threads verificarem as URLs em paralelo.
  • Estenda isso para extrair recursivamente links de todas as páginas no domínio www.google.org. Coloque um limite máximo de 100 páginas ou menos para que você não acabe sendo bloqueado pelo site.