selenium-powershellを使ってWindows上で環境構築せずにWebスクレイピングする備忘録

#初めに
Windowsで面倒な環境構築をせずにSeleniumでスクレイピングがしたい。
最近はWSL2でdockerコンテナを動かせば簡単にseleniumの環境構築もできると思いますが、会社の環境だとできなかったりするのでもっとお手軽にやりたいと思いました。

というわけで調べたところ[PowerShell用のSeleniumモジュール](https://github.com/adamdriscoll/selenium-powershell)があったので使用してみることに。
大まかな使いかたをまとめました。

私自身それほどSeleniumに関して詳しくないので使用していてわかった事があれば
随時追記していこうと思います。
もっと良いコマンドの使用方法など指摘あればコメント頂けると幸いです。


#環境
#### PowerShellのバージョン
```powershell
$PSVersionTable

Name                           Value                                                                                  
----                           -----                                                                                  
PSVersion                      5.1.19041.1                                                                            
PSEdition                      Desktop 
...
```

#### Seleniumモジュールのバージョン
```powershell
Get-InstalledModule -Name selenium

Version              Name                                Repository           Description                             
-------              ----                                ----------           -----------                             
3.0.1                Selenium                            PSGallery            Web automation using the Selenium Web...
```
現在(2020年10月)時点ではバージョン3.0.1が最新ですがもう少しでバージョン4が出そうです。
バージョン4が出たら随時更新したいと思います。


#### Chromeのバージョン
![Chromeのバージョン.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689062/d670b036-14d1-113b-c425-7bf09b03f152.png)


# 実際に使ってみる
## モジュールのインストール
下記コマンドで最新のモジュールをインストールできます。
インストールさえしてしまえばもうSeleniumでスクレイピングできます。

```powershell
Install-Module -Name Selenium
```


# ブラウザを起動する
## 起動コマンドレットとそのパラメーター
ブラウザを起動するには``Start-SeChrome``など``Start-Se＜ブラウザ名＞``を使用します。
下記は主に使うパラメーターを抜粋しています。

```powershell
Start-SeChrome
   [-Headless]　                   #ブラウザ非表示
   [-Maximized]                    #全画面
   [-DefaultDownloadPath <String>] #ダウンロード時のパス指定
   [-StartURL <String>]            #起動時に開くURL
```

### 基本的な仕様
ブラウザ起動時には``$Driver = Start-SeChrome``として変数``$Driver``へ格納しますが
これは他のコマンドレットのパラメーターでドライバーを指定する際に必須となるので
決まり文句としてどのブラウザを使用する場合でも実施しています。

``$Driver``は別ページへ推移したりブラウザの更新があると勝手に更新されます。（仮に手動で別ページへ推移させたとしても変数は勝手に更新されます。）
ブラウザの更新が完了した時点でのHTMLが自動取得されるので、更新を待つようなループ処理を作成する必要がありません。


## 各ブラウザ毎のコマンドレット
### Chromeを使用する場合
```powershell
$Driver = Start-SeChrome
```

### Edgeを使用する場合
```powershell
$Driver = Start-SeEdge
```

### Firefoxを使用する場合
```powershell
$Driver = Start-SeFirefox
```

### IEを使用する場合
```powershell
$Driver = Start-SeInternetExplorer
```

## ここではまった事
いつも通り``$Driver = Start-SeChrome``を実行するとエラーになりました。
Chromeをアップデートした後に発生したのでバージョンを確認して、対応するChromeDriverをダウンロードしなおしたら正常に動作しました。
[ダウンロードはこちら](https://chromedriver.chromium.org/downloads)

どうやらChromeのバージョンとChromeDriverのバージョンはそろえる必要があるようです。
私の環境では下記にのドライバーを更新しました。
C:\Users\ユーザ名\Documents\WindowsPowerShell\Modules\Selenium\3.0.1\assemblies\chromedriver.exe


## 指定のURLへ移動
```powershell
Enter-SeUrl -Url '<URL>' -Driver $Driver
```
指定したURLへ推移し、ブラウザの更新が完了した時点の情報が``$Driver``に格納されます。


# エレメントの取得
エレメントの取得には``Find-SeElement``コマンドレットを使用します。
このコマンドレットは条件に合致したエレメントをオブジェクトの配列で返します。
（もしエレメントが見つからない場合は10秒でタイムアウトし、何も出力せず終了します。）


例えばdivをすべて取得する場合は``Find-SeElement -Driver $Driver -TagName 'div'``とすればすべてのdivタグが配列として返されます。
配列で返されるので``(Find-SeElement -Driver $Driver -TagName 'input')[3]``のようにインデックスでエレメントを指定もできます。

## エレメントを取得した際に返されるオブジェクトのプロパティについて。
```powershell
Find-SeElement -Driver $Driver -TagName div | ?{$_.Text -ceq 'ログイン'}

WrappedDriver                        : OpenQA.Selenium.Chrome.ChromeDriver
TagName                              : div
Text                                 : ログイン
Enabled                              : True
Selected                             : False
Location                             : {X=0,Y=20}
Size                                 : {Width=1261, Height=0}
Displayed                            : True
LocationOnScreenOnceScrolledIntoView : {X=0,Y=20}
Coordinates                          : OpenQA.Selenium.Remote.RemoteCoordinates
```


これらプロパティはPowerShellのその他のオブジェクトと同様にパイプラインで渡すことで、フィルタリングできます。(上記ではTextプロパティを``Where-Object``でフィルタリングしています。)

下記のようにさらに複雑な条件でフィルタリングできます。（ただし低速）

```powershell
Find-SeElement -Driver $Driver -TagName div | ?{
    $_.Displayed -eq $TRUE -and
    $_.Text -ceq 'hogehoge' -and
    $_.Location.X -eq 0
}
```


## 全てのエレメントを取得
```powershell
Find-SeElement -Driver $Driver -TagName *
```
これは確かにすべてのエレメントを取得できますが、その後パイプラインで渡して``Where-Object``によるフィルタリングをする必要があります。それは非常に低速なのでお勧めしません。

下記に記載している各種パラメーターを使用し、ある程度フィルタリングした後に``Where-Object``によるフィルタリングをするのが無難と思われます。

## XPathで取得
idやclassあるなしにかかわらずドンピシャでエレメントを指定できます。
idがあってもなくても正確に取得できますのでこれでほとんど事足りると思います。

```powershell
Find-SeElement -Driver $Driver -XPath '<XPath>'
```


## IDで取得
ドンピシャで指定できるのでidがある場合はこれを使用すればよいと思います。

```powershell
Find-SeElement -Driver $Driver -Id '<ID>'
```


## TagNameで取得
これで大雑把にフィルタリングしてから詳細な条件でフィルタリングしたい場合などによく使用します。

```powershell
Find-SeElement -Driver $Driver -TagName '<Tagname>'
```
例えばliタグでテキストが「hogehoge」であるエレメントを取得したい場合は下記のようにします。

```powershell
Find-SeElement -Driver $Driver -TagName 'li' | ?{$_.Text -ceq 'hogehoge'}
```


## ClassNameで取得
```powershell
Find-SeElement -Driver $Driver -ClassName 'hogehoge'
```


## 特定のテキストを含むエレメントを全て取得（低速なので非推奨）
```powershell
Find-SeElement -Driver $Driver -TagName * | ?{$_.Text -ceq 'hogehoge'}
```

## 特定のテキストを含むdivを取得
```powershell
Find-SeElement -Driver $Driver -TagName 'div' | ?{$_.Text -ceq 'hogehoge'}
```

## 特定の属性値を持つdivを取得
下記はclass属性の値が「hogehoge」であるエレメントを取得しています。

```powershell
Find-SeElement -Driver $Driver -TagName div | ?{$_.GetAttribute('class') -ceq 'hogehoge'}
```
正規表現を使用したい場合は比較演算子を``-match``とするだけです。``-cmatch``としているのは大文字小文字を区別させるためです。

```powershell
Find-SeElement -Driver $Driver -TagName div | ?{$_.GetAttribute('class') -cmatch '^hogehoge$'}
```

## 特定のエレメント配下のエレメントを取得
下記は配下の更に配下のように再帰的にすべてのエレメントを取得しているわけではありません。
あくまでもid="hogehoge"直下のエレメントのみ取得しています。
XPathでエレメントを指定してその配下を``/*``としているだけです。もっといい方法がありそう。。

```powershell
Find-SeElement -Driver $Driver -XPath '//*[@id="hogehoge"]/*'
```


# 取得したエレメントに対する操作
下記例にて記載されている``$Element``には
``$Element = Find-SeElement -Driver $Driver -Id '<ID>'``などで一つのエレメントが取得されているものとします。

## 取得したエレメントの属性値を取得する
下記はclass属性の値を返します。

```powershell
$Element.GetAttribute('class')
```

## キーボード入力
### 通常文字列
```powershell
Send-SeKeys -Element $Element -Keys 'hogehoge'
```

### 特殊キー
下記はEnterキーの場合

```powershell
Send-SeKeys -Element $Element -Keys '{{ENTER}}'
Send-SeKeys -Element $Element -Keys ([OpenQA.Selenium.Keys]::Enter)
```

## クリックする
下記２つはどちらも通常のクリック。
同じ動作をするはず。。

```powershell
$Element.Click()
$Element | Invoke-SeClick -Driver $Driver 
```

### JavaScriptでクリック

```powershell
$Element | Invoke-SeClick -JavaScriptClick -Driver $Driver 
```

## スクリーンショットを保存する
```powershell
$Screenshot = Invoke-SeScreenshot -Target $Driver
Save-SeScreenshot -Screenshot $Screenshot -Path ./test.png
```
``-Path``パラメーターで任意のフォルダへ保存できます。


# 終了して閉じる
```powershell
Stop-SeDriver -Driver $Driver
```


#参考
[GitHub : selenium-powershell](https://github.com/adamdriscoll/selenium-powershell)