[Power Automate Desktop] Chromeで任意のURLをダウンロード





Power Automate Desktopで、HTTPの「[Webからダウンロードします](https://docs.microsoft.com/ja-jp/power-automate/desktop-flows/actions-reference/web#downloadfromweb)」やIEベースのオートメーションブラウザでの「[Web ページのダウンロード リンクをクリックする](https://docs.microsoft.com/ja-jp/power-automate/desktop-flows/actions-reference/webautomation#clickdownloadlink)」ではなく、実行中のChromeで任意のURLをダウンロードする処理を実装＆サブフロー化してみました。サブフローとしての呼び出し方は「使い方 > 応用」項を参照してください。使いたい場面については「背景 > 作成の動機」項を参照してください。

これでなんとかなるのではというレベルの、思い付きと拙いスキルによる実装です。そもそもこういうので済ませられるよとか、こういう実現方法のほうがよいのではとか、そのほかコメント等ありましたらぜひお寄せください。

# 実装

ダウンロード処理を実行するサブフローと、それを呼び出すメインフローからなります。
以下の手順でメインフローとサブフローを作成し、フローを保存してください。

## メインフローの作成

1. Power Automate Desktopの新しいフローを開きます。フローエディターで「Main」フローが表示された状態になります。
2. 以下のコードを「コピー」します。
3. 中央の「ここにはまだアクションがありません」と表示されている部分を右クリックして「貼り付け」します。<br/>![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55718/853103d1-ccda-69fa-3d0f-3166a211b0bd.png)


```robin
SET AutomationTitle TO $'''DownloadByChrome'''
Display.InputDialog Title: AutomationTitle Message: $'''取得対象URL入力''' DefaultValue: $'''https://''' InputType: Display.InputType.SingleLine IsTopMost: False UserInput=> DownloadUrl ButtonPressed=> ButtonPressed
IF ButtonPressed <> $'''OK''' THEN
    EXIT Code: 0
END
Text.ParseText.RegexParseForFirstOccurrence Text: DownloadUrl TextToFind: $'''^[^#?]+''' StartingPosition: 0 IgnoreCase: False Match=> DownloadFilename
Text.ParseText.RegexParseForFirstOccurrence Text: DownloadFilename TextToFind: $'''[^/\\\\]+$''' StartingPosition: 0 IgnoreCase: False Match=> DownloadFilename
Display.InputDialog Title: AutomationTitle Message: $'''保存ファイル名の指定''' DefaultValue: DownloadFilename InputType: Display.InputType.SingleLine IsTopMost: False UserInput=> DownloadFilename ButtonPressed=> ButtonPressed
IF ButtonPressed <> $'''OK''' THEN
    EXIT Code: 0
END
Display.ShowMessageDialog.ShowMessage Title: AutomationTitle Message: $'''Chromeを最前面にしてください''' Icon: Display.Icon.None Buttons: Display.Buttons.OK DefaultButton: Display.DefaultButton.Button1 IsTopMost: True ButtonPressed=> ButtonPressed
IF ButtonPressed <> $'''OK''' THEN
    EXIT Code: 0
END
WebAutomation.LaunchChrome.AttachToForegroundChrome BrowserInstance=> BrowserInstance
/# 以下の変数が設定された状態でサブフロー「DownloadByBrowser」を実行します。
%BrowserInstance% … ブラウザインスタンス
%DownloadUrl% … ダウンロード対象URL
%DownloadFilename% … ダウンロード時の保存ファイル名#/
CALL DownloadByBrowser
```

## サブフロー「DownloadByBrowser」の作成

1. 以下のコードを「コピー」します。
2. 「Main」タブの右の空欄を右クリックして「貼り付け」します。<br/>![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55718/74c9b7f3-341e-9994-39ac-306a697af0df.png)


```
FUNCTION DownloadByBrowser GLOBAL
    /# 既存のブラウザインスタンスで、指定されたURLを保存します。以下の変数を使用します。
%BrowserInstance% … ブラウザインスタンス
%DownloadUrl% … ダウンロード対象URL
%DownloadFilename% … ダウンロード時の保存ファイル名#/
    WebAutomation.GetDetailsOfWebPage BrowserInstance: BrowserInstance DetailsType: WebAutomation.DetailsType.URLAddress Details=> BrowserOrigin
    # Cross Originの場合は新しいタブを使用（download属性が無効化されるため）
    Text.ParseText.RegexParseForFirstOccurrence Text: BrowserOrigin TextToFind: $'''^[^/]+://[^/]+''' StartingPosition: 0 IgnoreCase: True Match=> BrowserOrigin
    Text.ParseText.RegexParseForFirstOccurrence Text: DownloadUrl TextToFind: $'''^[^/]+://[^/]+''' StartingPosition: 0 IgnoreCase: True Match=> DownloadOrigin
    IF BrowserOrigin = DownloadOrigin THEN
        SET DownloadInstance TO BrowserInstance
    ELSE
        WebAutomation.CreateNewTab.CreateNewTab BrowserInstance: BrowserInstance Url: DownloadUrl NewBrowserInstance=> DownloadInstance
        IF DownloadInstance.IsAlive = $'''False''' THEN
            # 「新しいタブを作成」は成功したのにisAliveがFalseの場合、おそらく新しいタブで開いた時点でダウンロードされタブが閉じられている
            EXIT FUNCTION
        END
    END
    # ダウンロードリンクを生成しクリック
    Cryptography.HashText HashAlgorithm: Cryptography.HashAlgorithm.SHA256 Encoding: Cryptography.EncryptionEncoding.UTF8 TextToHash: DownloadUrl HashedText=> ElementId
    SET ElementId TO $'''%AutomationTitle%-%ElementId%'''
    SET JSCode TO $'''function ExecuteScript() {
var d = document;
a = d.createElement(\'a\');
a.setAttribute(\'id\', \'%ElementId%\')
a.setAttribute(\'href\', \'%DownloadUrl%\')
a.setAttribute(\'download\', \'%DownloadFilename%\')
a.innerText = \'Download \"%DownloadFilename%\"\'
d.body.appendChild(a)
a.click()
}'''
    WebAutomation.ExecuteJavascript BrowserInstance: DownloadInstance Javascript: JSCode Result=> Result
    # クリーンアップ
    IF BrowserInstance <> DownloadInstance THEN
        WebAutomation.CloseWebBrowser BrowserInstance: DownloadInstance
        UIAutomation.FocusWindow.FocusByInstanceOrHandle WindowInstance: BrowserInstance
    ELSE
        WebAutomation.ExecuteJavascript BrowserInstance: BrowserInstance Javascript: $'''function ExecuteScript() {
document.getElementById(\'%ElementId%\').remove()
}''' Result=> Result
    END
END FUNCTION
```

# 使い方

このフローをそのまま実行できます。「実行」項をご覧ください。

ただし入力したURLを取得するだけのフローにはあまり有用性がないでしょう。実際には、フロー内でサブフロー `DownloadByBrowser` を呼び出して一連の作業の中にダウンロード処理を組込んだり、複数のURLをまとめてダウンロードするといった使い方が有用だと思います。サブフロー `DownloadByBrowser` の使い方については「応用」項をご覧ください。また上記のメインフローを、呼出しのサンプルとして参考にして下さい。

## 実行

1. Chromeをあらかじめ開いておきます。
1. 作成したフローを実行します。
1. 表示されるダイアログに従って、取得対象URLの入力、保存ファイル名の入力、Chromeを最前面にして「OK」をクリックします。

実行完了すると、対象URLがブラウザに設定されている保存先フォルダに通常は指定ファイル名で保存されます。サーバー側で保存ファイル名が指定されている場合、その名前になります。また指定した名前のファイルが既に存在する場合(2)などの連番がつきます（この辺りは通常のブラウザ動作によります）。

ダウンロード時、新規タブが開かれることがあります。実行時に開かれていたタブと取得対象URLのオリジン（URLの先頭部）が異なるときに、この動作をします。

## 応用

サブフロー `DownloadByBrowser` は任意のフローに組み込んで利用できます。呼出し前に、以下の変数をセットしてください。

* `BrowserInstance` … ブラウザインスタンス
* `DownloadUrl` … ダウンロード対象URL
* `DownloadFilename` … ダウンロード時の保存ファイル名

クロスオリジンアクセスになる（開かれているタブと取得対象URLの先頭部が異なる）場合、ダウンロード時に一時的に新しいタブが開かれます。オリジンが共通の複数のURLを取得するときは、サブフロー呼び出し前に該当URLの一つか、そのオリジンのURLなどに移動しておくと、タブが開いて閉じて繰り返さなくてよいと思います。

# 背景

## 作成の動機

このフロー（というよりサブフロー）は以下の動機で作成されました。

* PowerAutomateの「HTTP」ではなく「ブラウザ自動化」を使ってWeb関連の作業を自動化したいケースがあります。例えばサインインが必要で動線の複雑なサイトでは、手作業でChromeでのサインインと取得対象のあるページへの遷移を済ませた状態から、自動処理を行いたいケースがありました。
* Chromeでのダウンロードを自動化したいが、「[Web ページのダウンロード リンクをクリックする](https://docs.microsoft.com/ja-jp/power-automate/desktop-flows/actions-reference/webautomation#clickdownloadlink)」は[Internet Explolerのモバイルオートメーションブラウザー](https://docs.microsoft.com/ja-jp/power-automate/desktop-flows/actions-reference/webautomation#launchinternetexplorerbase)でしかできないようでした。
* ページ内にリンク表示されていないファイル（onClickイベントで別タブに開かれてしまうPDFとか）や、リンクではなく画像（img）タグで表示されているグラフチャートを取得したいといったケースがありました。

## 実現方法

主に以下のようなアイデアで実現しています。

1. [download属性が指定されたリンク（aタグ）](https://developer.mozilla.org/ja/docs/Web/HTML/Element/a#attr-download)をクリックした場合、Chromeはこの属性で指定されたファイル名で保存しようとします（同一オリジン時）。これでダウンロード操作を代替します。
1. download属性が指定されたリンク（aタグ）は、「[Web ページで JavaScript 関数を実行する](https://docs.microsoft.com/ja-jp/power-automate/desktop-flows/actions-reference/webautomation#executejavascript)」アクションにより、ブラウザで表示されているページ内に追加します。その後、リンククリックによりダウンロードを行い、最後に追加したリンクを削除します。
1. download属性はオリジン（URLのホスト名とポート番号までの部分）が異なる場合は無視されます。これを避けるため、表示されているURLと取得対象URLでオリジンが違うときは、新しいタブで取得先URLを開いてそのタブで上の手順を行います。その後、新しく開いたタブは閉じます。
1. 取得対象URLがダウンロード用に構成されている場合、新しいタブを開いた時点でダウンロードが開始され、タブが閉じられます。この時はフロー側での以降の処理を省略します。

----

本ページ内容は筆者がある時点での知識とスキルにより試行錯誤した結果をまとめた個人的なメモです。内容を保証するものではありません。
また筆者の所属組織等とは一切かかわりがありません。