3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

IEでしか動かない社内システムをseleniumとIE driverで自動操作する

3
Posted at

本記事はQiita Advent Calender 2025 AEONとわたなべの6日目です!
6日目は、イオンディライト株式会社 AD研究所の渡邊が作成します!

重複登録ですが、ご了承いただけますと幸いです🙇

今回は大企業でよく起こると思われるIEの自動操作がテーマです。

ChatGPT Image 2025年12月5日 23_28_44.png

もう令和なんだから勘弁してくれ〜

本知見はイオンリテール時代の実装となりますので、小売っぽい話題となります!


みなさん、大企業勤めてますか?
大企業のシステムといえば、そう!

IEでしか動かない!

ですよね。

  • IEでしか動かないシステムからデータをとらなければならない
  • 高頻度に使うからRPAにしたい
  • Chromeで動く範囲の自動化であれば良いけどIE依存の機能っぽい

という状況に陥っても困らないように、私が実装する過程でメモしたTipsを紹介したいと思います。まさか、IEで新規の開発をすることはないと思いますので、E2EテストではなくあくまでRPAレベルでの自動化です

それは突然降ってきた|即日便をRPAにしてくれ!

今回の実装に至った背景情報です。
技術的なTipsのみ知りたい方は次の章へお進みください。

私の所属していたネットスーパーでは即日便というサービスを運営しています。お客さまが店舗でお買い物をして、当日に店舗から配送。自宅で受け取れるというサービスです。
ライフスタイルの多様化に伴い 購買手段の多様化 ✖️ 受け取り手段の多様化 が求められる昨今、
店舗購入 ✖️ 自宅受け取り(即日)というチャネルに応えるサービスとなります。

参考|購買チャネル
  • 店舗購入 ✖️ 店舗受け取り = 従来の買い物:最も同期的でリアルタイム
  • web購入 ✖️ 自宅受け取り(後日) = EC:最も非同期
  • web購入 ✖️ 自宅受け取り(即日) = ネットスーパー:購入の効率化かつ即日需要
  • web購入 ✖️ 店舗受け取り = ピックアップ(いわゆるBOPIS):購入の効率化・配送時間に家にいない

などなど、購入手段と受け取り手段を組み合わせで多様化させつつも、破綻しないオペレーションを組むことが非常に重要です。

即日便自体は古くからあるサービスではあるものの、webで動くようなシステム化はされていました。ここにきて注目度が高まってきたため、KPIを設定・進捗を見ながら数値を上げていくことが求められてきました。

古くからあるサービス・・・
webで動くシステム・・・

ie.jpg

当然IE想定でした。EdgeのIEモードが立ち上がります。
社内のシステムもいくつかIE想定なものの、必要な範囲はChromeで動くためChromeのRPAが組まれているものも多々ありましたが、即日便のシステムはChromeで動かした後ダウンロードボタンを動かしてもビクともしませんでした。

さらにいうと、昔のシステムなのでweb上のダッシュボードなんてありません。期間を指定してエクセルがダウンロードされるのみです。複雑怪奇なエクセルを綺麗なpolarsDataframeにすることなど造作もないことでしたが、IEはダウンロードの挙動を制御するのが困難なため、それも悩みの種でした。

上司「どう?できそう?」
わたなべ「う〜ん、わかんないっす。」
上司「そんなに難しいの?」
わたなべ「やったことないので見当がつかないですね。まあ、なんとかしてみます」
上司「よろしく!」

という会話の後、コード1行も書かない人たちの間で仕様の議論が行われ、できる限り応える感じになりました。
まあ、記事になっている時点で実装できたんですけどね。

構成

流石に会社のものなんで、コード全文公開した暁には「おめーの籍ねーから」になるため、Tipsに関係あるところをかいつまんで説明していきます。

pyproject.toml
[project]
name = "same-day-delivery-rpa"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
    "fastexcel>=0.14.0",
    "polars>=1.31.0",
    "pyinstaller>=6.16.0",
    "pywin32>=310",
    "selenium>=4.34.0",
    "webdriver-manager>=4.0.2", # 使ってない(会社回線では使えないことに気づいた)
    "xlsxwriter>=3.2.5",
    "xlwings>=0.33.15",
]

fastexcel, polars, pyinstaller, xlwingsは別記事で出す予定かつ、今回あまり関係何ので説明は省きます。

