💻

Rust 初心者が Web スクレイピングしてみた

2022/10/25に公開

対象者

  • プログラミングにある程度慣れていて、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 クレートの参考文献

以下の記事を参考にしました。
https://qiita.com/Kumassy/items/fec47952d70b5073b1b7

また、tokio で main 関数がどのように非同期されているかは、以下の記事が参考になりそうでした。
https://qiita.com/ryuma017/items/1f31f5441ed5df80f1cc

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 セレクターを指すようです。
以下の記事がまとまってて参考になりました。
https://gammasoft.jp/support/css-selector-for-python-web-scraping/

終わりに

思ったよりも簡単に実装できた印象です。
C# にある程度慣れている人なら、すんなり書けるような気がしました。
これからも Rust 周り触っていって、いろいろ勉強していこうと思います。

Discussion