aws-summit-download を Rust と thirtyfour で再実装してみた
【前書き】aws-summit-download とは
先日、「AWS Summitの資料を一括でDLするスクリプトを作成しました。」というポストを発見しました。
とても便利なツールなので、早速使わせていただきました!
このようなツールを OSS で公開してくださることに感謝です🙌✨
Rewrite "aws-summit-download" In Rust
突然ですが、Rewriting It In Rust と言う言葉を聞いたことはあるでしょうか?
Rewriting It In Rust とは
Rust で既存のソフトウェアを再実装することを Rewriting It In Rust と言うことがあります。
再実装の目的は高速化など様々あるかと思いますが、個人開発などの場面では学びを目的とすることもできると思います。
よく学ぶためには、0から作ること・アプリケーションを作ることが一番だ言われますが、いざ何かを作ろうとしても何を作れば良いのか...と手が止まってしまうこともあるかと思います。
そんなときは Rewrite してみましょう!
自分の手で実装することで、得られるものは多いと思います😉
aws-summit-download の処理フロー
aws-summit-download のコードを確認して、処理の流れを見てみます。
- Chrome WebDriver のオプションを設定する
- Chrome WebDriver を初期化する
- 環境変数からユーザー名とパスワードを取得する
- ログインページにアクセスし、認証情報を入力してログインする
- AWS Summit Japan セッション資料一覧ページに移動する
- ページ内の PDF と ZIP ファイルへのリンクを抽出する
-
downloads
ディレクトリを作成する - ファイルをダウンロードする
ダウンロード処理の詳細は以下です。
- リンクの href 属性を取得する
- ファイル名を抽出する
- Axios を使用してファイルをダウンロードする
- ダウンロードしたファイルをローカルに保存する
また、使用している主なライブラリは以下です。
-
selenium-webdriver
- ブラウザを操作するためのブラウザ自動化ライブラリ
- Web スクレイピングする際などに使用
-
axios
- HTTP クライアントライブラリ
thirtyfour で Web スクレイピング
thirtyfour は Rust で Selenium ベースのブラウザ自動化を行うためのクレートです。
thirtyfour を使用すると、JavaScript で動的に生成されるコンテンツも含めて、より複雑な Web サイトのスクレイピングが可能になります。
なお、注意点として ChromeDriver をインストールし、実行しておく必要があります。
Rust で再実装
Rust で aws-summit-download を再実装してみます!!
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
ざっと、処理の流れを定義すると以下のようになります。
- 環境設定とセットアップ:
- dotenv で
.env
ファイルから環境変数を読み込む -
USERNAME
とPASSWORD
を環境変数から取得する - Chrome WebDriver を設定し、接続する
- dotenv で
- ログインと画面遷移:
- ログインページにアクセスし、認証情報を入力してログインする
- AWS Summit Japan セッション資料一覧ページに移動する
- ファイルのダウンロード:
- XPath でページ内の PDF と ZIP ファイルへのリンクを抽出する
-
downloads
ディレクトリを作成する - reqwest でファイルをダウンロードする
- ダウンロードしたファイルをローカルに保存する
- リソースの解放:
- 全ての処理が完了した後、
driver.quit().await?;
でブラウザを閉じます。
- 全ての処理が完了した後、
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 からも確認できます。
実行コマンド
前述したように、ChromeDriver をインストールし、実行しておく必要があります。
chromedriver
cargo run
注意事項
本家の aws-summit-download の README にも記載されておりますが、資料のダウンロードは各自の責任でお願いします。
また、運営等に注意された場合は aws-summit-download-rs のリポジトリを閉じます。
まとめ
Rust で Web スクレイピングする方法を学ぶ良い機会となりました!!
Web スクレイピングとファイルダウンロードを組み合わせることで、タスクを自動化することもできそうです...🤔🤔🤔
Discussion