[置顶] 信息检索-搜索引擎的搭建(Lucene)

时间:2022-12-08 08:04:32

搜索引擎为信息检索课程的实验设计,爬取山东大学新闻网,使用lucene等开源工具搭建小型搜索引擎。

要求 :

  1. Web网页信息抽取
    山东大学新闻网为起点进行网页的循环爬取,保 持爬虫在 view.sdu.edu.cn之内(既只爬取这个站点的网页),爬取的网页数量越多越好。
  2. 索引构建
    对上一步爬取到的网页进行结构化预处理,包括基于模板的信息抽取、分字段解析、分词、构建索引等。

  3. 检索排序
    对上一步构建的索引库进行查询,对于给定的查询,给出检索结果,明白排序的原理及方法。

  4. 检索评价
    对于给定的测试查询,利用百度的检索结果作为基准,对自己的检索结果进行评价,计算自己结果的查准率、查全率、F1值。

开发工具

  • Eclipse jdk1.8
  • Tomcat 7.0
  • Lucene4.3
  • python3.6

代码托管地址:

设计方案与过程

1. 信息爬取

爬取策略与分析

通过分析发现,山大新闻网的新闻都通过分类存放在首页上方的的二级导航中,首先,我们只爬取二级导航。
[置顶]        信息检索-搜索引擎的搭建(Lucene)

之后进入其中的一个二级导航,发现新闻在其中一条条的罗列出来,会有很多页,每一页都有,那么,我们只要获取页数,进行分页爬取,即可获取所有的新闻内容链接。
[置顶]        信息检索-搜索引擎的搭建(Lucene)
通过对新闻链接的比较发现,新闻页的链接形式为http://www.view.sdu.edu.cn/info/数字/数字.htm,则爬取时要提取这种形式的链接。


爬取过程

python爬虫使用的包为requests和BeautifulSoup。

在新闻的爬取过程中,首先获取新闻链接,然后进入链接爬取新闻内容,最后写入文本。我们可以设置一个集合set()存储爬取的链接,二级导航里每页的新闻会有重复,链接加入集合前要去重。新闻内容页的正则表达式为:

re.compile(r"info/\d+/\d+.htm")

前缀都是相同的http://www.view.sdu.edu.cn/

然后进行内容页的爬取,有些新闻页会出现乱码,链接打不开,或者格式错误等,这个时候只要抛出异常pass过去就行。我爬取了新闻的标题,链接,点击数,日期,作者,来源,正文,然后以此形式存储为txt文件,共爬取约近5万条新闻。爬取后每条新闻形式如下:
[置顶]        信息检索-搜索引擎的搭建(Lucene)

爬取内容中最难的应该是点击数的爬取,新闻点击数存放在javascript中
[置顶]        信息检索-搜索引擎的搭建(Lucene)
通过对不同页面分析,发现只有这个函数的第三个参数是变的,格式为_showDynClicks(“wbnews”, 1251758245, 新闻的id)。然后找到含有这个方法的js文件为dynclicks.js,找到相应的函数,发现函数中有一个链接,链接需要三个参数才能打开
[置顶]        信息检索-搜索引擎的搭建(Lucene)
把相应的三个参数写进链接,打开网页后内容即为点击数,爬取即可。

2.索引构建

根据文件来生成索引,如后缀为.txt等的文件:
1. FSDirectory.open(Paths.get(url));根据路径获取存储索引的目录。
2. new IndexWriter(Directory,IndexWriterConfig)创建索引
3. 索引指定目录的文件
4. 将文件写入lucene中的文档(Document)

分词工具使用lucene自带的分词工具StandardAnalyzer(),将新闻以对象的形式读入内存,然后根据需求将新闻的不同内容构建不同的域。对新闻的标题,正文,作者,来源构建了不同的域,并使用setboost()设置了不同的权值。

权值
news_title 8
news_content 4
news_editor 2
news_source 1
 /** * 获取文档,文档里再设置每个字段 * * @param f */ 
