クラウドソーシングのデータ入力を自動化する：PythonとSeleniumによる効率化の実践

![S__6570001.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3784222/d9cdf3f5-684c-9c8f-b67f-a208c4271044.jpeg)

# はじめに

最近、クラウドソーシングで様々な案件を探している中、「データ入力」の案件に出会いました。生成AIを使えば、迅速かつ低コストで対応できるのではないかと直感しました。

そこで**Python**を使った自動化プログラムを検討しましたが、Webブラウザの画面遷移で別タブに移動したり、モーダルウィンドウが表示されたりといった動作があり、実現が難しいと感じ、今回は断念しました。

しかし、クラウドソーシングには、**BYMA**や**eBay**などのECサイト向けのデータ入力作業が多く依頼されていることに気づきました。こうしたECサイトを運営されている方々も、生成AIを活用することでコストを削減できる可能性があると考え、この経験を共有するためにこの記事を書きました。

# 適用対象のWebサイトについて

本記事では、自動化を通じて特定のWebサイトで操作を行う手法を解説していますが、具体的なサイト名を公開すると運営者にご迷惑をおかけする恐れがあるため、特定できないように配慮しています。何卒ご了承ください。

サイトの識別を避けるため、今回は**GIMP**を初めて使用し、画面にモザイク処理を施しました。

なお、想定しているサイトの画面遷移は、以下のような流れをたどる形式となっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3784222/ee08e502-9d29-0c53-e636-edd885382a4d.png)

| 画面 | 説明 |
|:-|:-|
| 検索条件入力画面 | 検索条件を設定し、それに基づいたユーザー情報を検索します。 |
| 検索結果画面 | 検索結果が一覧表示されます。名前部分はリンクになっており、詳細情報画面に遷移します。 |
| 確認画面（モーダル） | 詳細情報画面へ移動する前に表示される確認画面です。スクレイピング対策として設置されている可能性があります。 |
| 詳細情報画面 | 検索結果画面で名前のリンクをクリックすることで表示される、ユーザーの詳細情報画面です。 |

このサイトには「登録番号」という項目があり、URLのクエリパラメータを利用して直接詳細情報を取得しようとしましたが、実現が難しかったため、**RPA**のように1件ずつ名前をクリックし、自動で情報を取得する方法を採用しました。



# デバッグモード

通常、画面にログインしてから情報を取得する検索処理を行う方法もありますが、今回は既に開いている画面に「横取り」して自動処理を行う手法（以下、アタッチ方式）の方が効率的だと考え、この方式を採用しました。

このアタッチ方式を使うには、Webブラウザを通常起動するのではなく、「デバッグモード」での起動が必要だということを初めて知りました。そのため、デバッグモードでポートを指定してChromeブラウザを起動する手順をとります。

加えて、この実行には`chromedriver`が必要なため、事前にインストールが必要です。以下のサイトで詳しいインストール手順が確認できます。

https://voicetechno-jp.secure-web.jp/ChromeDriverV115orNewer.html

インストール後は、`chrome`をデバッグモードで起動します。

```dosbatch
.\chrome.exe --remote-debugging-port=9222 --user-data-dir="C:/Users/Desktop/user_data_dir"
```

ここで指定する`--user-data-dir`は、デバッグモード中のコンテンツやログの保存先ディレクトリです。さらに、**9222**ポートを指定することで、他のブラウザからの干渉を防げます。



# プログラム
以下に、今回作成したプログラムを紹介します。

