【Python】MacでSeleniumを操作する事始め

# はじめに
Excelでwebブラウザの操作を自動化する最小機能のみ実装したフレームワークの「[TinySeleniumVBA](https://github.com/uezo/TinySeleniumVBA/blob/main/README.ja.md)」があります。
これは今のところActiveXを使用しているため、Windows専用になっています。ActiveXの部分を取り除き代替部分を[VBA-tools](https://github.com/VBA-tools)ライブラリー群を使えば、Mac上のExcelで動作するかもと取り組みました。
VBA-toolsを用いてWindows上では動作するようになったのですが、残念ながらMacではエラーで動作しませんでした。

何が原因なのか分からないため、MacにSeleniumをPythonで動かしてみようと思った次第です。
簡単かと思ったら結構ハマったので記事にしてみました。

【2023/06/06追記】
自記事は少し古いので、この記事を参考に新しいバージョン(Selenium 4.9.1)で動くように対応とのこと。

https://qiita.com/keijir/items/c692b0fe5a8c402aed11

【2023/10/14追記】
Selenium 4.6以降から、ドライバのインストールツールである Selenium Manager が操作対象となるウェブブラウザのバージョンも考慮してドライバをインストールしてくれるようになっています。

https://daeudaeu.com/python-selenium-manager/

# 環境構築
* Mac Book Pro(2.3 GHz 8コアIntel Core i9) Monterey 12.2.1
* Google Chrome Ver.100.0.4896.60（Official Build）
* Python 2.7.18 と Python 3.9.4 共存した状態

## Seleniumのインストール

```
pip3 install selenium
```

今回は、selenium ver 4.1.3 が入りました。

### pipとpip3の違い
Python 2系とPython 3系が共存する環境では、`pip`はPython2、`pip3`はPython3にインストールされます。
どちらか一方であれば、`pip`を使用すればいいです。

## Google Chromeドライバのインストール
右端上部にある「⋮」のメニュー → ヘルプ → Google Chromeについて

>（例）
Chrome は最新の状態です
バージョン: 100.0.4896.60（Official Build） （x86_64）

```
pip3 install chromedriver-binary-auto
```

バージョンを自動判断してインストールされることを期待するも、エラーでインストールできませんでした。

### バージョンを指定する
バージョンを入れないまま実行するとバージョンの一覧が表示されます。

```
pip3 install chromedriver-binary==
ERROR: Could not find a version that satisfies the requirement chromedriver-binary== 
(from versions: 2.29.1, 2.31.1, 2.33.1, 2.34.0, 2.35.0, 2.35.1, 2.36.0, 2.37.0, 2.38.0, 2.39.0,
　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　⋮
 99.0.4844.17.0, 99.0.4844.35.0, 99.0.4844.51.0, 100.0.4896.20.0, 100.0.4896.60.0, 101.0.4951.15.0)
ERROR: No matching distribution found for chromedriver-binary==
```

確認したChromeのバージョンと一致するものを指定します。
ChromeDriverのバージョンはそこまで厳密に合わせる必要はありません。
以下公式の「バージョンの選び方」説明ページで、最後の桁はあっていなくても良いと書かれています。一番番号が近いものを使いましょう。
>Each version of ChromeDriver supports Chrome with matching major, minor, and build version numbers. For example, ChromeDriver 73.0.3683.20 supports all Chrome versions that start with 73.0.3683.
↓翻訳
ChromeDriverの各バージョンは、メジャー、マイナー、ビルドの各バージョン番号が一致するChromeに対応しています。例えば、ChromeDriver 73.0.3683.20は73.0.3683で始まるすべてのChromeのバージョンをサポートします。
https://chromedriver.chromium.org/downloads/version-selection
```

# ChromeDriverのインストール
pip3 install chromedriver-binary==「確認したChromeのバージョン」
# 自分の環境だとこんな感じ
pip3 install chromedriver-binary==100.0.4896.60.0

Collecting chromedriver-binary==100.0.4896.60.0
  Using cached chromedriver-binary-100.0.4896.60.0.tar.gz (4.9 kB)
  Preparing metadata (setup.py) ... done
Using legacy 'setup.py install' for chromedriver-binary, since package 'wheel' is not installed.
Installing collected packages: chromedriver-binary
  Running setup.py install for chromedriver-binary ... done
Successfully installed chromedriver-binary-100.0.4896.60.0
```

【2023/11/24追記】
久しぶりに、chromedriverをインストール。
現在のバージョンは、119.0.6045.159 ですが、No matching となったので、119.0.6045.105.0を指定してインストールするも、エラーが発生

```
% pip install chromedriver-binary==119.0.6045.159
ERROR: No matching distribution found for chromedriver-binary==119.0.6045.159

% pip install chromedriver-binary==119.0.6045.105.0

error: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1123)>
```

下記サイトを参考にしてみた。

https://timonweb.com/python/fixing-certificate_verify_failed-error-when-trying-requests-html-out-on-mac/

```
# こっちでは駄目だった。
% pip install --upgrade certifi

# こっちなら成功　※　Pythonのバージョンを合わせることが必要！
% open /Applications/Python\3.9/Install\Certificates.command  
% pip install chromedriver-binary==119.0.6045.105.0
Successfully installed chromedriver-binary-119.0.6045.105.0
```

インストールの場所は、`pip show chromedriver-binary` のLocationで分かる。
更に配下の`chromedriver_binary/chromedriver`
※ 間の `-` は `_` になっている。


```
% pip show chromedriver-binary             
Name: chromedriver-binary
Version: 119.0.6045.105.0
Summary: Installer for chromedriver.
Home-page: https://github.com/danielkaiser/python-chromedriver-binary
Author: Daniel Kaiser
Author-email: daniel.kaiser94@gmail.com
License: MIT
Location: /Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages
Requires: 
Required-by:
```

### ダウンロードサイトから取得してインストール
それでも`pip`でインストールできない場合、ChromeDriverのダウンロードサイトから近いバージョンのものをダウンロードします。

https://sites.google.com/chromium.org/driver/

その場合、`/usr/local/bin/`フォルダにchromedriverを入れました。

# 実行する
```py
python3 selenium-test.py
```
実行時に「開発元を検証できないため開けません」が表示されたら、下記サイトを参考にしてください。

https://qiita.com/apukasukabian/items/77832dd42e85ab7aa568

## ソースコード
Chromeを起動して、Google検索で"Selenium"を検索する。
[Selenium 4で「DeprecationWarning」が出る場合の対策](https://self-development.info/selenium-4%E3%81%A7%E3%80%8Cdeprecationwarning%E3%80%8D%E3%81%8C%E5%87%BA%E3%82%8B%E5%A0%B4%E5%90%88%E3%81%AE%E5%AF%BE%E7%AD%96/)

今回はTinySeleniumVBAでも使用できるように、ChromeDriverは`/usr/local/bin/`フォルダに配置しました。`pip`コマンドでインストールした場合、executable_pathの指定は不要です。

```py
# ブラウザを開く
chrome_service = fs.Service(executable_path='/usr/local/bin/chromedriver')
　↓
import chromedriver_binary
# ブラウザを開く
chrome_service = fs.Service()
```

```py:selenium-test.py
# coding:utf-8
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from time import sleep
from selenium.webdriver.chrome import service as fs

# ブラウザを開く
chrome_service = fs.Service(executable_path='/usr/local/bin/chromedriver')
driver = webdriver.Chrome(service=chrome_service)
# Googleの検索TOP画面を開く
driver.get("https://www.google.co.jp/")

# 検索語として「selenium」と入力し、Enterキーを押す。
search = driver.find_element_by_name('q') 
search.send_keys("selenium")
search.send_keys(Keys.ENTER)

# 5秒間待機してみる。
sleep(5)
# ブラウザを終了する。
driver.close()
```

## 結果
![スクリーンショット 2022-04-03 23.24.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14053/2501507d-0051-3ba9-bf28-b03695018ccd.png)

# プロセスが残る
driver.close()では、プロセスが残ったままになります。何度も実行すると大量に残ったまま。

```bash
ps aux | grep chromedriver                      
yaju         88651   0.0  0.1 67767416  10180 s000  S    11:24PM   0:00.12 /usr/local/bin/chromedriver --port=56927
yaju         88589   0.0  0.1 67776632  10492 s000  S    11:24PM   0:00.12 /usr/local/bin/chromedriver --port=56893
yaju         88533   0.0  0.1 67767416  10640 s000  S    11:24PM   0:00.12 /usr/local/bin/chromedriver --port=56863
yaju         87247   0.0  0.1 67767416   9316 s000  S    11:14PM   0:00.14 /usr/local/bin/chromedriver --port=56769
yaju         47776   0.0  0.0 67390920   3620   ??  S     5:21PM   0:00.37 /usr/local/bin/chromedriver
yaju         89272   0.0  0.0 34132100    916 s000  S+   11:29PM   0:00.00 grep chromedriver
```

killall chromedriver でプロセスを全て終了します。

```bash
killall chromedriver    
ps aux | grep chromedriver                      
yaju         90473   0.0  0.0 34122884    880 s000  S+   11:38PM   0:00.00 grep chromedriver
```

## quit()を使用する
close()は現在のウィンドウの終了で、quit()は全てのウインドウとドライバーの終了という違いがあるが、ChromeDriverではquit()の実装がService.stop()も呼ぶようになっている。

## with構文を使う
[Webdriverの実装](https://github.com/SeleniumHQ/selenium/blob/9d9e4c7185cd44aafb18e707a34bcfc851fa9a31/py/selenium/webdriver/remote/webdriver.py#L177)を見てみると、__exit__でquit()が呼ばれているのでwith構文を使えば同じことができる。

```py
class WebDriver(BaseWebDriver):
    def __exit__(self, *args):
        self.quit()
```

with構文で書き換えてみる。

```py:selenium-test.py
# coding:utf-8
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from time import sleep
from selenium.webdriver.chrome import service as fs
#import chromedriver_binary

# ブラウザを開く。
chrome_service = fs.Service(executable_path='/usr/local/bin/chromedriver')
with webdriver.Chrome(service=chrome_service) as driver:
   # Googleの検索TOP画面を開く。
   driver.get("https://www.google.co.jp/")

   # 検索語として「selenium」と入力し、Enterキーを押す。
   search = driver.find_element_by_name('q') 
   search.send_keys("selenium")
   search.send_keys(Keys.ENTER)

   # 5秒間待機してみる。
   sleep(5)
```

## ブラウザを開いたまま処理を終了する
close()やquit()は使わず、chromeを開いたままプロセスを終了させます。

```py
from selenium import webdriver
import os
import signal

driver = webdriver.Chrome(service=chrome_service)

try:
    # ～いろいろな処理～
finally:
    os.kill(driver.service.process.pid,signal.SIGTERM)
```

# 最後に
SeleniumをPythonで動かすことができました。
MacでTinySeleniumVBAが動作しないのは、下記サイトで仕組みを知ることで原因が分かりました。

https://qiita.com/YoshikiIto/items/ee42fa8931f2d97ff60c

MacではExcelのバージョンによってShellコマンドを使用して、chromedriverを起動することが出来なくなっていたためでした。調査した結果、AppleScriptTask コマンドを使用しAppleScript スクリプト側でchromedriverが起動させることで解決できました。