基於htmlparser實現網頁內容解析


網頁解析,即程序自動分析網頁內容、獲取信息,從而進一步處理信息。

網頁解析是實現網絡爬蟲中不可缺少而且十分重要的一環,由於本人經驗也很有限,我僅就我們團隊開發基於關鍵詞匹配和模板匹配的主題爬蟲的經驗談談如何實現網頁解析。

首先,必須說在最前的是我們使用的工具——htmlparser

簡要地說,htmlparser包提供方便、簡潔的處理html文件的方法,它將html頁面中的標簽按樹形結構解析成一個一個結點,一種類型的結點對應一個類,通過調用其方法可以輕松地訪問標簽中的內容。

我所使用的是htmlparser2.0,也就是最新版本。強烈推薦。

好,進入正題。

對於主題爬蟲,它的功能就是將與主題相關的網頁下載到本地,將網頁的相關信息存入數據庫。

網頁解析模塊要實現兩大功能:1.從頁面中提取出子鏈接,加入到爬取url隊列中;2.解析網頁內容,與主題進行相關度計算。

由於網頁內容解析需要頻繁地訪問網頁文件,如果通過url訪問網絡獲取文件的時間開銷比較大,所以我們的做法是將爬取隊列中的網頁統統下載到本地,對本地的網頁文件進行頁面內容解析,最后刪除不匹配的網頁。而子鏈接的提取比較簡單,通過網絡獲取頁面文件即可。對於給定url通過網絡訪問網頁,和給定文件路徑訪問本地網頁文件,htmlparser都是支持的

1.子鏈接的提取:

做頁面子鏈接提取的基本思路是:

1.用被提取的網頁的url實例化一個Parser

2.實例化Filter,設置頁面過濾條件——只獲取<a>標簽與<frame>標簽的內容

3.用Parser提取頁面中所有通過Filter的結點,得到NodeList

4.遍歷NodeList,調用Node的相應方法得到其中的鏈接,加入子鏈接的集合

5.返回子鏈接集合

OK,上代碼:

 1 package Crawler;
 2 
 3 
 4 import java.util.HashSet;
 5 import java.util.Set;
 6 
 7 import org.htmlparser.Node;
 8 import org.htmlparser.NodeFilter;
 9 import org.htmlparser.Parser;
10 import org.htmlparser.filters.NodeClassFilter;
11 import org.htmlparser.filters.OrFilter;
12 import org.htmlparser.tags.LinkTag;
13 import org.htmlparser.util.NodeList;
14 import org.htmlparser.util.ParserException;
15 
16 public class HtmlLinkParser {
17     //獲取子鏈接,url為網頁url,filter是鏈接過濾器,返回該頁面子鏈接的HashSet
18     public static Set<String> extracLinks(String url, LinkFilter filter) {
19 
20         Set<String> links = new HashSet<String>();
21         try {
22             Parser parser = new Parser(url);
23             parser.setEncoding("utf-8");
24             // 過濾 <frame >標簽的 filter,用來提取 frame 標簽里的 src 屬性所表示的鏈接
25             NodeFilter frameFilter = new NodeFilter() {
26                 public boolean accept(Node node) {
27                     if (node.getText().startsWith("frame src=")) {
28                         return true;
29                     } else {
30                         return false;
31                     }
32                 }
33             };
34             // OrFilter 接受<a>標簽或<frame>標簽,注意NodeClassFilter()可用來過濾一類標簽,linkTag對應<標簽>
35             OrFilter linkFilter = new OrFilter(new NodeClassFilter(
36                     LinkTag.class), frameFilter);
37             // 得到所有經過過濾的標簽,結果為NodeList
38             NodeList list = parser.extractAllNodesThatMatch(linkFilter);
39             for (int i = 0; i < list.size(); i++) {
40                 Node tag = list.elementAt(i);
41                 if (tag instanceof LinkTag)// <a> 標簽
42                 {
43                     LinkTag link = (LinkTag) tag;
44                     String linkUrl = link.getLink();// 調用getLink()方法得到<a>標簽中的鏈接
45                     if (filter.accept(linkUrl))//將符合filter過濾條件的鏈接加入鏈接表
46                         links.add(linkUrl);
47                 } else{// <frame> 標簽
48                     // 提取 frame 里 src 屬性的鏈接如 <frame src="test.html"/>
49                     String frame = tag.getText();
50                     int start = frame.indexOf("src=");
51                     frame = frame.substring(start);
52                     int end = frame.indexOf(" ");
53                     if (end == -1)
54                         end = frame.indexOf(">");
55                     String frameUrl = frame.substring(5, end - 1);
56                     if (filter.accept(frameUrl))
57                         links.add(frameUrl);
58                 }
59             }
60         } catch (ParserException e) {//捕捉parser的異常
61             e.printStackTrace();
62         }
63         return links;
64     }
65 }

 

