1. 程式人生 > >Lucene學習——IKAnalyzer中文分詞(二)

Lucene學習——IKAnalyzer中文分詞(二)

一、環境

1、平臺:MyEclipse8.5/JDK1.5

2、框架:Lucene3.6.1/IKAnalyzer2012/htmlparser

二、目標

2、試圖發現些什麼

三、開發除錯

1、改寫原有的搜尋引擎程式,利用IKAnalyzer使之支援中文分詞,程式碼如下

package org.cyxl.lucene.test;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.IndexWriterConfig.OpenMode;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.Searcher;
import org.apache.lucene.search.TopScoreDocCollector;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
import org.htmlparser.NodeFilter;
import org.htmlparser.Parser;
import org.htmlparser.beans.StringBean;
import org.htmlparser.filters.NodeClassFilter;
import org.htmlparser.tags.LinkTag;
import org.htmlparser.util.NodeList;
import org.htmlparser.util.ParserException;
import org.htmlparser.visitors.HtmlPage;
import org.wltea.analyzer.lucene.IKAnalyzer;

public class ParseURL {
	//索引目錄
	private static final String INDEX_DIR = "myindex";
	//已經存在的url列表
	private static List<String> urls=new ArrayList<String>();

	/**
	 * 索引器,對目標url建立索引
	 * @param url	目標網址
	 * @throws IOException
	 * @throws ParserException
	 */
	@SuppressWarnings("deprecation")
	private static void indexer(String url) throws IOException, ParserException {
		//儲存索引的目錄
		File indexDir = new File(INDEX_DIR);
		//目錄不存在,建立該目錄
		if (!indexDir.exists()) {
			indexDir.mkdir();
		}
		//獲取網頁純文字
		String content = getText(url);
		//獲取網頁標題
		String title = getTitle(url);

		System.out.println("title:" + title);
		
		if(title==null || content==null || content.trim().equals(""))
		{
			return;
		}
//		System.out.println("content:" + content);
//		URL path=new URL(url);
//		InputStream stream=path.openStream();
//		
//		Reader reader=new InputStreamReader(stream);
		
//		Reader reader=new InputStreamReader(new ByteArrayInputStream(content.getBytes()));
//		Reader reader2=new InputStreamReader(new ByteArrayInputStream(title.getBytes()));
		
		Document doc = new Document();
		//加入url域
		doc.add(new Field("url", url, Field.Store.YES,
						Field.Index.NOT_ANALYZED));
		//加入標題域
		doc.add(new Field("title", title, Field.Store.YES,
				Field.Index.ANALYZED));
//		doc.add(new Field("title",reader2));
		//Index.ANALYZED分詞後構建索引
		//加入內容域
		doc.add(new Field("content", content, Field.Store.YES,
				Field.Index.ANALYZED));
//		doc.add(new Field("content",reader));
		
		//建立IKAnalyzer中文分詞物件
		Analyzer analyzer=new IKAnalyzer();
		//索引目錄
		Directory dir=FSDirectory.open(indexDir);
		//配置IndexWriterConfig
		IndexWriterConfig iwConfig = new IndexWriterConfig(Version.LUCENE_36 , analyzer);
		iwConfig.setOpenMode(OpenMode.CREATE_OR_APPEND);
		//建立寫索引物件
		IndexWriter writer = new IndexWriter(dir,iwConfig);
		//寫入文件
		writer.addDocument(doc);
		//關閉
		writer.close();
		//建立了索引的網址加入到已經存在的網址列表中
		urls.add(url);
	}

