Puppeteerでウェブのスクショを連続取得しSlackに投稿する

社内でとあるページのスクショをFireshotで取得して、Slackのチャンネルに特定の人にメンション付きで投稿という作業を毎日30件近く行っている。時間も労力も勿体無いなぁと思い、Excelに記録したURLをもとにPuppeteerでスクショを取り、メンション付きで、Slackの特定チャンネルに投稿するというものを作成しました。

色々と躓いた点があったので如何にまとめておきます。だいぶ、Puppeteer側も時間が経過して色々変わっていました。

今回利用するツール等

今回は、OAuth認証ではなく、APIキーを発行してSlack APIにリクエストを投げています。また、各種Slackの設定関係についてはJSONファイルに切り出してNode.js側で読み込む形にしています。

最終的には単一実行ファイル化して、ダブルクリックだけで仕事が終わるように構築する予定です。単一実行ファイル化については、以下のエントリーで説明しています。

Puppeteerを使ってX(旧Twitter)へのポストを自動化する

事前準備

Slackの必要な情報を取得する

setting.jsonについて

今回以下の項目で取得した内容については、setting.jsonというファイル内に記述して、index.jsと同じディレクトリに入れています。JSONファイルの構造は以下のようなスタイルです。

定型メッセージを毎回変える必要がある場合は、ソースコードを直す必要があります。

SlackチャンネルのIDを取得する

あらかじめ用意しておいたSlackのチャンネルには各々個別のIDが付いたチャンネルのIDというものがあります。これを取得してsetting.jsonに書き込んでおきます。

  1. 対象のSlackチャンネルをブラウザで開く
  2. URLは以下のようなスタイルです
  3. 上記のURLのうちチャンネルIDの部分をコピーしてsetting.jsonの該当箇所(channel)に書き込みます。

メンション先ユーザIDを取得する

今回は特定の人にメンションをつけて、画像投稿をお知らせするようにしています。このユーザIDとはメールアドレスではなく、各ユーザのプロファイル画面に表示されています。

  1. 適当にメンションなどのリンクをクリックするとその人のプロフィールが右サイドバーに開かれる
  2. 現地時間の下にある3つのボタンのうち、「︙」をクリック→メンバーIDをコピーをクリック
  3. 2.でコピーしたIDをsetting.jsonの該当箇所(user_id)に書き込みます。

図:メンバーIDがユーザ固有のIDです

アプリを作成しトークンを取得する

次にSlack上で動作するボットのアプリを作成して、Tokenを取得する必要があります。以下の手順で作成します。

  1. こちらのページにアクセスする
  2. Create New Appをクリックする
  3. From a manifestをクリックする
  4. Slack workspaceとしてすでに所有してるワークスペースを選択してNextをクリックする
  5. manifest画面はそのままCreateで次に進む。
  6. Review summaryはそのままCreateで次に進む。
  7. Basic Informationの画面になるので一番下の方にスクロールし、Display Informationを表示する
  8. App Nameを適当なものに変える。
  9. 上部にあるApp-Level-Tokensでは、Generate Token and Scopesをクリックする
  10. Add Scopeで今回はとりあえず3種類全部追加しています。
  11. Generateをクリックする
  12. 左サイドバーの「OAuth & Permissions」をクリックする
  13. 下の方にあるScopeに移動して、Add an OAuth Scopeをクリックする
  14. 今回自分は以下のスコープを入れていますが、files:write、chat:writeは必須ですがそれ以外はオプションです。
  15. 上部にあるOAuth TokensでInstall to テナントというボタンをクリックして、このテナントにこのbotを追加します。
  16. 同時にBot User OAuth Tokenというのが表示されているハズなので、ここのCopyをクリックしてTokenを取得します。
  17. 取得したTokenをsetting.jsonの該当箇所(slacktoken)に記述します。

チャンネルにアプリを追加する

これで情報は取得できましたが、まだこれではボットがチャンネルに投稿できません。以下の作業を行い、チャンネルに作成したbotを追加しましょう。

  1. チャンネルを開く
  2. 右上の「︙」をクリックして、設定を編集するをクリックする
  3. インテグレーションをクリックして、Appの所にある「アプリを追加する」をクリックする
  4. 検索画面が出てくるので、前の項目でつけたApp Nameで検索し追加ボタンをクリックします。

