2
5

More than 1 year has passed since last update.

[Python] SeleniumでXPathによる動的ID, Class取得

Last updated at Posted at 2022-12-28

作ったプログラムの備忘録

はじめに

  • Seleniumでスクレイピングをする際にIDやClassが動的に変わるサイトに遭遇
  • 直接ID, Class指定ができないので、他の要素からIDを取得する方法を検討した
  • 他のサイトにも有効な手段かどうかは不明だが、メモとして記載

動作テスト環境

OS: Windows 10 Pro 64bit
言語: Python 3.9.13
ライブラリ:Selenium 4.7.2

動的ID, Class取得方法

テスト用WEBサイト(Yahoo Japan)

  • 例としてYahoo Japanのトップにあるニュース部分で、Classが動的に変化して直接指定出来ないと仮定して、変化しない他の要素からClassを取得する方法について紹介
    ※Yahoo JapanでIDやClassが動的に変化するかは確認していない。あくまでサンプルとして使用

  • ここでClass以外で変化しないであろう要素としては、aria-label="主要ニュース"が考えられ、そこからClassを取得し、最終的にニュースのタイトルとURLを取得することを試みる

Yahoo Japan トップページ ニュース部抜粋
<section id="tabpanelTopics1" role="tabpanel" aria-labelledby="tabTopics1" aria-hidden="false">
    <header class="_5swsXRdQMtHOvhS2bwHid _3v9VCsGKsktTB7eaN70w-n">
        <h1>主要 ニュース</h1>
    </header>
    <div class="_1XAfHUWtx6tfYZuWDVjNxZ">
        <div class="_2jjSS8r_I9Zd6O9NFJtDN-" aria-label="主要 ニュース">
            <p class="_2vq5UBfOFpYcyqNRy51gDf">
                <span class="fQMqQTGJTbIMxjQwZA2zk _2VDw54wcDejORZkozpOysW">12/28(水) 9:17更新</span>
            </p>
            <ul>
                <li class="_2j0udhv5jERZtYzddeDwcv">
                    <article class="QLtbNZwO-lssuRUcWewbd" data-ual-view-type="list" data-ual="id_type:shannon_article;content_id:f5af286b551b5ebb7a21cefac4f9be8e9dc5d0d0;element_id:first-news-topics-text">
                        <a class="yMWCYupQNdgppL-NV6sMi _3sAlKGsIBCxTUbNi86oSjt" href="https://news.yahoo.co.jp/pickup/6449014" data-cl-params="_cl_vmodule:tpto;_cl_link:title;_cl_position:1;tpid:6449014;imgsize:s;cmt_num:650" data-ual-gotocontent="true" data-cl_cl_index="1">
                            <div class="_2cXD1uC4eaOih4-zkRgqjU">
                                <div class="TRuzXRRZHRqbqgLUCCco9">
                                    <h1 class="_3cl937Zpn1ce8mDKd5kp7u">
                                        <span class="fQMqQTGJTbIMxjQwZA2zk _1alzSpTqJzvSVUWqpx82d4">埼玉3人殺害 家族が過去6回被害届</span>
                                    </h1>
                                    <span class="_2obRU_TgAxzHaYqOXrZYlv">
                                        <span class="h4yLXygiSc5wwNlJOQEdz _1dr5aVDbNPF63JCS2bJhij _2M3AyDfFaeJl3Uo7lUPMAp" style="width:30px;height:12px">NEW</span>
                                    </span>
                                    <span class="_2dkkFdF6iwwsvj59371Trg">
                                        <span class="_2Uq6Pw5lfFfxr_OD36xHp6 _1dr5aVDbNPF63JCS2bJhij _1I_5hzC8N7rC_DyCqrkXsj" style="width:10px;height:10px"></span>
                                        <span class="fQMqQTGJTbIMxjQwZA2zk _2VDw54wcDejORZkozpOysW">
                                            <span class="ASQOQKzZhjBC7R0_pxI3g">650</span>
                                        </span>
                                    </span>
                                </div>
                            </div>
                        </a>
                    </article>
                </li>
                <li class="_2j0udhv5jERZtYzddeDwcv">(省略)</li>
                <li class="_2j0udhv5jERZtYzddeDwcv">(省略)</li>
                <li class="_2j0udhv5jERZtYzddeDwcv">(省略)</li>
                <li class="_2j0udhv5jERZtYzddeDwcv">(省略)</li>
                <li class="_2j0udhv5jERZtYzddeDwcv">(省略)</li>
                <li class="_2j0udhv5jERZtYzddeDwcv">(省略)</li>
                <li class="_2j0udhv5jERZtYzddeDwcv">(省略)</li>
            </ul>
        </div>
    </div>
(省略)

動的ID, Class取得

  • まず<div class="_2jjSS8r_I9Zd6O9NFJtDN-" aria-label="主要 ニュース">部分のClass取得について検討
動的ID, Class取得の例1
tag_list = driver.find_elements(By.XPATH, '//section/div/div')
for x in tag_list:
    if x.get_attribute('aria-label')=='主要 ニュース':
        dynamic_class = x.get_attribute('class')