	/**
	 * 搜尋器,根據輸入的文字去搜索
	 * @param words		輸入的文字
	 * @param field		搜尋的域
	 * @throws CorruptIndexException
	 * @throws IOException
	 * @throws ParseException
	 */
	@SuppressWarnings("deprecation")
	private static void searcher(String words,String field) throws CorruptIndexException,
			IOException, ParseException {
		File indexDir = new File(INDEX_DIR);
		//索引目錄
		Directory dir=FSDirectory.open(indexDir);
		//根據索引目錄建立讀索引物件
		IndexReader reader = IndexReader.open(dir);
		//搜尋物件建立
		IndexSearcher searcher = new IndexSearcher(reader);
		//IKAnalyzer中文分詞
		Analyzer analyzer = new IKAnalyzer();
		//建立查詢解析物件
		QueryParser parser = new QueryParser(Version.LUCENE_36,field, analyzer);
		parser.setDefaultOperator(QueryParser.AND_OPERATOR);
		//根據域和目標搜尋文字建立查詢器
		Query query = parser.parse(words);
		System.out.println("Searching for: " + query.toString(field));
		//對結果進行相似度打分排序
		TopScoreDocCollector collector = TopScoreDocCollector.create(5 * 10,false);
		searcher.search(query, collector);
		//獲取結果
		ScoreDoc[] hits = collector.topDocs().scoreDocs;

		int numTotalHits = collector.getTotalHits();

		System.out.println(numTotalHits + " total matching pages");
		//顯示搜尋結果
		for (int i = 0; i < hits.length; i++) {
			Document doc = searcher.doc(hits[i].doc);
			String url = doc.get("url");
			String title=doc.get("title");
			String content=doc.get("content");
			
			System.out.println((i + 1) + "." + title);
			System.out.println("-----------------------------------");
			System.out.println(content.substring(0,100)+"......");
			System.out.println("-----------------------------------");
			System.out.println(url);
			System.out.println();
		}
	}

	/**
	 * 收入網站
	 * @param url	網站首頁url,也可以為網站地圖url
	 * @throws ParserException
	 * @throws IOException
	 * @throws ParseException
	 */
	private static void addSite(String url) throws ParserException, IOException, ParseException
	{
		long start=System.currentTimeMillis();
		System.out.println("start add...");
		//獲取目標網頁的所有連結
		List<String> links = getLinks(url);
		System.out.println("url count:"+links.size());
		for(int i=0;i<links.size();i++)
		{
			String link=links.get(i);
			System.out.println((i+1)+"."+link);
			
			if(!urls.contains(link))
			{
				//對未建立過索引的網頁建立索引
				indexer(link);
			}
			else
			{
				System.out.println("["+link+"] exist");
			}
		}
		
		System.out.println("end...");
		
		long end=System.currentTimeMillis();
		
		System.out.println("cost "+(end-start)/1000+" seconds");
	}
	/**
	 * 獲取網頁純文字
	 * @param url	目標網址
	 * @return
	 * @throws ParserException
	 */
	private static String getText(String url) throws ParserException {
		StringBean sb = new StringBean();

		// 設定不需要得到頁面所包含的連結資訊
		sb.setLinks(false);
		// 設定將不間斷空格由正規空格所替代
		sb.setReplaceNonBreakingSpaces(true);
		// 設定將一序列空格由一個單一空格所代替
		sb.setCollapse(true);
		// 傳入要解析的URL
		sb.setURL(url);
		// 返回解析後的網頁純文字資訊
		String content = sb.getStrings();
		// System.out.println(content);
		return content;
	}

	/**
	 * 獲取網頁標題
	 * @param path
	 * @return
	 * @throws IOException
	 * @throws ParserException
	 */
	private static String getTitle(String path) throws IOException,
			ParserException {
		String title = "";

		
		try {
			
			Parser parser=new Parser(path);
			
			HtmlPage page = new HtmlPage(parser);  
	        parser.visitAllNodesWith(page); 
	        title=page.getTitle();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			//e.printStackTrace();
			title = "no title";
		}

		return title.trim();
	}

