1. 程式人生 > >網頁正文提取——Html2Article

網頁正文提取——Html2Article

為什麼要做正文提取

一般做輿情分析,都會涉及到網頁正文內容提取。對於分析而言,有價值的資訊是正文部分,大多數情況下,為了便於分析,需要將網頁中和正文不相干的部分給剔除。可以說正文提取的好壞,直接影響了分析結果的好壞。

對於特定的網站,我們可以分析其html結構,根據其結構來獲取正文資訊。先看一下下面這張圖:

正文部分,不同的網站,正文所在的位置不同,並且Html的結構也不同,對於爬蟲而言,抓取的頁面是各種各樣的,不可能針對所有的頁面去寫抓取規則來提取正文內容,因此需要一種通用的演算法將正文提取出來。

現有的網頁正文提取演算法

  • 基於標籤用途的正文提取演算法(比如title或h1,h2標籤一般用作標題,p一般表示正文段落,根據標籤的含義去提取正文)
  • 基於標籤密度判定(這個簡單,說白了就是字元統計,正文部分html標籤的密度比較低,確定一個閾值,按照標籤密度提取正文部分)
  • 基於資料探勘思想的網頁正文抽取方法(這裡會涉及到統計學和概率論的一些知識,在高深點就成了機器學習了,沒有深入研究)
  • 基於視覺網頁塊分析技術的正文抽取(CV這種高階大氣上檔次的東西,豈是我等這麼容易就能研究明白的。雖然實現上覆雜,但就提取效果而言,這種方法提取的精度還是不錯的)

前2中方法還是比較容易實現的,主要是處理簡單,先前我把標籤密度的提取演算法實現了,但實際用起來錯誤率還是蠻高的;後2種方法在實現上就略複雜了,從演算法效率上講應該也高不了哪去。

我們需要的是一種簡單易實現的,既能保證處理速度,提取的準確率也不錯的

演算法。於是結合前兩種演算法,研究網頁html頁面結構,有了一種比較好的處理思路,權且叫做基於文字密度的正文提取演算法吧。後來從網上找了一下類似的演算法,發現也有使用類似的處理方法來處理正文提取的,不過還是有些不同。接下來跟大家分享一下這個演算法的一些處理思想。

網頁分析

我任意取了百度,搜狐,網易的一篇新聞類網頁,拿來作分析。

先看一篇百度的文章

首先請求這個頁面,然後過濾到所有的html標籤,只保留文字資訊,我們可以看到正文資訊集中在一下位置:

使用Excel分析行數與每行的字元的關係可以發現:

很明顯,正文內容集中在65-100行之間的位置上,而這個區間的字元數也是比較密集的。

再來一篇網易的文章

還是先看下過濾html標籤後的正文部分:

再來一個Excel的分析結果:

正文部分集中在279-282行之間,從圖上看,也正是這麼幾行的文字密度特別高。

最後分析一篇搜狐的新聞

還是先看下過後標籤後的正文:

再看下Excel的分析結果:

而搜狐的這篇文章正文部分主要集中在200-255行之間。其餘的文字全部是雜亂的標籤文字。

抱歉,漏了很重要的一點說明:為什麼分析的時候要把html標籤過濾掉呢?過濾html標籤是為了降低干擾,因為我們關注的是正文內容,如果帶著這樣的標籤<span style="color: #0000ff;">var</span> chart = <span style="color: #0000ff;">new</span><span style="color: #000000;">去分析,可想而知,對我們的正文分析會有多大的干擾了,也正因如此需要將html標籤掉,只對文字做分析,降低干擾。

基於網頁分析構思出的正文提取演算法

回顧以上的網頁分析,如果按照文字密度來找提取正文,那麼就是寫這麼一個演算法,能夠從過濾html標籤後的文字中找到正文文字的起止行號,行號之間的文字就是網頁正文部分。

還是從上面三個網頁的分析結果看,他們都有這麼一個特性:正文部分的文字密度要高出非正文部分很多。我們按照這個特性就可以很容易將演算法實現,那就是基於閾(讀音:yu)值去分析正文所在的位置。

那麼接下來就需要解決一些問題:

  • 如何確定閾值?
  • 如何分析,一行行的分析?還是?