private Document getDocument(File f) throws Exception {

Document doc = new Document();
News news = getNews(f);
if(news!=null){

Field titlefield=new TextField("news_title", news.getTitle(), Store.YES);
doc.add(titlefield);
titlefield.setBoost(8);
Field editorfield=new TextField("news_editor", news.getEditor(), Store.YES);
doc.add(editorfield);
editorfield.setBoost(2);
Field sourcefield=new TextField("news_source", news.getSource(), Store.YES);
doc.add(sourcefield);
sourcefield.setBoost(1);
Field contentfield=new TextField("news_content", news.getContent(), Store.YES);
doc.add(contentfield);
contentfield.setBoost(4);

}
return doc;
}

3.索引查询

根据生成的索引来进行查询,索引在上一步的创建索引的目录中

  1. 设置索引查询目录
  2. 创建 indexSearcher. ,对要查询的query进行分词
  3. 对要查询的词去相应的域中进行比对,因为是全文搜索引擎,就要同时去不同的域中进行比对。
  4. 设置返回结果的条数的文档集合
  5. 根据不同的查询类型,设置对相应的时间和点击数的权重。

对要查询的词,同时在多个域进行比对,因为使用全文搜索,输入的查询词可能在标题中,也可能在正文中,作者中和来源中。
Lucene打分公式为:

score(q,d) = coord(q,d) · queryNorm(q) · ∑( tf(t in d) · idf(t)2 · t.getBoost() · norm(t,d) )
t in q

norm(t,d) = doc.getBoost() · lengthNorm(field) · ∏f.getBoost()
field f in d named as t

Document boost:此值越大,说明此文档越重要。
Field boost:此域越大,说明此域越重要。
lengthNorm(field) = (1.0 / Math.sqrt(numTerms)):一个域中包含的Term总数越多,也即文档越长,此值越小,文档越短,此值越大。
如果不进行设定,则Document Boost和Field Boost默认为1。

String[] fields = { "news_title", "news_content" ,"news_editor","news_source"};
Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_43);// 标准分词
MultiFieldQueryParser parser2 = new MultiFieldQueryParser(Version.LUCENE_43, fields, analyzer);

Query query2 = parser2.parse(key);
TopDocs topDocs = searcher.search(query2, 500);

对新闻的查询时,不仅希望查到的内容匹配度高,也更希望查询到时间近的,热度(阅读量)更高的。所以通过对时间和点击数设置不同的权值来优化相关页面的排序,使其更符合查询的需求。
我们先用全文搜索查询到500条按照Lucene按照分数排序后的新闻链表做基础,原始权重为1,时间排序后的链表权重占0.2,点击数排序后的链表权重占0.1,进行重新计算后进行排序。

    //通过时间进行排序
public class SortByTime implements Comparator{
public int compare(Object o1, Object o2) {
News n1=(News) o1;
News n2=(News) o2;

return n2.getTime().compareTo(n1.getTime());
}
}
//newsList是Lucene按时间排序后的新闻链表
//timenewsList是按时间排序后的新闻链表
//clicknewsList是按点击数排序后的新闻链表
//renewsList是重新计算权值后的链表
Collections.sort(timenewsList, new SortByTime());
Collections.sort(clicknewsList, new SortByClick());
for(int i=0;i<newsList.size();i++){
for(int j=0;j<timenewsList.size();j++){

for(int m=0;m<clicknewsList.size();m++){
if(newsList.get(i).equals(timenewsList.get(j))){
if(newsList.get(i).equals(clicknewsList.get(m))){
renewsList.get(i).sort=(int) ((i+0.2*j+0.1*m)/3);
}
}
}
}
}
Collections.sort(renewsList, new SortByRelece());//按照新权值重新排序,相关性最高

4.前端查询

  1. 使用jsp参考百度搜索页面编写用户界面
  2. 使用Lucene 的 Highlighter 类对返回文档中的关键字高亮,可以通过在关键字前面添加 css 片段来实现。
  3. 用户输入关键词以后提交表单,后台使用 servlet 接收用户查询, 之后把查询字符串作为搜索的 key 到索引库中搜索文档
  4. 根据用户的不同需求可以通过相关性,时间和热度进行查询

搜索效果:

[置顶]        信息检索-搜索引擎的搭建(Lucene)

[置顶]        信息检索-搜索引擎的搭建(Lucene)
[置顶]        信息检索-搜索引擎的搭建(Lucene)