	/**
	 * 獲取網頁中所有的連結
	 * @param url
	 * @return
	 * @throws ParserException
	 */
	private static List<String> getLinks(String url) throws ParserException
	{
		List<String> links=new ArrayList<String>();
		//建立連結節點的過濾器
		NodeFilter filter = new NodeClassFilter(LinkTag.class);  
		Parser parser = new Parser();  
		parser.setURL(url);
		//設定目標網頁的編碼方式
		//parser.setEncoding("utf-8");
		//因為有些時候不清楚目標網頁的編碼方式,這裡我們採用指定一
		//個編碼集合,然後通過試探的方式得到目標網頁的編碼方式
		parser.setEncoding(CharsetAutoSwitch.dectedEncode(url));  
		NodeList list = parser.extractAllNodesThatMatch(filter);  
		for (int i = 0; i < list.size(); i++) {  
			LinkTag node = (LinkTag) list.elementAt(i);
			//獲取連結的目標網址
			String link=node.extractLink();
			if(link!=null && !link.trim().equals("") && !link.equals("#"))
			{
				//將目標網址加入到該頁面的所有網址列表中
				links.add(link);  
			}
		}
		
		return links;
	}
	
	public static void main(String[] args) throws IOException, ParseException,
			InterruptedException, ParserException {
		String url = "http://www.csdn.net/";
		//收錄網站
		addSite(url);
		//搜有標題帶有“搜尋引擎”字眼的網頁
		searcher("搜尋引擎","title");
	}
	
}


2、測試結果,如下

載入擴充套件詞典:ext.dic
載入擴充套件停止詞典:stopword.dic
載入擴充套件停止詞典:chinese_stopword.dic
Searching for: +搜尋引擎 +搜尋 +索引 +引擎
3 total matching pages
1.搜尋引擎-CSDN.NET
-----------------------------------
搜尋引擎-CSDN.NET
業界
移動開發
雲端計算
軟體研發
專題
程式設計師雜誌
產品
創業
職場
人物
設計
開源
iOS
Android
WindowsPhone
H......
-----------------------------------
http://www.csdn.net/article/tag/%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E

2.充分使用HTML5特性進行搜尋引擎優化(SEO) | Intel? Developer Zone
-----------------------------------
充分使用HTML5特性進行搜尋引擎優化(SEO) | Intel? Developer Zone
跳轉到主要內容
Intel? Developer Zone
社群
按行業焦點選擇
遊戲和媒體......
-----------------------------------
http://g.csdn.net/5231640

3.龐果網-Pongo.cn -IT網際網路計算機通訊領域職位搜尋引擎
-----------------------------------
龐果網-Pongo.cn -IT網際網路計算機通訊領域職位搜尋引擎
企業使用者
登入
CSDN會員可直接登入
帳 號
密 碼
記住我 忘記密碼
").append("
我的pongo |"......
-----------------------------------
http://www.pongo.cn


3、具體的程式碼解釋根據程式碼註釋進行理解

四、總結

1、由於IKAnalyzer2012只支援Lucene3.3以上版本,所以我們這裡採用Lucene3.6.1,和之前的方式稍微有些差別,具體參見程式碼和註釋

2、該程式已經支援了中文站點的收錄的搜尋,以初步達成了中文分詞和搜尋的目的

3、試圖發現些什麼,主要有一下幾點

       1)ext.dic自己的詞典如何擴充套件。比如我們這裡收錄了CSDN網站,由於它是一個以程式開發為主的部落格站點,所以我們搜尋“搜尋引擎”時,應該將其看成一個詞,而不是分成“搜尋引擎 +搜尋 +索引 +引擎”,還有“儲存過程”等,我們可以將其加入到擴充套件詞典中。這裡主要跟專案的業務關係比較大,所以應該根據專案來確定擴充套件詞典

       2)chinese_stopword.dic的擴充套件也可以根據專案業務來擴充套件,但這個相對固定和簡單些

       3)對於網站的收錄,這裡只做了簡單的首頁中連結的挖掘。實際收錄時可以更加深層次的挖掘出所有該站點的頁面。但某頁面如何判定是否屬於該站點對我目前來說是一個難點,希望知者不吝賜教

       4)該程式只是個簡單的測試雛形,如何使之有用值得我去思考