网页解析,即程序自动分析网页内容、获取信息,从而进一步处理信息。
网页解析是实现网络爬虫中不可缺少而且十分重要的一环,由于本人经验也很有限,我仅就我们团队开发基于关键词匹配和模板匹配的主题爬虫的经验谈谈如何实现网页解析。
首先,必须说在最前的是我们使用的工具——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>标签中往往有对网页内容最精炼的描述,开发者应该考虑性能与代价
好,我的经验就介绍完了。我还很菜,如有说的不对、讲得不好的地方望读者指正、提出建议!