MAGAZINE

ルーターマガジン

Ruby

Nokogiriで正規表現にマッチするエレメントを取得する

2019.03.22
Pocket

こんにちは。
エンジニアの大壁です。
Nokogiriで正規表現を使いたいと思ったことはありませんか?
私はあります。

例えばこういうHTMLがあったとして。

<article>
  <h2>foo</h2>
  <div class="post-1">bar</div>
  <h2>baz</h2>
  <div class="post-2">qux</div>
</article>

クラス名が連番でつけられている。
これを全部抽出したい。
そういうケースがたまにあるので、正規表現を活用します。
では弊社トップページをパースしてみましょう。

require 'open-uri'
require 'nokogiri'
require "pp"

# 弊社トップページ
url = "https://rooter.jp/"

charset = nil
html = open(url) do |f|
  charset = f.charset
  f.read
end

doc = Nokogiri::HTML.parse(html, nil, 'utf-8')

"含む"を抽出する

例えばclass名に"content"を含むエレメントを取得したいとします。
xpathのメソッドにはcontainsがあり、以下のように使います。

elements = doc.xpath("//*[contains(@class, 'content')]")
pp elements.map{|v| v[:class]}
# ["site-content", "content-wrapper-index", "content-area"]

しかしxpathのメソッドについて把握している人は少ないでしょう。
xpathの力を借りずとも、rubyのselectメソッドを使えば同じ処理を実現できます。

all_elements = doc.xpath("//*")
elements = all_elements.select{|v| v[:class] =~ /content/}
pp elements.map{|v| v[:class]}
# ["site-content", "content-wrapper-index", "content-area"]

まずdoc.xpath("//*")でhtml内の全てのタグをエレメント化しています。
次にselectでv[:class] =~ /content/trueになるものを抽出しています。

正規表現で抽出する

ではidが"post-"の後に数字が振られているエレメントを取得したい場合はどうでしょう。
正規表現で書くならば/post-\d+$/ですが、xpathで正規表現は使えるのでしょうか?
調べてもわからなかったので、selectで抽出してしまいます。

all_elements = doc.xpath("//*")
elements = all_elements.select{|v| v[:id] =~ /post-\d+$/}
pp elements.map{|v| v[:id]}
# ["post-2132",
 # "post-2134",
 # "post-2138",
 # "post-2136",
 # "post-5721",
 # "post-5334",
 # "post-4495",
 # "post-5703",
 # "post-5740",
 # "post-5663"]

終わりに

selectの中身はなんでも良いので、好きなだけ自由に条件を書き足せます。
- 例: テキストに〇〇が含まれる
- 例: 子要素のクラスが〇〇である
など。
これをxpathで書こうとするとまずググることになるので、rubyの知識でやっちゃいましょうという話でした。

それでは

Pocket

CONTACT

お問い合わせ・ご依頼はこちらから