閾值的確定可以通過統計分析得出一個比較好的值,我在實際處理過程中,發現這個值取180是比較合適的,也就是分析文字的時候,如果所分析的文字超過了180,那麼就可以認為到達了正文部分。

再有就是如何分析的問題,這個其實比較容易確定,一行行的分析效果肯定不好,如果在按行分析的過程中往下在分析幾行作為一次分析效果比較好。也就是一次性分析上5行左右,將字元累加起來,看看有沒有達到設定的閾值,如果達到了,那麼認為已經進入正文部分了。

嗯,主要的處理邏輯就是這樣,怎麼樣,不復雜吧。

我把實現的核心演算法也貼出來吧:

int preTextLen = 0;         // 記錄上一次統計的字元數量(lines就是去除html標籤後的文字,_limitCount是閾值,_depth是我們要分析的深度,sb用於記錄正文)int startPos = -1;          // 記錄文章正文的起始位置for (int i = 0; i < lines.Length - _depth; i++){    int len = 0;    for (int j = 0; j < _depth; j++)    {        len += lines[i + j].Length;    }    if (startPos == -1)     // 還沒有找到文章起始位置,需要判斷起始位置    {        if (preTextLen > _limitCount && len > 0)    // 如果上次查詢的文字數量超過了限定字數,且當前行數字符數不為0,則認為是開始位置        {            // 查詢文章起始位置, 如果向上查詢,發現2行連續的空行則認為是頭部            int emptyCount = 0;            for (int j = i - 1; j > 0; j--)            {                if (String.IsNullOrEmpty(lines[j]))                {                    emptyCount++;                }                else                {                    emptyCount = 0;                }                if (emptyCount == _headEmptyLines)                {                    startPos = j + _headEmptyLines;                    break;                }            }            // 如果沒有定位到文章頭,則以當前查詢位置作為文章頭            if (startPos == -1)            {                startPos = i;            }            // 填充發現的文章起始部分            for (int j = startPos; j <= i; j++)            {                sb.Append(lines[j]);            }        }    }    else    {        if (len <= _endLimitCharCount && preTextLen < _endLimitCharCount)    // 當前長度為0,且上一個長度也為0,則認為已經結束        {            if (!_appendMode)            {                break;            }            startPos = -1;        }        sb.Append(lines[i]);    }    preTextLen = len;}

核心的提取演算法不足60行,經過驗證提取的效果還是非常不錯的,至少做到了正文提取正確率90%上,效率上做到了平均提取時間30ms左右。

還需解決的一些問題

html標籤剔除:這個簡單,直接使用正則表示式替換(Regex.Replace(html, "(?is)<.*?>", "")),將所有的html標籤剔除即可

html壓縮型網頁的處理: 壓縮後的html程式碼一般只有一行,對這類的html處理也比較簡單(不需要複雜的程式碼格式化),直接在標籤末尾強制新增換行符即可。

正文標題:大多數規範的網址會用h1標籤作文正文標題,處理時如果有h1那麼從h1標籤中提取標題,沒有的話,直接從title標籤中那吧。

文章釋出時間:並不是所有的文章都有釋出時間(不過貌似大多數都有哈),直接使用正則從去除標籤後的正文中提取時間吧。

保留帶標籤的正文:我們的演算法是和標籤無關的,因為演算法處理時首先要過濾html標籤,去除干擾,那麼如果想要帶標籤的正文怎麼辦(比如要保留正文中的圖片)?這時只能保留2個數組了,一個數組存放過濾標籤的文字,便於分析,另一個數組則保留html標籤,便於提取原始資訊。

Html2Article網頁正文提取演算法

Html2Article就是我基於以上思想實現的網頁正文提取演算法。有以下特點:

  • 標籤無關,提取正文不依賴標籤。
  • 支援從壓縮的html文件中提取正文內容。
  • 支援帶標籤輸出原始正文。
  • 核心演算法簡潔高效,平均提取時間在30ms左右。

演算法已開源(也算是為開源做點貢獻了吧):

使用方法請參考文件介紹說明。

演算法是用C#實現的,玩.NET的同學有福了,可以直接使用nuget將html2article新增到你的專案中哦。

另外發現直接從百度搜索“html2article”也能找到很快的找打它,演算法實現已經將近半年了,一直比較懶,也沒寫過文章跟大家分享一下。