0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Ruby : Nokogiriのパースでエンコーディングをnilに設定したとき

Last updated at Posted at 2020-05-19

2020/05/22 追記
この投稿の結論は誤っています。詳しくはこちら

#はじめに
Nokogiri::HTML.parseメソッドは、第三引数にエンコーディングを指定することができます。(UTF-8、Shift_JISなど)
しかし実はこのエンコーディングの引数をnilに設定しても、エラーとなることなくhtmlはパースされます。
このときnokogiriはどの文字コードでエンコーディングしているのか気になったのでまとめました。

#エンコーディングをnilに設定したとき
エンコーディングをnilに設定したときの文字コード判別について、公式含めいくつかのサイトに書かれていました。

charsetを指定しない場合、文字コードはlibxml2の自動判別に頼ることになります(Nokogiriのドキュメント)。「勝手にUTF-8として解釈される」ということではありません。
Nokogiri利用時のcharset=nilについて

Strings are always stored as UTF-8 internally. Methods that return text values will always return UTF-8 encoded strings. Methods that return XML (like to_xml, to_html and inner_html) will return a string encoded like the source document.
Nokogiri公式ドキュメント

Nokogiriでは、基本的にメソッドの文字コードは「UTF-8」で返され、XMLを返すようなメソッドは、ページソースの文字コードを参照すると書かれています。
このときのページソースの文字コード判別は、「libxml2」というライブラリが担っているようです。

#検証その1
本当に文字コードが判別できているか、実際のページのタイトルを取得して出力する簡単なスクレイピングを行って検証してみます。
「無印良品」と「zozotown」の2つのページで行ってみました。

  • 無印良品トップページの場合

無印良品トップページは「UTF-8」で書かれています。
Screenshot from 2020-05-19 18-04-31.png

まずは文字コードを「UTF-8」で明示して出力してみます。

mujirushi1.rb
require 'nokogiri'
require 'open-uri'

url = 'https://www.muji.com/jp/'
html = open(url).read

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

puts page.title
mujirushi1.rbの結果
$ ruby mujirushi1.rb
無印良品

きちんとタイトルが表示されています。
もちろん指定する文字コードを「Shift_JIS」などにすると文字化けします。

ここで文字コードをnilとしてみます。

mujirushi2.rb
require 'nokogiri'
require 'open-uri'

url = 'https://www.muji.com/jp/'
html = open(url).read

page = Nokogiri::HTML.parse(html, nil, nil)

puts page.title
mujirushi2.rbの結果
$ ruby mujirushi2.rb
無印良品

どうやらnilとした場合でも正常にタイトルが表示できているようです。

  • zozotownトップページの場合

zozotownトップページは「Shift_JIS」で書かれています。
Screenshot from 2020-05-18 19-00-11.png

文字コードを「Shift_JIS」で明示的に指定するときちんとタイトルが表示されます。(無印良品と同様なので割愛)

文字コードをnilとしてみると

zozotown.rb
require 'nokogiri'
require 'open-uri'

url = 'https://zozo.jp/'
html = open(url).read

page = Nokogiri::HTML.parse(html, nil, nil)

puts page.title
zozotown.rbの結果
$ ruby zozotown.rb
ファッション通販ZOZOTOWN

zozotownのページでも正常にタイトルが出力されていることが確認できました。

#検証その2
今度は実際のページではなく、自前のhtmlでパースはどうなるのか調べてみました。

最初は「UTF-8」で書かれたhtmlです。

hello_utf8.html
<!DOCTYPE html>

<html>
  <head>
    <title>こんにちわ</title>
    <meta charset="UTF-8">
  </head>
  <body>
  </body>
</html>

文字コードを「UTF-8」と明示してやれば、「こんにちわ」と表示されます。

文字コードをnilにして取得してみると、

utf8.rb
require 'nokogiri'

html = open('hello_utf8.html').read
page = Nokogiri::HTML.parse(html, nil, nil)

puts page.title
utf8.rb結果
$ ruby utf8.rb
こんにちわ

正常に取得できています。

次は「Shift_JIS」で書かれたhtmlです。

sjis.html
<!DOCTYPE html>

<html>
  <head>
    <title>こんにちわ</title>
    <meta charset="Shift_JIS">
  </head>
  <body>
  </body>
</html>

こちらは「Shift_JIS」で文字コードを明示すれば「こんにちわ」と表示されます。

それではエンコーディングnilで取得してみます。

sjis.rb
require 'nokogiri'

html = open('hello_sjis.html').read
page = Nokogiri::HTML.parse(html, nil, nil)

puts page.title
sjis.rbの結果
$ ruby sjis.rb
±��ɂ߂�

文字化けしました。
さらにこのときNokogiriはmetaタグのcharsetを参照しているというようにも見えません。

確認のため、「UTF-8」でエンコーディングを指定してタイトルを取得してみます。

sjis2.rb
require 'nokogiri'

html = open('hello_sjis.html').read
page = Nokogiri::HTML.parse(html, nil, 'utf-8')

puts page.title
sjis2.rbの結果
$ ruby sjis2.rb
±��ɂ߂�

文字化けしました。
文字化けした文字で判別するのでアレですが、エンコーディングをnilにすると「UTF-8」でエンコーディングして取得した結果と同じになるように見えます。

#ここまでのまとめ

  • 無印良品(utf-8)でnil -> 正常
  • zozotown(shift_jis)でnil -> 正常
  • 自前html(utf-8)でnil -> 正常
  • 自前html(shift_jis)でnil -> 文字化け

#さらに検証
上の検証結果を見るとわかりますが、「zozotown」と「自前html(shift_jis)」では、同じ「Shit_JIS」なのに結果が違っています。
そこで最後の検証として「zozotown」のhtmlを全コピーして、ローカルのファイルに保存し、Nokogiriでエンコーディングnilでタイトルを取得してみます。

「zozotown」のソースコード(ctrl + u)を全コピーして「zozo.html」という名前でローカルに保存しています。

zozo.rb
require 'nokogiri'

html = open('zozo.html').read
page = Nokogiri::HTML.parse(html, nil, nil)

puts page.title
zozo.rbの結果
$ ruby zozo.rb
�t�@�b�V�����ʔ�ZOZOTOWN

文字化けしました。
ちなみに「UTF-8」で明示的にエンコーディングすると、同じような文字化けを起こしました。

#総まとめ

  • web上のurlからのhtmlを、エンコーディングnilでパースすると、Nokogiriが良しなに文字コードを判別してエンコーディングしている(ように見える)
  • このときにNokogiriはmetaタグのcharsetを参照しているわけでもなさそう
  • エンコーディングnilでパースすると、Nokogiriは基本的に「UTF-8」でエンコーディングしているっぽい

#さいごに
公式サイトにも書かれていますが、エンコーディングnilのときにソースから文字コードを自動判別する「libxml2」は、100%文字コードを判別できるわけではないようです。

Remember that data is just a stream of bytes. Only us humans add meaning to that stream. Any particular set of bytes could be valid characters in multiple encodings, so detecting encoding with 100% accuracy is not possible. libxml2 does its best, but it can't be right 100% of the time.

If you want Nokogiri to handle the document encoding properly, your best bet is to explicitly set the encoding. Here is an example of explicitly setting the encoding to EUC-JP on the parser:

ソースの文字コードがわかっているのであれば、きちんと明示してパースするのが無難かと思われます。

「libxml2」がどのように文字コードを判別しているのかは要調査です。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?