此時可能有讀者在想:呵~呵~博主忽略了相對url鏈接的問題了(-.-)

其實我想到了,一開始我寫了一個private方法專門把任何url轉換成絕對url鏈接。后來調試的時候我發現我的方法根本沒用,因為htmlparser很人性化地自動完成了這個轉換!

另外,Parser是需要設置編碼的,在這段程序中我直接設置為utf-8。實際上網頁的編碼方式是多種多樣的,在<meta>標簽中有關於編碼方式的信息,如果編碼不正確,頁面的文本內容可能是亂碼。不過,在子鏈接提取的部分,我們僅對標簽內部的內容進行處理,這些內容是根據html語法編寫的,不涉及編碼的問題。

2.解析網頁內容:

基本思路:

1.讀取html文件,獲得頁面編碼,獲得String格式的文件內容

2.用頁面編碼實例化html文件的Parser

3.對需要提取的結點設置相應的Filter

4.根據給定的Filter,用Parser解析html文件

5.提取結點中的文本內容,進行處理(本例中是關鍵字匹配,計算主題相關度)

  1 import java.io.BufferedReader;
  2 import java.io.FileInputStream;
  3 import java.io.FileNotFoundException;
  4 import java.io.FileReader;
  5 import java.io.IOException;
  6 import java.io.InputStreamReader;
  7 import java.util.regex.Matcher;
  8 import java.util.regex.Pattern;
  9 
 10 import org.htmlparser.Parser;
 11 import org.htmlparser.filters.NodeClassFilter;
 12 import org.htmlparser.tags.HeadingTag;
 13 import org.htmlparser.tags.LinkTag;
 14 import org.htmlparser.tags.MetaTag;
 15 import org.htmlparser.tags.ParagraphTag;
 16 import org.htmlparser.tags.TitleTag;
 17 import org.htmlparser.util.NodeList;
 18 import org.htmlparser.util.ParserException;
 19 
 20 import java.util.Set;
 21 import multi.patt.match.ac.*;
 22 
 23 public class HtmlFileParser {
 24     String filepath=new String();//html文件路徑
 25     private static String[] keyWords;//關鍵詞列表
 26     /*static{
 27         keyWords=read("filePath");//從指定文件中讀取關鍵詞列表
 28     }*/
 29     public HtmlFileParser(String filepath){
 30         this.filepath=filepath;
 31     }
 32     public String getTitle(){//得到頁面標題
 33         FileAndEnc fae=readHtmlFile();
 34         int i=0;
 35         try{
 36             //實例化一個本地html文件的Parser
 37             Parser titleParser = Parser.createParser(fae.getFile(),fae.getEnc());
 38             NodeClassFilter titleFilter =new NodeClassFilter(TitleTag.class);
 39             NodeList titleList = titleParser.extractAllNodesThatMatch(titleFilter);
 40             //實際上一個網頁應該只有一個<title>標簽,但extractAllNodesThatMatch方法返回的只能是一個NodeList
 41             for (i = 0; i < titleList.size(); i++) {
 42                 TitleTag title_tag = (TitleTag) titleList.elementAt(i);
 43                 return title_tag.getTitle();
 44             }
 45         }catch(ParserException e) {
 46             return null;
 47         }
 48         return null;
 49     }
 50     public String getEncoding(){//獲得頁面編碼
 51         FileAndEnc fae=readHtmlFile();
 52         return fae.getEnc();
 53     }
 54     public float getRelatGrade(){//計算網頁的主題相關度
 55         FileAndEnc fae=readHtmlFile();
 56         String file=fae.getFile();
 57         String enC=fae.getEnc();
 58         String curString;
 59         int curWordWei = 1;//當前關鍵詞權重
 60         float curTagWei = 0;//當前標簽權重
 61         float totalGra = 0;//總相關度分
 62         int i;
 63         AcApply obj = new AcApply();//實例化ac自動機
 64         Pattern p = null;
 65         Matcher m = null;
 66         try{//根據不同標簽依次進行相關度計算
 67             //title tag    <title>
 68             curTagWei=5;
 69             Parser titleParser = Parser.createParser(file,enC);
 70             NodeClassFilter titleFilter =new NodeClassFilter(TitleTag.class);
 71             NodeList titleList = titleParser.extractAllNodesThatMatch(titleFilter);
 72             for (i = 0; i < titleList.size(); i++) {
 73                 TitleTag titleTag=(TitleTag)titleList.elementAt(i);
 74                 curString=titleTag.getTitle();
 75                 Set result = obj.findWordsInArray(keyWords, curString);//ac自動機的方法返回匹配的詞的表
 76                 totalGra=totalGra+result.size()*curTagWei;//計算相關度
 77             }
 78             //meta tag of description and keyword <meta>
 79             curTagWei=4;
 80             Parser metaParser = Parser.createParser(file,enC);
 81             NodeClassFilter metaFilter =new NodeClassFilter(MetaTag.class);
 82             NodeList metaList = metaParser.extractAllNodesThatMatch(metaFilter);
 83             p = Pattern.compile("\\b(description|keywords)\\b",Pattern.CASE_INSENSITIVE);
 84             for (i = 0; i < metaList.size(); i++) {
 85                 MetaTag metaTag=(MetaTag)metaList.elementAt(i);
 86                 curString=metaTag.getMetaTagName();
 87                 if(curString==null){
 88                     continue;
 89                 }
 90                 m = p.matcher(curString); //正則匹配name是description或keyword的<meta>標簽
 91                 if(m.find()){
 92                     curString=metaTag.getMetaContent();//提取其content
 93                     Set result = obj.findWordsInArray(keyWords, curString);
 94                     totalGra=totalGra+result.size()*curTagWei;
 95                 }
 96                 else{
 97                     curString=metaTag.getMetaContent();
 98                     Set result = obj.findWordsInArray(keyWords, curString);
 99                     totalGra=totalGra+result.size()*2;
100                 }
101             }
102             //heading tag <h*>
103             curTagWei=3;
104             Parser headingParser = Parser.createParser(file,enC);
105             NodeClassFilter headingFilter =new NodeClassFilter(HeadingTag.class);
106             NodeList headingList = headingParser.extractAllNodesThatMatch(headingFilter);
107             for (i = 0; i < headingList.size(); i++) {
108                 HeadingTag headingTag=(HeadingTag)headingList.elementAt(i);
109                 curString=headingTag.toPlainTextString();//得到<h*>標簽中的純文本
110                 if(curString==null){
111                     continue;
112                 }
113                 Set result = obj.findWordsInArray(keyWords, curString);
114                 totalGra=totalGra+result.size()*curTagWei;
115             }
116             //paragraph tag <p>
117             curTagWei=(float)2.5;
118             Parser paraParser = Parser.createParser(file,enC);
119             NodeClassFilter paraFilter =new NodeClassFilter(ParagraphTag.class);
120             NodeList paraList = paraParser.extractAllNodesThatMatch(paraFilter);
121             for (i = 0; i < paraList.size(); i++) {
122                 ParagraphTag paraTag=(ParagraphTag)paraList.elementAt(i);
123                 curString=paraTag.toPlainTextString();
124                 if(curString==null){
125                     continue;
126                 }
127                 Set result = obj.findWordsInArray(keyWords, curString);
128                 totalGra=totalGra+result.size()*curTagWei;
129             }
130             //link tag <a>
131             curTagWei=(float)0.25;
132             Parser linkParser = Parser.createParser(file,enC);
133             NodeClassFilter linkFilter =new NodeClassFilter(LinkTag.class);
134             NodeList linkList = linkParser.extractAllNodesThatMatch(linkFilter);
135             for (i = 0; i < linkList.size(); i++) {
136                 LinkTag linkTag=(LinkTag)linkList.elementAt(i);
137                 curString=linkTag.toPlainTextString();
138                 if(curString==null){
139                     continue;
140                 }
141                 Set result = obj.findWordsInArray(keyWords, curString);
142                 totalGra=totalGra+result.size()*curTagWei;
143             }        
144         }catch(ParserException e) {
145             return 0;
146         }
147         return totalGra;
148     }
149     private FileAndEnc readHtmlFile(){//讀取html文件,返回字符串格式的文件與其編碼
150         StringBuffer abstr = new StringBuffer();
151         FileAndEnc fae=new FileAndEnc();
152         try{
153             //實例化默認編碼方式的BufferefReader
154             BufferedReader enCReader= new BufferedReader(new InputStreamReader(new FileInputStream(filepath),"UTF-8"));
155             String temp=null;
156             while((temp=enCReader.readLine())!=null){//得到字符串格式的文件
157                 abstr.append(temp);
158                 abstr.append("\r\n");
159             }
160             String result=abstr.toString();
161             fae.setFile(result);
162             String encoding=getEnc(result);
163             fae.setEnc(encoding);//得到頁面編碼
164             //根據得到的編碼方式實例化BufferedReader
165             BufferedReader reader= new BufferedReader(new InputStreamReader(new FileInputStream(filepath),encoding));
166             StringBuffer abstrT = new StringBuffer();
167             while((temp=reader.readLine())!=null){
168                 abstrT.append(temp);
169                 abstrT.append("\r\n");
170             }
171             result=abstrT.toString();
172             fae.setFile(result);//得到真正的頁面內容
173         } catch (FileNotFoundException e) {
174             System.out.println("file not found");
175             fae=null;
176         } catch (IOException e) {
177             // TODO Auto-generated catch block
178             e.printStackTrace();
179             fae=null;
180         } finally {
181             return fae;
182         }
183     }
184     private String getEnc(String file){//根據正則匹配得到頁面編碼
185         String enC="utf-8";
186         Pattern p = Pattern.compile("(charset|Charset|CHARSET)\\s*=\\s*\"?\\s*([-\\w]*?)[^-\\w]"); 
187         Matcher m = p.matcher(file);
188         if(m.find()){ 
189             enC=m.group(2);
190         }
191         return enC;
192     }
193 }

 

 

 讀者需要注意兩點:

1.用BufferedReader讀取文件是需要編碼方式的,但是第一次讀取我們必然不知道網頁的編碼。好在網頁對於編碼的描述在html語言框架中,我們用默認的編碼方式讀取文件就可以獲取編碼。但這個讀取的文件的文本內容可能因為編碼不正確而產生亂碼,所以得到編碼后,我們應使用得到的編碼再實例化一個BufferedReader讀取文件,這樣得到的文件就是正確的了(除非網頁本身給的編碼就不對)。

獲得正確的編碼對於解析網頁內容是非常重要的,而網絡上什么樣的網頁都有,我推薦使用比較基礎、可靠的方法獲得編碼,我使用的是正則匹配。

舉個例子:

這是http://kb.cnblogs.com/page/143965/的對編碼的描述:

<meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>

這是http://www.ucsd.edu/的對編碼的描述:

<meta charset="utf-8"/>

2.不熟悉html的讀者可能有所不知<meta>的作用,來看看博客園首頁的源碼:

<meta name="keywords" content="博客園,開發者,程序員,軟件開發,編程,代碼,極客,Developer,Programmer,Coder,Code,Coding,Greek,IT學習"/><meta name="description" content="博客園是面向程序員的高品質IT技術學習社區,是程序員學習成長的地方。博客園致力於為程序員打造一個優秀的互聯網平台,幫助程序員學好IT技術,更好地用技術改變世界。" />

這兩類<meta>標簽的很好的描述了網頁的內容

@編輯 博客園首頁這個keyword的內容里這“Greek”……極客是“Geek”,“Greek”是希臘人

3.由於網頁的正文通常是一段最長的純文本內容,所以當我們得到一個<p>,<li>,<ul>標簽的純文本后,我們可以通過判斷字符串的長度來得到網頁的正文。

對頁面大量的信息進行處理是很費時的,頁面的<title>標簽和<meta>標簽中往往有對網頁內容最精煉的描述,開發者應該考慮性能與代價

 

 好,我的經驗就介紹完了。我還很菜,如有說的不對、講得不好的地方望讀者指正、提出建議!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM