自動化厨のプログラミングメモブログ │ CODE:LIFE

Python/VBA/GAS/JavaScript/Raspberry Piなどで色んなことを自動化

Webスクレイピングの必須知識「XPath」のチートシート Webスクレイピング超入門③

Webスクレイピング超入門① で紹介した IMPORTXML 関数でXPathを開発者ツールによって自動取得する方法がありました。

codelife.cafe

ただ、この方法にはいくつかの欠点があります。 HTML構造の変更に弱く n 番目の tr などの指定では上に一つ tr が追加されたら全て1つズレる、XPathの表記が長すぎてあとから人間が理解しづらいなど。 これらの対策として、できれば「XPathを理解して自分で指定する」のがベストかと思います。(ChatGPTに相談するというのもアリかもしれません)

ということで、XPathの使い方チートシートを置いておくので参考にしつつ開発者ツールの検索機能でいい感じに指定できてるかチェックしてみましょう。

構文 説明 結果
/ ルートノードからの絶対パス /html/body ルートノードからhtml、その子のbodyを選択
// 現在の位置から全ての子孫ノードを検索 //div ドキュメント内の全てのdivノードを選択
. 現在のノードを指す ./title 現在のノードの子のtitleノードを選択
.. 親ノードを指す ../div 現在のノードの親のdivノードを選択
@attribute 属性値を取得 //a/@href 全てのaタグのhref属性を取得
* 任意のタグを指定 //div/* 全てのdivの直下の子ノードを選択
[条件] 条件に一致するノードを選択 //li[@class='active'] class="active"liタグを選択
[n] n番目のノードを選択 //ul/li[1] 最初のliノードを選択
last() 最後のノードを選択 //ul/li[last()] 最後のliノードを選択
contains() 部分一致 //a[contains(@href, 'example')] href属性にexampleを含むaタグを選択
text() ノードのテキストを取得 //p/text() 全てのpタグのテキストを取得
starts-with() 文字列が特定の値で始まるノードを選択 //a[starts-with(@href, 'https')] href属性がhttpsで始まるaタグを選択
not() 条件が一致しないノードを選択 //div[not(@id)] id属性を持たないdivタグを選択
| 異なる複数のノードを同時に選択 //h1 | //h2 h1タグとh2タグを選択
@* 全ての属性を選択 //img/@* 全てのimgタグの属性を選択
ancestor:: 特定の祖先ノードを選択 //li/ancestor::ul liタグの祖先にあるulタグを選択
descendant:: 特定の子孫ノードを選択 //div/descendant::p divタグの子孫である全てのpタグを選択
following-sibling:: 特定の後続の兄弟ノードを選択 //h2/following-sibling::p h2タグの後に続く全ての兄弟pタグを選択
preceding-sibling:: 特定の前の兄弟ノードを選択 //h2/preceding-sibling::p h2タグの前にある全ての兄弟pタグを選択

基本構文

XPathはXMLやHTML文書をツリー構造として扱い、特定の要素や属性を選択するための言語。 以下は基本的な構文です。

  • /:絶対パスを指定(ルートノードから始まる)
  • //:相対パスを指定(文書内のどこからでも検索)
  • .:現在のノード
  • ..:親ノード
  • @:属性を指定
  • *:任意の要素や属性を指定
  • text():テキストノードを取得

ノード とは1つ1つのタグ(要素)やテキスト(タグの中に書かれている文字)などのこと。

よく使う指定方法

1. 特定の要素を選択

  • :
<html>
  <body>
    <h1>タイトル</h1>
    <p>段落1</p>
    <p>段落2</p>
  </body>
