Rust 初心者が Web スクレイピングしてみた
対象者
- プログラミングにある程度慣れていて、Rust はそんなに触ってない人
- 筆者は C# を書き慣れているので、C# を例に書くことが多いと思います。
- Rust でコンソールアプリケーションを作りたい人
また、Rust を書ける環境はインストール済みである前提で書きます。
プロジェクト作成
cargo new プロジェクト名 --bin
でプロジェクト作成。
cargo build
でビルドを実行して、
cargo run
で実行できるか確認します。
HTTP アクセスをしたい
スクレイピングをするにあたり、HTTP アクセスが必要です。
まずはその準備をします。
非同期プログラミングのために tokio クレートを使う
async fn main() {
println!("Hello, world!");
}
C# のノリで main 関数に async を書くと、ビルドした時にエラーになります。
error[E0752]: `main` function is not allowed to be `async`
エラーコードのページからも、main 関数には非同期であることが許可されてないことが分かります。
そこで、tokio クレート を利用します。
tokio クレートの追加
プロジェクト作成時に Cargo.toml
というファイルが生成されていると思います。
このファイルの中に [dependencies]
という項があるので、ここに依存するクレートを追加していきます。
[dependencies]
tokio = { version = "1", features = ["full"] }
と追加しました。
tokio クレートを利用する
追加後は、以下のように書けば非同期化します。
#[tokio::main]
async fn main() {
println!("Hello, world!");
}
これだけじゃよくわからないので、1秒待ってから plintln するようなコードを書いてみました。
use std::time::Duration;
use tokio::time::sleep;
#[tokio::main]
async fn main() {
for i in 1..5 {
wait_func().await;
println!("Wait {} sec.", i);
}
}
async fn wait_func() {
sleep(Duration::from_secs(1)).await;
}
これで 1秒待って出力、が実現できます。
C# と違って関数の前に await を書くのではなく、関数().await
とするのが違いですね。
tokio クレートの参考文献
以下の記事を参考にしました。
また、tokio で main 関数がどのように非同期されているかは、以下の記事が参考になりそうでした。
reqwest クレートを使う
さて、これで非同期プログラミングができるようになったので、
いよいよ HTTP アクセスです。
これには、reqwest クレート を利用します。
reqwest クレートの追加
[dependencies]
tokio = { version = "1", features = ["full"] }
reqwest = "0.11"
tokio にプラスして、reqwest クレートも追加します。
reqwest クレートの利用
GET リクエストを飛ばして、結果を出力するというのをやってみます。
以下のようにしました。
#[tokio::main]
async fn main() {
let result = get_reqwest().await;
println!("body = {:?}", result);
}
async fn get_reqwest() -> Result<String, Box<dyn std::error::Error>> {
let body = reqwest::get("https://www.rust-lang.org").await?.text().await?;
Ok(body)
}
リクエスト飛ばすところは Result 型で返してあげる必要があるようでした。
ので、こんな記法になってます。
これで GET リクエストはできるようになったので、あとは取得した HTML を解析できれば Web スクレイピングができます。
HTML の解析をする
Scraper クレート を利用します。
Cargo.toml に scraper = "0.13.0"
を追加します。
そして、以下のような実装を追加しました。
use scraper::Html;
use scraper::Selector;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>>{
let result = get_reqwest().await?;
try_parse_html(&result);
Ok(())
}
async fn get_reqwest() -> Result<String, Box<dyn std::error::Error>> {
let body = reqwest::get("https://www.rust-lang.org").await?.text().await?;
Ok(body)
}
fn try_parse_html(html: &str) {
let document = Html::parse_document(html);
let selector_str = "h2";
let selector = Selector::parse(selector_str).unwrap();
for element in document.select(&selector) {
if let Some(unicode) = element.text().next() {
println!("{}", unicode);
}
}
}
get_reqwest で取得した HTML 文字列を Html::parse_document で解析。
そして、Selector::parse で作ったセレクターを使い、document から要素を抽出するという流れです。
このセレクターというのは、CSS セレクターを指すようです。
以下の記事がまとまってて参考になりました。
終わりに
思ったよりも簡単に実装できた印象です。
C# にある程度慣れている人なら、すんなり書けるような気がしました。
これからも Rust 周り触っていって、いろいろ勉強していこうと思います。
Discussion