接着上一节的内容,我们已经构建了一个爬取百度百科并持久化的爬虫。现在客户使用,我们需要构建一个搜索工具。
1.信息检索
这个项目的下一个阶段是实现一个搜索工具。我们需要的部分包括:
- 一个界面,其中用户可以提供检索词并查看结果。
- 一种查找机制,它接收每个检索词并返回包含它的页面。
- 用于组合来自多个检索词的搜索结果的机制。
- 对搜索结果打分和排序的算法。
2.布尔搜索
大多数搜索引擎可以执行“布尔搜索”,这意味着你可以使用布尔逻辑来组合来自多个检索词的结果。例如:
- 搜索“java + 编程”(加号可省略)可能只返回包含两个检索词:“java”和“编程”的页面。
- “java OR 编程”可能会返回包含任一检索词但不一定同时出现的页面。
- “java -印度尼西亚”可能返回包含“java”,不包含“印度尼西亚”的页面。
包含检索词和运算符的表达式称为“查询”。
当应用给搜索结果时,布尔操作符+
,OR
和-
对应于集合操作 交,并和差。例如,假设
s1
是包含“java”的页面集,s2
是包含“编程”的页面集,以及s3
是包含“印度尼西亚”的页面集。
在这种情况下:
s1
和s2
的交集是含有“java”和“编程”的页面集。s1
和s2
的并集是含有“java”或“编程”的页面集。s1
与s2
的差集是含有“java”而不含有“印度尼西亚”的页面集。
布尔搜索实现:
/**
* @Author Ragty
* @Description 计算两个搜索结果的并集(更广了)
* @Date 23:13 2019/5/30
**/
public WikiSearch or(WikiSearch that) {
Map<String,Integer> union = new HashMap<String, Integer>(map);
for(String term: that.map.keySet()) {
union.put(term,totalRelevence(this.getRelevance(term),that.getRelevance(term)));
}
return new WikiSearch(union);
}
/**
* @Author Ragty
* @Description 计算搜索项的交集
* @Date 23:22 2019/5/30
**/
public WikiSearch and(WikiSearch that) {
Map<String,Integer> intersection = new HashMap<String, Integer>();
for(String term: that.map.keySet()) {
if(map.containsKey(term)) {
intersection.put(term,totalRelevence(this.getRelevance(term),that.getRelevance(term)));
}
}
return new WikiSearch(intersection);
}
/**
* @Author Ragty
* @Description 计算搜索项的差集
* @Date 23:35 2019/5/30
**/
public WikiSearch minus(WikiSearch that) {
Map<String,Integer> difference = new HashMap<String, Integer>(map);
for (String term: that.map.keySet()) {
if (difference.containsKey(term)) {
difference.remove(term);
}
}
return new WikiSearch(difference);
}
3.打分排序
现在需要根据检索词获取用户最需要的数据,WikiSearch
对象包含 URL 到它们的相关性分数的映射。在信息检索的上下文中,“相关性分数”用于表示页面多么满足从查询推断出的用户需求。相关性分数的构建有很多种方法,但大部分都基于“检索词频率”,它是搜索词在页面上的显示次数。一种常见的相关性分数称为 TF-IDF,代表“检索词频率 - 逆向文档频率”。
下面是WikiSearch
的初始构造:
public class WikiSearch {
// map from URLs that contain the term(s) to relevance score
private Map<String, Integer> map;
public WikiSearch(Map<String, Integer> map) {
this.map = map;
}
public Integer getRelevance(String url) {
Integer relevance = map.get(url);
return relevance==null ? 0: relevance;
}
}
WikiSearch
对象包含 URL 到它们的相关性分数的映射。在信息检索的上下文中,“相关性分数”用于表示页面多么满足从查询推断出的用户需求。相关性分数的构建有很多种方法,但大部分都基于“检索词频率”,它是搜索词在页面上的显示次数。一种常见的相关性分数称为 TF-IDF,代表“检索词频率 - 逆向文档频率”。
我们将从一些更简单的 TF 开始:
- 如果查询包含单个检索词,页面的相关性就是其词频;也就是说该词在页面上出现的次数。
- 对于具有多个检索词的查询,页面的相关性是检索词频率的总和;也就是说,任何检索词出现的总次数。
搜索结果排序实现:
public List<Entry<String,Integer>> sort(){
List<Entry<String,Integer>> entries = new LinkedList<Entry<String, Integer>>(map.entrySet());
Comparator<Entry<String,Integer>> comparator = new Comparator<Entry<String, Integer>>() {
@Override
public int compare(Entry<String, Integer> o1, Entry<String, Integer> o2) {
return o2.getValue().compareTo(o1.getValue());
}
};
Collections.sort(entries,comparator);
return entries;
}
有两个sort
版本:
- 单参数版本接受列表并使用它的
compareTo
方法对元素进行排序,因此元素必须是Comparable
。 - 双参数版本接受任何对象类型的列表和一个
Comparator
,它是一个提供compare
方法的对象,用于比较元素。
4.检索测试?
下面提供检索代码:
public static void main(String[] args) throws IOException {
Jedis jedis = JedisMaker.make();
JedisIndex index = new JedisIndex(jedis);
// search for the first term
String term1 = "java";
System.out.println("Query: " + term1);
WikiSearch search1 = search(term1, index);
search1.print();
// search for the second term
String term2 = "programming";
System.out.println("Query: " + term2);
WikiSearch search2 = search(term2, index);
search2.print();
// compute the intersection of the searches
System.out.println("Query: " + term1 + " AND " + term2);
WikiSearch intersection = search1.and(search2);
intersection.print();
}
检索结果为:
Query: java?
https://baike.baidu.com/item/Java/85979 38
https://baike.baidu.com/item/%E6%AC%A7%E6%9C%8B%E6%B5%8F%E8%A7%88%E5%99%A8 12
https://baike.baidu.com/item/%E7%94%B2%E9%AA%A8%E6%96%87/471435 10
https://baike.baidu.com/item/PHP/9337 6
https://baike.baidu.com/item/Brendan%20Eich 6
https://baike.baidu.com/item/Rhino 6
https://baike.baidu.com/item/LiveScript 6
https://baike.baidu.com/item/Sun/69463 4
Query: programming?
https://baike.baidu.com/item/C%E8%AF%AD%E8%A8%80 6
https://baike.baidu.com/item/%E8%84%9A%E6%9C%AC%E8%AF%AD%E8%A8%80 1
Query: java AND programming?
https://baike.baidu.com/item/C%E8%AF%AD%E8%A8%80 7
https://baike.baidu.com/item/%E8%84%9A%E6%9C%AC%E8%AF%AD%E8%A8%80 3
5.搜索优化
现存的问题:
若同一主题的文章,A1万字,它的关键词出现的次数肯定会高,B100字,出现的次数肯定会少。对于具有多个检索词的查询,每个页面的总体相关性目前是每个检索词的相关性的总和。但实际上采用我们上述所用的方法不能客观的去选出内容最符合的文章。
我们需要的解决方案:
去掉网络中止词后,显示一个词在文本中所出现的频率,频率越高,则说明该搜索词与文章的相关性越大。
后期可用
TF-IDF
算法优化