今回は拡張編として、前回までに作成したWebページのスクリーンショットを取得するスクリプトにWebページ全体のスクリーンショットを撮る機能を追加する。ここまでできるようになれば、業務にも十分使えるはずだ。

→連載「PowerShell Core入門 - 基本コマンドの使い方」の過去回はこちらを参照。

前回のおさらい

まずは前回のおさらいからだ。前回まででWebページのスクリーンショットを取得するPowerShellスクリプト「getss.ps1」はほとんど完成した。成果物は次の通りだ。

#!/usr/bin/env pwsh

#========================================================================
# スクリーンショットを取得する
#========================================================================

#========================================================================
# 引数を処理
#   -URI uri        スクリーンショットを取得するリソースのURI
#   -Width width    スクリーンショットの幅
#   -Height height  スクリーンショットの高さ
#   -OutputFilePath path スクリーンショットを保存するファイル
#   -Agent      Webサーバへ送るUser-Agent文字列を指定
#========================================================================
Param(
    [Parameter(Mandatory=$false)][String]$URI = "desktop:",
    [Int]$Width = 1200,
    [Int]$Height = 800,
    [String]$OutputFilePath = "${env:HOME}/ss.png",
    [String]$Agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
)

#========================================================================
# スクリーンショットの取得に利用するアプリケーションほか
#========================================================================
$msedge='C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe'
$snippingtool='SnippingTool.exe'
$curl='C:\Windows\System32\curl.exe'

#========================================================================
# スクリーンショットを保存するファイルのパスを絶対パスへ変換
#========================================================================
if  (-not (Split-Path -IsAbsolute $OutputFilePath))
{
    $OutputFilePath = [System.IO.Path]::GetFullPath($OutputFilePath)
    Write-Warning                           `
        "スクリーンショット保存先として $OutputFilePath を使用。"
}

#========================================================================
# どの方法でスクリーンショットを取得するか判断
#========================================================================
switch  -Wildcard ($URI)
{
    'http*'
    {
        #========================================================
        # Microsoft Edgeを使って取得
        #========================================================
        $method='msedge'
        break
    }

    'desktop:*'
    {
        #========================================================
        # Snipping Toolを使って取得
        #========================================================
        $method='snippingtool'
        break
    }

    default
    {
        $method='none'
    }
}

#========================================================================
# スクリーンショットを撮るリソース種類の特定
#========================================================================
$contenttype='none'
switch  -Wildcard ($URI)
{
    #================================================================
    # コンテンツの種類を特定
    #================================================================
    'http*.html'
    {
        $contenttype='text/html'
        break
    }
    'http*.htm'
    {
        $contenttype='text/html'
        break
    }
    'http*.shtml'
    {
        $contenttype='text/html'
        break
    }
    'http*/'
    {
        $contenttype='text/html'
        break
    }
    'http*.txt'
    {
        $contenttype='text/plain'
        break
    }
    'http*.csv'
    {
        $contenttype='text/csv'
        break
    }
    'http*csv=1'
    {
        $contenttype='text/csv'
        break
    }
    'http*.pdf'
    {
        $contenttype='application/pdf'
        break
    }
    'http*.zip'
    {
        $contenttype='application/zip'
        break
    }
    default
    {
        $contenttype=(  & $curl     --location      `
                        -A $Agent       `
                        -Ss -I          `
                        $URI            |
                Select-String   "^Content-Type:"    )
        break
    }
}

#========================================================================
# スクリーンショットが取れないWebリソースの場合、代わりに
# http://example.com/ のスクリーンショットを取っておく
#========================================================================
switch  -Wildcard ($contenttype)
{
    #================================================================
    # コンテンツの種類を特定
    #================================================================
    '*text/html*'
    {
        break
    }
    '*text/plain*'
    {
        break
    }
    '*/*'
    {
        $URI='http://example.com/'
        break
    }
}

#========================================================================
# スクリーンショットを取得
#========================================================================
switch  ($method)
{
    #================================================================
    # Microsoft Edgeを使って取得
    #================================================================
    'msedge'
    {
        $o1='--headless'
        $o2='--screenshot="' + $OutputFilePath + '"'
        $o3="--window-size=$Width,$Height"
        $o4='--user-agent="$Agent"'
        $o5='--hide-scrollbars'

        Start-Process   -FilePath $msedge           `
                -ArgumentList $o1,$o2,$o3,$o4,$o5,$URI  `
                -Wait
        break
    }

    #================================================================
    # Snipping Toolを使って取得
    #================================================================
    'snippingtool'
    {
        Start-Process   -FilePath $snippingtool 
        # ※ -Waitは指定しても機能しない
        break
    }
}

