3、使用Lucene实现千度搜索
1、新建Web项目
新建一个Web项目,我命名为SearchEngine,然后导入Java包:
除了上篇博客中的Jar包外,我还引入了 IKAnalyzer2012_FF.jar 包和struts2的相关包:
IKAnalyzer:是用来进行中文分词的一个jar包,他会把中文分词一个个合理的词来进行检索;
Struts2:一会儿搜索结果,使用Struts2展示到浏览器中;
2.准备数据源
我使用linux 命令 wget 爬了一个网站内的一部分html网页,同样将它放在一个纯英文的目录:
3、创建索引
新建一个类CreateIndex:
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.TrueFileFilter;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.IndexWriterConfig.OpenMode;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
import org.junit.Test;
import org.wltea.analyzer.lucene.IKAnalyzer;
import com.HtmlBeanUtil;
import com.model.HtmlBean; public class CreateIndex {
public static final String DATA_DIR="E:/data/engine/www.bjsxt.com";
public static final String INDEX_DIR="E:/data/engine/index";
public void createIndex() throws IOException{
FSDirectory dir = FSDirectory.open(new File(INDEX_DIR));
// 使用中文分词的jar包进行分词
IKAnalyzer analyzer = new IKAnalyzer(true);
IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_4_9, analyzer);
config.setOpenMode(OpenMode.CREATE_OR_APPEND);
IndexWriter writer = new IndexWriter(dir, config);
File file = new File(DATA_DIR);
Collection<File> files = FileUtils.listFiles(file, TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE);
for(File f : files){
// 将原数据源内的内容通过抓取,返回一个实体类方便存储
HtmlBean hb = HtmlBeanUtil.parseHtml(f);
if(hb!=null && hb.getTitle()!=null && !hb.getTitle().trim().equals("")){
Document doc = new Document();
// 存储三个内容,标题,内容,url (实际上内容可能会更多比如关键字,描述等)
doc.add(new TextField("title",hb.getTitle(), Store.YES));
doc.add(new TextField("content",hb.getContent(), Store.YES));
doc.add(new TextField("url",hb.getUrl(), Store.YES));
writer.addDocument(doc);
}
}
writer.close();
}
}
实体HtmlBean和HtmlBeanUtil:
public class HtmlBean {
private String title;
private String content;
private String url;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}
import java.io.File;
import java.io.IOException;
import net.htmlparser.jericho.Element;
import net.htmlparser.jericho.HTMLElementName;
import net.htmlparser.jericho.Source;
import com.model.HtmlBean;
public class HtmlBeanUtil {
public static HtmlBean parseHtml(File file){
try {
Source source = new Source(file);
Element title = source.getFirstElement(HTMLElementName.TITLE);
String content = source.getTextExtractor().toString();
HtmlBean hb = new HtmlBean();
if(title==null || title.getTextExtractor() == null){
return null;
}
hb.setTitle(title.getTextExtractor().toString());
hb.setContent(content);
String path = file.getAbsolutePath();
String url = "http://"+path.substring(15);
url = url.replace("\\", "/");
hb.setUrl("http://"+path.substring(15));
return hb;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
使用单元测试跑一下创建索引的方法,最后会得到这么几个索引数据库文件:
4、创建检索类SearchIndex:
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.document.Document;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.TopScoreDocCollector;
import org.apache.lucene.search.highlight.Highlighter;
import org.apache.lucene.search.highlight.InvalidTokenOffsetsException;
import org.apache.lucene.search.highlight.QueryScorer;
import org.apache.lucene.search.highlight.SimpleHTMLFormatter;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
import org.wltea.analyzer.lucene.IKAnalyzer; import com.model.HtmlBean;
import com.model.Page; public class SearchIndex {
public Page search(String keyWord,int pageNum,int pageSize) throws IOException, ParseException, InvalidTokenOffsetsException{
Directory dir = FSDirectory.open(new File(CreateIndex.INDEX_DIR));
IndexReader reader = DirectoryReader.open(dir);
IndexSearcher searcher = new IndexSearcher(reader);
// 使用中文分词器把用户输入的内容进行分词
Analyzer analyzer = new IKAnalyzer(true);
QueryParser parser = new QueryParser(Version.LUCENE_4_9, "title", analyzer);
Query query = parser.parse(keyWord);
//format 用来制定要高亮显示的词的样式
SimpleHTMLFormatter format = new SimpleHTMLFormatter("<font color='red'>","</font>");
Highlighter high = new Highlighter(format ,new QueryScorer(query));
// pageNum*pageSize 控制显示的最大条数
TopScoreDocCollector results = TopScoreDocCollector.create(pageNum*pageSize, false);
searcher.search(query, results);
// 检索出来想要的结果的条数,可以实现分页
TopDocs topDocs = results.topDocs((pageNum-1)*pageSize, pageNum*pageSize);
Page page = new Page();
page.setPageNum(pageNum);
page.setPageSize(pageSize);
page.setTotalCount(topDocs.totalHits);
ScoreDoc[] docs = topDocs.scoreDocs;
List<HtmlBean> list = new ArrayList<HtmlBean>();
for(ScoreDoc scoreDoc : docs){
Document document = reader.document(scoreDoc.doc);
String title = document.get("title");
String content = document.get("content");
String url = document.get("url");
//获取到检索的结果以后,可以使用Highlighter获取高亮效果
title = high.getBestFragment(analyzer, "title", title);
content = high.getBestFragment(analyzer, "content", content);
HtmlBean hb = new HtmlBean();
hb.setTitle(title);
hb.setContent(content);
hb.setUrl(url);
list.add(hb);
}
// 计算记录的总页数
if(page.getTotalCount() <= pageSize){
page.setTotalPageCount(1);
}else{
if(page.getTotalCount() % pageNum == 0){
page.setTotalPageCount(page.getTotalCount() / pageSize);
}else{
page.setTotalPageCount(page.getTotalCount() / pageSize + 1);
}
}
page.setList(list);
return page;
}
}
同时我还用到了一个Page的实体,用来存放并返回查到的结果:
import java.util.List; public class Page {
private long totalCount;
private int pageSize;
private int pageNum;
private long totalPageCount;
private List<HtmlBean> list;
public long getTotalCount() {
return totalCount;
}
public void setTotalCount(long totalCount) {
this.totalCount = totalCount;
}
public int getPageSize() {
return pageSize;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
public int getPageNum() {
return pageNum;
}
public void setPageNum(int pageNum) {
this.pageNum = pageNum;
}
public List<HtmlBean> getList() {
return list;
}
public void setList(List<HtmlBean> list) {
this.list = list;
}
public long getTotalPageCount() {
return totalPageCount;
}
public void setTotalPageCount(long totalPageCount) {
this.totalPageCount = totalPageCount;
}
}
5、页面呈现内容
页面呈现内容,由于过于简单就不描述太多了;
Struts.xml和web.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.1//EN" "http://struts.apache.org/dtds/struts-2.1.dtd">
<struts>
<constant name="struts.118n.encoding" value="UTF-8"></constant>
<constant name="struts.action.extension" value="do"></constant>
<package name="pages" namespace="/pages" extends="struts-default">
<action name="engine_*" class="com.actions.SearchEngineAction" method="{1}">
<result name="message">/WEB-INF/message.jsp</result>
<result name="index">/index.jsp</result>
</action>
</package>
</struts>
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<display-name>SearchEngine</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
</web-app>
Action:
import java.io.File;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.search.highlight.InvalidTokenOffsetsException;
import org.apache.struts2.ServletActionContext;
import com.lucene.CreateIndex;
import com.lucene.SearchIndex;
import com.model.Page; public class SearchEngineAction {
private int pageNum;
private String keyword;
public String create(){
HttpServletRequest request = ServletActionContext.getRequest();
try {
File file = new File(CreateIndex.INDEX_DIR);
if(file.exists()){
for(File f : file.listFiles()){
f.delete();
}
file.delete();
file.mkdirs();
}
CreateIndex createIndex = new CreateIndex();
createIndex.createIndex();
request.setAttribute("message", "创建索引完成...");
} catch (Exception e) {
e.printStackTrace();
request.setAttribute("message", e.getMessage());
}
return "message";
} public String search() throws IOException, ParseException, InvalidTokenOffsetsException{
HttpServletRequest request = ServletActionContext.getRequest();
int pageSize = 10;
if(pageNum < 1){
setPageNum(1);
}
if(keyword!=null && !keyword.trim().equals("")){
SearchIndex search = new SearchIndex();
Page page = search.search(keyword, pageNum, pageSize); request.setAttribute("page", page);
request.setAttribute("keyword", keyword);
}
return "index";
} public int getPageNum() {
return pageNum;
} public void setPageNum(int pageNum) {
this.pageNum = pageNum;
} public String getKeyword() {
return keyword;
} public void setKeyword(String keyword) {
this.keyword = keyword;
}
}
页面展示:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>千度一下,你就知道</title>
<style type="text/css">
body{padding-left: 132px;padding-right: 132px;}
#keyword{width: 521px; height: 20px; padding: 9px 7px;font: 16px arial; border: 1px solid #b8b8b8; border-bottom: 1px solid #ccc;
vertical-align: top; outline: none; box-shadow: none;}
.s_btn { width: 100px; height: 40px; color: white; border: solid 1px #3385ff; font-size:13pt; letter-spacing: 1px;background: #3385ff;
border-bottom: 1px solid #2d78f4;outline: medium; -webkit-appearance: none; -webkit-border-radius: 0;}
h4{ font-weight: normal;}
</style>
</head>
<body>
<form action="/SearchEngine/pages/engine_search.do" method="post">
<di>
<input name="keyword" id="keyword" type="text" maxlength="100" autocomplete="off" value="${keyword }" />
<input type="submit" class="s_btn" value="千度一下" />
</div>
<hr />
<c:if test="${page != null }" >
<div>千度为您找到相关结果约${page.totalCount}个</div>
<c:forEach items="${page.list }" var="record">
<h4><a href="${record.url }" target="_black">${record.title }</a></h4>
<p>${record.content }
<br />
<a href="${record.url }" target="_black">${record.url }</a>
</p> </c:forEach>
<div>
<c:forEach var="i" begin="1" end="${page.totalPageCount }" step="1">
<a> ${i} </a>
</c:forEach>
</div>
</c:if>
</form>
</body>
</html>
运行一下看一下效果:
没有搜索的时候:
我们搜索一个比较复杂的词:“尚学堂java官方网站中国媒体报道培训机构” 这些词都是完全合在一起的,看看中文分词器能否给分出来:
完全可以,把能够组合的词全显示出来了;
说明:在该DEMO中,我爬了尚学堂的网站,但这里没有要给他们打广告的意思哦!纯粹感觉好玩!
关键Lucene 根据权重检索,其中有一个算法是TF-IDF算法,详细可查看这篇文章:
http://www.cnblogs.com/biyeymyhjob/archive/2012/07/17/2595249.html
小结:在该文中做了一个史上最简陋的搜索引擎,但Lucene的强大是显而已见的,搜索速度也非常快;该小Demo还有很多功能需要补全,比如分页,比如按权重搜索等等等等等...有感兴趣的伙伴可以试着补充全一下,让千度搜索比百度搜索更NB(在梦中YY);
3、使用Lucene实现千度搜索的更多相关文章
- Lucene5.5.4入门以及基于Lucene实现博客搜索功能
前言 一直以来个人博客的搜索功能很蹩脚,只是自己简单用数据库的like %keyword%来实现的,所以导致经常搜不到想要找的内容,而且高亮显示.摘要截取等也不好实现,所以决定采用Lucene改写博客 ...
- Lucene.net站内搜索—6、站内搜索第二版
目录 Lucene.net站内搜索—1.SEO优化 Lucene.net站内搜索—2.Lucene.Net简介和分词Lucene.net站内搜索—3.最简单搜索引擎代码Lucene.net站内搜索—4 ...
- Lucene.net站内搜索—5、搜索引擎第一版实现
目录 Lucene.net站内搜索—1.SEO优化 Lucene.net站内搜索—2.Lucene.Net简介和分词Lucene.net站内搜索—3.最简单搜索引擎代码Lucene.net站内搜索—4 ...
- Lucene.net站内搜索—4、搜索引擎第一版技术储备(简单介绍Log4Net、生产者消费者模式)
目录 Lucene.net站内搜索—1.SEO优化 Lucene.net站内搜索—2.Lucene.Net简介和分词Lucene.net站内搜索—3.最简单搜索引擎代码Lucene.net站内搜索—4 ...
- Lucene.net站内搜索—3、最简单搜索引擎代码
目录 Lucene.net站内搜索—1.SEO优化 Lucene.net站内搜索—2.Lucene.Net简介和分词Lucene.net站内搜索—3.最简单搜索引擎代码Lucene.net站内搜索—4 ...
- Lucene.net站内搜索—2、Lucene.Net简介和分词
目录 Lucene.net站内搜索—1.SEO优化 Lucene.net站内搜索—2.Lucene.Net简介和分词Lucene.net站内搜索—3.最简单搜索引擎代码Lucene.net站内搜索—4 ...
- Lucene.net站内搜索—1、SEO优化
目录 Lucene.net站内搜索—1.SEO优化 Lucene.net站内搜索—2.Lucene.Net简介和分词Lucene.net站内搜索—3.最简单搜索引擎代码Lucene.net站内搜索—4 ...
- Lucene.Net 站内搜索
Lucene.Net 站内搜索 一 全文检索: like查询是全表扫描(为性能杀手)Lucene.Net搜索引擎,开源,而sql搜索引擎是收费的Lucene.Net只是一个全文检索开发包(只是帮我们 ...
- 【Lucene3.6.2入门系列】第03节_简述Lucene中常见的搜索功能
package com.jadyer.lucene; import java.io.File; import java.io.IOException; import java.text.SimpleD ...
随机推荐
- 在COM组件中调用JavaScript函数
转载自: http://blog.csdn.net/cheungmine/article/details/1451489 要求是很简单的,即有COM组件A在IE中运行,使用JavaScript(JS) ...
- 【总结】OJ练习,进行的一些编程语言方面总结
1.STL vector只有四个构造函数 ) explicit vector (const allocator_type& alloc = allocator_type()); fill () ...
- Combination Sum 解答
Question Given a set of candidate numbers (C) and a target number (T), find all unique combinations ...
- Best Time to Buy and Sell Stock II 解答
Question Say you have an array for which the ith element is the price of a given stock on day i. Des ...
- SQL Server 日期函数:某天是星期几?
[鹏城万里] 发表于 www.sqlstudy.com 要得到某一天是星期几,需要用到 SQL Server 中的日期函数:datename(). 今天是星期几,例子 1: set language ...
- Android自定义带标题边框的Layout
今天工作中又碰到个小问题,项目需要用到像Java Swing的JPanel一样带标题边框的布局,Android里没有类似控件,想到这个也不难,自己画了一个,是继承LinearLayout的一个自定义布 ...
- 基于express框架的应用程序骨架生成器介绍
作者:zhanhailiang 日期:2014-11-09 本文将介绍怎样使用express-generator工具高速生成基于express框架的应用程序骨架: 1. 安装express-gener ...
- [Linked List]Partition List
Total Accepted: 53879 Total Submissions: 190701 Difficulty: Medium Given a linked list and a value x ...
- 注册宝第五期beta2插件模块下载及说明
原文:http://bbs.84zcb.com/showtopic-1882.aspx [软件名称]:注册宝插件模块 [软件版本]:V1.4 [软件大小]:6.36M [软件语言]:简体中文 [授权方 ...
- spring下配置dbcp,c3p0,proxool[转]
不管通过何种持久化技术,都必须通过数据连接访问数据库,在Spring中,数据连接是通过数据源获得的.在以往的应用中,数据源一般是Web应用服务器提供的.在Spring中,你不但可以通过JNDI获取应用 ...