前回までで、Webページからデータを抽出して棒グラフの画像データを生成するPowerShellスクリプトを作成した。次は、このスクリプトをベースに、より汎用的に使用できるように書き換えていく。

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

CSVデータを活用した汎用的なグラフ作成

前回までに作成したPowerShellスクリプトは、そのままでもそれなりに使えるはずだ。ただし、もっと汎用的に使えるようにするには少し中身を整理して、整える必要がある。

まずはデータを用意する部分と、データをグラフに加工する部分を分ける。データについては、CSVで公開しているWebサイトも多いので、とりあえずCSVデータをグラフの元データとする。PowerShellにはCSVデータをインポート/エクスポートする機能が用意されているので、データ形式としてCSVデータを使うのはその意味でも理にかなっている。

csv2barchart.ps1:最初の一歩

まず、前回まではWebページから取得していたデータを、いったん次のようなCSVデータ「data.csv」としてファイルに保存しておく。

"場所","気温"
"小河内","17.6"
"青梅","20.8"
"練馬","21.4"
"八王子","20.2"
"府中","20.7"
"東京","21.3"
"江戸川臨海","19.6"
"羽田","18.7"
"大島","19.8"
"大島北ノ山","19.2"
"新島","18.8"
"神津島","17.9"
"三宅島","19.0"
"三宅坪田","20.6"
"八重見ヶ原","21.2"
"八丈島","20.5"
"父島","24.2"

そして前回のPowerShellスクリプトを次のように書き換える(csv2barchart.ps1)。

#!/usr/bin/env pwsh

#========================================================================
# 利用するファイルやURLなど
#========================================================================
$CSVFile    = $Args[0]
$OutFile    = $env:HOMEDRIVE + $env:HOMEPATH + '\out.html'
$PNGFile    = $env:HOMEDRIVE + $env:HOMEPATH + '\out.png'

#========================================================================
# 気温データを取得
#========================================================================
$CsvData    = Import-Csv $CSVFile -Header 1,2
# ヘッダを変数に格納
foreach     ($Row in $CsvData) {
    $CsvHeader1 = $Row.1
    $CsvHeader2 = $Row.2
    break
}

#========================================================================
# Google Charts用HTMLの用意
#========================================================================
$GraphTitle = '東京都の現在の気温'

$GoogleChartsHTML1 = @"
<html>
  <head>
    <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
    <script type="text/javascript">
      // Google Visualization APIおよびコアチャートパッケージを読み込み
      google.charts.load('current', {'packages':['corechart']});

      // Google Visualization API読み込み完了後に実行
      google.charts.setOnLoadCallback(drawChart);
      function drawChart() {
        // データテーブルを作成
        var data = new google.visualization.DataTable();
        data.addColumn('string', '');
        data.addColumn('number', '$CsvHeader2');
        data.addRows([
"@

$GoogleChartsHTML2 = @"
        ]);

        // チャートオプションを設定
        var options = {'title':'$GraphTitle',
                       'width':2000,
                       'height':1200};

        // 初期化およびチャートの生成
        var chart = new google.visualization.BarChart(document.getElementById('chart_div'));

        // チャートイメージをbase64エンコードされたPNG画像データとして出力
        google.visualization.events.addListener(chart, 'ready', function () {
          document.getElementById('chart_base64').innerHTML = chart.getImageURI();
        });

        // チャートを描画
        chart.draw(data, options);
      }
    </script>
  </head>
  <body>
    <div id="chart_div"></div>
    <div id="chart_base64"></div>
  </body>
</html>
"@

#========================================================================
# CSVデータをGoogle Chartsで使える形式へ加工
#========================================================================
$GraphData = ""
$FirstLineIs = $true
foreach     ($Row in $CsvData) {
    if ($FirstLineIs) {
            $FirstLineIs = $false
            continue
    }

    $GraphData = $GraphData + "['" + $Row.1 + "'," + $Row.2 + "],"
}
$GraphData = $GraphData.trim(",")

#========================================================================
# Google Charts用のHTMLを出力
#========================================================================
$GoogleChartsHTML1                                  > $OutFile
$GraphData                                          >>$OutFile
$GoogleChartsHTML2                                  >>$OutFile

#========================================================================
# WebDriver起動
#========================================================================
webdriver_edge_start.ps1

