一. 项目简介
项目目标:实现一个针对Java文档的搜索引擎
文档:每个待搜索的网页;
正排索引:通过文档id查找文档内容;
倒排索引:通过 词 查找文档id
- 索引模块
1)扫描下载到的文档,分析文档内容,构建出正排索引+倒排索引,并把索引内容保存到文 文件中;
2)加载制作好的索引,并提供一些API实现查正排和查倒排的功能
- 搜索模块
调用搜索模块,实现完整的搜索过程
输入:用户的查询词;
输入:完整的搜索结果(包含多条记录,每个记录有标题,描述,展示URL,点击能够跳转)
3.web模块
实现简单的web程序,能够通过网页形式与用户进行交互
二. 索引模块
分词:把完整的句子分成多个词,可通过现成的第三方库来完成分词
例如:一个 / 人 / 感觉 / 好 / 孤单
分词原理:
1.基于词库
尝试把所有的词进行穷举,把穷举结果放到词典文件中,依次取句子中的内容,每个一个/两个...词查一下;
2.基于原理
收集"语料库",进行人工标注,进一步知道哪些词在一起的概率比较大
实现索引模块:创建一个类,通过这个类完成制作索引的过程Parse:读取下载好的文档,解析文档的内容,并完成索引的操作
模块索引小结:
1.1 实现了一个Parser 类
1)通过递归方式枚举出所有的 html 文件;
2)针对每个 html 进行解析操作:
a)标题:使用文件标题;
b)URL:基于文件路径进行简单拼接(离线文档和线上文档路径的关系);
c)正文:核心操作:去标签,使用 < > 作为是否要进行拷贝数据的开关;
3)把解析结果放到 Index 类中(addDoc)
注意:html 要求内容中出现的 < 使用 < 代替,> 使用 > 代替
Java标准库中,既提供了按照字节读取的类(FileInputStream),也提供了按照字符读取的类(FileReader)
保证所有文档处理完毕再保存索引:CountDownLatch
CountDownLatch 类似于跑步比赛的裁判,所有选手都撞线就认为比赛结束了
在构造 CountDownLatch 的时候指定一下比赛选手的个数;
每个选手撞线,都要通知一下 countDown();
通过 await() 来等待所有的选手撞线完毕
守护线程 VS 非守护线程
守护线程:此线程的运行状态不会影响到进程结束
非守护线程:此线程的运行状态会影响到线程结束
之前学过的线程创建手段,默认创建非守护线程,需要通过 setDaemon 方法手动设置为守护线程
1.2 实现了一个 Index 类
核心属性:
1)正排索引,ArrayList<DocInfo> ,每个 DocInfo 表示一个文档,文档中包含了id,title,url,content;
2)倒排索引,HashMap<String, ArrayList<Weight>>,每个键值对,表示这个词在哪些文档中出现过。Weight 包含文档id和权重信息
核心方法:
1)查正排,直接按照下标来取 ArrayList<DocInfo> 中的元素即可;
2)查倒排,直接按照 key 来取 HashMap<String, ArrayList<Weight>> 中的 value 即可;
3)添加文档,Parser 类在构建索引的时候调用该方法:
a)构建正排,构造 DocInfo 对象,添加到正排索引末尾;
b)构建倒排,先进行标题和正文分词,统计词频,遍历分词结果,更新倒排索引中对应的倒排拉链,注意其中的线程安全问题;
4)保存索引,基于 json 格式把索引数据保存到指定文件中;
5)加载索引,基于 json格式对数据进行解析,把文件中的内容读出来,解析到内存中
三. 搜索模块
调用索引模块,完成搜索的核心过程
分词:针对用户输入的查询词进行分词(用户输入的查询词可能是一句话);
触发:拿着每个分词结果去倒排索引中查找,找到相关性的文档(调用 Index 类中的查倒排方法);
合并:针对多个分词结果触发出的相同文档,进行权重合并;
排序:针对上面触发出来的结果,进行排序(按照相关性降序排序) ;
包装结果:根据排序后的结果,依次去查正排,获取到每个文档的详细信息,包装成一定结构的数据返回
生成描述(正文摘要)的思路:
获取到所有查询词的分词结果,遍历分词结果,看哪个结果在正文中出现。
针对这个被包含的分词结果,去正文中查找,找到对应的位置,以这个位置为中心往前截取60字符,作为描述的开始,再从描述开始截取160个字符,作为整个描述。
正则表达式:
. 表示匹配一个非换行字符(不是 \n,不是 \r)
* 表示前面的字符可以出现若干次
.* 表示匹配非换行字符出现若干次
去掉普通标签(不去除内容):<.*?>
去掉 script 标签和内容:<scrip.*?>(.*?)</script>
? 表示"非贪婪匹配",匹配符合条件的最短结果;不带 ? 表示"贪婪匹配",匹配符号条件的最长结果
四. web模块
提供一个 web 接口,最终以网页的形式,把程序呈现给用户
前端(HTML+CSS+JS)+后端(Java,Servlet / Spring)
请求:
GET /searcher?query=[查询词] HTTP/1.1
响应:
HTTP/1.1 200 OK
{ title: "这是标题", url: "这是url", desc: "这是描述" } { } ......
.ajax: 是一个变量名,是 jquery 库提供的一个内置的对象的变量名,jquery 中的函数与方法都是 对象提供的。有的语言(Java/JS)允许 作为变量名,有的语言(C/C++)不允许。
遇见的bug:
内容太多,超出屏幕:通过CSS属性,设置 overflow: auto,让内容都局限在 .container 这个 div 内部滚动,而不是整个页面滚动。
点击后搜索页面被目标页面替换:给 a 标签加上 target="_blank" 属性即可。
实现标红逻辑:
1.修改后端代码,生成搜索结果的时候,给包含查询词的部分加上一个标记,例如套上一层 <i> 标签;
- 在前端代码中针对 <i> 标签设置样式,然后浏览器根据样式进行显示。
处理停用词:
停用词:高频但没意义的内容,如 a,is,have ...... 这类词不应该参与触发。
当查询词为 "array list" 时,分词结果为 "array"," ","list" 。
因此代码中会拿 空格 来查询倒排索引,所以要处理这类词。
重复文档问题:
把多个分词结果触发出的文档,按照 docID 进行去重,同时进行权重的合并。
去重的核心思路:
数据结构中有合并两个有序链表,此处可以按照类似思路合并n个数组,先把分词结果进行排序处理(按照 docID 升序排序),再进行合并,根据 docID 值相同的情况做权重相加
五. 部署到云服务器
- 准备好前提环境
先拥有一个云服务器
给云服务器装好系统,如 Centos7
安装一些依赖的程序,如 jdk,tomcat
- 把程序依赖的数据拷贝到云服务器上
注意:搜索程序运行时,不光代码本身,项目依赖的正排索引,倒排索引,暂停词文件, 都需要拷贝
- 调整代码路径
目的是使代码能够找到云服务器上的文件
- 将项目打包成 jar 包上传