Web上からHTMLファイルを読み取るときの文字コードを自動で指定したい

タグの編集
投稿者 VANITAS  (社会人) 投稿日時 2011/7/6 22:49:59
Dim internet As New Net.WebClient
        Dim st As System.IO.Stream = internet.OpenRead("http://hogehoge.com/foobar.html")
        Dim sr As New System.IO.StreamReader(st, Encoding.GetEncoding("EUC-JP"))
        Dim source As String = sr.ReadToEnd()

        st.Close()
        internet.Dispose()

        tbSource.Text = source


このようなコードを書いたとします。なおURLを仮のものです。
ここでは文字コードが「EUC-JP」となっていますが、実際不確定のURLからhtmlファイルを読み込むときは文字コードがわかりません。
どのようにすれば正しい文字コードを読み込む前に判別できるのでしょうか?
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2011/7/7 07:43:29
> http://hogehoge.com/foobar.html
> なおURLを仮のものです。

URL やメールアドレスを例示する場合には、hogehoge.com を使うのではなく
example.com や example.net 等を使うようにしましょう。
これは例示するために予約されたドメイン名です。(RFC 2606 の 3 章を参照)

http://allabout.co.jp/gm/gc/23887/
http://neta.ywcafe.net/000501.html


> どのようにすれば正しい文字コードを読み込む前に判別できるのでしょうか? 
Web ページにおける文字コードの取得には
 (1) HTTPレスポンスヘッダー中の
  「Content-Type:text/html; charset=ISO-2022-JP」。
 (2) HTML の <head> タグ配下にある
  「<meta http-equiv="Content-Type" content="text/html; charset=euc-jp">」。
 (3) 文書の先頭にある XML 宣言
  「<?xml version="1.0" encoding="Shift_JIS"?>」。
 (4) 文書の先頭数バイトが示す バイトオーダーマーク(BOM)。
  「EF BB BF」のバイナリだった場合は UTF-8、
  「FF FE」のバイナリだった場合は、(リトルエンディアンの) UTF-16、
  「FE FF」のバイナリだった場合は、(ビッグエンディアンの) UTF-16、
を使う事ができるとされています。


しかし(1)は、charset が未指定なサーバーも少なくないため、当てにはできません。
そもそも、ここの charset が実際のファイルの内容と合致していないケースもままあります。
HTML ファイルの内容と違って、HTTP ヘッダーの内容は確認しずらいということもあり、
サイト管理者が十分な知識を持たない場合、誤った設定であることに気づきにくいためです。

次に(2)。これは文書自体に誤記されていなければある程度信頼できますが、
これが記載されていない HTML 文書も存在するため、確実な方法ではありません。

(3) はかなり信頼できる情報ですが、そもそも HTML 文書では XML 宣言がありません。
これが含まれるのは、XML 文書や XHTML 文書用の記述に限られます。しかも、たとえ
XML 文書であったとしても、Unicode (UTF-8 とか UTF-16 とか)で記述された文書では、
XML 宣言を省略できることになっています。

(4) も信頼性の高い情報です。これが検出された場合は、確実に文字コードを判定できます。
ただし、BOM そのものは必須ではありませんので、これらが検出されない文書の方が多いです。
しかも BOM が使われるのは UTF-7、UTF-8、UTF-16、UTF-32 に限られており、
Shift_JIS や EUC-JP 等では利用されません。


…ということで、残念ながら確実な方法というものは実はありません。

ですから、まずは文字列として読み取るのではなく、バイナリとして取得しておき、
そこから複数のエンコーディング候補を一つ一つ試してみて、その中で
デコードエラーが起きた物を除外する方向で実装してみては如何でしょう。


エンコードエラーやデコードエラーを検知する場合には、
System.Text.Encoding.GetEncoding メソッドの第2/第3引数に
 System.Text.EncoderExceptionFallback クラス
 System.Text.DecoderExceptionFallback クラス
を指定すれば OK です。その文字コードでは扱えないデータが
含まれていた場合には例外が発生しますので、Try~Catch で検知します。
(例外の内容から、どのデータが例外の原因になったのかを調べることもできます)