網頁抽取技術和算法


網頁抽取技術和算法,持續更新。本文由WebCollector提供,轉載請標明出處。

目錄:


一. 網頁抽取簡介

網頁抽取在大多數情況下,指提取網頁中的結構化數據。網頁抽取技術近十幾年方法和工具變化都較快。

常見的網頁抽取方法有 :

  • 基於正則表達式的網頁抽取
  • 基於CSS選擇器的網頁抽取
  • 基於XPATH的網頁抽取
  • 基於機器學習的網頁抽取

由於CSS選擇器和XPATH都是網頁DOM樹的特征,切較為相似,本教程不介紹基於XPATH的網頁抽取。本文會着重介紹基於機器學習的網頁抽取。


二. 基於正則表達式的網頁抽取

利用正則表達式進行網頁抽取,是在html源碼的基礎上做字符串級別的檢索。要詳細了解如何利用正則表達式進行網頁抽取,只要了解正則表達式的基本用法即可,與網頁特征無關。

基於正則表達式的網頁抽取有下面幾個缺點:

  • 正則表達式不直觀,維護較為困難
  • 對於復雜的頁面,正則規則編寫較為復雜
  • 正則表達式是字符串級別的信息檢索,並沒有利用網頁的特征(例如DOM樹中的CSS選擇器或XPATH)

由於上面這些缺點,我們不推薦使用正則表達式進行網頁抽取。因此這里我們只舉一個簡單的例子演示正則抽取。

原網頁為:

<html>
<body>
<h2>(標題)此內容不要被抽取</h2>
<div class="main">
(正文)此內容要被抽取
</div>
<div class="foot">
(頁腳)此內容不要被抽取
</div>
</body>
</html>

在html中我們描述了待抽取的內容。

  String html="<html><body>" +
"<h2>(標題)此內容不要被抽取</h2>" +
"<div class=\"main\">(正文)此內容要被抽取</div>" +
"<div class=\"foot\">(頁腳)此內容不要被抽取</div>" +
"</body></html>";

//正則表達式中的點(.)代表任意字符,星號(*)代表出現任意次,
//因此.*表示任意字符串(包括空字符串)
//.*?中的問號(?)表示.*(任意字符串)的長度盡可能短,
//如果沒有這個限制,抽取結果將變為:
//(正文)此內容要被抽取</div><div class="foot">(頁
//腳)此內容不要被抽取
Pattern pattern=Pattern.compile("<div class=\"main\">(.*?)</div>");


if(matcher.find()){
//正則表達式里的括號代表group,group(0)代表整個正則表
//達式匹配的內容,group(n)代表第n個括號中的內容
System.out.println("抽取結果:"+matcher.group(1));
}else{
System.out.println("無抽取結果");
}

對於結構復雜的網頁,正則表達式的設計往往較為困難,上面例子中

xxxxx包含的是純文本,但如果div中也包含標簽,上面的正則表達式就不適用了。例如:

<html>
<body>
<h2>xxxxxx</h2>
<div class="main">
<p>xxxxx</p>
<div>xxxxxx</div>
<p>xxxxxxxx</p>
</div>
<div class="foot">
xxxxxx
</div>
</body>
</html>

