🍀

aws-summit-download を Rust と thirtyfour で再実装してみた

2024/06/30に公開

【前書き】aws-summit-download とは

先日、「AWS Summitの資料を一括でDLするスクリプトを作成しました。」というポストを発見しました。

https://x.com/bondai_engineer/status/1805428579985232015

とても便利なツールなので、早速使わせていただきました!

このようなツールを OSS で公開してくださることに感謝です🙌✨

Rewrite "aws-summit-download" In Rust

突然ですが、Rewriting It In Rust と言う言葉を聞いたことはあるでしょうか?

Rewrite It In Rust とは

Rust で既存のソフトウェアを再実装することを Rewriting It In Rust と言うことがあります。

再実装の目的は高速化など様々あるかと思いますが、個人開発などの場面では学びを目的とすることもできると思います。

よく学ぶためには、0から作ること・アプリケーションを作ることが一番だ言われますが、いざ何かを作ろうとしても何を作れば良いのか...と手が止まってしまうこともあるかと思います。

そんなときは Rewrite してみましょう!
自分の手で実装することで、得られるものは多いと思います😉

aws-summit-download の処理フロー

aws-summit-download のコードを確認して、処理の流れを見てみます。

  1. Chrome WebDriver のオプションを設定する
  2. Chrome WebDriver を初期化する
  3. 環境変数からユーザー名とパスワードを取得する
  4. ログインページにアクセスし、認証情報を入力してログインする
  5. AWS Summit Japan セッション資料一覧ページに移動する
  6. ページ内の PDF と ZIP ファイルへのリンクを抽出する
  7. downloads ディレクトリを作成する
  8. ファイルをダウンロードする

ダウンロード処理の詳細は以下です。

  1. リンクの href 属性を取得する
  2. ファイル名を抽出する
  3. Axios を使用してファイルをダウンロードする
  4. ダウンロードしたファイルをローカルに保存する

また、使用している主なライブラリは以下です。

  • selenium-webdriver
    • ブラウザを操作するためのブラウザ自動化ライブラリ
    • Web スクレイピングする際などに使用
  • axios
    • HTTP クライアントライブラリ

thirtyfour で Web スクレイピング

thirtyfour は Rust で Selenium ベースのブラウザ自動化を行うためのクレートです。

thirtyfour を使用すると、JavaScript で動的に生成されるコンテンツも含めて、より複雑な Web サイトのスクレイピングが可能になります。

なお、注意点として ChromeDriver をインストールし、実行しておく必要があります。

Rust で再実装

Rust で aws-summit-download を再実装してみます!!

Cargo.toml

必要なクレートを追加します。

Cargo.toml
[dependencies]
dotenvy = "0.15.7"
reqwest = { version = "0.12.5", features = ["stream"] }
thirtyfour = "0.32.0"
tokio = { version = "1", features = ["full"] }
url = "2.3"

main.rs

ざっと、処理の流れを定義すると以下のようになります。

  1. 環境設定とセットアップ:
    • dotenv で .env ファイルから環境変数を読み込む
    • USERNAMEPASSWORD を環境変数から取得する
    • Chrome WebDriver を設定し、接続する
  2. ログインと画面遷移:
    • ログインページにアクセスし、認証情報を入力してログインする
    • AWS Summit Japan セッション資料一覧ページに移動する
  3. ファイルのダウンロード:
    • XPath でページ内の PDF と ZIP ファイルへのリンクを抽出する
    • downloads ディレクトリを作成する
    • reqwest でファイルをダウンロードする
    • ダウンロードしたファイルをローカルに保存する
  4. リソースの解放:
    • 全ての処理が完了した後、driver.quit().await?; でブラウザを閉じます。
main.rs
use dotenvy::dotenv;
use reqwest::Client;
use std::env;
use std::fs::{self, File};
use std::io::copy;
use std::path::Path;
use thirtyfour::prelude::*;
use tokio;
use url::Url;

#[tokio::main]
async fn main() -> WebDriverResult<()> {
    dotenv().ok();

    println!("Start download");

    let login_url = "https://jpsummit-smp24.awsevents.com/public/mypage?lang=ja";
    let download_page_url = "https://jpsummit-smp24.awsevents.com/public/application/add/273";
    let username = env::var("USERNAME").expect("USERNAME not set");
    let password = env::var("PASSWORD").expect("PASSWORD not set");

    let caps = DesiredCapabilities::chrome();
    let driver = WebDriver::new("http://localhost:9515", caps).await?;

    // Open the login page
    driver.goto(login_url).await?;

    // Find the username and password fields and enter the credentials
    let username_field = driver.find(By::Name("login_id")).await?;
    username_field.send_keys(&username).await?;
    let password_field = driver.find(By::Name("login_password")).await?;
    password_field.send_keys(&password).await?;

    // Find and click the login button
    let login_button = driver
        .find(By::XPath(
            "//button[contains(@class, 'btn-primary') and text()='ログイン']",
        ))
        .await?;
    login_button.click().await?;

    driver
        .find(By::XPath("//a[@title='資料一覧サイト']"))
        .await?;

    println!("success login and move download page");

    driver.goto(download_page_url).await?;

    // Find all download links
    let links = driver
        .find_all(By::XPath(
            "//a[contains(@href, '.pdf') or contains(@href, '.zip')]",
        ))
        .await?;

    println!("Found {} downloadable links on the page.", links.len());

    // Create a directory for downloads if it doesn't exist
    fs::create_dir_all("downloads").expect("Failed to create downloads directory");

    let client = Client::new();

    // Download each file
    for link in links {
        let href = link.attr("href").await?.expect("No href attribute");
        let url = Url::parse(&href).expect("Failed to parse URL");
        let file_name = url
            .path_segments()
            .and_then(|segments| segments.last())
            .unwrap_or("unknown");
        let file_path = Path::new("downloads").join(file_name);

        let response = client
            .get(&href)
            .send()
            .await
            .expect("Failed to GET from URL");
        let mut dest = File::create(&file_path).expect("Failed to create file");
        let resp_file = response
            .bytes()
            .await
            .expect("Failed to get the full response body as Bytes");
        copy(&mut resp_file.as_ref(), &mut dest).expect("Failed to copy content");

        println!("Downloaded: {}", file_name);
    }

    println!("All files downloaded.");

    // Close the browser
    driver.quit().await?;

    Ok(())
}

ソースコードは Github からも確認できます。

https://github.com/codemountains/aws-summit-download-rs

実行コマンド

前述したように、ChromeDriver をインストールし、実行しておく必要があります。

chromedriver
cargo run

注意事項

本家の aws-summit-download の README にも記載されておりますが、資料のダウンロードは各自の責任でお願いします。

また、運営等に注意された場合は aws-summit-download-rs もリポジトリを閉じます。

まとめ

Rust で Web スクレイピングする方法を学ぶ良い機会となりました!!

Web スクレイピングとファイルダウンロードを組み合わせることで、タスクを自動化することもできそうです...🤔🤔🤔

参考

コラボスタイル Developers

Discussion