什么是搜索引擎?
1.有一个主页,有搜索框,在搜索框中输入的内容称为“查询词”。
例如:百度搜索
2.还有搜索结果页,包含若干条搜索结果。
3.针对每一个搜索结果,都会包含查询词或者查询词的一部分和查询词具有一定的相关性。
例如:上图中标红的词。
4.每个搜索结果包含好几个部分:
4.1标题
4.2描述—通常是页面的摘要信息
4.3子链接
4.4展示url
4.5图片
4.6点击url----点击url将会跳转到目标url上
搜索引擎的功能
查找用户输入的查询词在哪些页面中出现过或者出现过一部分,把结果展示到页面上,点击结果就能跳转到该页面。
自然搜索结果:网页的数据通常是通过爬虫来获取的。
广告搜索结果:广告代理商把内容提交给平台。
像百度,搜狗这样的搜索引擎,是全网搜索,处理的数据量是非常巨大。
当前项目是搜索Java官方API文档。
为什么做Java API文档搜索?
1.官方文档上没有一个好用的搜索框。
2.JavaAPI文档数量比较少,设备资源有限。
3.文档内容不需要使用爬虫来获取,可以直接在官网上下载。
搜索引擎是如何工作的?
搜索引擎需要在很多的网页数据中找到用户输入的查询词(部分查询词)。
搜索引擎后台已经获取了很多很多的网页数据,每个网页数据都是一个html。称为一个“文档”。搜索引擎要知道该查询词在哪些文档出现过。
1.暴力搜索
依次遍历每个文件,查看当前文件中是否包含查询词。(文档数据多,效率低下)
2.倒排索引(这是一种特殊的数据结构)
要理解倒排索引,我们先要了解正排索引。首先我们要进行分词,然后对每个词的出现进行统计。
正排索引就是:现有文档,每个文档都有一个ID,然后文档中的内容进行分词,统计每个词出现的次数。通过文档->词建立关系,这个就是正排索引。
例如:
假设我们搜一个关键字(Tom),当100个网页的10个网页含有Tom这个关键字。但是由于是正排是文档id作为索引的,所以我们不得不把100个网页都扫描一遍,然后找出其中含有Tom的10个网页。然后再进行rank,sort等。效率就比较低了。
正排索引易维护,但是查找效率慢。
倒排索引就是通过词->文档建立关系,建立的是出现这个词的所有文档。
例如:
假设我们搜一个关键字(Tom),当100个网页的10个网页含有Tom这个关键字。由于是倒排是词作为索引的,所以我们只需把10个网页扫描一遍,再进行rank,sort等。效率就比较高了。
倒排索引查找效率高,但是维护教难。
倒排索引是项目最核心的部分,也是搜索引擎中最核心的部分
对用户的查询词进行分词
使用现成的第三方库进行分词
我们先下载好官方API文档,分析里面的内容,在搜索结果中填上线上文档的链接地址
项目模块划分:
1.预处理模块:把下载好的html文档进行一次初步的处理(简单分析结构并且处理掉其中的html标签)
2.索引模块:预处理得到的结果,构建正排索引+倒排索引
3.搜索模块:完成一次搜索过程基本流程(从用户输入查询词,到得到最终的搜索结果)。
4.前端模块:有一个页面,展示结果并且让用户输入数据
预处理模块
把api目录中所有的html文件进行处理->得到一个单个文件。使用行文本的方式进行组织。
这个得到的临时文件中,每一行对应到一个html文档,每一行中又包含3列。第一列表示这个文档的标题,第二列表示这个文档的url,第三列表示这个文档的正文(去掉html标签).
去除html是一个字符一个字符的读取,如果字符为‘<’,我们将标志位置位false,如果是‘>’我们将标志位置位true,如果标志位为true,我们才进行写入到StringBuilder中
去掉之后将得到的结果输入到文件中。
package parser;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
//遍历文档目录,读取所有的 html 文档内容,把结果解析成行文本文件
//每一行都对应一个文档,每一行都包含文档标题,文档的url,文档的正文
//Parser 是一个单独的可执行类
public class Parser {
//输入目录表示下载好的 JavaAPI文档
private static final String INPUT_PATH = "";
//输出目录表示预处理模块输出文件存放的目录
private static final String OUTPUT_PATH = "";
public static void main(String[] args) throws IOException {
FileWriter resultFileWriter = new FileWriter(new File(OUTPUT_PATH));
//通过main完成整个预处理的过程
//1.枚举出 INPUT_PATH 中所有的 html 文件(递归)
ArrayList<File> fileList = new ArrayList<>();
enumFile(INPUT_PATH, fileList);
//2.针对枚举出来的 html 文件路径进行遍历,依次打开每个文件,并读取内容
// 把内容转换成需要的结构化的数据(DocInfo对象)
for (File f : fileList) {
System.out.println("converting " + f.getAbsolutePath() + "...");
//最终输出的是 raw_data 文件是一个行文本文件.每一行对应一个 html 文件
//line 这个对象就对应到一个文件
String line = convertLine(f);
//3.把得到的结果写入到最终的输出文件中(OUTPUT_PATH),写成行文本的形式
resultFileWriter.write(line);
}
resultFileWriter.close();
}
private static String convertLine(File f) throws IOException {
//1.根据f 转换出标题
String title = convertTitle(f);
//2.根据f 转换出url
String url = convertUrl(f);
//3.根据f 转换出正文
String content = convertContent(f);
//4.把这三个部分拼成一行文本
// \3是分割3个部分的作用 \3是不可见的
return title + "\3" + url + "\3" + content + "\n";
}
private static String convertContent(File f) throws IOException {
//这个方法做两件事情:
//1.把html中的标签去掉
//2.把 \n 去掉
//一个一个字符读取并判定
FileReader fileReader = new FileReader(f);
boolean isContent = true;
StringBuilder output = new StringBuilder();
while (true) {
int ret = fileReader.read();
if (ret == -1) {
//读取完毕
break;
}
char c = (char) ret;
if (isContent) {
//当前这部分是正文
if (c == '<') {
isContent = false;
continue;
}
if (c == '\n' || c == '\r') {
c = ' ';
}
output.append(c);
} else {
//当前是标签
//不去写 output
if (c == '>') {
isContent = true;
}
}
}
fileReader.close();
return output.toString();
}
private static String convertUrl(File f) {
//url由两部分组成
//第一部分https://docs.oracle.com/javase/8/docs/api
//第二部分 /java/util/Collection.html
String part1 = "https://docs.oracle.com/javase/8/docs/api";
String part2 = f.getAbsolutePath().substring(INPUT_PATH.length());
return part1