MAGAZINE
ルーターマガジン
Nokogiriのcssメソッドで「あるテキストを含むノード」を検索する
こんにちは。エンジニアの高橋です。
NokogiriのCSSセレクタでは「あるテキストを含むノード」を検索することができます。
表から新宿の天気だけ取得したい
▲この表から「新宿の天気」のみ取得することを考えます
<table border="1">
<tr>
<td rowspan="3">東京</td>
<td>新宿:雨</td>
</tr>
<tr>
<td>渋谷:大雨</td>
</tr>
<tr>
<td>池袋:豪雨</td>
</tr>
</table>
▲写真の表のソースコード。tdが複数あるが、classやidが割り振られていないため、「tdでのループ」をロジックに入れる必要がある。・・?
ここではcontainsというcssセレクタの記法を使用して、at_cssメソッドで、"新宿"をテキストに含むtdタグノードのみ取得ができます。以下のコードで取得を試してみました。
require "nokogiri"
html = <<'EOS'
<table border="1">
<tr>
<td rowspan="3">東京</td>
<td>新宿:雨</td>
</tr>
<tr>
<td>渋谷:大雨</td>
</tr>
<tr>
<td>池袋:豪雨</td>
</tr>
</table>
EOS
doc = Nokogiri::HTML.parse(html)
puts doc.at_css("td:contains('新宿')").text
# >> 新宿:雨
注意
- contains記法ですが、Nokogiriの公式ドキュメント等で動作する根拠が確認できていません。使用には十分注意してください。
- Nokogiriは1.10.1で動作確認をしました。
解説
- xpathには、"テキストでの検索"が標準機能として存在していますが、css3には機能が存在しません。
これを踏まえて、Nokogiri::XML::Searchable#cssの実装を見てみましょう。
Nokogiri::XML::Searchable#css(104行目)
重要なのはcss_internalメソッドです。
xpath_internalメソッドに、css_rules_to_xpathメソッドの戻り値を渡しています。
これはその名の通り、cssセレクタを同義のxpathに変換し、xpathでドキュメントのパースを行なっているのです。
ちなみに、上記のcssセレクタ"td:contains('新宿')"は、css_rules_to_xpathメソッドにより、下記のように変換されます。
変換前(cssセレクタ) | 変換後(xpathセレクタ) |
---|---|
td:contains('新宿') | //td[contains(., '新宿')] |
xpathのcontainsの第一引数"."は、「子孫ノードを含めた、テキスト」で検索するという意味合いです。
まとめ
Nokogiriでcssセレクタ"td:contains('新宿')"がなぜ動くかというと、Nokogiriは内部的にcssセレクタをxpathに変換しているため、xpathのcontains記法を間接的に使用しているからということになります。
Nokogiriのcssセレクタにおける"contains"記法ですが、調査した結果、かなりトリッキーな記法だということがわかりました。使えるには使えるが、サポートや互換性は不明なため、積極的な使用は推奨しない、といった位置付けになるのではないでしょうか。
CONTACT
お問い合わせ・ご依頼はこちらから