Excelファイルを整備する

ここまででSlack関係の事前準備は完了しました。次に、スクリーンショットを連続で取得するのでデータを整備します。今回はkinoko.xlsxというファイルを用意して、その中に記述しています。

中には「kinoko」というシートが1枚あるだけで、ページ名, URLの2つの列があるだけです。この2列にページの名前とそれぞれスクショを撮るサイトのURLを記述しておきます。

図:Excelファイルに列挙しておく

使い手側での事前準備

デフォルトプロファイルでログイン

今回のPuppeteerでの自動化ではログイン自動化は装備していません。Puppeteer-coreで普段使ってるChromeを起動して使うようにしているため、もしログインを要するサイトのスクショを撮る場合には、Chromeのデフォルトプロファイルでサイトへのログイン→2段階認証済ませるというところまで完了しておく必要があります。

ターミナルを開いて以下のコマンドラインを実行し、開いたChromeで該当サイトへログインを完了し保存しておいてください。ユーザ名の場所は自身のログインユーザ名を入れてください。

※但し、Windowsの場合、上記の例の場所にインストールされてるとは限らないので要注意(C:\Program Files (x86)\Google\Chrome\Application\chrome.exeというケースがある)。インストール先を見つけてパスは書き換えてください。

これで、Puppeteer-coreで起動したときも同じプロファイルを使って操作されるので、ログイン画面が出ることがなくなります。ユーザに毎回入力してもらい、ログイン完了まで待機させるという方法もあります。

うまくいかない場合は、初回だけPuppeteerで起動時にログインし、もう一度Puppeteerで実行すると良いでしょう。ログイン情報を利用して再度ログインは不要になります。

普段使ってるプロファイルのまま利用する

普段使ってるプロファイルのまま操作したいケースがあります。この場合、PuppeteerをLauchする時のuserDataDirの指定方法をDefaultではなく、以下のように指定すると良いです。

ただし、ユーザが普段使ってるプロファイルで別個起動しようとすると「Error: Failed to launch the browser process!」というエラーが出て止まるので、Chromeは全て起動していない状態でなければこの手法は使うことが出来ません。注意が必要です。

ソースコード

冒頭の変数宣言等

冒頭ではモジュール読み込みや各種変数の設定を行っています。またPuppeteer-coreを使っているので、ユーザのPCにあるChromeのパスを特定しています。

  • setting.jsonを読み取ってSlack用のデータを変数に格納しています。
  • xlsxファイルを読み取ってworkbookとして格納しています。
  • 利用者のログインユーザ名を取得し、またOS情報を取得しています。
  • 取得したOS情報でmacOSとWindowsの場合のそれぞれのChromeのパスを特定しています。また、DefaultプロファイルのCookieも使うので、userDataDirも指定しています。
  • Windowsの場合はChrome以外にもEdgeでも動かせるのでChromeが無い場合にも対処させています。
  • 最後にmain()を実行して、Puppeteerを実行します。

メインの処理

PuppeteerでChromeを操縦するメインの関数の処理です。Puppeteer-coreを使ってるのでChromiumではなく、インストール済みのChromeを使って操縦します。

  • 冒頭でChromeとuserDataDirを指定してpuppeteer-coreを起動します。
  • ログインが必要なページの操作がある場合には、一度Default Profileにてログイン処理をしておく必要があります。
  • 日付時間でスクショ格納用のフォルダを作成します。
  • ページ名を元にスクショ画像のファイル名としています。
  • for await ... ofの構文を使うと、Forループを同期的に回すことが可能です。
  • 本来ページの初期化は一回のみで良いのですが、スクショを連続取得しようとすると止まるページがあったので、ページの初期化含めてループ内で行わせています。
  • new Page()の前の時点でsleepを5000ms入れているのは、これを入れないと早すぎて「Session with given id not foundエラー」になる場合があるため。
  • 対象ページを開いて、waitForNetworkIdleでロードが終わるまで待機させます。
  • スクショはpage.setViewportの画面サイズに影響を受けるので、viewportの値は小さすぎないように注意が必要。
  • page.screenshotで保存場所とファイル名を指定するとスクショが撮影可能。この時、fullPageをtrueにしておくとFireshot同様にページ全体をスクショ撮影します。
  • slack投稿の関数にファイルのパス、ファイル名、ページ名を渡してスクショをそのまま投稿します。
  • 連続してスクショを撮ろうとしたら、特定のページで何故か停止してしまったので、ここで一旦pageをcloseする処理を入れています。
  • Slack APIのQuotaに引っかからないように、Sleepで2秒間ウェイトを入れています。必要に応じて数値を盛ります。
  • 最後にbrowser.closeを実行して閉じて完了。