如果仍使用<div class="main">(.*?)</div>作為正則表達式抽取,抽取結果會變為<p>xxxxx</p><div>xxxxxx,這時就需要重新設計正則表達式,例如<div class="main">(.*?)</div>\s*<div class="foot">(程序里,雙引號和\之前要加\進行轉義。


三. 基於CSS選擇器的網頁抽取

瀏覽器在收到服務器返回的html源碼后,會將網頁解析為DOM樹。CSS選擇器(CSS Selector)是基於DOM樹的特征,被廣泛用於網頁抽取。目前最流行的網頁抽取組件Jsoup(Java)和BeautifulSoup(Python)都是基於CSS選擇器的。

對於上面的例子:

<html>
<body>
<h2>(標題)此內容不要被抽取</h2>
<div class="main">
(正文)此內容要被抽取
</div>
<div class="foot">
(頁腳)此內容不要被抽取
</div>
</body>
</html>

使用CSS選擇器將大大提升代碼的可讀性:

 public static void cssExtract() {
String html="<html><body>" +
"<h2>(標題)此內容不要被抽取</h2>" +
"<div class=\"main\">(正文)此內容要被抽取</div>" +
"<div class=\"foot\">(頁腳)此內容不要被抽取</div>" +
"</body></html>";

//Jsoup中的Document類表示網頁的DOM樹
Document doc= Jsoup.parse(html);

//利用select方法獲取所有滿足css選擇器的Element集合
// (實際是一個Elements類型的對象)
//由於在本網頁的結構中,只會有一個Element滿足條件
// 因此只要返回集合中的第一個Element即可
Element main=doc.select("div[class=main]").first();

//main是一個Element對象,這里main對應了網頁中
//的<div class="main">(正文)此內容要被抽取</div>
//我們調用Element的text()方法即可提取中間的文字
if(main!=null){
System.out.println("抽取結果:"+main.text());
}else{
System.out.println("無抽取結果");
}

}

CSS選擇器有標准的規范,但Jsoup(Java)和BeautifulSoup(Python)這些組件並沒有完全按照規范實現CSS選擇器。因此在使用每種組件之前,最好閱讀一下組件文檔中對於CSS選擇器的描述。

Jsoup對於CSS選擇器實現較好,如希望了解CSS選擇器的使用,建議閱讀Jsoup的CSS選擇器規范文檔

瀏覽器中的javascript是直接支持CSS選擇器的,如果電腦里裝有firefox或者chrome,打開瀏覽器,按F12(調出開發者界面),隨便打開一個網頁,選擇其中的控制台(Console)標簽頁,在Console里輸入

document.querySelectorAll("a")

回車后,發現輸出了頁面中所有的超鏈接,document.querySelectorAll(CSS選擇器)獲取頁面中所有滿足CSS選擇器的元素,放到一個數組中返回。
如果只希望獲取第一個滿足CSS選擇器的元素,可以使用document.querySelector(CSS選擇器)這個方法。
瀏覽器js中的CSS選擇器與Jsoup(Java)和BeautifulSoup(Python)中實現的CSS選擇器有細微差別,不過大體相同。


四. 基於機器學習的網頁抽取

基於正則或CSS選擇器(或xpath)的網頁抽取都基於屬於基於包裝器(wrapper)的網頁抽取,這類抽取算法的通病就在於,對於不同結構的網頁,要制定不同的抽取規則。如果一個輿情系統需要監控10000個異構網站,就需要編寫並維護10000套抽取規則。從2000年左右就開始有人研究如何用機器學習的方法,讓程序在不需要人工制定規則的情況下從網頁中提取所需的信息。

從目前的科研成果看,基於機器學習的網頁抽取的重心偏向於新聞網頁內容自動抽取,即輸入一個新聞網頁,程序可以自動輸出新聞的標題、正文、時間等信息。新聞、博客、百科類網站包含的結構化數據較為單一,基本都滿足{標題,時間,正文}這種結構,抽取目標很明確,機器學習算法也較好設計。但電商、求職等類型的網頁中包含的結構化數據非常復雜,有些還有嵌套,並沒有統一的抽取目標,針對這類頁面設計機器學習抽取算法難度較大。

本節主要描述如何設計機器學習算法抽取新聞、博客、百科等網站中的正文信息,后面簡稱為網頁正文抽取(Content Extraction)。

基於機器學習的網頁抽取算法大致可以分為以下幾類:

三類算法中,第一類算法是最好實現的,也是效果最好的。

我們簡單描述一下三類算法,如果你只是希望在工程中使用這些算法,只要了解第一類算法即可。

下面會提到一些論文,但請不要根據論文里自己的實驗數據來判斷算法的好壞,很多算法面向早期網頁設計(即以表格為框架的網頁),還有一些算法的實驗數據集覆蓋面較窄。有條件最好自己對這些算法進行評測。


4.1 基於啟發式規則和無監督學習的網頁抽取算法

基於啟發式規則和無監督學習的網頁抽取算法(第一類算法)是目前最簡單,也是效果最好的方法。且其具有較高的通用性,即算法往往在不同語種、不同結構的網頁上都有效。

早期的這類算法大多數沒有將網頁解析為DOM樹,而是將網頁解析為一個token序列,例如對於下面這段html源碼:

<body>
<div>廣告...(8字)</div>
<div class="main">正文...(500字)</div>
<div class="foot">頁腳...(6字)</div>
</body>

程序將其轉換為token序列:

標簽(body),標簽(div),文本,文本....(8次),標簽(/div),標簽(div),文本,文本...(500次),標簽(/div),標簽(div),文本,文本...(6次),標簽(/div),標簽(/body)

早期有一種MSS算法(Maximum Subsequence Segmentation)以token序列為基礎,算法有多個版本,其中一個版本為token序列中的每一個token賦予一個分數,打分規則如下:

  • 一個標簽給-3.25分
  • 一個文本給1分

根據打分規則和上面的token序列,我們可以獲取一個分數序列:

-3.25,-3.25,1,1,1...(8次),-3.25,-3.25,1,1,1...(500次),-3.25,-3.25,1,1,1...(6次),-3.25,-3.25

MSS算法認為,找出token序列中的一個子序列,使得這個子序列中token對應的分數總和達到最大,則這個子序列就是網頁中的正文。從另一個角度來理解這個規則,即從html源碼字符串中找出一個子序列,這個子序列應該盡量包含較多的文本和較少的標簽,因為算法中給標簽賦予了絕對值較大的負分(-3.25),為文本賦予了較小的正分(1)。

如何從分數序列中找出總和最大的子序列可以用動態規划很好地解決,這里就不給出詳細算法,有興趣可以參考《Extracting Article Text from the Web with Maximum Subsequence Segmentation》這篇論文,MSS算法的效果並不好,但本文認為它可以代表早期的很多算法。

MSS還有其他的版本,我們上面說算法給標簽和文本分別賦予-3.25和1分,這是固定值,還有一個版本的MSS(也在論文中)利用朴素貝葉斯的方法為標簽和文本計算分數。雖然這個版本的MSS效果有一定的提升,但仍不理想。

無監督學習在第一類算法中也起到重要作用。很多算法利用聚類的方法,將網頁的正文和非正文自動分為2類。例如在《CETR - Content Extraction via Tag Ratios》算法中,網頁被切分為多行文本,算法為每行文本計算2個特征,分別是下圖中的橫軸和縱軸,紅色橢圓中的單元(行),大多數是網頁正文,而綠色橢圓中包含的單元(行),大多數是非正文,使用k-means等聚類方法,就可以很好地將正文和非正文分為兩類,然后再設計一些啟發式算法,即可區分兩類中哪一類是正文,哪一類是非正文。

網頁抽取技術和算法

早期的算法往往將token序列、字符序列作為計算特征的單元,從某種意義來說,這破壞了網頁的結構,也沒有充分利用網頁的特征。在后來的算法中,很多使用DOM樹的Node作為特征計算的基本單元,例如《Web news extraction via path ratios》、《Dom based content extraction via text density》,這些算法仍然是利用啟發式規則和無監督學習,由於使用DOM樹的Node作為特征計算的基本單元,使得算法可以獲取到更好、更多的特征,因此可以設計更好的啟發式規則和無監督學習算法,這些算法在抽取效果上,往往遠高於前面所述的算法。由於在抽取時使用DOM樹的Node作為單元,算法也可以較容易地保留正文的結構(主要是為了保持網頁中正文的排版)。

我們在WebCollector(1.12版本開始)中,實現了一種第一類算法,可以到官網直接下載源碼使用。


4.2 基於分類器的網頁抽取算法(第二類機器學習抽取算法)

實現基於分類器的網頁抽取算法(第二類算法),大致流程如下:

  • 找幾千個網頁作為訓練集,對網頁的正文和非正文(即需要抽取和不需要抽取的部分)進行人工標注。
  • 設計特征。例如一些算法將DOM樹的標簽類型(div,p,body等)作為特征之一(當然這是一個不推薦使用的特征)。
  • 選擇合適的分類器,利用特征進行訓練。

對於網頁抽取,特征的設計是第一位的,具體使用什么分類器有時候並不是那么重要。在使用相同特征的情況下,使用決策樹、SVM、神經網絡等不同的分類器不一定對抽取效果造成太大的影響。

從工程的角度來說,流程中的第一步和第二步都是較為困難的。訓練集的選擇也很有講究,要保證在選取的數據集中網頁結構的多樣性。例如現在比較流行的正文結構為:

<div>
<p>xxxx</p>
<p>xxxxxxxx</p>
<span>xxx</span>
<p>xxxxx</p>
<p>xxxx</p>
</div>

如果訓練集中只有五六個網站的頁面,很有可能這些網站的正文都是上面這種結構,而恰好在特征設計中,有兩個特征是:

  • 節點標簽類型(div,p,body等)
  • 孩子節點標簽類型頻數(即孩子節點中,div有幾個,p有幾個…)

假設使用決策樹作為分類器,最后的訓練出的模型很可能是:

如果一個節點的標簽類型為div,且其孩子節點中標簽為p的節點超過3個,則這個節點對應網頁的正文。

雖然這個模型在訓練數據集上可以達到較好的抽取效果,但顯而易見,有很多網站不滿足這個規則。因此訓練集的選擇,對抽取算法的效果有很大的影響。

網頁設計的風格一致在變,早期的網頁往往利用表格(table)構建整個網頁的框架,現在的網頁喜歡用div構建網頁的框架。如果希望抽取算法能夠覆蓋較長的時間段,在特征設計時,就要盡量選用那些不易變化的特征。標簽類型是一個很容易變化的特征,隨着網頁設計風格的變化而變化,因此前面提到,非常不建議使用標簽類型作為訓練特征。

上面說的基於分類器的網頁抽取算法,屬於eager learning,即算法通過訓練集產生了模型(如決策樹模型、神經網絡模型等)。與之對應的lazy learning,即事先不通過訓練集產生模型的算法,比較有名的KNN就是屬於lazy learning。

一些抽取算法借助KNN來選擇抽取算法,可能聽起來有些繞,這里解釋一下。假設有2種抽取算法A、B,有3個網站site1,site2,site3。2種算法在3個網站上的抽取效果(這里用0%-100%的一個數表示,越大說明越好)如下:

網站 A算法抽取效果 B算法抽取效果
site1 90% 70%
site2 80% 85%
site3 60% 87%

可以看出來,在site1上,A算法的抽取效果比B好,在site2和site3上,B算法的抽取效果較好。在實際中,這種情況很常見。所以有些人就希望設計一個分類器,這個分類器不是用來分類正文和非正文,而是用來幫助選擇抽取算法。例如在這個例子中,分類器在我們對site1中網頁進行抽取時,應該告訴我們使用A算法可以獲得更好的效果。

舉個形象的例子,A算法在政府類網站上抽取效果較好,B算法在互聯網新聞網站上抽取效果較好。那么當我對政府類網站進行抽取時,分類器應該幫我選擇A算法。

這個分類器的實現,可以借助KNN算法。事先需要准備一個數據集,數據集中有多個站點的網頁,同時需要維護一張表,表中告訴我們在每個站點上,不同抽取算法的抽取效果(實際上只要知道在每個站點上,哪個算法抽取效果最好即可)。當遇到一個待抽取的網頁,我們將網頁和數據集中所有網頁對比(效率很低),找出最相似的K個網頁,然后看着K個網頁中,哪個站點的網頁最多(例如k=7,其中有6個網頁都是來自CSDN新聞),那么我們就選擇這個站點上效果最好的算法,對這個未知網頁進行抽取。


4.3 基於網頁模板自動生成的網頁抽取算法

基於網頁模板自動生成的網頁抽取算法(第三類算法)有很多種。這里例舉一種。在《URL Tree: Efficient Unsupervised Content Extraction from Streams of Web Documents》中,用多個相同結構頁面(通過URL判斷)的對比,找出其中異同,頁面間的共性的部分是非正文,頁面間差別較大的部分有可能是正文。這個很好理解,例如在一些網站中,所有的網頁頁腳都相同,都是備案信息或者版權申明之類的,這是頁面之間的共性,因此算法認為這部分是非正文。而不同網頁的正文往往是不同的,因此算法識別出正文頁較容易。這種算法往往並不是針對單個網頁作正文抽取,而是收集大量同構網頁后,對多個網頁同時進行抽取。也就是說,並不是輸入一個網頁就可以實時進行抽取。

注:本文尚在更新中。本文由WebCollector提供,轉載請標明出處。


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com