Python Selenium with文を使ってwebdriverの閉じ漏れをなくす


# はじめに
本記事のコードは以下の環境で検証しています。
※OSやバージョンが異なる場合、挙動が変わる可能性があります。

OS: Windows10
Python: 3.12.2
selenium: 4.17.2

ブラウザはChromeで記載していますが、Firefoxなど別ブラウザでも同様の対策ができるようです。

# driverの生成の仕方でwebdriverが残存することがある
driverを生成する方法によっては**driverを閉じることが出来ず、プロセスが残ってしまう**ことがあります。
これによりメモリなどを無駄に使ってしまい、リソースを圧迫し他のプログラムに影響を与えてしまうことがあります。
こういったwebdriverの閉じ漏れ、対策方法についてまとめていきます。

# driverが残存するおそれのある書き方
それではいきなり、driverが残存する**おそれ**のある書き方を見てみましょう。
```python:
from selenium import webdriver


driver = webdriver.Chrome()

driver.get("http://selenium.dev")

driver.quit()
```

このコード実は**seleniumの公式サイトに記載された記述方法**です...

https://www.selenium.dev/ja/documentation/


# どういう場合にdriverの閉じ漏れが発生するのか
まずはdriverを閉じる方法を確認します。
driverを閉じる方法としては
```python
driver.quit()
```
このコードが実行されることによりdriverが閉じられます。


そのため
・```driver.quit()```を記載しなかった場合
・```driver.quit()```**を記載していたが、driverの操作中にエラーが発生し実行されなかった場合**
にdriverが残存してしまいます。

# 閉じ漏れの発生するコード例
実務でコードを書くときはエラーを検知できるよう、エラーハンドリングしていると思います。
[driverが残存するおそれのある書き方](#driverが残存するおそれのある書き方)のコードを```try``` ```except``` で囲ってエラーハンドリングしてみましょう。
```driver.get``` を存在しないURLに変更しエラーを起こすようにしています。
これにより
**```driver.quit()```が実行されずに```except```へ行ってしまい、driverが閉じられません。**
```python
from selenium import webdriver

try:
    driver = webdriver.Chrome()
    # 存在しないURLを指定してエラーを起こす
    driver.get("http://not-exists.selenium.dev") # ここでエラーになってexceptに行く
    driver.quit() # ここを通らないため、driverが閉じない
except Exception as e:
    # エラー通知代わりのプリント文
    print(e)
```

**いくらシンプルなコードでも、通信の問題などでエラーが発生してしまう可能性もあるため、こういったdriverの生成、エラーハンドリングは避けるべきです。**


# 閉じ漏れを防ぐdriverの生成方法
driverの生成を以下のように変更することで防ぐことが出来ます。
他にも[finallyでdriverを閉じる](#finallyでdriverを閉じる)という方法もありますが
これが**driver生成のベストプラクティス**、これだけ覚えればいい気がします。 
```python:
from selenium import webdriver

try:
    with webdriver.Chrome() as driver:
        driver.get("http://not-exists.selenium.dev")
except Exception as e:
    print(e)
```
このようにwith文で書くことにより
**エラーが有っても無くてもwithブロックを抜けたときに自動的に```driver.quit()```が呼び出される**ようになります。
なので```driver.quit()```を明示的に書く必要が無くなります。

正確に言うと、withブロックを抜けるとクラスの```__exit__```メソッドが呼び出されます。
そのためseleniumライブラリ selenium/webdriver/remote/webdriver.py内
WebDriverクラスの```__exit__```メソッドの```self.quit()```が実行されています。


# (おまけ)
## finallyでdriverを閉じる
with文での書き方だけ覚えればいい気がしますが
finallyでエラーが有っても無くてもdriver.quit()する方法です。
```python:
from selenium import webdriver

try:
    driver = webdriver.Chrome()
    # 存在しないURLを指定してエラーを起こす
    driver.get("http://not-exists.selenium.dev") # ここでエラーになってexceptに行く
except Exception as e:
    # エラー通知代わりのプリント文
    print(e)
finally: # finallyはエラーが有っても無くても実行される
    driver.quit()
```

## Pythonのコード上で chromedriver.exe の残存を確認
プロセスに"chromedriver.exe"が存在するかはタスクマネージャーを見れば分かりますが
以下のようなコードで、Pythonのコード上で確認することが出来ます。

※psutilというライブラリを使用しているので```pip install psutil```でインストールが必要です。

```python
import psutil

exists_chromedriver = False
for proc in psutil.process_iter():
    if proc.name() == "chromedriver.exe":
        exists_chromedriver = True
        break

if exists_chromedriver:
    print("chromedriver.exe プロセスが存在します。")
else:
    print("chromedriver.exe プロセスが存在しません。")
```

実際に動作させてみると

```python: with文ではない記述
import psutil
from selenium import webdriver

try:
    driver = webdriver.Chrome()
    driver.get("http://not-exists.selenium.dev")
    driver.quit()
except Exception as e:
    print(e)

exists_chromedriver = False
for proc in psutil.process_iter():
    if proc.name() == "chromedriver.exe":
        exists_chromedriver = True
        break

if exists_chromedriver:
    print("chromedriver.exe プロセスが存在します。")
else:
    print("chromedriver.exe プロセスが存在しません。")

# >>> chromedriver.exe プロセスが存在します。
```

with文で書いていない方は、driverが残存し

```python: with文での記述
import psutil
from selenium import webdriver

try:
    with webdriver.Chrome() as driver: # withブロックを抜けるとquit()が呼び出される
        driver.get("http://not-exists.selenium.dev")
except Exception as e:
    print(e)

exists_chromedriver = False
for proc in psutil.process_iter():
    if proc.name() == "chromedriver.exe":
        exists_chromedriver = True
        break

if exists_chromedriver:
    print("chromedriver.exe プロセスが存在します。")
else:
    print("chromedriver.exe プロセスが存在しません。")

# >>> chromedriver.exe プロセスが存在しません。
```

with文で書いた方は、きちんとdriverが閉じられていることが確認できます。