HTMLParserライブラリの勉強中

http://htmlparser.sourceforge.net/

お題その1: Parserオブジェクトを使ってみる

プログラム
import org.htmlparser.*;
import org.htmlparser.util.*;

public class ParserTest {
	public static void main( String[] args ) throws ParserException {
		// パーサの生成
		Parser parser = new Parser( "http://zxp044.u-aizu.ac.jp" );
		// タグフィルタなしでパーシング
		parser.parse( null );
	}
}
コメント

Parser#parseは,標準出力からパース結果が出力されるだけ.
個人的には,パース結果の木構造でも返してくれるものだとばかり思ってたよ...

お題その2: Parser#visitAllNodesWithを使ってみる

プログラム
import org.htmlparser.*;
import org.htmlparser.util.*;
import org.htmlparser.visitors.*;
import org.htmlparser.tags.*;

public class ParserVisiterTest {
	public static void main( String[] args ) throws ParserException {
		// パーサーの生成
		Parser parser = new Parser( "http://zxp044.u-aizu.ac.jp/" );
		// リンクにのみ反応するvisitorを用いて,HTML内部を散策する
		parser.visitAllNodesWith( new NodeVisitor(){
			public void visitLinkTag( LinkTag linkTag ){
				System.out.println( "link: "+linkTag.getLink() );
			}
		} );
	}
}
コメント

これで,比較的自由に,HTMLのフィルタリングを行うことができそう.

お題その3: サイト内からリンクされている「ほとんど」全てのページをリストアップする

プログラム
import org.htmlparser.*;
import org.htmlparser.util.*;
import org.htmlparser.visitors.*;
import org.htmlparser.tags.*;
import java.util.*;
import java.net.*;

public class ParserBrowsingTest {
	public static void main( String[] args )
	  		throws ParserException, MalformedURLException {
		
		// 探索を開始するルートディレクトリ
		final String root = "http://zxp044.u-aizu.ac.jp/";
		
		// 今まで辿ってきたWEBページへのリンク集
		final Set visited = new HashSet();
		
		// 今まで辿ってきたWEBページから張られているWEBページへのリンク集
		final List unvisited = new LinkedList();
		unvisited.add( "http://zxp044.u-aizu.ac.jp/" );

		// リンクにのみ反応するvisitorを用いて,WEBページを収集する
		// ただし,親ディレクトリへは行かないようにする
		while( !unvisited.isEmpty() ){
			
			// 未訪問のWEBページへのリンクを取得する
			String link = (String)unvisited.remove(0);
			
			// 未訪問のリンクが,httpプロトコルでなければ,パスする.
			if( !link.startsWith("http://") ) continue;
			
			// 未訪問のリンクが,HTMLへのアクセスでなければ,
			// パスする.
			// /でアクセスできるphpスクリプトも許してしまうが,
			// 無限ループに陥ることはない.
			if( !link.endsWith(".html")
			 && !link.endsWith(".htm") 
			 && !link.endsWith(".HTM")
			 && !link.endsWith(".HTML")
			 && !link.endsWith("/") ) continue;
			
			// 未訪問のリンクを訪問済みとする.
			visited.add( link );
			
			// 未訪問のリンクのためのパーサを生成する.
			Parser parser = null;
			try {
				parser = new Parser( link );
			}catch( ParserException e ){
				// 何らかの理由で,パーサ生成に失敗したとき,
				// そのリンクは無視する.
				continue;
			}
			
			// パーサを用いて,未訪問リンクからリンクされている,
			// 新しいリンクを取得する.
			try {
				parser.visitAllNodesWith( new NodeVisitor(){
					public void visitLinkTag( LinkTag linkTag ){
						String link = linkTag.getLink();
						// リンクが,訪問済みでなく,
						// かつ未訪問リンクリストにも登録されておらず,
						// かつルートよりも下に存在するならば,
						// 未訪問リンクとする.
						if( !visited.contains(link)
						 && !unvisited.contains(link)
						 && link.startsWith(root) ){
							unvisited.add( link );
						}
					}
				} );
			}catch( EncodingChangeException e ){
				// 文字コードの判定にミスったら,もう一度,リンク探査の旅に出る.
				parser.reset();
				parser.visitAllNodesWith( new NodeVisitor(){
					public void visitLinkTag( LinkTag linkTag ){
						String link = linkTag.getLink();
						if( !visited.contains(link)
						 && !unvisited.contains(link)
						 && link.startsWith(root) ){
							unvisited.add( link );
						}
					}
				} );
			}
		}
		
		// 最終的に,どこにページに訪れたかを表示する.
		for( Iterator i=visited.iterator(); i.hasNext(); /*DoNothing*/ ){
			System.out.println( (String)i.next() );
		}
	}
}
コメント

長いな.
cgiなどを真面目に扱うと頭が痛くなりそうだったので,スタティックなWEBページに対象を限定しました.
文字コードが変化した例外は,よく分からなかったので,適当に回避する.
あとは,永遠とリンクを引っ張ってきます.

まとめ

とりあえず,サイト内の探索まではできるようになった.
今日は,ここまで.
しかし,ネットワークを意識せずにプログラミングできるなんて,なんて幸せなライブラリなんだ.