#========================================================================
# Microsoft EdgeでGoogle Charts用のHTMLをオープン
#========================================================================
$FileURL = "file:///" + $OutFile.Replace('\','/')

'Microsoft Edgeでグラフを描画します。'
Set-SeUrl -Url $FileURL

#========================================================================
# 描画されたグラフをBase64エンコードされたPNGデータとして取得
#========================================================================
'描画したグラフをBase64エンコードされたPNGデータとして取得します。'
$Element = Get-SeElement -By XPath -Value '//*[@id="chart_base64"]'
$Base64 = $Element.Text -replace 'data:image/png;base64,',''

#========================================================================
# Base64エンコードデータをデコードしてファイルへ保存
#========================================================================
'Base64エンコードされたPNGデータをデコードして保存します。'
$Bytes = [Convert]::FromBase64String($Base64)
[IO.File]::WriteAllBytes($PNGFile, $Bytes)

#========================================================================
# WebDriverを終了
#========================================================================
webdriver_edge_stop.ps1

#========================================================================
# 作業用の一時ファイルを削除
#========================================================================
Remove-Item $OutFile

このスクリプトを「.\csv2barchart.ps1 .\data.csv」のように実行すれば前回と同じような結果を得ることができる。

書き換えた内容

書き換えたのは次の部分だ。まず、csv2barchart.ps1は引数にCSVファイルのパスを取ることにしたので、次のようにパスを変数に格納する。

$CSVFile    = $Args[0]

そしてこのパスからCSVデータを読み込んでオブジェクトとして保持する。PowerShellはこの目的で「Import-Csv」というコマンドレットを提供しているので、それをそのまま使用する。

$CsvData    = Import-Csv $CSVFile -Header 1,2

Import-Csvコマンドレットはオブジェクトを連想配列のようなデータ形式として加工して読み込む。1行目がヘッダとして解釈されるのだが、とりあえず扱いを簡単にしておきたいので「-Header 1,2」のようにパラメータを指定して1行目ではなく人為的にヘッダをつけてしまう。

そして、1行目をヘッダデータとして変数に取り出して保持しておく。次のような処理でそれを実施している。

foreach     ($Row in $CsvData) {
    $CsvHeader1 = $Row.1
    $CsvHeader2 = $Row.2
    break
}

グラフでは凡例が必要なので、その凡例を先程取り出したヘッダ名でまかなう。これまでハードコーディングされていた部分だが、この部分はデータから持ってくるようにするのが自然な処理だ。

        data.addColumn('number', '$CsvHeader2');

そして、CSVデータをGoogle Chartで使用するデータ形式(JSON)へ加工する。1行目はヘッダデータが入っていると仮定して、次のように1行目は抜かして2行目以降をJSONデータへ加工する。

$GraphData = ""
$FirstLineIs = $true
foreach     ($Row in $CsvData) {
    if ($FirstLineIs) {
            $FirstLineIs = $false
            continue
    }

    $GraphData = $GraphData + "['" + $Row.1 + "'," + $Row.2 + "],"
}
$GraphData = $GraphData.trim(",")

それ以外の処理はほぼ同じだ。

なお、前回までの作りではグラフ画像の解像度が低くてあまり綺麗ではなかったので、ここでは次のように解像度を引き上げてある。

        var options = {'title':'$GraphTitle',
                       'width':2000,
                       'height':1200};

これで今回の書き換えは完了だ。簡単な書き換えだしまだまだ荒削りだが、単体として使用できるPowerShellスクリプトにしていくには最初はこんなところだ。

実行して動作を確認

書き換えたPowerShellスクリプトを実行して、早速動作を確認する。

  • csv2barchart.ps1の実行サンプル

    csv2barchart.ps1の実行サンプル

これで次のようなグラフデータが生成される。

  • 生成されたグラフデータ

    生成されたグラフデータ

CSVデータがあればこうした画像データを自動生成できる。使ってみると結構便利だ。

まだまだ粗削り

csv2barchart.ps1はまだまだ粗削りだ。出力される画像ファイルのパスは固定されているし、書き換えるべきグラフのタイトルもハードコーディングされたままだ。画像サイズも固定されている。まずはこのあたりを整理してより汎用的に使えるようにするのが次のステップになる。

PowerShellはそうした細かい部分の整理に関してはparam()で綺麗に整理できるような作りになっているので、あとは必要に応じて整理していくだけでよい。こうした作業が簡単にできるのもPowerShellの良いところだ。

付録

webdriver_edge_start.ps1

#!/usr/bin/env pwsh

#========================================================================
# Microsoft Edge WebDriverを起動する
#========================================================================

#========================================================================
# 動作しているMicrosoft Edge WebDriverをすべて終了
#========================================================================
webdriver_edge_stop.ps1

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

#========================================================================
# Microsoft Edge WebDriverを起動
#========================================================================
'Microsoft Edge WebDriverを起動します。'
$Size = '1200,800'
if  (-Not (Start-SeDriver -Browser Edge -Size $Size 2> $Null 3> $Null))
{
    #================================================================
    # Microsoft EdgeとMicrosoft Edge WebDriverのバージョンが一致して
    # いないためにドライバが動作しなかった可能性がある。
    #================================================================

    #================================================================
    # 不要なドライバプロセスを終了
    #================================================================
    webdriver_edge_stop.ps1

    #================================================================
    # Microsoft Edgeのバージョン番号
    #================================================================
    $EdgeDir='C:\Program Files (x86)\Microsoft\Edge\Application\'
    $EdgeVersion=(  Get-ChildItem -Name $EdgeDir                    | 
                    Where-Object { $_ -NotMatch "[a-zA-Z]+" }       |
                    Select-Object -First 1                          )
                    # ↑ 【Select-Object -First 1の理由】
                    # 更新前のバージョンと更新後のバージョンが同時に
                    # 存在するタイミングがあるので、更新後のバージョン
                    # のみを取得するためにSelect-Objectを実行している。

    #================================================================
    # Microsoft Edge WebDriverダウンロードURLとデプロイ先パス
    #================================================================
    $DriverURL="https://msedgedriver.azureedge.net/$EdgeVersion/edgedriver_win64.zip"

    $SeModVer=(Get-InstalledModule -Name Selenium).Version -replace "-.+$",""
    $DriverDir="$env:HOME\Documents\powershell\Modules\Selenium\$SeModVer\assemblies"
    $DriverDownloadDir="$DriverDir\_download"

    #================================================================
    # WebDriverダウンロード用の一時ディレクトリを作成
    #================================================================
    New-Item        $DriverDownloadDir -ItemType Directory -Force

    #================================================================
    # Microsoft Edgeと同じバージョンのMicrosoft Edge WebDriverを
    # ダウンロード
    #================================================================
    "Microsoft Edge WebDriver version $EdgeVersion をダウンロードします。"
    curl            -get                                            `
                    -o      $DriverDownloadDir\edgedriver_win64.zip `
                    $DriverURL

    #================================================================
    # Microsoft Edge WebDriverをデプロイ
    #================================================================
    "Microsoft Edge WebDriver version $EdgeVersion をインストールします。"
    Expand-Archive  -Path $DriverDownloadDir\edgedriver_win64.zip   `
                    -Destination $DriverDownloadDir                 `
                    -Force

    Copy-Item       -Path $DriverDownloadDir\msedgedriver.exe       `
                    -Destination $DriverDir\msedgedriver.exe        `
                    -Force

    #================================================================
    # WebDriverダウンロード用の一時ディレクトリを削除
    #================================================================
    Remove-Item     $DriverDownloadDir -Recurse -Force

    #================================================================
    # Microsoft Edge WebDriverを起動する
    #================================================================
    if      (-Not (Start-SeDriver -Browser Edge -Size $Size 2> $Null 3> $Null)) 
    {
            #========================================================
            # 原因不明の起動不能
            #========================================================

            #========================================================
            # 不要なドライバプロセスを終了
            #========================================================
            webdriver_edge_stop.ps1

            Exit
    }
}
'Microsoft Edge WebDriverの起動処理完了。'

webdriver_edge_stop.ps1

#!/usr/bin/env pwsh

#========================================================================
# Microsoft Edge WebDriverを終了する
#========================================================================

#========================================================================
# WebDriverプロセスを終了
#========================================================================
if  (Get-Process -Name msedgedriver 2> $Null) 
{
    '動作しているMicrosoft Edge WebDriverを終了します。'
    Get-Process -Name msedgedriver 2> $Null

    # Microsoft Edge WebDriverを終了
    Stop-SeDriver 2> $Null

    # まだ動作しているほかのMicrosoft Edge WebDriverを終了
    if      (Get-Process -Name msedgedriver 2> $Null) 
    {
            Get-Process -Name msedgedriver 2> $Null | Stop-Process
    }

    '動作しているMicrosoft Edge WebDriverの終了処理完了。'
}