Selenium で ZIA Admin Portal の操作を自動化してみる（URL Lookup編）

# 1. はじめに

みなさん、こんにちわ。ゼットスケーラーの大谷です。ゼットスケーラーではカスタマーサクセスマネージャとして、お客様が弊社のサービスを活用いただくことでゼットスケーラーのサービスの価値を感じていただけるように様々なご支援を担当しています。

この記事は[Zscaler Advent Calendar 2023](https://qiita.com/advent-calendar/2023/zscaler)向けの記事として、ウェブブラウザの操作を自動化する Selenium で ZIA Admin Portal を操作してみた結果を書いています。

https://qiita.com/advent-calendar/2023/zscaler

今回は2回目の記事なので少し複雑な操作ということで、URL Lookup を実行してみたいと思います。

基本的な ZIA Admin Portal へのログイン・ログアウトについては第１回目の記事をご覧ください。

[Selenium で ZIA Admin Portal の操作を自動化してみる（ログイン編）](https://qiita.com/Shojiro_Otani/items/c22c1725b0a53dd3e492)


# 2. URL Lookup とは？

URL Lookup とは、ある URL がどの URL カテゴリに属するか、どの Cloud App に属するかを検索することで、ゼットスケーラーではいくつかの方法をご提供しています。

この表では方法ごとに、URL に対して何を検索してくれるかをまとめています。

|方法|Site Review|POST /urlLookup|GET /cloudApplications/lite| GUI の URL Lookup|
|---|:-:|:-:|:-:|:-:|
|インターフェース|GUI|API  |API  |GUI|
|URLカテゴリの検索|◯<br>一度に３URLまで|◯|X|◯|
|Cloud App名の検索|X|◯|△<br>URL検索ではなくすべてのCloud Appを出力するのみ|◯|
|Cloud Appカテゴリの検索|X|X|X|<font color="red">◯</font>|
|詳細|[リンク](https://help.zscaler.com/ja/zia/using-site-review-lookup-urls)|[リンク](https://help.zscaler.com/ja/zia/url-categories#/urlLookup-post)|[リンク](https://help.zscaler.com/ja/zia/shadow-it-report-api#/cloudApplications/lite-get)|[リンク](https://help.zscaler.com/ja/zia/lookup-urls-admin-portal)|

ちなみに、ゼットスケーラーではベストプラクティスとして URL が Cloud App に該当する場合にはできるだけ Cloud App Control ポリシーで制御することを推奨しています。それは以下のような理由からです。

1. Cloud App Control ポリシーは URL ごとにではなくクラウドアプリごとに管理ができて、さらにクラウドアプリの種類に応じたきめ細かい制御ができるため
2. Cloud App Control ポリシーは URL フィルタリングよりも先に処理されるため
3. さらに、（デフォルトでは）Cloud App Control ポリシーで許可されたトランザクションは URL フィルタリングによる評価がスキップされるため

そのため Cloud App Control ポリシーを優先するという運用となった場合には、ある URL が Cloud App に該当するかをまず検索する必要があるのですが、ある程度多くの URL を検索することになった場合に面倒なことになります。

それは、多くの URL の検索には API が向いていますが、上の表にあるとおり```POST /urlLookup```は Cloud Appカテゴリの名前を出力してくれない、ということです。Cloud App名は出力してくれるので、それをもとに１０個以上の[Cloud App カテゴリ](https://help.zscaler.com/ja/zia/understanding-cloud-app-categories)ごとに１つずつウインドウを開いてどのカテゴリにその Cloud App が存在するのかを調べる必要があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622818/34f1b9ca-9ede-be71-bd74-9b6b42f11aa3.png)
> ZIA Help - [クラウド アプリのカテゴリーについて](https://help.zscaler.com/ja/zia/understanding-cloud-app-categories)より

一方で GUI の URL Lookup では Cloud App カテゴリも出力してくれますが多くの URL の検索には不向きです。そこで Selenium で自動化できないか？と考えました。


# 3. Selenium を使って URL Lookup を自動化してみる

### 設定

今回は URL が書かれたリスト```./list/url_list.txt```から URL を１つずつ読み込んで検索し、その URL と URL カテゴリ、Cloud App 名、それに Cloud App カテゴリを CSV ファイル```./export/url_lookup_output.csv```に書き出します。

ここでは入力ファイルと出力ファイルなどについて設定します。

```python
#
#
# ユーザ設定
#
#
# ZIA クラウドの名前
cloud_name = '<環境に合わせて設定>'
#
# ログインユーザ名
username = '<環境に合わせて設定>'
#
# URL Lookup したい URL のリストファイル
url_list_file = './list/url_list.txt'    
#
# 出力先CSVファイル
output_file = './export/url_lookup_output.csv'
```


### ブラウザの起動、ZIA Admin Portal へアクセス、ログイン

こちらについては第１回目の記事で解説しています。
[Selenium で ZIA Admin Portal の操作を自動化してみる（ログイン編）](https://qiita.com/Shojiro_Otani/items/c22c1725b0a53dd3e492)

### URL Lookup を開く

まずクエスチョンマークのボタンの先にある URL Lookup まで移動します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622818/8d90b8c0-695d-4471-5a30-3cae0e3d8940.png)
> ZIA Help - [ZIA Admin PortalでのURLの検索](https://help.zscaler.com/ja/zia/lookup-urls-admin-portal)より

```python
# "URL Lookup"のリンクがその先にあるクエスチョンマークのボタンが表示されるまで待機
question_circle_element = WebDriverWait(driver, 60).until(
    EC.presence_of_element_located((By.CSS_SELECTOR, ".fa-question-circle"))
)

# クエスチョンマークのボタンをマウスオーバー
action = ActionChains(driver)
action.move_to_element(question_circle_element).perform()

# "URL Lookup"がクリック可能になるまで待機
url_lookup_element = WebDriverWait(driver, 120).until(
    EC.element_to_be_clickable((By.XPATH, "//li[@data-item='url-lookup']/span[text()='URL Lookup']"))
)

# "URL Lookup"をクリック
url_lookup_element.click()
```

これで URL Lookup が表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622818/b2485e39-1d6b-854d-6502-15801ae06c18.png)


### URL Lookup の実行

ここではリストファイルから URL を１つずつ読み込んで Enter URL に入力して、Lookup URL ボタンをクリック。そして URL Classifications と Cloud Application の値を取得して、それを CSV に書き込み、最後の URL の処理が終わったら Close ボタンをクリックしています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622818/52f00f96-5a6c-a755-8536-0c12a3a72eae.png)
> ZIA Help - [ZIA Admin PortalでのURLの検索](https://help.zscaler.com/ja/zia/lookup-urls-admin-portal)より

```python
# URL Lookup したい URL のリストファイルを読み込んでそれぞれ処理する
with open(url_list_file, 'r') as url_file:
    urls = url_file.readlines()

# 出力先CSVファイルを開く
with open(output_file, 'w', newline='') as csvfile:
    csv_writer = csv.writer(csvfile)
    # ヘッダーを書き込む
    csv_writer.writerow(["URL", "URL Categories", "Cloud Application"])

    for url in urls:

        # 空白文字と改行文字をトリミングする
        url = url.strip()

        # "Enter URL" の入力フォームをクリアする
        driver.find_element(By.CSS_SELECTOR, ".-js-urls .form-input-text").clear()

        # "Enter URL" の入力フォームに URL を入力する
        driver.find_element(By.CSS_SELECTOR, ".-js-urls .form-input-text").send_keys(url)

        # "Lookup URL"ボタンが表示されるまで待つ
        lookup_button = WebDriverWait(driver, 30).until(
            EC.element_to_be_clickable((By.CSS_SELECTOR, ".-js-search"))
        )

        # "Lookup URL" ボタンをクリックする
        lookup_button.click()

        # Lookup 中の待機状態が解除されるのを待つ
        WebDriverWait(driver, 30).until_not(
            EC.presence_of_element_located((By.CSS_SELECTOR, ".waiting-overlay"))
        )

        # URL カテゴリが表示されるまで待つ
        url_classification_element = WebDriverWait(driver, 10).until(
            EC.presence_of_element_located((By.CLASS_NAME, '-js-list'))
        )

        # Cloud App カテゴリと Cloud App の名前が表示されるまで待つ
        cloud_application_element = WebDriverWait(driver, 10).until(
            EC.presence_of_element_located((By.CLASS_NAME, '-js-application'))
        )

        # URL カテゴリの値を取得する
        url_classifications_value = url_classification_element.find_element(By.CLASS_NAME, '-js-text').text

        # Cloud App カテゴリと Cloud App の値を取得する
        cloud_application_value = cloud_application_element.find_element(By.CLASS_NAME, '-js-text').text

        # それぞれ CSV に書き込む
        csv_writer.writerow([url, url_classifications_value, cloud_application_value])

# URL Lookup の Close ボタンをクリックする
driver.find_element(By.CSS_SELECTOR, ".button.big.-js-cancel-button").click(
```


### デモ

では、スクリプトを実行してみましょう。ここでは５０個の有名なURL（ChatGPTに出してもらいました）を検索してみました。GIFファイルのサイズの都合で ZIA Admin Portal へのログイン部分は省略しています。ログイン部分は[第1回目の記事](https://qiita.com/Shojiro_Otani/items/c22c1725b0a53dd3e492)をご覧ください。

![urlLookup.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622818/f2ed32b3-ec9b-5d7f-59d1-a8fda1ac61af.gif)

そしてできあがった CSV ファイルがこちらです。Cloud App に該当する URL は Cloud App カテゴリが記録されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622818/f65d2c08-4d51-4b6a-3c62-365065f74b96.png)


# 5. 今回のスクリプト

今回使用したスクリプトはこちらです。

```python
import time
import json
import csv
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.options import Options
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.common.action_chains import ActionChains
import getpass
from selenium.common.exceptions import TimeoutException
#
#
# ユーザ設定
#
#
# ZIA クラウドの名前
cloud_name = '<環境に合わせて設定>'
#
# ログインユーザ名
username = '<環境に合わせて設定>'
#
# URL Lookup したい URL のリストファイル
url_list_file = './list/url_list.txt'    
#
# 出力先CSVファイル
output_file = './export/url_lookup_output.csv'
#
#
# 処理の開始
#
#
# Prompt for password and receive hidden input / パスワードの入力を促すプロンプトを表示し、入力を非表示で受け取る
password = getpass.getpass("Enter your password: ")

# ウェブブラウザの起動
driver = webdriver.Chrome()

# ZIA Admin Portal へアクセス
driver.get(f"https://admin.{cloud_name}") 

# ログインIDとパワワード、サインインのボタンの要素を取得する
username_form = driver.find_element(By.ID, "login-panel-input-email")
password_form = driver.find_element(By.ID, "login-panel-input-password")
login_button = driver.find_element(By.ID, "login-panel-signin-button")

# 一旦入力フォームを空にする
username_form.clear()
password_form.clear()

# ログインIDとパスワードを入力する
username_form.send_keys(username)
password_form.send_keys(password)

# サインインのボタンをクリックする
login_button.click()

time.sleep(5)

# ポップアップが出た場合は Close する
try:
    popup_element = WebDriverWait(driver, 60).until(
        EC.presence_of_element_located((By.CLASS_NAME, "dialog-mask-splash"))
    )
    
    # 要素が表示されているか確認
    if popup_element.is_displayed():
        # Close ボタンをクリック
        close_button = popup_element.find_element(By.CLASS_NAME, "-js-cancel-button")
        close_button.click()
        
except Exception as e:
    print(f"An error occurred: {e}")

# "URL Lookup"のリンクがその先にあるクエスチョンマークのボタンが表示されるまで待機
question_circle_element = WebDriverWait(driver, 60).until(
    EC.presence_of_element_located((By.CSS_SELECTOR, ".fa-question-circle"))
)

# クエスチョンマークのボタンをマウスオーバー
action = ActionChains(driver)
action.move_to_element(question_circle_element).perform()

# "URL Lookup"がクリック可能になるまで待機
url_lookup_element = WebDriverWait(driver, 120).until(
    EC.element_to_be_clickable((By.XPATH, "//li[@data-item='url-lookup']/span[text()='URL Lookup']"))
)

# "URL Lookup"をクリック
url_lookup_element.click()

# URL Lookup したい URL のリストファイルを読み込んでそれぞれ処理する
with open(url_list_file, 'r') as url_file:
    urls = url_file.readlines()

# 出力先CSVファイルを開く
with open(output_file, 'w', newline='') as csvfile:
    csv_writer = csv.writer(csvfile)
    # ヘッダーを書き込む
    csv_writer.writerow(["URL", "URL Categories", "Cloud Application"])

    for url in urls:

        # 空白文字と改行文字をトリミングする
        url = url.strip()

        # "Enter URL" の入力フォームをクリアする
        driver.find_element(By.CSS_SELECTOR, ".-js-urls .form-input-text").clear()

        # "Enter URL" の入力フォームに URL を入力する
        driver.find_element(By.CSS_SELECTOR, ".-js-urls .form-input-text").send_keys(url)

        # "Lookup URL"ボタンが表示されるまで待つ
        lookup_button = WebDriverWait(driver, 30).until(
            EC.element_to_be_clickable((By.CSS_SELECTOR, ".-js-search"))
        )

        # "Lookup URL" ボタンをクリックする
        lookup_button.click()

        # Lookup 中の待機状態が解除されるのを待つ
        WebDriverWait(driver, 30).until_not(
            EC.presence_of_element_located((By.CSS_SELECTOR, ".waiting-overlay"))
        )

        # URL カテゴリが表示されるまで待つ
        url_classification_element = WebDriverWait(driver, 10).until(
            EC.presence_of_element_located((By.CLASS_NAME, '-js-list'))
        )

        # Cloud App カテゴリと Cloud App の名前が表示されるまで待つ
        cloud_application_element = WebDriverWait(driver, 10).until(
            EC.presence_of_element_located((By.CLASS_NAME, '-js-application'))
        )

        # URL カテゴリの値を取得する
        url_classifications_value = url_classification_element.find_element(By.CLASS_NAME, '-js-text').text

        # Cloud App カテゴリと Cloud App の値を取得する
        cloud_application_value = cloud_application_element.find_element(By.CLASS_NAME, '-js-text').text

        # それぞれ CSV に書き込む
        csv_writer.writerow([url, url_classifications_value, cloud_application_value])

# URL Lookup の Close ボタンをクリックする
driver.find_element(By.CSS_SELECTOR, ".button.big.-js-cancel-button").click()

# Logout ボタンが要素が表示され、クリックできるようになるまで待機
logout = WebDriverWait(driver, 60).until(
    EC.presence_of_element_located((By.CSS_SELECTOR, ".fa-arrow-alt-from-right")),
    EC.element_to_be_clickable((By.CSS_SELECTOR, ".fa-arrow-alt-from-right"))
)

# Logout ボタンをマウスオーバーさせて Tooltip を表示させる
action = ActionChains(driver)
action.move_to_element(logout).perform()

# Logout の Tooltip が表示されているのを確認するため待機
time.sleep(1)

# Logout ボタンをクリックする
logout.click()

# ウェブブラウザをクローズする
driver.quit()
```

# 6. 最後に

次回は・・・検討中です。年末にかけて忙しくなってきましたね。