</html>
  • h1要素を取得: /html/body/h1
    • 取得される要素: <h1>タイトル</h1>
  • すべてのp要素を取得: //p
    • 取得される要素: <p>段落1</p> <p>段落2</p>
  • 最初のp要素を取得: (//p)[1]
    • 取得される要素: <p>段落1</p>

2. 属性を指定

  • 構文: 要素[@属性='文字列']
  • :
<div class="content">内容</div>
<a href="https://example.com">リンク</a>
  • class属性が"content"のdiv要素を取得: //div[@class='content']
    • 取得される要素: <div class="content">内容</div>
  • href属性を持つすべてのa要素を取得: //a[@href]
    • 取得される要素: <a href="https://example.com">リンク</a>

3. テキストを取得

  • 構文: 要素/text()
  • :
<p>この文章を取得します。</p>
  • テキストノードを取得: //p/text()
    • 取得される要素: この文章を取得します。

※スプレッドシート関数 IMPORTXML では自動的にテキストを抽出するので指定不要

4. 条件を指定

  • 構文: 要素[条件]
  • :
<ul>
  <li>アイテム1</li>
  <li>アイテム2</li>
  <li>アイテム3</li>
</ul>
  • 2番目のli要素を取得: //li[position()=2]
    • 取得される要素: <li>アイテム2</li>
  • "アイテム2"を含むli要素を取得: //li[contains(text(), 'アイテム2')]
    • 取得される要素: <li>アイテム2</li>

応用的な指定方法

1. 部分一致(contains関数)

  • 構文: 要素[contains(@属性, '文字列')]
  • :
<div class="product-item">商品A</div>
<div class="product-item">商品B</div>
  • class属性に"product"を含む要素を取得: //div[contains(@class, 'product')]
    • 取得される要素: <div class="product-item">商品A</div> <div class="product-item">商品B</div>

2. 複数条件の指定

  • 構文: 要素[条件1 and 条件2]
  • :
<div class="item" data-id="123">商品A</div>
<div class="item" data-id="456">商品B</div>
  • classが"item"かつdata-idが"123"の要素を取得: //div[@class='item' and @data-id='123']
    • 取得される要素: <div class="item" data-id="123">商品A</div>

3. 子孫ノードの指定

  • 構文: 親要素//子孫要素
  • :
<div class="container">
  <span>テキスト1</span>
  <span>テキスト2</span>
</div>
  • container内のすべてのspan要素を取得: //div[@class='container']//span
    • 取得される要素: <span>テキスト1</span> <span>テキスト2</span>

4. 特定の範囲を指定

  • :
<table>
  <tr>
    <td>1行目1列目</td>
    <td>1行目2列目</td>
  </tr>
  <tr>
    <td>2行目1列目</td>
    <td>2行目2列目</td>
  </tr>
</table>
  • 2行目の2列目を取得: //table//tr[2]/td[2]
    • 取得される要素: <td>2行目2列目</td>

親子関係を指定するXPath

XPathでは、親子関係をスラッシュ(/)やダブルスラッシュ(//)を使って表現。

直接の親子関係

  • 構文: 親要素/子要素
  • :
<div>
  <p>テキスト1</p>
  <p>テキスト2</p>
</div>
  • //div/p<div> の直下にあるすべての <p> 要素を取得。

子孫関係(すべての子要素を含む)

  • 構文: 親要素//子孫要素
  • :
<div>
  <section>
    <p>テキスト1</p>
  </section>
</div>
- `//div//p`→ `<div>` のすべての子孫要素の中から `<p>` 要素を取得。

隣接する要素を指定するXPath

XPathでは、隣接する要素を指定するために「兄弟ノード」を扱う軸(sibling axes)を使用します。

直後の兄弟要素(following-sibling)

  • 構文: 要素/following-sibling::要素
  • :
<ul>
  <li>アイテム1</li>
  <li>アイテム2</li>
  <li>アイテム3</li>
</ul>
  • //li[1]/following-sibling::li[1]→ 最初の <li> 要素の直後にある <li> 要素(「アイテム2」)を取得します。

すべての後続の兄弟要素

  • 構文: 要素/following-sibling::*
  • :
    • //li[1]/following-sibling::*→ 最初の <li> 要素以降のすべての兄弟要素を取得します。

直前の兄弟要素(preceding-sibling)

  • 構文: 要素/preceding-sibling::要素
  • :
    • //li[2]/preceding-sibling::li[1]→ 2番目の <li> 要素の直前にある <li> 要素(「アイテム1」)を取得します。

親要素を指定するXPath

XPathでは、親要素を指定するために parent 軸を使用します。

親要素の取得

  • 構文: 要素/parent::要素
  • :
<div>
  <p>テキスト</p>
</div>
  • //p/parent::div<p> 要素の親である <div> 要素を取得。

複数条件を組み合わせた指定

XPathでは、親子関係や隣接要素を条件付きで絞り込むことも可能。

例: 特定の条件を持つ兄弟要素

  • 構文: 要素/following-sibling::要素[条件]
  • :
<ul>
  <li class="active">アイテム1</li>
  <li>アイテム2</li>
  <li>アイテム3</li>
</ul>
  • //li[@class='active']/following-sibling::li[1]→ クラスが「active」の <li> 要素の直後にある <li> 要素(「アイテム2」)を取得。

まとめ

壊れにくいXPathの作成

壊れにくいXPathを作成するには、以下のポイントを意識します。

  1. 絶対パスより相対パスを優先: HTML構造が変わっても影響を受けにくい。
    • 悪い例: /html/body/div[1]/span
    • 良い例: //div[@class='content']/span
  2. 安定した属性を使用: IDやclassなど、変更されにくい属性を利用。
    • 悪い例: //div[1]
    • 良い例: //div[@id='main']
  3. contains関数を活用: 部分一致で柔軟性を持たせる。
    • 悪い例: //button[1]
    • 良い例: //button[contains(text(), '送信')]

idやclassが無くても親子関係の指定でなんとかなるかも

親子関係や隣接する要素を柔軟に指定することができます。

以下のポイントを押さえておくと便利です。

  • 親子関係: /(直接の子)、//(すべての子孫)
  • 隣接要素: following-sibling(後続の兄弟)、preceding-sibling(前の兄弟)
  • 親要素: parent 軸を使用
  • 条件付き指定: [条件] を使って絞り込み

これらを組み合わせることで、複雑なHTML構造から必要な要素を効率的に取得できます。