自動操作はseleniumを選択しました。
browserとwebdriverのバージョンを合わせるのが面倒なため、普段は絶対に使わないのですが、puppeteerplaywrightではできなさそうだったので仕方なく(という意味ではweb自動操作は普段TypeScriptRustで実装してますね。Rustはデスクトップアプリにしたいとき)。

selenium使う上でドキュメントが一番豊富なpythonを選択しました。

自動操作の下準備(IEdriver)

私はブラウザ自動化する際

import logging
import time
import os
from dotenv import load_dotenv
from selenium import webdriver
from selenium.webdriver.ie.service import Service as IEService
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from modules.date_function import DateDict
from modules.utils import download_file_via_requests

class WebHandler:
  def __init__(self, download_directory: str = "./data", default_timeout: int = 15):
    self.driver = None
    self.wait = None
    self.download_directory = os.path.abspath(download_directory)
    self.default_timeout = default_timeout
    os.makedirs(self.download_directory, exist_ok=True)

という感じのclassを定義して、その中に操作単位でmethod書いてます。
そのため、以下関数っぽいものの引数にselfとってますが、そういうことだと解釈していただければと思います。

  def open_browser(self):
    """Edge IEモードで指定されたURLにアクセスします。"""
    logging.info("Edge IEモードを起動します。")
    iedriver_path = os.path.abspath("./template/IEDriverServer.exe")
    ie_service = IEService(executable_path=iedriver_path)
    edge_path = r"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe"
  
    # IEモード用オプション設定
    ie_options = webdriver.IeOptions()
    ie_options.attach_to_edge_chrome = True
    ie_options.edge_executable_path = edge_path
    ie_options.ignore_protected_mode_settings = True
    
    self.driver = webdriver.Ie(service=ie_service, options=ie_options)
    self.driver.get("web-url")
    self.wait = WebDriverWait(self.driver, self.default_timeout)
    logging.info("ログインページにアクセスしました。")

IEモードでブラウザを開くためのコードです(前述しているコードのimportも確認してください)。動かすためには

1. IEdriverServerのインストール

こちらからインストールしてください。リンク切れてたら探しましょう!
私は/template配下に置いていましたが、iedriver_path = os.path.abspath("./template/IEDriverServer.exe")にあるようにパスを指定して渡せば大丈夫です。

PC自体はwin11の64bitでしたが、recommendedのIEDriverServer_Win32_4.14.0で問題ありませんでした。まあ、ダウンロードして試せば良いかと。

2. Edgeのパスの取得

Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\msedge.exe" | Select-Object -ExpandProperty '(Default)'

IEを自動化しなければならない人はきっとwindowsだと思うので、powershellのスクリプトを載せています。

3. 保護モード

web上の他のIEモード自動化記事を見ると保護モードを有効にしろという記述をみますが、そんなものはありませんでした。IE単独ではPCからアクセスできず、保護モードのためのダイアログを開くことができなかったためです。

ie_options.ignore_protected_mode_settings = True

一応これで、保護モードの設定ができていなくても動くという記述をどこかでみたので書き込んでいます。

IEモードでのDOM解析

IEモードで自動操作する下地を整えたら、自動化するためにDOMを確認しなければなりません。ですが、驚くべきことにIEはF12を押しても開発者モードが開かないのです!

ですが、さすがはChatGPT!疑問に答えてくれました

## IEChooser.exe を使用して DevTools を起動する

IE モードでは通常の F12 キーや右クリックメニューから DevTools を開くことができません。代わりに、以下の手順で IEChooser.exe を使用して DevTools を起動します:

Windows の「ファイル名を指定して実行」ダイアログを開きます(Windows キー + R)。

以下のパスを入力し、Enter キーを押します:

`%systemroot%\system32\f12\IEChooser.exe`

表示される「デバッグするターゲットの選択」画面で、先ほど IE モードで開いたページのタイトルを選択します。

選択すると、対象ページの DevTools が起動し、DOM 要素の解析や編集が可能になります。

という感じで、IE用の開発者ツールっぽいものを開いて、DOM要素を確認できました。
スクショがなくて申し訳ないのですが、黒っぽくてオシャレな画面でした。
操作感は通常のdevtoolsでHTML見るときとあまり変わらなかったです!

ダウンロード操作

