การใช้ Rust สำหรับการขูดเว็บ

Feb 13 2023
Web Scraping เป็นเทคนิคการดึงข้อมูลจากเว็บไซต์ เป็นกระบวนการอัตโนมัติในการดึงข้อมูลจำนวนมากจากเว็บไซต์ ประหยัดเวลาและความพยายาม และทำให้ข้อมูลพร้อมใช้งานสำหรับวัตถุประสงค์ต่างๆ

Web Scrapingเป็นเทคนิคการดึงข้อมูลจากเว็บไซต์ เป็นกระบวนการอัตโนมัติในการดึงข้อมูลจำนวนมากจากเว็บไซต์ ประหยัดเวลาและความพยายาม และทำให้ข้อมูลพร้อมใช้งานสำหรับวัตถุประสงค์ต่างๆ

ในโพสต์นี้ ฉันจะสาธิตวิธีที่คุณสามารถใช้Rustเพื่อแยกคำพูดจากเว็บไซต์

ข้อดีของการใช้ Rust สำหรับ Web Scraping

  1. ประสิทธิภาพ : Rust เป็นภาษาที่คอมไพล์แล้วพิมพ์แบบสแตติกซึ่งให้ประสิทธิภาพสูง ทำให้เป็นตัวเลือกที่เหมาะสำหรับการขูดเว็บ
  2. การทำงานพร้อมกัน : Rust ให้การสนับสนุนการทำงานพร้อมกันในตัว ซึ่งทำให้ง่ายต่อการจัดการคำขอหลายรายการพร้อมกัน ปรับปรุงประสิทธิภาพของการขูดเว็บ
  3. ความปลอดภัยของหน่วยความจำ : สนิมให้การรับประกันความปลอดภัยของหน่วยความจำที่แข็งแกร่ง ซึ่งช่วยป้องกันข้อบกพร่องที่เกี่ยวข้องกับหน่วยความจำทั่วไป เช่น ข้อยกเว้นพอยน์เตอร์แบบ null และบัฟเฟอร์ล้น ทำให้ปลอดภัยยิ่งขึ้นสำหรับการขูดเว็บ
  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 .

มาทำ Web Scraping กันเถอะ เราจะ?

Toscrape.comเป็นแซนด์บ็อกซ์สำหรับการขูดเว็บสำหรับผู้ที่ต้องการโจมตีในความพยายามดังกล่าวในวิศวกรรมซอฟต์แวร์

คุณสามารถใช้มันเป็นสนามเด็กเล่นของคุณได้โดยไม่ดึงดูดความสนใจที่ไม่พึงประสงค์จากนักกฎหมายองค์กรของแพลตฟอร์มองค์กรหรือองค์กร ใช่คุณไม่ต้องการเห็นคนเหล่านั้น! บางคนใส่สูทคู่กับAir Jordan 1 Chicago

รหัสการขูดเว็บสนิม

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::ClientURL ของสตริง และสร้างคำขอ 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 ที่มีความหมายเชิงไดนามิก

ฉลาด สนุกกับการท่องเว็บ และหลีกเลี่ยงทนายความของบริษัท จะมีทนายความแม้ว่า! แฮชแท็กว่าไง? #เงินเก็บ