このPowerShellスクリプトで機能的に足りないのは、Webページ全体のスクリーンショットを撮る機能だ。現在のスクリプトは「-Width 幅 -Height 高さ」でサイズを指定してスクリーンショットを撮ることができるが、ページ全体のスクリーンショットを撮るとなると高さがわからないので指定ができない。

そこで今回は、Webページ全体のスクリーンショットを取得する機能を追加する。これでWebページのスクリーンショットを取得するスクリプトとして必要な機能は、ほぼ揃うことになる。

Webページ全体のスクリーンショットを取得する方法の考え方

Webブラウザのヘッドレスモードでは、コマンドラインオプションからWebページ全体のスクリーンショットを撮る機能は提供していない。これまでに作ってきたgetss.ps1は、コマンドラインからMicrosoft Edgeに対して「ヘッドラインモードで動作せよ」「スクリーンショットを取得せよ」「この幅と高さでスクリーンショットを取得せよ」といった指定を行って動作させている。この部分でWebページを全体を指定するオプションは存在していないので、「高さ」をWebページ全体が入るサイズで指定する必要がある。

Webページ全体のスクリーンショットを取得する方法は実はいくつもあるのだが、これまでに作ってきたgetss.ps1の構造を壊すことなく機能を追加するには、何らかの方法で「Webページ全体の高さ」を取得する機能を追加すればよいことになる。

ここではその方法として「Webドライバ (WebDriver)」を経由してFirefoxを利用することにする。詳しい説明は次回以降に行うが、Webブラウザをより細かく制御しようと思ったらWebドライバを経由する方法になる。PowerShellの利便性を引き上げる強力な機能になるので、今回はWebドライバ利用への導入編だと思ってもらってもいいくらいだ。

Webドライバ経由でWebブラウザを操作する

Webブラウザは、外部から操作するためのインタフェース/プロトコルとしてWebドライバ を提供している。これは主に、開発者がWebページやWebアプリケーションのテストを行うために利用することを想定したものだ。しかしながら、当然それ以外の用途でも使用できる(参考「[WebDriver | MDN]」)。

Webドライバはインタフェースでありプロトコルなので、実際にはWebドライバを利用するためのソフトウエアを利用する。広く使われているのは「Selenium」だ。主要なWebブラウザに対応しており、デファクトスタンダードのポジションにある。

Selenium自体はPowerShellをサポートしていないが、SeleinumはC#をサポートしている。C#をサポートしているということはPowerShellをサポートしていることになる。それを裏付けるようにPowerShellにはSeleniumを使うためのモジュールが存在しており、簡単にSeleniumを利用できるようになっている。今回使うのは、このモジュールの機能だ。

この辺りは1回で説明するのが難しいので、今後、何度かに分けて説明を行う。PowerShellからMicrosoft EdgeやChromeの制御ができると業務の自動化の幅が大幅に広がる。とても重要な機能なのでトピックとして重点的に取り上げていくつもりだ。

必要な機能を追加する

まず、ページ全体を取得するためのパラメータとして「-HeightFull」を追加する。全高さということだ。これが指定してあったらページ全体のスクリーンショットを取得するようにする。Param()を次のように書き換える。

#========================================================================
# 引数を処理
#   -URI uri        スクリーンショットを取得するリソースのURI
#   -Width width    スクリーンショットの幅
#   -Height height  スクリーンショットの高さ
#   -HeightFull     スクリーンショットの高さをページ全体に設定
#   -OutputFilePath path スクリーンショットを保存するファイル
#   -Agent      Webサーバへ送るUser-Agent文字列を指定
#========================================================================
Param(
    [Parameter(Mandatory=$false)][String]$URI = "desktop:",
    [Int]$Width = 1200,
    [Int]$Height = 800,
    [Switch]$HeightFull,
    [String]$OutputFilePath = "${env:HOME}/ss.png",
    [String]$Agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
)

PowerShellからSeleniumを利用する方法で最も簡単なのは、Seleniumモジュールを利用することだ。Seleniumモジュールがインストールされていない場合にはSeleniumモジュールを自動的にインストールするように次の処理を追加する。

