Tidy 是 W3C 用來解析網頁的一個軟件包,可以方便地將 HTML 文檔轉換為符合 XML 標準的文檔,由于 XML 可以方便地使用 XSLT 技術對內容進行抽取,所以使用 Tidy 配合 XSLT 可以方便地將各種網頁的內容抽取出來,保存成我們需要的格式。
通過 JTidy 可以方便地將標準的 HTML 網頁轉換為 XML 的 DOM 對象,然后,通過 XPaht 和 XSLT 將需要的內容抽取出來。
?
使用 JTidy 抽取網頁內容的代碼如下:
?
package com.tsinghua;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.tidy.Configuration;
import org.w3c.tidy.Tidy;
import org.xml.sax.SAXException;
public class HTMLParserByW3CDOM {
?private Templates template;
?/*
? * 解析網頁
? * XSLTFileName?用于解析網頁的樣式表文件名
? * HTMLFileName 待解析的網頁文件名
? * OutputFileName 輸出文件名
? */
?public void parser(String HTMLFileName, String OutputFileName)
?{
??if( this.template != null){
???Document doc =? this.HTMLToXML( HTMLFileName );?// 解析網頁,返回 W3c Document 文檔對象
???Transformer(doc, OutputFileName);????// 使用樣式表轉換 Document 為最終結果
??}
?}
?
?/**
? * 解析網頁,轉換為 W3C Document 文檔對象
? * @param fileName?HTML 網頁的文件名
? * @return???utf-8 W3C Document 文檔對象
? */
?private Document HTMLToXML(String fileName) {
??Logger log = Logger.getLogger("HTMLToXML");
??Document doc = null;
??try{
???FileInputStream in = new FileInputStream( fileName );?// 打開文件,轉換為 UTF-8 編碼?
???InputStreamReader isr = new InputStreamReader(in, "GB2312");?// 源文件編碼為 gb2312
???
???File tmpNewFile = File.createTempFile("GB2312",".html");?// 轉換后的文件,設定編碼為 utf-8
???FileOutputStream out = new FileOutputStream( tmpNewFile );?// 需要將文件轉換為字符流
???OutputStreamWriter osw = new OutputStreamWriter( out , "UTF-8");// 指定目標編碼為 utf-8
???osw.write("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
???
???char[] buffer = new char[10240];??????// 文件緩沖區
???int len = 0;???????????// 使用字符讀取方式,循環讀取源文件內容
???while( (len = isr.read(buffer)) !=-1 )?????// 轉換后寫入目標文件中
???{
????osw.write( buffer, 0, len);
???}
???osw.close();???????????// 轉換完成
???isr.close();
???out.close();
???in.close();
???
???if( log.isLoggable( Level.INFO)){
????log.info("HTML 文檔轉 UTF-8 編碼完成!");
???}
???
???//??設置 tidy ,準備轉換
???Tidy tidy = new Tidy();
???tidy.setXmlOut(true);????// 輸出格式 xml
???tidy.setDropFontTags(true);???// 刪除字體節點
???tidy.setDropEmptyParas(true);??// 刪除空段落
???tidy.setFixComments(true);???// 修復注釋
???tidy.setFixBackslash(true);???// 修復反斜桿
???tidy.setMakeClean(true);???// 刪除混亂的表示
???tidy.setQuoteNbsp(false);???// 將空格輸出為
???tidy.setQuoteMarks(false);???// 將雙引號輸出為 "
???tidy.setQuoteAmpersand(true);??// 將 & 輸出為 &
???tidy.setShowWarnings(false);??// 不顯示警告信息
???tidy.setCharEncoding(Configuration.UTF8);?// 文件編碼為 UTF8
???
???
???FileInputStream src = new FileInputStream( tmpNewFile );?//
???doc = tidy.parseDOM( src ,null );?// 通過 JTidy 將 HTML 網頁解析為
???src.close();???????????// W3C 的 Document 對象
???tmpNewFile.delete();?????????// 刪除臨時文件
???
???NodeList list = doc.getChildNodes();?????// 頁面中 DOCTYPE 中可能問題
???for(int i=0; i<list.getLength(); i++)?????// 刪除 DOCTYPE 元素
???{
????Node node = list.item(i);
????if( node.getNodeType() == Node.DOCUMENT_TYPE_NODE)?// 查找類型定義節點
????{
?????node.getParentNode().removeChild( node );
?????if( log.isLoggable( Level.INFO)){
??????log.info("已經將文檔定義節點刪除!" );
?????}
????}
???}
???
???list = doc.getElementsByTagName("script");????// 腳本中的注釋有時有問題
???for(int i=0; i<list.getLength(); i++){?????// 清理 script 元素
????Element script = (Element) list.item(i);
????if( script.getFirstChild() != null){
?????if( log.isLoggable( Level.FINEST)){
??????log.finest("刪除腳本元素: " + script.getFirstChild().getNodeValue());
?????}
?????script.removeChild( script.getFirstChild());
????}
???}
???
???list = doc.getElementsByTagName("span");????// sina 中 span 元素有時有問題
???for(int i=0; i<list.getLength(); i++){?????// 清理 span 元素
????Element span = (Element) list.item(i);
????span.getParentNode().removeChild( span );
????if( log.isLoggable( Level.FINEST)){
?????log.finest("刪除 span 元素: " );
????}
????
???}
???
???list = doc.getElementsByTagName("sohuadcode");???// 清除 sohuadcode 元素
???for(int i=0; i<list.getLength(); i++){
????Element sohuadcode = (Element) list.item(i);
????sohuadcode.getParentNode().removeChild( sohuadcode );
???}
???
???if( log.isLoggable( Level.INFO)){
????log.info("HTML 文檔解析 DOM 完成.");
???}
??}
??catch(Exception e)
??{
???log.severe(e.getMessage());
???e.printStackTrace();
??}finally
??{
???
??}
??return doc;
?}
?
?/**
? * 解析轉換的樣式表,保存為模板
? * @param xsltFileName??樣式表文件名
? * @return?????樣式表模板對象
? */
?public Templates setXSLT(String xsltFileName)
?{
??Logger log = Logger.getLogger( "setXSLT" );
??File xsltFile = new File( xsltFileName );
??StreamSource xsltSource = new StreamSource( xsltFile );??// 使用 JAXP 標準方法建立樣式表的模板對象
??TransformerFactory tff = TransformerFactory.newInstance();?// 可以重復利用這個模板
??Templates template = null;
??try {
???template = tff.newTemplates( xsltSource );
???if( log.isLoggable( Level.INFO)){
????log.info("樣式表文件 " + xsltFileName + " 解析完成");
???}
??} catch (TransformerConfigurationException e) {
???log.severe( e.getMessage() );
??}
??this.template = template;
??return template;
?}
?
?/**
? * 使用樣式表轉換文檔對象,得到最終的結果
? * @param doc???文檔對象
? * @param outFileName?保存轉換結果的文件名
? */
?private void Transformer(Document doc , String outFileName )
?{
??Logger log = Logger.getLogger( "Transformer" );
??try {
???Source source = new DOMSource( doc );
???
???File outFile = new File( outFileName );
???Result result = new StreamResult( outFile );
???
???Transformer transformer = template.newTransformer();?// 使用保存的樣式表模板對象
???transformer.transform(source, result );?????// 生成轉換器,轉換文檔對象
???if( log.isLoggable( Level.INFO)){
????log.info("轉換完成, 請查看 " + outFileName + " 文件。");
???}
??} catch (Exception e) {
???log.severe( e.getMessage() );
??}?
?}
}