```python:
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import Select
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time

chrome_service = Service(executable_path='C:/Users/Desktop/chromedriver-win64/chromedriver.exe')

# Chromeオプションを設定
chrome_options = Options()
chrome_options.debugger_address = "localhost:9222"  # デバッグモードのポート番号に合わせる

# 既に開いているChromeブラウザに接続
driver = webdriver.Chrome(service=chrome_service, options=chrome_options)

# データ登録の操作を実行
driver.get('https://***********************')  # ここにRPAを実施したいURLを登録します。

# 現在開かれているタブのハンドルを取得
original_window = driver.current_window_handle



# ===================================================================
# ========================== 詳細情報画面 ============================
# ===================================================================
def detail_view_control():
    time.sleep(3)

    all_windows = driver.window_handles
    # 新しいタブに切り替え
    for window in all_windows:
        if window != original_window:
            driver.switch_to.window(window)
            break

    # 情報を取得
    try:
        wait = WebDriverWait(driver, 10)
        elements_gray = wait.until(EC.presence_of_all_elements_located((By.CSS_SELECTOR, "div.col-8.cont-t.bg-lgray.p-2 span")))
        elements_white = wait.until(EC.presence_of_all_elements_located((By.CSS_SELECTOR, "div.col-8.cont-t.bg-white.p-2 span")))
        print("登録番号:", elements_gray[0].text)
        print("氏名（カナ）:", elements_white[0].text)
        print("登録年月日:", elements_gray[1].text)
        print("事務所名称:", elements_white[1].text)
        print("事務所所在地:", elements_gray[2].text)
        print("事務所電話番号:", elements_white[2].text)
        print()
    except Exception as e:
        print("エラーが発生しました:", e)
    driver.close()
    # driver.quit()


# ===================================================================
# ======================== 確認画面（モーダル） =======================
# ===================================================================
def modal_dialog_ok():
    time.sleep(3)

    # モーダル画面が表示されるので、「検索」ボタン押下して画面遷移させる
    button = driver.find_element(By.CLASS_NAME, "btn.btn-yes")
    button.click()


# ===================================================================
# ================ 検索条件入力画面・検索結果画面 ======================
# ===================================================================
def main():
    # 都道府県
    dropdown = Select(driver.find_element(By.ID, "ConditionsPerson_Prefecture"))
    dropdown.select_by_visible_text("北海道")
    # 氏名
    textbox = driver.find_element(By.NAME, 'ConditionsPerson.SeinmKanji')
    textbox.send_keys('佐藤')
    # チェックボックス (2024/10/26 どうしてもうまくいかない。interactiveエラー)
    # wait = WebDriverWait(driver, 1)
    # checkbox = wait.until(EC.presence_of_element_located((By.NAME, "ConditionsPerson.Handling1[1][0].Checked")))
    # if not checkbox.is_selected():
    #     checkbox.check()
    # 「検索」ボタン押下
    button = driver.find_element(By.ID, "search_p")
    button.click()
    time.sleep(3)

    # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    # 件数を100件にして再度検索
    dropdown = Select(driver.find_element(By.NAME, "RowsPerPage"))
    dropdown.select_by_visible_text("100件ずつ表示")
    time.sleep(3)

    # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    # 一覧表を１つ１つLOOPさせる
    try:
        wait = WebDriverWait(driver, 10)
        links = wait.until(EC.presence_of_all_elements_located((By.CSS_SELECTOR, "a.lname")))
        for link in links:
            link.click()
            modal_dialog_ok()
            detail_view_control()
            driver.switch_to.window(original_window)
    except Exception as e:
        print("エラーが発生しました:", e)
        
if __name__ == '__main__':
    main()
```

## プログラム詳細

プログラムの詳細を説明していきます。

### `chromedriver`設定
デバッグモードで起動したブラウザに接続するため、次のように`chromedriver`を指定します。

```python:
chrome_service = Service(executable_path='C:/Users/Desktop/chromedriver-win64/chromedriver.exe')

# `Chrome`オプションを設定
chrome_options = Options()
chrome_options.debugger_address = "localhost:9222"  # デバッグモードのポート番号に合わせる

# 既に開いているChromeブラウザに接続
driver = webdriver.Chrome(service=chrome_service, options=chrome_options)

# データ登録の操作を実行
driver.get('https://***********************')  # ここにRPAを実施したいURLを登録します。
```
まず、`chrome_service`で始まる行では、デバッグモードで接続するために、事前に`chromedriver`サイトからダウンロードした**exe**ファイルのパスを設定しています。