#================================================================
# Seleniumモジュールがない場合にはインストールする
#================================================================
if (-Not (Get-InstalledModule -Name Selenium 2> $Null)) {
    "Seleniumモジュールをインストールします。"
    Install-Module -Name Selenium -AllowPrerelease -Force
}

処理の詳細は、今後説明する。今回はSeleniumモジュールのバージョン4系を使いたいので、上記のようにInstall-Moduleコマンドレットに「-AllowPrerelease」を指定してある。

そして、次のコードでWebドライバ経由でFirefoxを起動し、ページ全体の高さを取得し、そして起動したWebドライバを終了している。

#================================================================
# Firefoxをヘッドレスモードで起動してページ長を計測する
#================================================================
Start-SeDriver  -Browser Firefox                `
        -StartURL $URI                  `
        -PrivateBrowsing                `
        -Size "$Width,800"              `
        -State Headless                 `
        -UserAgent $Agent               > $Null
$Height = Invoke-SeJavascript                   `
        -Script 'return document.body.clientHeight'
Stop-SeDriver

WebドライバおよびSeleniumを使うコードの詳細は、今後の連載で説明する。今回は次のポイントを押さえておいてもらえればと思う。

  • Webドライバ経由で起動するMicrosoft Edgeはヘッドレスモードをサポートしていない。getss.ps1はヘッドレスモードでの動作を前提に組まれているので、これでは使えない。Firefoxであればヘッドレスモードで起動できるので、ここではFirefoxを使う。当然、システムにFirefoxをインストールしておく必要がある
  • ページを読み込んだあとで「return document.body.clientHeight」というJavaScriptをWebドライバ経由でFirefoxへ送り込んで実行し、その結果を得ている。このJavaScriptがWebページ全体の高さを返してくれるので、その値を$Heightに保持して使っている
  • Stop-SeDriverで起動したドライバを終了している

Webドライバを介してWebブラウザとやり取りするため、使い方が若干わかりにくいが、いったん慣れてしまえばこれほど便利な機能はない。この機能を活用して楽する方法を習得していこう。

getss.ps1をアップデート

これらの新機能をgetss.ps1へまとめると次のようになる。

#!/usr/bin/env pwsh

#========================================================================
# スクリーンショットを取得する
#========================================================================

#========================================================================
# 引数を処理
#   -URI uri        スクリーンショットを取得するリソースのURI
#   -Width width    スクリーンショットの幅
#   -Height height  スクリーンショットの高さ
#   -HeightFull     スクリーンショットの高さをページ全体に設定
#   -OutputFilePath path スクリーンショットを保存するファイル
#   -Agent      Webサーバへ送るUser-Agent文字列を指定
#========================================================================
Param(
    [Parameter(Mandatory=$false)][String]$URI = "desktop:",
    [Int]$Width = 1200,
    [Int]$Height = 800,
    [Switch]$HeightFull,
    [String]$OutputFilePath = "${env:HOME}/ss.png",
    [String]$Agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
)

#========================================================================
# スクリーンショットの取得に利用するアプリケーションほか
#========================================================================
$msedge='C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe'
$snippingtool='SnippingTool.exe'
$curl='C:\Windows\System32\curl.exe'

#========================================================================
# スクリーンショットを保存するファイルのパスを絶対パスへ変換
#========================================================================
if  (-not (Split-Path -IsAbsolute $OutputFilePath))
{
    $OutputFilePath = [System.IO.Path]::GetFullPath($OutputFilePath)
    Write-Warning                           `
        "スクリーンショット保存先として $OutputFilePath を使用。"
}

#========================================================================
# どの方法でスクリーンショットを取得するか判断
#========================================================================
switch  -Wildcard ($URI)
{
    'http*'
    {
        #========================================================
        # Microsoft Edgeを使って取得
        #========================================================
        $method='msedge'
        break
    }

    'desktop:*'
    {
        #========================================================
        # Snipping Toolを使って取得
        #========================================================
        $method='snippingtool'
        break
    }

    default
    {
        $method='none'
    }
}

#========================================================================
# スクリーンショットを撮るリソース種類の特定
#========================================================================
$contenttype='none'
switch  -Wildcard ($URI)
{
    #================================================================
    # コンテンツの種類を特定
    #================================================================
    'http*.html'
    {
        $contenttype='text/html'
        break
    }
    'http*.htm'
    {
        $contenttype='text/html'
        break
    }
    'http*.shtml'
    {
        $contenttype='text/html'
        break
    }
    'http*/'
    {
        $contenttype='text/html'
        break
    }
    'http*.txt'
    {
        $contenttype='text/plain'
        break
    }
    'http*.csv'
    {
        $contenttype='text/csv'
        break
    }
    'http*csv=1'
    {
        $contenttype='text/csv'
        break
    }
    'http*.pdf'
    {
        $contenttype='application/pdf'
        break
    }
    'http*.zip'
    {
        $contenttype='application/zip'
        break
    }
    default
    {
        $contenttype=(  & $curl     --location      `
                        -A $Agent       `
                        -Ss -I          `
                        $URI            |
                Select-String   "^Content-Type:"    )
        break
    }
}

