RustでJSが実行できて、その内容をRust側で使えたらなー。
という要件が出てきたので、やってみたよ的な内容。
「必要な情報がHTMLのForm上には落ちていないけど、JS上には落ちている」
みたいなニッチな条件下での利用を想定。
https://github.com/theduke/quickjs-rs
quick-js
クレートを使えば簡単でした。
use quick_js::{Context, JsValue}; let context = Context::new().unwrap(); // Eval. let value = context.eval_as::<String>(" var x = 100 + 250; x.toString() ").unwrap(); println!("value = {}", value); // 350
とりあえず一番雑なEval。
これでJSを実行したあとのxの値がとれます。
本題
さて、本題。
最近のフロントエンドのコードは、基本的にビルドされ、そしてミニファイされています。
そして、このビルド・ミニファイはコンテンツ量によっては一定ではないです。
コード中に多く出現するようなものはa='hogehoge'
のような1文字変数に変換され、使い回されたり。
そういう感じで容量削減されます。
スクレイピングする側にとっては都合が悪いということです。
毎回同じ位置にあれば、<script>
内のJSコードにほしい情報があったとしても、文字列として抜き出して、どうにかすれば力技で解決できます。
しかし、コンテンツ量によって一定ではない時点で、規則性も何もかもなくなってしまいます。
なら、<script>
の中を実行した結果があれば問題ないはずです。
ということです。
reqwest
,scraper
,serde
,tokio
と合わせて、quick_js
を絡めていきます。
use quick_js::Context; use scraper::{Html, Selector}; #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { let client = reqwest::Client::builder().cookie_store(true).build()?; let res = client.get(url).send().await?; let body = res.text().await?; let document = Html::parse_document(&body); // body>scriptをとる(ここはほしいscriptタグに合わせていく) let selector = Selector::parse("body>script").unwrap(); let script = document.select(&selector).next().unwrap(); // scriptタグの中にJSコードがそのままあるパターンのみ let script_text = script.inner_html(); // windowに入れている場合、windowが存在しないから作るようなコードにする let _code = format!( "const window ={{}};{};JSON.stringify(window.APP.data[0])", script_text ); let context = Context::new().unwrap(); // _codeを実行してwindow.APP.data[0]を取得 let result = context.eval_as::<String>(&_code).unwrap(); // json文字列をjsonに変換 let json: serde_json::Value = serde_json::from_str(&result).unwrap(); // 以降json['property']として扱える println!("{:?}", json["property"]); Ok(()) }
当たり前ですが、ビルドされてようがミニファイされてようが、JSとして動かすことができれば、規則性を見つけなくてもとりあえずはどうにかなるよって話です。
静的コンテンツに対しては有効な手段かなって思います。(JS側にselectの一覧持ってるとか)