deno は使っていますか?使っていますよね? そして、 Puppeteer のようにスクリプトからブラウザ操作をしたいということが良くあります。ありませんか?
本エントリでは、 deno 版の Puppeteer である astral を使用したブラウザの操作について紹介します。
Note
- このエントリでは 2025年5月時点での最新版である astral@v0.5.2 で解説しています。
- deno 2.2.8 / v8 13.5.212.10-rusty / typescript 5.7.3 を使用しています。
- このエントリは生成AIは使用していません。
Warning
サイトに負荷をかけると最悪犯罪になるためお気をつけください。
astral とは
説明に Astral is a high-level puppeteer/playwright-like library that allows for control over a web browser とあるように Puppeteer やPlaywright ライクなブラウザを操作するパッケージです。
基本的な操作はほぼ Puppeteer などと似ており直感的な操作が可能です。 JSR のパッケージとして配布されているので deno から利用する場合は、
deno add jsr:@astral/astral
とするだけで利用可能です。JSR のインストール方法などは Using JSR with Deno - Docs - JSR を参照してください。
そして操作するブラウザは基本的に明示的な設定をしない限り、astral 用の Chrome が自動でインストールされるようです。
一応、Firefoxを使用することも可能っぽい記述はあるのですがうまく動かなかったのでここでは解説はしません。
Tip
astral ではデバッグモードで起動することができます。
DEBUG=true deno run --allow-env your_script.ts
のように実行することで起動するブラウザのコマンドライン引数やAPIのやり取りなどのログを確認できます。
スクリーンショットを撮る
Note
この記事を含めた具体的なスクリプト例を GitHub - cateiru/astral-playground にて紹介しています。合わせて参考にしてみてください。
さて、では実際に astral を実際に動かして例を見ていきましょう。最初はブラウザ操作も醍醐味と勝手に思っているスクリーンショットをしていきます。
まずは、 astral を import しましょう。
import { launch } from "jsr:@astral/astral";
launch()
を呼び出すことでブラウザ操作の準備ができます。戻り値は Promise<Browser>
型が返ります。そのため、初めに以下のようにします。
const browser = await lunch();
Puppeteer も const browser = await puppeteer.launch();
なので同じような機能です。 さらに引数として渡せる LunchOptions
は astral/src/browser.ts at main · lino-levan/astral · GitHub によると以下のような項目が設定可能のようです。
path?: string
- ブラウザのパスのようです。未指定の場合は自動で見つけてくれるので基本的には未指定で良さそうです。
args?: string[]
- ヘッドレスブラウザに渡すコマンドライン引数を設定します。
- chromium で設定できる項目は List of Chromium Command Line Switches « Peter Beverloo に一覧があります。
cache?: string
- ブラウザが無い場合、自動でブラウザがインストールされますが、そのキャッシュディレクトリのパスを指定します。
- デフォルトは mac などでは
/Library/Caches/astral
などが使われるようです。詳しくは astral/src/cache.ts at main · lino-levan/astral · GitHub を参照してください。
headless?: boolean
- ヘッドレスモードで起動するかのフラグです。 未指定の場合は true で、 false にすると実際にブラウザを起動した状態で実行がきでます。
product?: "chrome" | "firefox"
- 使用するブラウザを選べるようです。デフォルトは chrome です。
userAgent?: string
- 使用できる UserAgent を設定します。後述する
newPage
でも設定できますが、その場合は上書きされるようで、こちらはデフォルトの値になるようです。
- 使用できる UserAgent を設定します。後述する
lunch ができ、 Browser のインスタンス変数が作成できたら次は URL を指定して Page を作ります。これによりブラウザがページにアクセスできます。
const page = await browser.newPage("https://example.test");
newPage
にも第二引数でオプションを指定でき、以下のような項目が設定可能です。
waitUntil?: "none" | "load" | "networkidle0" | "networkidle2"
- ナビゲーションの待機イベントを指定できます。
- この設定項目は Puppeteer と同じようなので Understanding Puppeteer waitUntil | BrowserStack を参照すると良いです。
sandbox?: boolean
userAgent?: string
- アクセスする UserAgent を指定できます。
lunch
で設定していても上書きすることができます。
- アクセスする UserAgent を指定できます。
もし、 Basic認証やビューポートサイズを変えてアクセスしたい場合は以下のように goto
メソッドを呼び出します。
// ここでは引数をなにも設定しない const page = await browser.newPage(); await page.authenticate({ username: "postman", password: "password" }); // ここで URL や Options を設定する await page.goto("https://example.test");
goto
の第二引数で指定できるオプションも newPage
で指定できるオプションと同じです。
次にビューポートサイズの設定を行います。スクリーンショットを取得する際には、このビューポートサイズが画像のサイズとなるため必ず設定しておきます。 page
のインスタンスに setViewportSize
メソッドがあるのでこちらを使用します。
await page.setViewportSize({ width: 1280, height: 800, });
最後にスクリーンショットを取ります。スクリーンショットは screenshot
メソッドを使用します。
const image = await page.screenshot(); Deno.writeFileSync(`${import.meta.dirname}/image.png`, image);
戻り値は Uint8Array<ArrayBufferLike>
が返るので Deno.writeFileSync
などでファイルに書き出しをすると良いです。
Tip
import.meta.dirname
を使用することで実行する ts ファイル直下に画像を書き出しすることが可能です。
screenshot
メソッドには以下のようなオプションが指定できます。
format?: "jpeg" | "png" | "webp"
- 書き出す画像の形式を指定します。デフォルトは
png
です。
- 書き出す画像の形式を指定します。デフォルトは
quality?: number
- jpeg のクオリティを指定します。0~100 の値で設定します。
clip?: Page_Viewport
- スクリーンショットをする位置を座標で設定します。設定する座標は
x
,y
,width
,height
,scale
です。 - このオプションは ScreenshotClip interface | Puppeteer と同じようです。
- スクリーンショットをする位置を座標で設定します。設定する座標は
fromSurface?: boolean
*1- 値を
true
にすると View ではなく Surface からスクリーンショットを取得するオプションです。デフォルトはtrue
です。 - Surface というのは Surfaces のことのようです。
- 値を
captureBeyondViewport?: boolean
true
にすることでビューポート外の要素もキャプチャできるようにします。デフォルトはfalse
です。
optimizeForSpeed?: boolean
true
にするとキャプチャスピードの速度を優先してエンコードするフラグのようです。デフォルトはfalse
です。
指定した要素のスクリーンショットを撮る
さて、先程までは画面全体(ビューポート全体)のスクリーンショットについてを解説しましたが、次に指定した要素のスクリーンショットを撮る例について解説します。
astral では以下のようにして要素を指定して、スクリーンショットメソッドでスクリーンショットを撮る機能が一応実装されています。
// `.target-select` にスクリーンショットを撮りたい要素の CSSセレクタ を指定 const targetElement = page.$(".target-selector"); if( targetElement == null) { throw new Error("Element not found"); } const image = await targetElement.screenshot(); Deno.writeFileSync(`${import.meta.dirname}/image.png`, image);
しかし、これではスクリーンショットが正しく撮れないようです。要素のインスタンス変数 ElementHandle
で定義されている screenshot
メソッドは以下のように実装されています。
/** * This method scrolls element into view if needed, and then uses `Page.screenshot()` to take a screenshot of the element. */ async screenshot( opts?: Omit<ScreenshotOptions, "clip">, ): Promise<Uint8Array> { await this.scrollIntoView(); const box = await this.boundingBox(); if (!box) { throw new Error( "No bounding box found when trying to screenshot element", ); } return await this.#page.screenshot({ ...opts, clip: { ...box, scale: 1, }, }); }
最初に await this.scrollIntoView();
をしているのがポイントです。これにより、指定した位置までページをスクロールします。その後に boundingBox
メソッドで位置の絶対位置を取得します。しかし、 boundingBox
はスクロールを含まない画面からの絶対位置です。そうすると、スクロール分 y
座標がズレてしまうことで正しく撮ることができません。
Tip
JavaScript で要素の絶対位置を取得する場合、
window.pageYOffset + element.getBoundingClientRect().top
とします。
そのため、 1. スクロールする前の初期位置の状態で絶対座標を取得する方法 か、 2. スクロール量を取得する方法 の2種類の案が考えられます。
1. スクロールする前の初期位置の状態で絶対座標を取得する方法
この方法では、 scrollIntoView
を実行する前に boundingBox
を実行します。というか、 scrollIntoView
は必要ありません。代わりに captureBeyondViewport: true
としてViewPort 外でもスクリーンショットを撮れるようにします。
const boundingBox = await targetElement.boundingBox(); if (boundingBox == null) { throw new Error("Element bounding box not found"); } const image = await page.screenshot({ captureBeyondViewport: true, clip: { x: boundingBox.x, y: boundingBox.y, width: boundingBox.width, height: boundingBox.height, scale: 1, }, }); Deno.writeFileSync(`${import.meta.dirname}/image.png`, image);
2. スクロール量を取得する方法
1の方法はシンプルです。しかし、事前に scrollIntoView
をしていると壊れてしまう可能性があります。これらを解決するため window.pageYOffset
を JavaScript 経由で取得して y
座標に足すことで正しい座標を指定します。
const scrollY = await targetElement.evaluate(() => { return (window as any).pageYOffset; }); const boundingBox = await targetElement.boundingBox(); if (boundingBox == null) { throw new Error("Element bounding box not found"); } const image = await page.screenshot({ captureBeyondViewport: true, clip: { x: boundingBox.x, y: boundingBox.y + scrollY, width: boundingBox.width, height: boundingBox.height, scale: 1, }, }); Deno.writeFileSync(`${import.meta.dirname}/image.png`, image);
JavaScript の API を呼び出すには evaluate
を使用します。引数に関数を指定することでその関数を実行できます。注意点として、渡す関数は内部で toString()
*2 の後ブラウザのAPIに渡されます。そのため関数内部で変数を外から渡して使用したい場合は evaluate
の引数で渡します。
const returnValue = await targetElement.evalute((element, value1, value2, value3) => { ... return value; }, {args: [value1, value2, value3]});
フォームを自動で入力する
次にフォームの自動入力について解説していきましょう。ブラウザ操作では、動作確認などで自動でログインをしたいケースなどがあるかもしれません。そういった場合に利用可能です。
1. 文字入力型
type="input"
や type="password"
、 textarea
などの文字を入力するタイプのフォーム要素では type
メソッドが astral に用意されていてこちらが利用できます。
const textElement = await page.$("input[type='text']"); if (textElement == null) { throw new Error("textElement is null"); } await textElement.type("こんにちは", { delay: 100 });
type
では第二引数に delay
を含めることができ、これに値を設定すると指定したミリ秒分タイプ感覚を遅延させます。上記の場合は こ [100ms] ん [100ms] に [100ms] ち [100ms] は
となります。change イベントなどで入力中にバリデートなどをしている場合はこの遅延を入れると便利です。
2. クリック型
type="button"
や type="checkbox"
、 type="radio"
などは click
メソッドを使用して擬似的にクリックすることが可能です。
const checkboxElement = await page.$("input[type='checkbox']"); if (checkboxElement == null) { throw new Error("checkboxElement is null"); } await checkboxElement.click();
3. value に値が入るケース
type="date"
や type="week
、 type="color"
などの value
属性に値が入るケースでは専用のメソッドが用意されていないため evaluate
を使用して JavaScript 経由で value
に値を入れてあげる必要があります。
const datetimeElement = await page.$( "input[type='datetime-local']" ); if (datetimeElement == null) { throw new Error("datetimeElement is null"); } await datetimeElement.evaluate( (element: any, date) => { element.value = date; }, { args: ["2023-10-01T12:00"] } );
4. select
select も .select()
といったメソッドが astral にはない*3ので evaluate
を使用する必要があります。
const selectElement = await page.$("select"); if (selectElement == null) { throw new Error("selectElement is null"); } const selectedValue = "option2"; await selectElement.evaluate( (element: any, v) => { const optionElement = element.querySelector(`option[value='${v}']`); if (optionElement != null) { optionElement.selected = true; } }, { args: [selectedValue] } );
おわりに
いかがだったでしょうか? deno は手軽にスクリプトを書けるのが魅力で astral も deno に最適化されているためすぐ使い始めることができ非常に便利なライブラリです。ぜひこの機会に試してみてはいかがでしょうか?