この記事は Go Advent Calendar 2017 の記事... ではありません
追記あり
実はあったりします。
Golang で URL から charset を取得するのを書いたのですが、他にもっとよい方法があるとおもうのです... - Qiita
他によい方法がある気がするのですが...わからないので、書いてみました。
https://qiita.com/mochizukikotaro/items/ddc0c6b1b98cd33f451e
golang.org/x/net/html/charset
を使うと良いです。
package main
import (
"bufio"
"fmt"
"log"
"net/http"
"golang.org/x/net/html/charset"
)
func main() {
resp, err := http.Get("https://google.com/")
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
br := bufio.NewReader(resp.Body)
if data, err := br.Peek(1024); err == nil {
if _, name, ok := charset.DetermineEncoding(data, resp.Header.Get("content-type")); ok {
fmt.Println(name)
}
}
}
charset.DetermineEncoding
を使うと BOM や Content-Type
ヘッダ、meta タグ等といった情報から charset 名を得られます。日本のIPアドレスからであれば shift_jis
と表示されると思います。
しかしながらこの charset 名を使ってどうやって HTML からテキストを得るかという問題が起きると思います。そこで go-encoding という物があります。
GitHub - mattn/go-encoding
https://github.com/mattn/go-encoding
オフィシャルが提供する golang.org/x/text/encoding
には各エンコーディングに対する utf-8 へのデコード処理が書かれているのですがエンコーディング名とのマッチングがありません。そこで使うのが go-encoding です。
br := bufio.NewReader(resp.Body)
var r io.Reader = br
if enc := encoding.GetEncoding(name); enc != nil {
r = enc.NewDecoder().Reader(br)
}
この様に名称から Encoding オブジェクトを得る事が出来ます。実装はただただ並べただけの物なので見ても面白い物はありません。あとはこれを使って HTML をパースすれば HTML からテキストのみを抽出する事が出来る様になります。
package main
import (
"bufio"
"bytes"
"fmt"
"io"
"log"
"net/http"
"strings"
"github.com/mattn/go-encoding"
"golang.org/x/net/html"
"golang.org/x/net/html/charset"
)
func text(resp *http.Response) (string, error) {
br := bufio.NewReader(resp.Body)
var r io.Reader = br
if data, err := br.Peek(1024); err == nil {
if _, name, ok := charset.DetermineEncoding(data, resp.Header.Get("content-type")); ok {
if enc := encoding.GetEncoding(name); enc != nil {
r = enc.NewDecoder().Reader(br)
}
}
}
var buffer bytes.Buffer
doc, err := html.Parse(r)
if err != nil {
return "", err
}
walk(doc, &buffer)
return buffer.String(), nil
}
func walk(node *html.Node, buff *bytes.Buffer) {
if node.Type == html.TextNode {
data := strings.Trim(node.Data, "\r\n ")
if data != "" {
buff.WriteString("\n")
buff.WriteString(data)
}
}
for c := node.FirstChild; c != nil; c = c.NextSibling {
switch strings.ToLower(node.Data) {
case "script", "style", "title":
continue
}
walk(c, buff)
}
}
func main() {
resp, err := http.Get("http://example.com/")
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
s, err := text(resp)
if err != nil {
log.Fatal(err)
}
fmt.Println(s)
}
追記
ヘッダが shift_jis を返して来ない場合は DetermineEncoding の最後の戻り値が ok を戻さない様なので修正。
package main
import (
"bufio"
"bytes"
"fmt"
"io"
"log"
"net/http"
"strings"
"github.com/mattn/go-encoding"
"golang.org/x/net/html"
"golang.org/x/net/html/charset"
)
func text(resp *http.Response) (string, error) {
br := bufio.NewReader(resp.Body)
var r io.Reader = br
if data, err := br.Peek(4096); err == nil {
enc, name, _ := charset.DetermineEncoding(data, resp.Header.Get("content-type"))
if enc != nil {
r = enc.NewDecoder().Reader(br)
} else if name != "" {
if enc := encoding.GetEncoding(name); enc != nil {
r = enc.NewDecoder().Reader(br)
}
}
}
var buffer bytes.Buffer
doc, err := html.Parse(r)
if err != nil {
return "", err
}
walk(doc, &buffer)
return buffer.String(), nil
}
func walk(node *html.Node, buff *bytes.Buffer) {
if node.Type == html.TextNode {
data := strings.Trim(node.Data, "\r\n ")
if data != "" {
buff.WriteString("\n")
buff.WriteString(data)
}
}
for c := node.FirstChild; c != nil; c = c.NextSibling {
switch strings.ToLower(node.Data) {
case "script", "style", "title":
continue
}
walk(c, buff)
}
}
func main() {
resp, err := http.Get("http://www.itmedia.co.jp/news/articles/1710/26/news006.html")
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
s, err := text(resp)
if err != nil {
log.Fatal(err)
}
fmt.Println(s)
}