MAGAZINE
ルーターマガジン
クローリング/スクレイピング
HTMLパーサーでテキストノードを取得すると改行が大量に入ってしまう問題と解決策
2023.03.14
ルーターエンジニアのsasaokaです。
スクレイピングをする際、あるノードの子ノードのテキストを全て取得したい場面がときどきあります。今回はそのときに起きる問題と解決策を、RubyのNokogiri、PythonのBeautifulSoupの例を挙げて紹介します。
問題点
サンプルとして次のHTMLを用いて、table
タグの中のテキストを取得してみます。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>タイトル</title>
</head>
<body>
<table>
<tr>
<th>ヘッダー1</th>
<th>ヘッダー2</th>
</tr>
<tr>
<td> 値1
</td>
<td>値2</td>
</tr>
</table>
</body>
</html>
Nokogiri
RubyのNokogiriを使う場合は次のようなコードになります。traverse
メソッドを使うことで、あるノードの子孫のノードに再帰的にアクセスすることができます。
require 'nokogiri'
html = File.open('sample.html').read
text_arr = []
doc = Nokogiri::HTML.parse(html)
doc.at_css('table').traverse do |node|
text_arr.push(node.text) if node.name == 'text'
end
pp text_arr
ヘッダー1、ヘッダー2、値1、値2の4つのみ出力して欲しいのですが、このコードを実行すると次の結果が返ってきます。
["\n" + "\t\t",
"\n" + "\t\t\t\t",
"ヘッダー1",
"\n" + "\t\t\t\t",
"ヘッダー2",
"\n" + "\t\t\t",
"\n" + "\t\t\t",
"\n" + "\t\t\t\t",
" 値1\n" + "\n" + "\t\t\t\t",
"\n" + "\t\t\t\t",
"値2",
"\n" + "\t\t\t",
"\n" + "\t\t"]
ブラウザではHTMLをレンダリングするときにソースコード上の改行は空白は無視されますが、これらもテキストノードとして扱われてしまっているため、改行やタブを取得してしまっています。
次に改行を取り除いたHTML:
<!DOCTYPE html><html lang="ja"><head><meta charset="utf-8"><title>タイトル</title></head><body><table><tr><th>ヘッダー1</th><th>ヘッダー2</th></tr><tr><td> 値1</td><td>値2</td></tr></table></body></html>
を用意して、上のRubyコードを実行してみると次のようになります。
["ヘッダー1", "ヘッダー2", " 値1", "値2"]
"値1"の前のスペースは残っていますが、期待通り4つのテキストのみ抽出できています。
BeautifulSoup
次にPythonのBeautifulSoupを用いて同様の実験をしました。コードは以下です。
import bs4
html = open('sample.html', 'r').read()
soup = bs4.BeautifulSoup(html, "html.parser")
text_list = []
for node in soup.find('table').descendants:
if isinstance(node, bs4.NavigableString):
text_list.append(node.string)
print(text_list)
改行ありのHTMLからは
['\n', '\n', 'ヘッダー1', '\n', 'ヘッダー2', '\n', '\n', '\n', ' 値1\n\n\t\t\t\t', '\n', '値2', '\n', '\n']
と13個の値が取り出されました。一方改行なしのHTMLからは
['ヘッダー1', 'ヘッダー2', ' 値1', '値2']
とNokogiriと同様に4つのテキストのみ取り出されました。
解決策
不要な改行やタブを取得しないように、テキスト前後の改行やタブ、空白をstrip
メソッドで除いた後、それが空かどうかを判定して、空でないときに配列に入れれば良いです。
次の例はNokogiriで、あるノードの子ノードのテキストの配列を返す関数です。
def extract_child_node_text_arr(parent)
text_arr = []
parent.traverse do |child|
if child.name == 'text'
text = child.text.strip
text_arr.push(text) unless text.empty?
end
end
text_arr
end
BeautifulSoupでは次のようになります。
def extract_child_node_text_list(parent):
text_list = []
for child in parent.descendants:
if isinstance(child, bs4.NavigableString):
text = child.string.strip()
if text: text_list.append(text)
return text_list
これらの関数を用いると、改行ありのHTMLからも改行なしのHTMLからと同じように['ヘッダー1', 'ヘッダー2', '値1', '値2']
の4つのテキストを抽出することができます。 CONTACT
お問い合わせ・ご依頼はこちらから