Documentation
¶
Overview ¶
Package extract は、HTMLコンテンツから本文テキストを高精度に抽出します。
取得(HTTP アクセス)はこのパッケージの責務ではありません。呼び出し側が用意した io.Reader を解析するだけなので、HTTP でもファイルでもテスト用の文字列でも同じ経路です。
本文抽出のルール ¶
「記事本文だけを残す」ことを目的にしたヒューリスティックで、次の順に処理します。
0. 文字コードを判定する。BOM → Content-Type の charset → <meta charset> → 本文のバイト列、の順に判定して UTF-8 に変換します。パーサは入力を UTF-8 とみなすため、 これが無いと非 UTF-8 のページが丸ごと文字化けします。Content-Type ヘッダーが 手元にあるなら TextWithContentType に渡してください。
1. ノイズを落とす。script、style、form、nav、aside、noscript、template、 [hidden]、[aria-hidden="true"]、および広告・SNS・コメント欄まわりのクラス (.related-posts、.social-share、.comments、.ad-banner、.advertisement)を ページ全体から除去します。noscript / template はパーサからは中身がただの テキストに見えるため、落とさないと囲っている段落の本文に混ざります。
2. 本文の範囲を決める。article、main、div[role='main']、#main、#content、 .post-content、.article-body、.entry-content、.markdown-body、.readme に 最初に一致した要素を本文とします。見つからない場合はページ全体を本文とみなし、 そのときだけ header / footer / .sidebar も落とします(記事の内側の header は 見出しを、footer は署名を含むことがあるため、常に落とすと本文が欠けます)。
3. ブロック要素を順に拾う。p、h1〜h6、li、dt、dd、figcaption、blockquote、 table、pre を DOM の出現順に走査します。入れ子(<li><p>…</p></li> など)は 一度だけ出力され、表の中身は表の行としてだけ出ます。<br> は空白として扱います。 出力の形は次のとおりです。
- title — 「【記事タイトル】 」を付けて先頭に
- h1〜h6 — 「## 」を付ける(MinHeadingLength 文字以上のもの)
- p, blockquote — MinParagraphLength 文字以上のものだけ
- li, dt, dd, figcaption — 長さを問わず出力(短くても項目として意味を持つため)
- table — 「【表題】 」付きキャプションと「セル | セル」の行。セルの中は 長さを問わず平坦化し、空の行は出力しない
- pre — コードフェンスで囲む
しきい値はバイト数ではなく文字数で測ります。len() だと日本語は 1 文字 3 バイトで しきい値が実質 1/3 になり、ナビゲーションの断片が本文として残ります。
Index ¶
Examples ¶
Constants ¶
const ( // MinParagraphLength は、本文として採用する段落の最小文字数(バイト数ではない)です。 MinParagraphLength = 20 // MinHeadingLength は、見出しとして採用する最小文字数です。「概要」のような 2 文字の見出しを残します。 MinHeadingLength = 2 )
Variables ¶
This section is empty.
Functions ¶
func Text ¶
Text は取得済みのHTMLコンテンツから整形されたテキストを抽出します。
第2戻り値は本文が見つかったかどうかです。タイトルしか取れなかった場合は テキストを返しつつ false になります(エラーではありません)。
Example ¶
package main
import (
"context"
"fmt"
"strings"
"github.com/shouni/go-web-reader/extract"
)
func main() {
html := `<html><head><title>Example</title></head><body>
<nav><a href="/">Home</a></nav>
<article>
<h1>Heading</h1>
<p>This paragraph is long enough to be treated as body text by the extractor.</p>
</article>
</body></html>`
text, hasBody, err := extract.Text(context.Background(), strings.NewReader(html))
if err != nil {
panic(err)
}
fmt.Println(hasBody)
fmt.Println(text)
}
Output: true 【記事タイトル】 Example ## Heading This paragraph is long enough to be treated as body text by the extractor.
func TextWithContentType ¶ added in v1.4.0
func TextWithContentType(ctx context.Context, r io.Reader, contentType string) (text string, hasBodyFound bool, err error)
TextWithContentType は、Content-Type ヘッダーの値を添えて Text します。
文字コードはヘッダーの charset が <meta charset> より優先されます。<meta> で 宣言しない Shift_JIS / EUC-JP のページはヘッダーだけが手がかりなので、手元に あるなら渡してください。contentType が空なら Text と同じです。