次に、`chrome_options.debugger_address`で、すでにデバッグモードで起動中のChromeブラウザのポート番号に合わせます。

これにより、今回の「アタッチ方式」による操作が可能となるため、`driver`を設定し、ターゲットとするURLを`driver.get()`で指定して準備が完了します。

### `detail_view_control`関数
詳細情報画面で必要なデータを取得し、標準出力します。

```python:
    all_windows = driver.window_handles
    # 新しいタブに切り替え
    for window in all_windows:
        if window != original_window:
            driver.switch_to.window(window)
            break
```
この部分は、複数のタブが開かれている場合に、どのタブを操作するかを決める処理です。

「検索条件入力画面」を基点として、お名前のリンクをクリックするたびに「詳細情報画面」が新しいタブで開かれるため、この新しいタブをターゲットとして操作します（これを「カーソルタブ」と呼びます）。1人分の詳細情報の取得が完了したら、そのタブを閉じ、再び「検索条件入力画面」に戻ってカーソルタブを切り替える操作を行っています。

```python:
elements_gray = wait.until(EC.presence_of_all_elements_located((By.CSS_SELECTOR, "div.col-8.cont-t.bg-lgray.p-2 span")))
```
このコードは、以下のようなHTML構造の中で、**20789**のような情報を取得するために使用しています。

```html:
<div class="mt-3">
    <div class="row">
        <div class="col col-4 cont-d bg-dgray p-2">
            <span class="">登録番号</span>
        </div>
        <div class="col col-8 cont-t bg-lgray p-2">
            <span>20789</span>
        </div>
        ..........
```
CSSセレクターの指定方法は、divから始め、colクラスを除外しながら、それぞれのクラス名をドットで繋いで、最後に半角スペースを追加することで目的の要素にたどり着きました。試行錯誤の末に動作する形になったため、なぜうまくいったかの詳細な理由は不明ですが、この方法で情報を取得できます。

### `modal_dialog_ok` 関数

この関数は、途中で表示される「確認画面」への対策として用意しています。特に複雑な処理はありませんが、タイムラグが生じるとエラーになる可能性があるため、`time.sleep(3)`で3秒の待機時間を設けています。
### `main` 関数

この関数では、リスト選択やテキスト入力を通じて検索条件を設定します。ただし、「検索条件入力画面」の下部にある多くのリストボックスにチェックを入れる際、なぜか「インタラクティブエラー」が発生し、操作ができなかったため、今回はその部分を見送っています。

```python
try:
    wait = WebDriverWait(driver, 10)
    links = wait.until(EC.presence_of_all_elements_located((By.CSS_SELECTOR, "a.lname")))
    for link in links:
        link.click()
        modal_dialog_ok()
        detail_view_control()
        driver.switch_to.window(original_window)
except Exception as e:
    print("エラーが発生しました:", e)
```

ここでは、「検索結果画面」に表示された一覧の各お名前リンクを順にクリックし、「詳細情報画面」で情報を取得する処理を行っています。


# 結果

このようにして、コンソールに出力される結果が得られました。
```dosbatch:
登録番号:*********
氏名（カナ）:*********
登録年月日:*********
事務所名称:*********
事務所所在地:*********
事務所電話番号:*********

登録番号:*********
氏名（カナ）:*********
登録年月日:*********
事務所名称:*********
事務所所在地:*********
事務所電話番号:*********

登録番号:*********
氏名（カナ）:*********
登録年月日:*********
事務所名称:*********
事務所所在地:*********
事務所電話番号:*********
.....
```

また、自動処理の様子をGIFでご覧いただければ幸いです。
![Animation3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3784222/4ce6e4c8-430d-44bb-8662-ce0fdc4af940.gif)

# おわりに

冒頭でもお伝えしたように、今後、クラウドソーシングの「データ入力」案件は、プログラミングによる自動処理に置き換わっていくのではないかと考えています。これまで「システム開発」や「マクロ」の案件に注目していましたが、今後は「データ入力」まで対象を広げていきたいと思っています。