dynamic_class 出力結果
['_2jjSS8r_I9Zd6O9NFJtDN-']
  • driver.find_elements で指定するタグ構造を全てリストに格納(今回はサイト内で<section><div><div> となるタグ部分をすべて取得)

  • 次にリストのなかで 'aria-label' 要素の値が '主要ニュース' になっているタグを検索して、そのタグの Class 要素を取得する

  • if x.get_attribute('aria-label')=='主要 ニュース' は状況によって if '主要 ニュース' in x.get_attribute('aria-label') としてもよいが、他の要素に aria-label がない場合や要素に None が存在する場合は、inでエラーが出る

  • また、長くなるが、内包表記を使えば1行で書ける

動的ID, Class取得の例2(内包表記)
dynamic_class = [x.get_attribute('class') for x in driver.find_elements(By.XPATH, '//section/div/div') if x.get_attribute('aria-label')=='主要 ニュース']

URL, ニュースタイトル取得

  • Classが一意に定まったので、そのClassを用いてURLが取得可能となる
News URL取得
news_url = [x.get_attribute('href') for x in driver.find_elements(By.XPATH, f'//*[@class="{dynamic_class[0]}"]/ul/li/article/a')]
news_url 出力結果
['https://news.yahoo.co.jp/pickup/6449032', 'https://news.yahoo.co.jp/pickup/6449017',
'https://news.yahoo.co.jp/pickup/6449027', 'https://news.yahoo.co.jp/pickup/6449024',
'https://news.yahoo.co.jp/pickup/6449035', 'https://news.yahoo.co.jp/pickup/6449034',
'https://news.yahoo.co.jp/pickup/6449038', 'https://news.yahoo.co.jp/pickup/6449041']
  • XPathの書き方は参考文献を確認してください

  • 同様にテキストも取得可能

News テキスト取得
news_txt = [x.text for x in driver.find_elements(By.XPATH, f'//*[@class="{dynamic_class[0]}"]/ul/li/article/a/div/div/h1/span')]
news_txt 出力結果
['日本海側 29日から雪強まる見込み', '中国が水際大幅緩和へ 医療は限界',
'日本語学校増 背景に中国受験競争', '松本元死刑囚の三女語る 宗教2世',
'埼玉3人殺害 数分間の犯行か', '元中日・平田良介 引退を発表',
'深夜アニメで町おこし なぜ15年も', 'GLAYのTAKURO 4人で終わりたい']
  • また取得する要素が1つの場合は、driver.find_elementsではなく、driver.find_element を使って、以下のようにしてもOK
    (ちなみにdriver.find_element は該当する要素が複数存在する場合には最初の要素を返す)
News テキスト取得
news_txt = driver.find_element(By.XPATH, f'//*[@class="{dynamic_class[0]}"]/ul/li/article/a/div/div/h1/span').text
news_txt 出力結果
日本海側 29日から雪強まる見込み

サンプル

一連の流れをまとめた場合のソースコード

sample.py
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.edge.service import Service

driver_path = 'edgedriver_win64/msedgedriver.exe'
edge_service = Service(executable_path=driver_path)
driver = webdriver.Edge(service=edge_service)

driver.get("https://www.yahoo.co.jp/")

dynamic_class = [x.get_attribute('class') for x in driver.find_elements(By.XPATH, '//section/div/div') if x.get_attribute('aria-label')=='主要 ニュース']
news_url = [x.get_attribute('href') for x in driver.find_elements(By.XPATH, f'//*[@class="{dynamic_class[0]}"]/ul/li/article/a')]
news_txt = [x.text for x in driver.find_elements(By.XPATH, f'//*[@class="{dynamic_class[0]}"]/ul/li/article/a/div/div/h1/span')]

driver.close()
driver.quit()

for i in range(len(news_url)):
    print(news_txt[i], news_url[i])
sample.py 出力結果
日本海側 29日から雪強まる見込み https://news.yahoo.co.jp/pickup/6449032
中国が水際大幅緩和へ 医療は限界 https://news.yahoo.co.jp/pickup/6449017
国立競技場 管理に公費年10億円も https://news.yahoo.co.jp/pickup/6449046
茅ヶ崎の刺殺 業者を装い呼び出し https://news.yahoo.co.jp/pickup/6449044
香港便停止の要請 沖縄観光に衝撃 https://news.yahoo.co.jp/pickup/6449040
元中日・平田良介 引退を発表 https://news.yahoo.co.jp/pickup/6449034
深夜アニメで町おこし なぜ15年も https://news.yahoo.co.jp/pickup/6449038
GLAYのTAKURO 4人で終わりたい https://news.yahoo.co.jp/pickup/6449041

まとめ

  • 動的ID, Classを取得するには、driver.find_elements で広くタグを取得して、そのあとにID, Classを特定可能な別の要素で絞り込んでID, Classを取得する

  • その後、取得したID, Classを使って目的のデータを再度取得する

  • この2段階のステップを踏めば、毎回変わる動的なID, Classのサイトでもほぼすべての要素をソースコードの変更なしで取得できるはず。。。

一般化
# ID取得
get_id = [x.get_attribute('id') for x in driver.find_elements(By.XPATH, '//TAG') if '検索語' in x.get_attribute('要素')]
# 取得要素が複数の場合(リスト取得)
get_list = [x.text for x in driver.find_elements(By.XPATH, f'//*[@id="{get_id[0]}"]/')]
# 取得要素が1つの場合
get_elem = driver.find_element(By.XPATH, f'//*[@id="{get_id[0]}"]/').text

参考文献

2
5
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
2
5