웹 스크래핑에 Rust 사용하기
웹 스크래핑은 웹사이트에서 정보를 추출하는 기술입니다. 웹 사이트에서 대량의 데이터를 가져오고, 시간과 노력을 절약하고, 정보를 다양한 목적으로 사용할 수 있도록 하는 자동화된 프로세스입니다.
이 게시물에서는 Rust를 사용하여 웹사이트에서 인용문을 추출하는 방법을 보여드리겠습니다 .
웹 스크래핑에 Rust를 사용할 때의 이점
- 성능 : Rust는 정적으로 유형이 지정된 컴파일된 언어로 고성능을 제공하므로 웹 스크래핑에 이상적인 선택입니다.
- 동시성 : Rust는 내장된 동시성 지원을 제공하여 여러 요청을 동시에 처리하기 쉽게 하여 웹 스크래핑의 효율성을 향상시킵니다.
- 메모리 안전 : Rust는 강력한 메모리 안전 보장을 제공하여 널 포인터 예외 및 버퍼 오버플로와 같은 일반적인 메모리 관련 버그를 방지하여 웹 스크래핑을 더 안전하게 만듭니다.
- 배우기 쉬움 : Rust는 다른 프로그래밍 언어에 비해 상대적으로 간단한 구문을 가지고 있어 개발자가 배우기 쉽습니다.
- 가파른 학습 곡선 : Rust의 구문은 상대적으로 단순하지만 특히 정적으로 유형이 지정되고 컴파일된 언어를 처음 사용하는 개발자에게는 언어를 배우기가 어려울 수 있습니다.
- 제한된 커뮤니티 : Rust에는 성장하는 커뮤니티가 있지만 여전히 다른 프로그래밍 언어만큼 널리 사용되지는 않습니다. 즉, 리소스와 지원을 찾는 것이 어려울 수 있습니다.
- 내장 웹 스크래핑 라이브러리 없음 : 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
reqwestRust 웹 스크래핑 라이브러리 , 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 .
웹 스크래핑을 해봅시다. 할까요?
Toscrape.com은 소프트웨어 엔지니어링 분야에서 그러한 노력을 기울이고자 하는 사람들을 위한 웹 스크래핑 샌드박스입니다.
기업 플랫폼이나 조직의 기업 변호사로부터 원치 않는 관심을 끌지 않고 놀이터로 사용할 수 있습니다. 그래, 당신은 그 사람들을보고 싶지 않아! 일부는 에어 조던 1 시카고 와 짝을 이루는 수트를 입습니다 .
녹 웹 스크래핑 코드
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("es)?;
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("e_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 코드의 진입점이며 다음 단계를 수행합니다.
- 를 사용하여 새 HTTP 클라이언트를 만듭니다
reqwest::Client. - 함수 를 호출
get_quotes하고 클라이언트를 전달하여Quote구조체 벡터를 반환합니다. - 함수 를 호출 하고 따옴표를 .csv 파일에 쓰는 구조체
write_quotes_to_csv벡터를 함수에 전달합니다 .Quote 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("es)?;
Ok(())
}
이 get_quotes함수는 에 대한 참조를 인수로 사용 하고 웹사이트에서 스크랩한 구조체 reqwest::Client벡터를 반환합니다 .Quotehttp://quotes.toscrape.com". 다음은 우리가 수행하는 작업에 대한 분석입니다.
- 구조체 의 빈 벡터를 초기화합니다
Quote. - 스크랩할 페이지를 1부터 까지 반복합니다
MAX_PAGE. - 각 페이지에 대해 함수를 사용하여 페이지의 URL에 GET 요청을 만들고
retry_get페이지의 HTML을 검색합니다. - HTML을
scraper::Html조각으로 구문 분석합니다. - CSS 선택기를 사용하여 HTML에서 인용문
text,author및 을 선택합니다.tags - 각 인용문에 대한 새
Quote구조체를 만들고 인용문 벡터에 추가합니다. - 따옴표로 구성된 벡터를 반환합니다.
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("e_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
.csv이름으로 새 파일을 만듭니다toscrape_quotes.csv.csv::Writer상자를 사용하여 새로 만드십시오csv.- 따옴표 조각의 각 따옴표에 대해 .csv 파일에 새 레코드를 쓰고 따옴표
text,author및tags쉼표로 구분합니다. - 기록기를 플러시하여 모든 데이터가 파일에 기록되도록 합니다.
- 모든 것이 성공한 경우 쓰기 작업의 결과를 반환하거나
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함수는 문자열 URL에 대한 참조 reqwest::Client와 참조를 취하고 클라이언트를 사용하여 URL에 HTTP GET 요청을 합니다. 요청이 실패하면 함수는 각 재시도 사이의 MAX_RETRIES휴면 시간(초)으로 요청을 최대 횟수 재시도합니다 . RETRY_SLEEP_SECONDS우리가 하는 일에 대한 분석은 다음과 같습니다.
- 재시도 횟수 카운터를 초기화합니다.
- 클라이언트를 사용하여 URL에 GET 요청을 만들고 응답을 검색합니다.
- 응답이 성공하면(즉, 응답의 상태 코드가 2xx 범위에 있음) 응답을 반환합니다.
- 응답이 실패하면 재시도 카운터를 증가시킵니다.
- 재시도 카운터가 미만이면 몇 초
MAX_RETRIES동안 기다렸다가RETRY_SLEEP_SECONDSGET 요청을 재시도하십시오. - 재시도 카운터가 같으면
MAX_RETRIESGET 요청이 실패했음을 나타내는 오류를 반환합니다.
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과 같은 고급 프로그래밍 언어에 익숙한 사람들에게는 더 어려울 수 있습니다.
똑똑하고 웹 스크랩을 즐기며 기업 변호사를 피하십시오. 하지만 변호사는 있을 것입니다! 해시태그, 뭐? #청구서

![연결된 목록이란 무엇입니까? [1 부]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)



































