手动解析HTML是一件很崩溃的事情,sun的swing里也有解析HTML的东东,不过已经是古董了,实在不好拿出来丢Java的人了。
今天要用的是Apache的一个开源项目,html parser。
它的强大不用多说,且看它提供的几个sample吧。
首先去htmlparser.sourceforge.net上去下载,在解压开之后目录里有几个目录,分别存放着src,jars,javadoc之类的,其中bin里有好几个xxx.cmd,用命令行运行那几个脚本,参数就加某个网页的地址就行了。
主意不要拿javaeye的网页做测试哦,不行的,因为sample里的HTTP客户端是用的sun的URLConnection,请求javaeye的话响应的内容是一个空文档,这只是URLConnection的众多bug之一,建议大家不要用哦,如果你只是测试的话那到无所谓咯。
其中stringextractor.cmd是解析出网页内的纯文本内容的,linkextractor.cmd是解析网页里的连接的,非常有用哦。
接下来就要开始编程了,如何使用这个好工具捏?貌似官方并米有详细的文档说明,就这几个sample就已经大致说清楚如何用了。我们找到这几个cmd文件,打开找到其中调用的是那个类,因为它的sample并没有单独拿出来,得要我们自己去它的source里去找。我的建议是在Eclipse的工程里添加那个jar文件,然后把jar文件设置src,这样代码看起来爽一些啦。
小研究一下那几个sample是如何实现的,然后想想你自己的需求,借题发挥吧,这是java程序员的强项了。
上面提到了那几个脚本的参数是网页地址,它的代码里也是在构建Parser对象的时候用的也是url字符串作为参数的,其实如果你想用URLConnection之外的其他Http客户端组件来代替html parser的内置请求方式,也不用担心,因为Parser对象同样提供了html内容作为参数的构建方式:Parser p = Parser.createParser(String html, String charser);
下面是我在项目中用的两个小method,模仿了sample里的代码
/**
* 用来抽取html里的连接地址,并转换了相对地址为绝对地址
*/
private void extractLinks(URL pageURL, Parser parser) {
Map<String, String> links = new HashMap<String, String>();
try {
NodeFilter filter = new NodeClassFilter(LinkTag.class);
NodeList list = parser.extractAllNodesThatMatch(filter);
for (int i = 0; i < list.size(); i++) {
LinkTag n = (LinkTag) list.elementAt(i);
String link = n.getLink();
if (!n.isHTTPLink()) {
continue;
}
if (link.startsWith("http://")) {
try {
link = new URL(link).toString();
links.put(HashUtil.md5(link), link);
} catch (MalformedURLException e) {
continue;
}
} else {
try {
link = new URL(pageURL, link).toString();
links.put(HashUtil.md5(link), link);
} catch (MalformedURLException e) {
continue;
}
}
}
} catch (ParserException e) {
// TODO 处理异常
e.printStackTrace();
} catch (RuntimeException e) {
// TODO 处理异常
e.printStackTrace();
}
doc.setDocs(createDoc(links));
}
/**
* 抽取html里的文本内容,里面使用了一个自定义的节点访问器,TextExtractVisitor,下面一段有定义
*/
private Map<String, Object> extractText(Parser parser) {
try {
TextExtractVistor textExtractor = new TextExtractVistor();
parser.visitAllNodesWith(textExtractor);
doc.getText().setText(textExtractor.getText());
doc.getText().setDigest(textExtractor.getDigest());
doc.getText().setKeywords(textExtractor.getKeywords());
doc.getText().setTitle(textExtractor.getTitle());
return textExtractor.getHashedTextTags();
} catch (ParserException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
/**
* 用来抽取html里文本等相关内容的节点访问器
*/
public class TextExtractVistor extends NodeVisitor {
StringBuilder textBuf = new StringBuilder(4096);
private boolean mIsScript;
private boolean mIsStyle;
private boolean mIsPre;
protected int mCollapseState;
private final String NEWLINE = System.getProperty("line.separator");
private final int NEWLINE_SIZE = NEWLINE.length();
private boolean mIsMeta;
private boolean mIsH;
private boolean isStrong;
private StringBuilder digest = new StringBuilder(512);
private StringBuilder keywords = new StringBuilder(100);
private boolean isTtitle;
private String title;
private Map<String, Object> textTags = new HashMap<String, Object>();
protected void carriageReturn() {
int length;
length = textBuf.length();
if ((0 != length)
&& ((NEWLINE_SIZE <= length) && (!textBuf.substring(
length - NEWLINE_SIZE, length).equals(NEWLINE))))
textBuf.append(NEWLINE);
mCollapseState = 0;
}
public void visitStringNode(Text string) {
if (!mIsScript && !mIsStyle) {
String text = string.getText();
textTags.put(HashUtil.md5(text), null);
if (!mIsPre) {
text = Translate.decode(text);
text = text.replace('\u00a0', ' ');
collapse(textBuf, text);
} else {
textBuf.append(text);
if(mIsMeta){
keywords.append(text);
}else if(mIsH || isStrong){
digest.append(text);
}else if(isTtitle){
title = text;
}
}
}
}
public void visitTag(Tag tag) {
String name = tag.getTagName();
if (name.equalsIgnoreCase("PRE"))
mIsPre = true;
else if (name.equalsIgnoreCase("SCRIPT"))
mIsScript = true;
else if (name.equalsIgnoreCase("STYLE"))
mIsStyle = true;
else if (name.equalsIgnoreCase("META"))
mIsMeta = true;
else if (name.startsWith("[H|h]"))
mIsH = true;
else if (name.equalsIgnoreCase("STRONG"))
isStrong = true;
else if (name.equalsIgnoreCase("TITLE"))
isTtitle = true;
if (tag.breaksFlow())
carriageReturn();
}
public void visitEndTag(Tag tag) {
String name;
name = tag.getTagName();
if (name.equalsIgnoreCase("PRE"))
mIsPre = false;
else if (name.equalsIgnoreCase("SCRIPT"))
mIsScript = false;
else if (name.equalsIgnoreCase("STYLE"))
mIsStyle = false;
else if (name.equalsIgnoreCase("META"))
mIsMeta = false;
else if (name.startsWith("[H|h]"))
mIsH = false;
else if (name.equalsIgnoreCase("STRONG"))
isStrong = false;
else if (name.equalsIgnoreCase("TITLE"))
isTtitle = false;
}
public String getText() {
return textBuf.toString();
}
public String getDigest(){
return digest.toString();
}
public String getKeywords(){
return keywords.toString();
}
public String getTitle(){
return title;
}
public Map<String, Object> getHashedTextTags(){
return textTags;
}
protected void collapse(StringBuilder buffer, String string) {
int chars;
char character;
chars = string.length();
if (0 != chars) {
for (int i = 0; i < chars; i++) {
character = string.charAt(i);
switch (character) {
// see HTML specification section 9.1 White space
// http://www.w3.org/TR/html4/struct/text.html#h-9.1
case '\u0020':
case '\u0009':
case '\u000C':
case '\u200B':
case '\r':
case '\n':
if (0 != mCollapseState)
mCollapseState = 1;
break;
default:
if (1 == mCollapseState)
buffer.append(' ');
mCollapseState = 2;
buffer.append(character);
}
}
}
}
}