その他の関数

ここではSlackに投稿する関数、フォルダ用の日付生成関数、スリープ用の関数を用意しています。

  • Slack APIのファイルアップロードのメソッドだけだと投稿したメッセージのthread_tsというスレッドIDが取得出来ないので、まずは通常のメッセージだけをメンションで投稿し、thread_tsをthreadmanに格納します。
  • メンションはusernameが使えなくなってるので、取得済みのuser_idの値を持ってしてメンションをします。
  • web.filesUploadV2でファイルをアップロードします。Puppeteerで生成したファイルを指定し、スレッド返信するためにthread_tsにも指定を入れます。
  • file_uploadsにて複数のファイルを同時にアップロードも可能です。

実行結果

さて、これでnode index.jsをターミナルから実行してみると、Default Profileで起動したPuppeteerが起動して、Excel記載のURLを元に次々にスクショを撮ってSlackに投稿してくれます。最後に自動で閉じて完了となります。

以下はPuppeteerの操作の画面のみですが、この速度で次々にスクショを取得してはSlackに投稿を連続で自動で行っています。

図:元スレッドにぶら下げて画像が投稿されました

Windowsでトラブル

概要

前述のコード、macOSでは何ら問題なく動作しましたが、Windowsで同じコードを実行すると、Node Slack SDKでトラブルが出ていて、投稿ができない状態です。具体的にはNode Slack SDKの中で呼び出してる「Axios」というモジュールが以下のようなトラブルを起こしています(IntelおよびARMの両方の環境で確認しています)。

  • axiosだけ古いものに置き換えても変わらず
  • Nodeのバージョンを23から古い22や20に変えても変わらず
  • StackOverflowでも報告されてる内容を確認しましたが、解決に至らず。
  • Puppeteerやキャプチャについては問題なく動作しています。

Node Slack SDKのIssueにも報告は特になく、何が原因か不明であるため、これをFetch APIに置き換えようと思います。

Fetch APIで書き直す

と言ってもSDKと違ってかなり大変。アップロードは少なくとも

  • files.uploadはもうすぐ廃止されます
  • 代わりに以下の手順でファイルをアップロードする必要があります。
    1. files.getUploadURLExternalでまずアップロード準備。ファイルID, Upload URLというのが手に入る。
    2. Upload URLに対してPOSTで画像をBlob型にしたものをつけて実際にアップロードする
    3. files.completeUploadExternalにて、実際にアップロードしたファイルに対して情報を加えてSlackにポストする。
  • 上記の3回投げることでようやくSDKと同じ挙動を実現することが可能です。

となっています。実際に書き換えたところ、WindowsでもmacOSでも問題なく画像のアップと投稿ができるようになりました。以下ソースコードです。slackmsg関数を書き直します。

  • 初回だけ合計4回HTTPリクエストが必要です。
  • fetch APIをawaitで実行し、レスポンスを取得するといったことの繰り返しになります。
  • ファイルアップロード準備だけGETリクエストで、エンドポイントURLに対してファイルの名前やファイルサイズを引数で繋げます。
  • 準備時にファイルIDとアップロードURLが手に入るので、これに対してPOSTで画像を送信する。
  • 画像はBlobで取得して送る必要性があります。Unit8Arrayやnew Blobを経てバイナリオブジェクトにしています。
  • アップロードが完了したら、最後のリクエストで完了リクエストを持ってSlackに画像添付した状態で投稿します。
  • アップ完了リクエストではSDKと違ってparamはfilesとしてファイルIDとファイル名をつける仕様になっています。

関連リンク

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)