通常のブラウザ自動操作であれば、ファイルのダウンロード先は設定できます。
ですがIEdriverServerのドキュメントを読んでもそのような記述は見当たらず、IEの場合ダウンロード先はIE側の設定によるとのことでした(読み落としだったらすみません)。

前述の通り、IEは開けないので設定は変更できません。
RPAとして動かすことを考え、PC依存の部分は残したくなかったのでなんとかならないかと考えました。

リクエストを生成する

ダウンロードリンクのURLをみたところ、excelデータのbufferが送られてきているようでした。
よくあるGETリクエストでダウンロードさせているようでしたので、それを再現できればダウンロードできると考えたところ、問題なく成功しました。

  def download_data(self, start_date: str, end_date: str, file_name: str, timeout: int = 60):
    if not self.driver:
      raise Exception("ブラウザが起動していません。")

    logging.info(f"ダウンロードデータ: 開始日={start_date}, 終了日={end_date}")

    elem = self.wait.until(EC.presence_of_element_located((By.ID, "ctlSelectItem003")))
    self.driver.execute_script("arguments[0].scrollIntoView(true);", elem)
    time.sleep(1)
    elem.click()

    date_from_input = self.wait.until(EC.presence_of_element_located((By.NAME, "ctlDateFrom")))
    self.driver.execute_script("arguments[0].value = '';", date_from_input)
    date_from_input.send_keys(start_date)

    date_to_input = self.driver.find_element(By.NAME, "ctlDateTo")
    # date_to_input.clear()
    self.driver.execute_script("arguments[0].value = '';", date_to_input)
    date_to_input.send_keys(end_date)

    excel_btn = self.driver.find_element(By.XPATH, "//input[@value='Excel出力']")
    self.driver.execute_script("arguments[0].scrollIntoView(true);", excel_btn)
    time.sleep(1)
    excel_btn.click()

    logging.info("データ集計処理を待機しています...")

    download_link_element = self.wait.until(EC.presence_of_element_located((By.NAME, "lnkDown")))
    download_url = self.driver.execute_script("return arguments[0].getAttribute('href');", download_link_element)
    print(download_url)
    save_file_path = os.path.join("data", file_name)
    cookies = {cookie['name']: cookie['value'] for cookie in self.driver.get_cookies()}
    download_file_via_requests(download_url, save_file_path, session_cookies=cookies)
    
    time.sleep(4)
    
    back_btn = self.wait.until(EC.presence_of_element_located((By.NAME, "btnDownBack")))
    self.driver.execute_script("arguments[0].scrollIntoView(true);", back_btn)
    time.sleep(1)
    back_btn.click()
  • 通常のダウンロード操作を行い、ダウンロードリンクを出現させます
  • ポイントとなるのは、そのリンクをクリックせずGETリクエストを生成して実行するところです。
modules/utils.py
# utils.py
import requests

def download_file_via_requests(url: str, save_path: str, session_cookies=None, headers=None):
  """
  URLから直接ファイルをHTTP GETで取得して保存する。
  セッション認証が必要ならcookiesやheadersを渡す。
  """
  with requests.get(url, stream=True, cookies=session_cookies, headers=headers) as r:
    r.raise_for_status()
    with open(save_path, 'wb') as f:
      for chunk in r.iter_content(chunk_size=8192):
        if chunk:
          f.write(chunk)
  print(f"ファイルを保存しました: {save_path}")

一応、GPTに言われるがままcookieを再現していますが、download linkをDOM上に配置した後は、curlにurl直でも落とせたので、ここまでのコードは不要かもしれません。
一応、知見として記述します。

諦めずに自動化しましょう!

という感じで、なんとか実装できました!
この後、私のPCにて動いていた自動操作は、ディライトへの出向に伴いpyinstallerにより.exe化。他者のPCに移植したのちも動作しているとのことで一安心です。

イオングループで、一緒に働きませんか?

イオングループでは、エンジニアを積極採用中です。少しでもご興味をもった方は、キャリア登録やカジュアル面談登録などもしていただけると嬉しいです。
皆さまとお話できるのを楽しみにしています!
ATH_engineer_Zenn%E3%83%8F%E3%82%99%E3%83%8A%E3%83%BC.png

AEONのアドベントカレンダーでもあるので宣伝つけておきました。
偉すぎ。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?