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