XPathでXML・HTMLから必要なデータを取り出す方法
XPathは、XMLやHTMLの文書から特定の要素・属性・テキストを指定して取り出すための記法です。スクレイピング、設定ファイルの解析、SOAPやRSSの処理、そしてSeleniumやPlaywrightでの要素指定など、使う場面は幅広くあります。ただし、書いた式が本当に狙ったものを取れているかは、実際に評価してみないと分かりません。XPath Tester は、XMLやHTMLを貼り付けて式を入力すると、マッチした結果をその場で一覧表示するツールです。ブラウザ標準のXPathエンジンで評価するため、実装で使うときと同じ結果を確認できます。
パスの基本と、//(再帰下降)の使いどころ
XPathの基本は、スラッシュで区切ったパスで要素をたどることです。スラッシュ1つは直下の子要素を意味し、たとえば catalog/book と書くと catalog の直下にある book だけが対象になります。一方、スラッシュ2つは階層を問わず子孫全体から探すため、//book と書けば文書のどこにあっても book が見つかります。手軽なので //book のような書き方を多用しがちですが、文書全体を走査するため、大きな文書では処理が重くなります。また、意図しない階層の同名要素まで拾ってしまう危険もあります。構造が分かっているなら、なるべく具体的なパスで書く方が安全で速い、というのが基本方針です。まずは // で当たりを付け、結果を見ながら絞り込んでいくと効率よく式を組み立てられます。
述語(角かっこ)で条件を絞る
XPathの表現力の中心は、角かっこで書く述語です。属性で絞るには @ を使い、たとえば category 属性が tech のものだけを選ぶといった指定ができます。数値との比較も書けるため、価格が一定額を超えるものだけを抽出する、といった条件も表現できます。位置で選ぶこともでき、数字を書けばその番目、last() と書けば最後の要素を指します。注意点として、XPathの位置は1から始まります。プログラミング言語の配列が0始まりであることに慣れていると間違えやすい部分です。部分一致には contains() を使い、文字列の一部を含む要素を選べます。述語は複数並べて書くこともでき、and や or で条件を組み合わせることもできます。まずは1つの条件で試し、結果を確認しながら足していくのが確実です。
テキスト・属性の取り出しと軸の指定
要素そのものではなく中身が欲しい場合は、末尾に text() を付けてテキストノードを取り出します。属性値だけが欲しい場合は @ と属性名を指定します。ここで知っておくと便利なのが、階層をさかのぼる指定です。2つのドットを書くと親要素を指すため、「特定のテキストを持つ要素の親」を取得する、といった逆方向の指定ができます。これは、目印になる値から周辺の情報を取りたいときに有効です。たとえば、ある商品名を含む行を見つけて、その行全体の情報を取得する、といった使い方です。また、count() のような関数を使うと、マッチした件数を数値として得られます。XPath Tester では、要素・属性・テキスト・数値・真偽値のいずれが返っても結果の型とあわせて表示するので、式が何を返しているかを把握しやすくなります。
CSSセレクタとの使い分け
HTMLを扱う場合、XPathとCSSセレクタのどちらを使うか迷うことがあります。CSSセレクタは記法が簡潔で読みやすく、クラスやIDでの指定に向いています。一方XPathは、テキストの内容で要素を選べる、親をさかのぼれる、位置や条件を柔軟に指定できる、という点で表現力が上です。「このテキストを含むボタン」「この見出しの次にある表」といった指定は、CSSセレクタでは書けないかXPathの方が素直に書けます。実務では、単純な指定はCSSセレクタ、構造やテキストに依存する指定はXPath、と使い分けるのが現実的です。CSSセレクタ側の動作を確認したい場合は、CSSセレクタテスターで同じように実際のDOMに対して試せます。両方を試して、壊れにくい方を選ぶという判断もよく行われます。
つまずきやすい点と対処
最もハマりやすいのが名前空間です。XML宣言で既定の名前空間が指定されている文書では、その名前空間に属する要素は接頭辞なしのパスでは選択できません。//book と書いても何も返らない場合、まずこれを疑います。対処としては、local-name() を使って名前空間を無視して名前だけで照合する方法があります。次に多いのが、HTMLをXMLとして解釈しようとして構文エラーになるケースです。HTMLは閉じタグの省略などが許容されるため、厳密なXMLとしては不正になることがあります。XPath Tester は文書の内容からXMLとHTMLを判別して適切に解析するため、この問題は起きにくくなっています。また、取得できたはずの要素が空に見える場合、その中身がJavaScriptで後から生成されている可能性があります。静的なHTMLにはまだ存在しないため、スクレイピングでは別の手段が必要になります。