Sử dụng Rust để quét web

Feb 13 2023
Web Scraping là một kỹ thuật trích xuất thông tin từ các trang web. Đó là một quá trình tự động lấy một lượng lớn dữ liệu từ các trang web, tiết kiệm thời gian và công sức, đồng thời cung cấp thông tin cho nhiều mục đích khác nhau.

Web Scraping là một kỹ thuật trích xuất thông tin từ các trang web. Đó là một quá trình tự động lấy một lượng lớn dữ liệu từ các trang web, tiết kiệm thời gian và công sức, đồng thời cung cấp thông tin cho nhiều mục đích khác nhau.

Trong bài đăng này, tôi sẽ trình bày cách bạn có thể sử dụng Rust để trích xuất các trích dẫn từ một trang web.

Ưu điểm của việc sử dụng Rust để quét web

  1. Hiệu suất : Rust là một ngôn ngữ được biên dịch, được gõ tĩnh, cung cấp hiệu suất cao, làm cho nó trở thành một lựa chọn lý tưởng cho việc quét web.
  2. Đồng thời : Rust cung cấp hỗ trợ đồng thời tích hợp, giúp dễ dàng xử lý đồng thời nhiều yêu cầu, nâng cao hiệu quả của việc quét web.
  3. An toàn bộ nhớ : Rust cung cấp các đảm bảo an toàn bộ nhớ mạnh mẽ, giúp ngăn ngừa các lỗi phổ biến liên quan đến bộ nhớ, chẳng hạn như ngoại lệ con trỏ null và lỗi tràn bộ đệm, giúp việc quét web an toàn hơn.
  4. Dễ học : Rust có cú pháp tương đối đơn giản, giúp các nhà phát triển dễ học hơn so với các ngôn ngữ lập trình khác.
  1. Steep Learning Curve : Mặc dù cú pháp của Rust tương đối đơn giản, nhưng ngôn ngữ này có thể khó học, đặc biệt đối với các nhà phát triển chưa quen với các ngôn ngữ được biên dịch, nhập tĩnh.
  2. Cộng đồng hạn chế : Mặc dù Rust có một cộng đồng đang phát triển, nhưng nó vẫn chưa được sử dụng rộng rãi như các ngôn ngữ lập trình khác, nghĩa là việc tìm kiếm tài nguyên và hỗ trợ có thể khó khăn.
  3. Không có thư viện quét web tích hợp : Rust không có thư viện tích hợp để quét web, nghĩa là các nhà phát triển phải sử dụng thư viện của bên thứ ba, điều này có thể kém thuận tiện hơn.

Bây giờ, chúng ta sẽ cài đặt Rust, xác minh phiên bản của Cargo, và cài đặt ba thư viện quét web , reqwest, scrapervà selectthông qua giao diện dòng lệnh.

Cài đặt Rust

Để cài đặt Rust, bạn có thể sử dụng công cụ Rustup, đây là cách được khuyến nghị để cài đặt Rust. Bạn có thể chạy lệnh sau trong thiết bị đầu cuối của mình:

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

cài đặt hàng hóa

Cargo là trình quản lý gói cho Rust và được cài đặt tự động khi bạn cài đặt Rust. Bạn có thể kiểm tra xem Cargo đã được cài đặt chưa bằng cách chạy lệnh sau:

cargo --version

Chạy lệnh sau trong thiết bị đầu cuối của bạn:

cargo new toscrape_quotes

Để cài đặt các thư viện Rust web scraping reqwest, scraper, và select, bạn có thể thêm các phần phụ thuộc sau vào Cargo.tomltệp của mình:

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

Hãy thực hiện một số thao tác tìm kiếm trên web. Chúng ta nhé?

Toscrape.com là một hộp cát quét web dành cho những ai muốn dấn thân vào nỗ lực như vậy trong công nghệ phần mềm.

Không thu hút sự chú ý không mong muốn từ các luật sư công ty của các tổ chức hoặc nền tảng doanh nghiệp, bạn có thể sử dụng nó làm sân chơi của mình. Vâng, bạn không muốn nhìn thấy những người đó! Một số mặc vest kết hợp với Air Jordan 1 Chicago .

Rust web cạo mã

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

chức mainnăng

