概要
http.Get
でhtmlを取得する際にGoでGETを使ってHTMLを文字列で取得してみるのような実装を行うと思います。大体のページはこれで大丈夫なのですが、ただこの実装だと文字コードutf-8以外だと文字化けしてしまいます。
というわけで、utf-8以外の文字コードが設定されたhtmlをどう取得するかのメモ書きを残しておきます。
対応方針
今回はstackoverflowのgolang HTML charset decodingの回答にある、charset.DetermineEncoding
メソッドを使用する方法で試してみることとします。このstackoverflowの内容だと以下2点課題があったので、別途考慮しました。
一点目はcharset.DetermineEncoding
では、Determining the character encodingのドキュメントに説明がある通り、複数要素で文字コードを推論しているようです。そして、shift-jisのページだと判定時のcertain
がfalseになってしまうことがあるようです(いくつかページを試してみたところ)。今回はcertainの結果は使用しない形にしました。
二点目は文字コード判定用で一度Readerでの読み込みが必要になるので、全部のhtmlを読み取る場合はこれを複製する必要があることです。対応は【golang】io.Readerを使いまわしてContentType判定、S3アップロードしたらハマった話の記事にいくつか紹介されていますが、今回はio.TeeReader
を使うこととしました。
実装サンプル
package main
import (
"bufio"
"bytes"
"fmt"
"io"
"net/http"
"golang.org/x/net/html/charset"
"golang.org/x/text/encoding/htmlindex"
)
func detectContentCharset(body io.Reader) string {
r := bufio.NewReader(body)
if data, err := r.Peek(1024); err == nil {
// certainがfalseの場合もそのまま使う
_, name, _ := charset.DetermineEncoding(data, "")
if name != "" {
return name
}
}
return "utf-8"
}
func main() {
// htmlページの取得
// webPage := ("https://mayukasports.blogspot.com/") // UTF-8のサイト
webPage := ("http://abehiroshi.la.coocan.jp/top.htm") // Shift_JISのサイト
resp, err := http.Get(webPage)
if err != nil {
fmt.Println(err)
return
}
defer resp.Body.Close()
// 文字コードチェック用のreadのためにbodyをコピーしておく
body := new(bytes.Buffer)
bodyForCheck := io.TeeReader(resp.Body, body)
// 文字コードの判定
charset := detectContentCharset(bodyForCheck)
encode, err := htmlindex.Get(charset)
if err != nil {
fmt.Println(err)
return
}
// htmlのRead
var contentBytes []byte
if name, _ := htmlindex.Name(encode); name != "utf-8" {
encodeBody := encode.NewDecoder().Reader(body)
contentBytes, err = io.ReadAll(encodeBody)
} else {
contentBytes, err = io.ReadAll(body)
}
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(contentBytes))
}