جستجوگر پیوند چند تِردی

اجازه دهید از دانش جدید خود برای ایجاد یک جستجوگر لینک multi-thread استفاده کنیم. باید از یک صفحه وب شروع شود و بررسی کنید که لینک‌های موجود در صفحه معتبر هستند. باید به‌صورت بازگشتی صفحات دیگر را در همان دامنه بررسی کند و این کار را تا زمانی که همه صفحات تأیید نشده‌اند ادامه دهد.

برای این کار به یک کلاینت HTTP مانند reqwest نیاز دارید. شما همچنین به راهی برای یافتن لینک‌ها نیاز دارید، ما می توانیم از reqwest استفاده کنیم. در نهایت، ما به روشی برای رسیدگی به خطاها نیاز داریم پس درنتیجه از thiserror استفاده خواهیم کرد.

Create a new Cargo project and reqwest it as a dependency with:

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

اگرcargo add با error: no such subcommand ناموفق بود، لطفاً فایل Cargo.toml را دستی ویرایش کنید و وابستگی‌های ذکر شده در زیر را اضافه کنید.

فراخوانی‌ها cargo add را در فایل Cargo.toml به‌صورت زیر به‌روزرسانی می‌کند:

[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"

اکنون می توانید صفحه شروع را دانلود کنید. با یک سایت کوچک مانند https://www.google.org/ امتحان کنید.

فایل src/main.rs شما باید چیزی شبیه به این باشد:

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!("بررسی {:#}", 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!("On {base_url:#}: ignored unparsable {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!("نمی‌تواند لینک را باز کند: {err:#}"),
    }
}

این کد را در src/main.rs اجرا کنید

cargo run

Task

  • برای بررسی موازی لینک‌ها از threadها استفاده کنید: URLهایی را که باید بررسی شوند به یک channel ارسال کنید و اجازه دهید چند thread به‌صورت موازی URLها را بررسی کنند.
  • این را به صورت بازگشتی پیوندها گسترش دهید تا از همه صفحات را در دامنه «www.google.org» استخراج کنید. حد بالا را حدود ۱۰۰ صفحه یا بیشتر قرار دهید تا در نهایت توسط سایت مسدود نشوید.