Hàm này mainlà điểm vào của mã Rust và nó thực hiện các bước sau:

  1. Tạo ứng dụng khách HTTP mới bằng cách sử dụng reqwest::Client.
  2. Gọi get_quoteshàm và chuyển ứng dụng khách tới nó, hàm này trả về một vectơ Quotecấu trúc.
  3. Gọi write_quotes_to_csvhàm và chuyển vectơ Quotecấu trúc cho hàm, hàm này ghi các trích dẫn vào tệp .csv.
  4. Trả về kết quả của write_quotes_to_csvhàm, đó là kết quả Ok(())nếu mọi thứ thành công hoặc lỗi nếu xảy ra sự cố.

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

Hàm get_quotesnhận một tham chiếu đến reqwest::Clientlàm đối số và trả về một véc-tơ gồm Quotecác cấu trúc được lấy từ trang web "http://quotes.toscrape.com". Dưới đây là bảng phân tích về những gì chúng tôi đang làm:

  1. Khởi tạo một vectơ rỗng của Quotecác cấu trúc.
  2. Lặp lại các trang cần cạo, từ 1 đến MAX_PAGE.
  3. Đối với mỗi trang, hãy thực hiện yêu cầu GET tới URL của trang bằng hàm retry_getvà truy xuất HTML của trang.
  4. Phân tích cú pháp HTML thành một scraper::Htmlđoạn.
  5. Chọn trích dẫn text, authorvà tagstừ HTML bằng cách sử dụng bộ chọn CSS.
  6. Tạo một Quotecấu trúc mới cho mỗi trích dẫn và nối nó vào vectơ của các trích dẫn.
  7. Trả về vectơ của dấu ngoặc kép.

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

Hàm write_quotes_to_csvlấy một tham chiếu để cắt Quotecác cấu trúc và ghi các trích dẫn vào một .csvtệp. Đây là một sự cố về những gì chúng tôi đang làm:

  1. Tạo một .csvtệp mới với tên toscrape_quotes.csv.
  2. Tạo mới csv::Writerbằng cách sử dụng csvthùng.
  3. Viết một bản ghi mới vào tệp .csv cho mỗi trích dẫn trong lát trích dẫn, với trích dẫn text, author, và tagsđược phân tách bằng dấu phẩy.
  4. Xóa trình ghi để đảm bảo tất cả dữ liệu được ghi vào tệp.
  5. Trả về kết quả của thao tác ghi, nếu Ok(())mọi thứ thành công hoặc lỗi nếu xảy ra sự cố.

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

Hàm retry_getlấy một tham chiếu đến reqwest::Clientvà một tham chiếu đến URL chuỗi và nó tạo một yêu cầu HTTP GET tới URL bằng ứng dụng khách. Nếu yêu cầu không thành công, chức năng sẽ thử lại yêu cầu tối đa nhiều MAX_RETRIESlần với thời gian ngủ là RETRY_SLEEP_SECONDSgiây giữa mỗi lần thử lại. Đây là một sự cố về những gì chúng tôi đang làm:

  1. Khởi tạo một bộ đếm cho số lần thử lại.
  2. Thực hiện yêu cầu GET tới URL bằng ứng dụng khách và truy xuất phản hồi.
  3. Nếu phản hồi thành công (nghĩa là mã trạng thái của phản hồi nằm trong phạm vi 2xx), hãy trả lại phản hồi.
  4. Nếu phản hồi không thành công, hãy tăng bộ đếm thử lại.
  5. Nếu bộ đếm thử lại nhỏ hơn MAX_RETRIES, hãy đợi RETRY_SLEEP_SECONDSvài giây và thử lại yêu cầu GET.
  6. Nếu bộ đếm thử lại bằng MAX_RETRIES, hãy trả về lỗi cho biết yêu cầu GET không thành công.

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

Tuy nhiên, Rust là một ngôn ngữ tương đối mới so với các ngôn ngữ lập trình quét web tiên tiến hơn như Python. Do đó, nó có thể có một cộng đồng nhỏ hơn và ít thư viện hơn. Cú pháp của Rust cũng có thể khó khăn hơn đối với người mới bắt đầu, đặc biệt là những người đã quen thuộc với các ngôn ngữ lập trình cấp cao như Python với ngữ nghĩa động.

Hãy thông minh, vui vẻ lướt web và tránh các luật sư của công ty. Sẽ có luật sư, mặc dù! Hashtag, cái gì? #Billables