#========================================================================
# スクリーンショットが取れないWebリソースの場合、代わりに
# http://example.com/ のスクリーンショットを取っておく
#========================================================================
switch  -Wildcard ($contenttype)
{
    #================================================================
    # コンテンツの種類を特定
    #================================================================
    '*text/html*'
    {
        break
    }
    '*text/plain*'
    {
        break
    }
    '*/*'
    {
        $URI='http://example.com/'
        break
    }
}

#========================================================================
# ページ全体を取得する指定になっている場合にページ高さを取得
#========================================================================
if  ($HeightFull)
{
    #================================================================
    # Seleniumモジュールがない場合にはインストールする
    #================================================================
    if (-Not (Get-InstalledModule -Name Selenium 2> $Null)) {
        "Seleniumモジュールをインストールします。"
        Install-Module -Name Selenium -AllowPrerelease -Force
    }

    #================================================================
    # Firefoxをヘッドレスモードで起動してページ長を計測する
    #================================================================
    Start-SeDriver  -Browser Firefox                `
            -StartURL $URI                  `
            -PrivateBrowsing                `
            -Size "$Width,800"              `
            -State Headless                 `
            -UserAgent $Agent               > $Null
    $Height = Invoke-SeJavascript                   `
            -Script 'return document.body.clientHeight'
    Stop-SeDriver

    # ※ Seleniumモジュール経由でEdgeを起動する場合、ヘッドレスモードが
    # 使用できないため代わりにFirefoxを使っている。Edgeがヘッドレス
    # モードで起動できるのであればEdgeへ実装を置き換えることも可能。
}

#========================================================================
# スクリーンショットを取得
#========================================================================
switch  ($method)
{
    #================================================================
    # Microsoft Edgeを使って取得
    #================================================================
    'msedge'
    {
        $o1='--headless'
        $o2='--screenshot="' + $OutputFilePath + '"'
        $o3="--window-size=$Width,$Height"
        $o4='--user-agent="$Agent"'
        $o5='--hide-scrollbars'

        Start-Process   -FilePath $msedge           `
                -ArgumentList $o1,$o2,$o3,$o4,$o5,$URI  `
                -Wait
        break
    }

    #================================================================
    # Snipping Toolを使って取得
    #================================================================
    'snippingtool'
    {
        Start-Process   -FilePath $snippingtool 
        # ※ -Waitは指定しても機能しない
        break
    }
}

長くなってきたが、実際にはコメントが多くコードそのものはそれほど難しくない。しかしそれでもこれだけ実用的な処理をこなすことができる。

動作確認

動作確認を行ってみよう。まず、次のようにgetss.ps1を実行してこれまでのデフォルトの動きを確認する。

getss.ps1 https://news.mynavi.jp/techplus/
  • 取得されたスクリーンショット

    取得されたスクリーンショット

取得されたスクリーンショットは上記のようにデフォルトサイズである1200x800になっている。

今度は次のように「-HeightFull」を指定してgetss.ps1を実行する。

getss.ps1 -HeightFull https://news.mynavi.jp/techplus/
  • 取得されたスクリーンショット

取得されたWebページのスクリーンショットは、上記のように縦に長いものになる。

「getss.ps1 -HeightFull」を定期的に実行すれば、保存しておきたいWebページの情報を画像として手元に置いてくことができる。この機能は結構使える。特にWebページの長さが一定ではないページの情報を取得しておきたい場合に便利だ。