活動日誌の写真

python selenium でCrossCampusへの招待メールを自動送信する

こんにちは!現在Giving Campaignに出場中のTeam Birdman Trialです。

今回は、Geminiを使って、python seleniumでCrossCampusへの招待メールを自動送信するプログラムを書いたので、その経緯と成果をまとめようと思います。

経緯

GivingCampaignの賞の一つに、「一致団結賞」という、登録部員数のうち何人が自団体に投票したかで決まる賞があります。

引用元:チャレンジ賞 | 芝浦工業大学
引用URL:https://shibaura-it.2025.giving-campaign.jp/challenge
引用日:2025/10/19

実は、先日の主催団体様のLive配信で、この「一致団結賞」の自団体部員投票数のカウント方法について説明があり、単に自団体部員が投票したというだけではダメで、その部員が、CrossCampusという、参加団体用のダッシュボードへ参加している必要があるとのことでした!

引用元:Cross Campus
引用URL:https://cross-campus.jp/gc2025/dashboard/
引用日:2025/10/19

広報のメンバーしかダッシュボードへ登録していなかった我々は、慌ててメンバーを追加することにしたのですが、部員に招待メールを送信する際、以下の写真のように、一人ひとり手打ちでメールを送信する必要があることに気づきました、、、弊部は171人いるマンモス部です。到底人間ができる所業ではありません、、、

引用元:Cross Campus
引用URL:https://cross-campus.jp/gc2025/dashboard/
引用日:2025/10/19

そこで、pythonのseleniumという、ウェブサイトを自動で操作できるフレームワークを活用して、Excelに登録されているすべての部員のidのメールアドレスに対して、招待を自動で送る仕組みを作ろうと考えました!

開発の流れ

まず、パソコンにpythonとselenium、そしてchromeを操作するためのドライバをインストールします。以下の記事を参考にさせていただきました。

「seleniumが使いたい人 in Python」
https://qiita.com/chi1180/items/b66247243d6c3cb9cd88

続いて、A列にメンバーのid(メールアドレスの@の前まで)が格納されたExcelファイルを作成し、CSV形式に変換します。ファイル名はgiving.csvにしました。

続いてプログラムを書いていきます。ひと昔前であれば、数日かかっていた所業ですが、AIを活用し、一瞬で書けるようにな時代になりましたね、、、

以下が、Gemini Proに指示したプロンプトです。

pythonのseleniumを用いて、csvファイルに格納されているメールアドレスに対し、
自動で招待メールを送るプログラムを作りたいです。
要件は以下の通りです。



・csvファイル

プログラムが格納されているディレクトリと同階層にある、
giving.csvというファイルです。このcsvファイルのA列には、
招待メールを自動送信したいメンバーのidが書かれています(例:〇〇〇〇)。
このidの末尾に、「@〇〇〇〇」をつけることで、メールアドレスになります。
つまり、csvファイルのA列に格納されているデータは、単なるメンバーのIDなので、
自動送信用のメールアドレス(id+@〇〇〇〇)に変換するための処理を
プログラム内に書く必要があります。



・招待メールの送信方法

GivingCampaignというサイトのダッシュボードにアクセスするための
招待メールを送りたいため、メールは、このGivingCampaignのサイト内
のツールを利用して送ることになります。具体的には、「https:〇〇〇〇←
ダッシュボードのurl」にアクセスして、「メンバーを招待」のボタンを押します。
そうすると、メールアドレスを入力するためのポップアップが表示されます。
このとき、メールアドレスを入力するテキストボックスは、自動で選択状態
になるようになっています。ここに、先ほどのcsvファイルから引っ張て来た
メールアドレスを入力し、「招待を送信」ボタンを押すことで、招待メールを送信できます。
なお、このメールアドレス入力ボックスには、一度に1つのメールアドレスしか
入力することができないため、一度「招待を送信」ボタンを押したら、また再度
「メンバーを招待」ボタンを押して、ポップアップを開く必要があります。これを、
csvファイルに掲載されているidすべてに対して繰り返し行う必要があります。



・「https:〇〇〇〇←ダッシュボードのurl」のファイル構造

招待を送るサイトの構造は、以下のようになっています。

-メールアドレスを入力するポップアップが出てないとき
---------------------
<body>
〇〇〇〇
</body>
↑ダッシュボードのbodyタグ内のHTML構造
---------------------

-メールアドレスを入力するポップアップが出ているとき
---------------------
<body>
〇〇〇〇
</body>
↑ダッシュボードのbodyタグ内のHTML構造
---------------------

各状況における、ダッシュボードのHTMLファイルを貼り付けることで、Geminiが、ダッシュボードを操作するpythonのプログラムを、簡単に書けるようにしました。

結果、かなりいい感じのプログラムを最初から書いてくれましたが、その後、ユーザのログインに関する部分や、エラー発生時の処理、デバッカー、読み込み用の待機時間などについて問題があることが分かったので、さらに追加でプロンプトを書きました。そうして出来上がったのが以下のコードです。

import csv
import time
import traceback # エラー追跡のために追加
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from webdriver_manager.chrome import ChromeDriverManager
from selenium.common.exceptions import TimeoutException, NoSuchElementException

# --- Settings ---
CSV_FILE = 'giving.csv'
LOGIN_URL = 'https:〇〇〇〇←ダッシュボードのurl'
DOMAIN = '@〇〇〇〇'

# --- WebDriver Setup ---
print("Setting up WebDriver...")
try:
    service = Service(ChromeDriverManager().install())
    driver = webdriver.Chrome(service=service)
    wait = WebDriverWait(driver, 15)
    print("WebDriver setup complete.")
