【王道の情報集め】Python×SeleniumでWebスクレイピング

こんにちは、KimYasです。

今回はWeb上の情報を自動収集する作業、いわゆるWebスクレイピングを行ってみました。

採用したのは、PythonでSeleniumを利用する王道パターン。
そのため、色々な方がやり方をWeb上に発信されており、それらを参考に進めました。

きっかけ

ことの発端は休日の朝6時。急に目が覚め、Webスクレイピングをやってみたい衝動に駆られました。
どんな流れでコーディングしようか。実現可能性はどれぐらいか。事前調査でクリアする条件は何か…なんてことを考えているうちに目がパッチリ冴えてしまったので、いそいそと起きだしてPCに向かいました。
そして終わったのは夕方4時。思った以上に時間がかかりすぎましたが、たまには計画性を度外視して、気の向くままプログラミングするのも悪くないですね。

やりたいこと

ログインが必要な会員サイトにアクセスし、所望のページに移動する。
その後、表示されたページをmhtml形式でローカルに保存する。

※注意※
サイトによっては、ページの保存やWebスクレイピングを禁止している場合があります。ご利用の際は会員規約をよく読み、自己責任で実施をお願いします。

要件

  • ページは、mhtml形式で保存する。
  • ページの保存場所は、コード実行時に選択できるようにする。
  • 保存するファイルの名前には、連番と、対象ページ内に存在するサブタイトル名を含めることとする。ただし、サブタイトルに記号「~」や「/」が含まれている場合は、これら記号を削除する。

環境

  • Python 3.9.12
  • Selenium 4.4.0
  • Git Bash 2.36.0.windows.1
  • Google Chrome 104.0.5112.81(Official Build) (64 ビット)
  • ChromeDriver 104.0.5112.79

    ※PythonとGit Bashは既にインストール済みだったので、今回新たにインストールしていません。

事前準備

  • Seleniumインストール
$ pip install selenium
  # 省略
$ pip list | grep selenium
	# selenium 4.4.0
  • ChromeDriverインストール
    以下サイトから、使用しているChromeブラウザのバージョンに近いドライバ「chromedriver.exe」を入手し、作業ディレクトリの直下(後述)に配置しました。
    https://chromedriver.chromium.org/downloads
  • 環境変数(シークレット情報)設定
    ホームディレクトリ直下に、ファイル「.bash_profile」, 「.bashrc」を作成し、環境変数にシークレット情報を格納しました。
# シェル起動時に.bashrcを読み込むよう設定
if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi
# 環境変数にシークレット情報を格納
export MAIL_ADDRESS='foo@bar.com'
export PASSWORD='abcdefghijk'
export KEYWORD='lmopqrstu'
  • ディレクトリ構成
C:.            ←任意の作業ディレクトリ
│  chromedriver.exe    ←上記手順でダウンロードしたやつ
│  main.py         ←処理を記述するファイル
│
└─store           ←所望のmhtmlファイルを保存するフォルダ
    ├─folder1        ←mhtmlファイルをお好みの階層に分けて保存
			folder2 
			...

コーディング

試行錯誤しながらコーディングしました。
冗長で長ったらしくなってしまったので、ここから手を加える際は処理ブロック毎に関数化しようと思います。

from IPython import embed
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
from tkinter import messagebox
from tkinter import filedialog
import os
import time
#ファイル保存パスをダイアログボックスで指定
save_folder_path = filedialog.askdirectory(initialdir = './store')
#目的のサイトに移動
driver = webdriver.Chrome(executable_path='.\chromedriver.exe')
driver.get('https://www.fooooooo.com)
#環境変数を読み込み
xPath_mail = os.environ.get('MAIL_ADDRESS')
xPath_Password = os.environ.get('PASSWORD')
#ログイン処理
driver.find_element(By.XPATH, "/html/body/div/div/foooooooo").send_keys(xPath_mail)
driver.find_element(By.XPATH, "/html/body/div/div/barrrrrrrrr").send_keys(xPath_Password)
login_button = driver.find_element(By.XPATH, "/html/body/div/div/horrrrrrrrrrrrr")
ActionChains(driver)\
  .click(login_button)\
  .perform()

time.sleep(5)

count = 0
#aタグ要素を取得
elements = driver.find_elements(By.TAG_NAME, "a")
for e in elements:
  count += 1
  #任意のキーワードが含まれている場合のみページ遷移
  if "KEY_WORD" in e.get_attribute("href") :
    ActionChains(driver)\
      .click(e)\
      .perform()
    time.sleep(3)
    #サブタイトル取得
    try:
      e_sub_title = driver.find_element(By.XPATH, "//*[@id='__foo']/div/div/foooooo")
      sub_title = e_sub_title.get_attribute('innerHTML').replace('/', '')
    except:
      sub_title = ''
    #ページ保存処理
    res = driver.execute_cdp_cmd('Page.captureSnapshot', {})
    file_path = f'{save_folder_path}/{count}_{sub_title}.mhtml'
    with open(file_path, 'w', newline='') as f:
      f.write(res['data'])
    #ページを戻る
    time.sleep(1)
    driver.back()
    time.sleep(3)
messagebox.showinfo('通知','作業完了!')

反省・改善点

  • seleniumのバージョンが最近新しくなった4.4.0のため、従来のバージョンでは使用可能だったメソッドが廃止されていました。特にfind_element関連の要素取得方法が軒並み変更されており、適応するのに手間取りました。
    Web上に転がっているブログ等の二次情報は、従来のバージョンに対応した方法が大半のため、そのままコピペしてもうまく動作しないことが多いです。一次情報である公式ドキュメントを見ることがいかに大切か、改めて気づきました。
  • 画面遷移の待ち時間設定が、かなり適当になってしまいました。
    今回は一律time.sleep で対応したため、待ち時間が余分だったり短すぎたりと、スマートでありません。
    今後はHTTPリクエスト/レスポンスの状態やステータスコードを見るなど、よりクールな解決方法を探したいです。
  • 文字列のエスケープに悩まされました。
    今回、ページのサブタイトルから取得した文字列を保存ファイルパスに使用しています。しかし、その文字列にスラッシュ/が含まれていたことにより、保存ファイルパスの途中で新たなフォルダ階層が出現する事態に。保存処理が上手くいかない原因がここであることになかなか気づかず、解決に時間を要しました。
  • 環境変数を上手く管理する方法は無いか。
    今回はとりあえず.bachrcに格納したものの、セキュリティ面でもっとセキュアな方法があると思います。
  • デバッグ方法の確立。
    今回はIPythonを使用しましたが、まだまだ使いこなせていないです。
    IPythonについては日本語の二次情報が少なく感じたので、英語の一次情報(公式ドキュメント)や海外のコミュニティから情報を集めたいです。

まとめ

いかがだったでしょうか。
今回は割と勢いで始めた作業だったものの、楽しんで最後までやり遂げられました。
自分の内側から湧き上がってくる「やってみたい!」という思いが、何にも勝るモチベ―ションになりました。

皆さんもよきWebスクレイピングライフを!

KimYas