摘 要:使用java字符串處理和多線程并發技術,構造實用的頁面解析工具,實現網站內容高效、低噪地批量析取。
關鍵詞:噪音;線程;析取
隨著網絡應用的普及、使用、加工網站信息的需求日益增多,如何方便、及時、快捷的獲取互聯網網站的信息成為網絡編程開發的一項新課題。近年來,java技術在網絡編程領域悄然興起,在各種開源產品的支持下,java技術的應用如火如荼,網站信息的析取方面,出現了DOM、SAX等成熟的開發技術,這些技術的基本特征是先格式化網站頁面,再使用標簽模板過濾析取相關內容,但是了解并掌握這些技術需要有較高的java開發水平,對于java的初學者來說學習消化的周期比較長,難以上手。
筆者曾利用字符串處理等較易掌握的技術開發出了網站信息的批量析取系統,在實際工作中取得了較好的應用效果,在此共享出來,供各位讀者參考。
1 網站頁面析取
這是系統功能實現的基礎,把網站頁面轉換為可以操作的字符串形式,這里使用了一款開源產品HTMLPARSER,筆者通過實際測試,發現HTMLPARSER比其他同類產品(如Jtidy等)的容錯性、通用性更好,解析頁面的完整度更高,具體做法如下:
1.1 構造解析器
這里采用LinkTag.class格式解析頁面中的部分,可以解析到javascript的跳轉代碼,而HTMLPARSER中推薦的StringExtractor用法只能解析到
核心代碼如下:
NodeFilter filter = new NodeClassFilter(LinkTag.class);
Parser parser = new Parser();
parser.setURL(url);//url:需要解析的頁面地址
parser.setEncoding(codeset);//codeset:頁面編碼設置
NodeList list = parser.extractAllNodesThatMatch(filter);
list就是解析出的結果集,再使用toHtml()方法就能得到一般的html代碼了:
for (int i = 0; i < list.size(); i++)
{
String s[i]= list.elementAt(i).toHtml();
}
至此,系統完成了頁面解析工作,網站頁面轉換成了普通的html代碼,存入數組s中,為下一步進行字符串操作做好準備。
1.2 頁面內容抽取
頁面轉換完成后,如何從一大堆字符中獲取需要的文本呢?目前許多做法是使用DOM技術進行節點遍歷,但是由于封裝了許多底層的操作,使得程序靈活性、執行效率大打折扣,同時頁面的匹配問題使得抽取內容不可避免的帶有噪聲。
進行網站頁面的抽取工作應該是基于這樣一個前提:網站頁面是由統一的發布系統生成的,頁面具有統一的樣式。如果沒有這樣一個前提,那么任何一個工具都不可能對樣式繁多的頁面進行統一匹配。如果不能對頁面實行統一匹配,那批量抽取也就不可能了,頁面內容的抽取就失去了意義。如今大型網站內容的發布基本上都是使用統一的發布工具進行的,這就使得網站頁面具有統一的格式,具備了內容抽取的應用前提。
經過解析器的解析,就可以使用java方便的字符處理功能對規律性頁面內容進行抽取,筆者開發的系統以用戶指定的標題頁面為起始點,自動抽取頁面中所有標題的內容,以下是抽取代碼片段:
/*--參數說明--
t_start:開始析取具體標題行的起始標志
l_start:標題頁面超聯區域起始點標識串
l_end:標題頁面超聯區域終結點標識串
l_len:l_start的字符串長度
txt_start:內容頁面起始標識串
txt_len:txt_start字符串的長度
txt_end:內容頁有效區域終結點標識
*/
……
for (int i = 0; i < list.size(); i++)
{
if(s[i].indexOf(t_start)!=-1)
{
s1=s[i].substring(s[i].indexOf(l_start)+l_len);
m[n][0]=s1.substring(0,s1.indexOf(l_end)); //標題超聯
s3=s1.substring(s1.indexOf(\">\")+1);
s3=s3.replaceAll(\"[<][^>]*\",\"\");
m[n][1]=s3.replaceAll(\"[>]\",\"\");//標題主體
try
{
//獲得具體內容
StringExtractor xx=new StringExtractor(m[n][0]);
String yy=xx.extractStrings(1);
if(txt_start.equals(\"\"))
{
m[n][2]=yy.substring(yy.indexOf(s3.trim()),yy.indexOf(txt_end));
}
else
{
m[n][2]=yy.substring(yy.indexOf(txt_start)+txt_len,yy.indexOf(txt_end));
}
n++;//數組增加一行
}
……
}
}
……
執行完成后,頁面的鏈接、標題和內容就儲存在二維數組m中,其行數就是頁面包含的標題條數。然后可以根據需要利用java的數據庫操作或文件操作,將數組m中的數據存入數據庫或寫成文本文件。
通過字符串操作方式抽取出的文本最大的特點是數據噪音小,更加干凈,便于進一步的分析使用。
2 析取數據的批量操作
通過頁面解析與內容抽取,已經可以非常輕松地得到頁面干凈的純文本數據,接下來的工作就是讓計算機高效率地為我們析取多個網站的頁面,這里需要使用java的多線程技術。
從便于代碼封裝的角度考慮,筆者使用 Runnable 接口來實現多線程,把第一部分中頁面內容析取代碼統一寫成一個方法,再創建 一個Thread 類的實例,并將該方法置入run()中,實現多線程的并發運行。相關java多線程的開發細節讀者可以去查閱具體開發手冊,在這里重點闡述一下線程同步過程中的關鍵點,即共享互斥的控制。
在Java中,當多個線程試圖同時修改某個實例的內容時,就會造成沖突,要實現共享互斥,使多線程同步,最簡單的方法是使用synchronized標記,對同一個實例來說,任一時刻只能有一個synchronized方法在執行。當一個方法正在執行某個synchronized方法時,其他線程如果想要執行這個實例的任意一個synchronized方法,都必須等待當前執行synchronized方法的線程退出此方法后,才能依次執行,因此對多線程共享互斥的控制關鍵是確定需要使用synchronized方法保護。
筆者將網站析取代碼統一寫成一個方法,主程序采用循環調用的方式啟動多個并發線程,各個線程通過接受不同的頁面參數來實現不同網站頁面的析取,這就要確保線程參數在執行前不被置換,采用生產者——消費者模式,利用synchronized標記實現線程并發控制。核心代碼如下:
//--主程序--
public static void main(String[] args)
{
int k=0;
MultiExtractInfo r=new MultiExtractInfo();//初始化類
Thread[] th = new Thread[20];//線程數量
for (k=0;k<20;k++)
{
synchronized(r)
{
isdone=1;
if (num>5) //線程最大并發量為6
{
isWait=true;
}
while (isWait)
{
try
{
r.wait();
}
……
}
r.SetV();//析取頁面參數賦值
th[k]=new Thread(r);
th[k].start();//啟動線程
num++;
while (!isdone)
{
try
{
r.wait();//等待析取頁面參數被賦值
}
……
}
}
}
}
//--run方法--
public void run()
{
this.result_js();//頁面析取方法
synchronized(this)
{
if (num>0)
{
num--;
}
if(isWait)
{
isWait = 1;
this.notify();
}
}
}
//--頁面析取方法封裝--
void result_js()
{
……
try
{
synchronized(this)
{
if (!isdone)
{
isdone=true;
this.notify();
}
}
……
}
……
}
這樣,使用以上生產者——消費者模式,利用java的synchronized標記,實現了多進程的并發運行。
3 系統運行
具備了頁面析取的核心功能,接下來就是讓系統在實際工作環境中有效運轉起來,為此,還需要進行數據存儲、數據連續析取等功能的開發。筆者將析取的內容放入數據庫使用,析取的網站數據通過jdbc存入Ms SQL Server中,每次析取時將獲得的數據與上次析取的最新數據比較,若相同則終止析取,若不同則存入數據庫,以此實現數據的連續析取。讀者也可以根據系統的核心功能在其他方面進行擴展,比如以文件和時間戳的方式實現析取數據的連續存儲,為搜索引擎提供數據源等等。
筆者開發的網站信息批量析取系統目前正在安徽省政府網站管理平臺上運行,在全省網站監督、信息采集等方面發揮了巨大作用。系統結構簡潔、功能強大、伸縮性和適應性較好,運行至今,表現十分穩定,得到的數據噪音小,是一款高效、實用的應用系統。
注:本文中所涉及到的圖表、注解、公式等內容請以PDF格式閱讀原文。