except Exception as e:
    print(f"An error occurred during WebDriver setup: {e}")
    exit()

try:
    # 1. Access the site
    driver.get(LOGIN_URL)
    print(f"\nAccessed site: {LOGIN_URL}")

    # 2. Wait for 30 seconds for manual login
    print("\n[Manual Action Required] Please log in and navigate to the member invitation page within 30 seconds.")
    print("Automation will resume in 30 seconds...")
    time.sleep(30)
    print("Manual operation time has ended. Resuming automation.")

    # 3. Read emails from CSV
    email_list = []
    try:
        with open(CSV_FILE, 'r', encoding='utf-8-sig') as f:
            reader = csv.reader(f)
            for row in reader:
                if row and row[0].strip():
                    email = row[0].strip() + DOMAIN
                    email_list.append(email)
        if not email_list:
            print(f"Error: Could not read any valid IDs from {CSV_FILE}.")
            exit()
        print(f"Prepared {len(email_list)} email addresses from {CSV_FILE}.")
    except FileNotFoundError:
        print(f"Error: {CSV_FILE} not found.")
        exit()
    
    # 4. Loop and send invitations
    print("\n--- Starting Invitation Process ---")
    successful_invites = 0
    failed_invites = []

    for i, email in enumerate(email_list):
        print(f"\n({i+1}/{len(email_list)}) Inviting {email}...")
        
        try:
            invite_button = wait.until(
                EC.element_to_be_clickable((By.XPATH, "//button[contains(., 'メンバーを招待')]"))
            )
            invite_button.click()

            email_input = wait.until(
                EC.visibility_of_element_located((By.ID, "email"))
            )
            email_input.send_keys(email)
            
            send_button = wait.until(
                EC.element_to_be_clickable((By.XPATH, "//button[@type='submit' and contains(., '招待を送信')]"))
            )
            send_button.click()
            
            try:
                short_wait = WebDriverWait(driver, 2)
                short_wait.until(
                    EC.visibility_of_element_located((By.XPATH, "//*[contains(text(), '招待メールの送信に失敗しました')]"))
                )
                
                print(" -> ❌ Failed (Site error)")
                failed_invites.append(email)
                
                close_dialog_button = wait.until(
                    EC.element_to_be_clickable((By.XPATH, "//div[@role='dialog']//button[.//svg[contains(@class, 'lucide-x')]]"))
                )
                close_dialog_button.click()

            except TimeoutException:
                print(" -> ✅ Success")
                successful_invites += 1
            
            wait.until(
                EC.element_to_be_clickable((By.XPATH, "//button[contains(., 'メンバーを招待')]"))
            )
            time.sleep(1)

        # --- 【変更点】エラーの詳細なレポートを取得 ---
        except Exception as e:
            print("\n\n" + "="*25)
            print("! AN UNEXPECTED ERROR OCCURRED !")
            print("="*25)
            # エラーの詳細を出力
            traceback.print_exc()
            print("="*25)
            print("Attempting to recover by refreshing the page...")
            
            failed_invites.append(email)
            try:
                driver.refresh()
                time.sleep(5)
            except Exception as refresh_error:
                print(f"Could not refresh the page. Halting script. Error: {refresh_error}")
                # 回復不能な場合はループを抜ける
                break
    # --- 変更点ここまで ---

    # --- Display Results ---
    print("\n--- All Processes Complete ---")
    print(f"Success: {successful_invites}")
    if failed_invites:
        print(f"Failed: {len(failed_invites)}")
        print("Failed email addresses:")
        for email in failed_invites:
            print(f"- {email}")

finally:
    input("\nProcessing complete. Press Enter to close the browser...")
    driver.quit()

実行結果

先ほど作成したpythonのファイルを、giving.csvと同じディレクトリに配置し、実行します!以下が、実際に実行している画面です。カーソルが動いていないのに、招待メールが次々と自動送信されているのが分かると思います。

実行ボタンを押すと、自動的にchromeが立ち上がり、ダッシュボードが開きます。注意点としては、ログイン情報を入力しなければならないということです。プログラム内にログインの処理まで書くこともできたと思うのですが、面倒だったので、ダッシュボードが立ち上がりログイン情報の入力画面が出たら、30秒間待機するという処理になっています。この間に、私が自分のログイン情報を手動で入力し、招待メールを送信するページである「メンバー管理」画面まで遷移すればokです。

するとあら不思議!カーソルもキーボードも操作していないのに、数秒おきに、giving.csvファイル内のidのメアドへ、自動的に招待メールが送られるではありませんか!20分くらいパソコンを放置していたら、すべての部員への招待メール送信が完了していました!

一部、送信エラーが発生してしまいましたが、後で手動でやってみても、そのメールアドレスへは送信できなかったので、seleniumの問題ではない、何らかのバグかと思われます。

まとめ

というわけで今回は、python selenium でCrossCampusへの招待メールを自動送信する方法について書きました。「需要めっちゃ限定的じゃねえか?」と、ここまで書いてから気づきました(笑)。来年は、こんなことしなくても、メールアドレスを複数登録してまとめて招待できる機能が実装されてると嬉しいですね!(とうとうこの記事の価値が無くなりますが、、、)

まあでも、自動化のおかげで、もともと登録メンバー20人くらいしかいなかったのが、既に70名近くのメンバーが登録してくれているので、挑戦して良かったかなと思っています。

最後まで読んでいただきありがとうございました!