自分用の備忘録

VimとかJSとか。やったことのメモ。自分のため。

Rustで簡易的にJSを実行した結果を扱いたい

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の一覧持ってるとか)