目录

文档搜索引擎

目录

文档搜索引擎

首先获取很多网页(爬虫->一个http客户端,发送http请求获取http响应结果(就是网站))(批量化的获取很多的页面) 再根据用户输入的查询词,在网页中进行查找 用户输入查询词之后,如何让查询词和当前这些网页进行匹配 ->使用倒排索引 倒排索引 1.文档: 每个待搜索的网页(被爬虫技术,把各个网站的各个网页搜集在一起,然后保存到服务器上) 2 正排索引: 文档Id(身份标识)-> 文档内容(包含很多段落,句子,词..) 3 倒排索引: 词-> 文档id列表(包含词) key和value不一样 https://i-blog.csdnimg.cn/direct/79ecdb5f438049349ee67734b5814fc3.png 项目目标: 实现一个针对Java文档的搜索引擎 百度是属于"全站搜索",搜索震哥哥互联网上的网站 B站是属于"站内搜索".只针对某个网站的内容进行搜索 把相关的文档获取到,才能制作正排索引和倒排索引 我们直接从官网上下载文档的压缩包即可 离线文档和在线文档的路径都是对应的, 我们可以根据离线文档的路径直接构造出在线文档的路径 https://i-blog.csdnimg.cn/direct/625b63bd0e7a4609b37fdfbf77fbb836.png 模块划分 1> 索引模块 2> 搜索模块 3> web模块 https://i-blog.csdnimg.cn/direct/c7f2897eda3845c7a12b46fbe4430d41.png 分词的概念 把一个完整的句子,切分为多个词 基于现成的第三方库来实现分词 https://i-blog.csdnimg.cn/direct/6956c469294b49efa42fa8109c0bef5e.png 分词的实现原理 1 基于词库 2 基于统计: 某俩个字在一起使用的概率很大,此刻我们就可以把它们标注为一个词 单身狗这种在词库里基本找不到,因为是网络造的词 https://i-blog.csdnimg.cn/direct/0cd31d0d3d1248548bb4bef5c7c1d2a5.png 实现分词的第三方库: ansj org.ansj ansj_seg 5.1.6 分词的使用 https://i-blog.csdnimg.cn/direct/d14ee102178049949347face20ed403e.png 实现索引模块 Parser: 读取下好的文档,然后解析文档内容,完成索引的制作 一个文件的扩展名,都是随便起的,一般代表文件的格式 解析获取到的html https://i-blog.csdnimg.cn/direct/32a86ef861ce44ed9a2e6fd278e00ae7.png 1> 解析html标题 我们要想办法把html后缀名给去除 https://i-blog.csdnimg.cn/direct/b1a790cc45fc442ebe2780ceae17b4ba.png 去除 https://i-blog.csdnimg.cn/direct/121503af2a1145c1b395f5ef360fa7ad.png 完成 https://i-blog.csdnimg.cn/direct/952e3d21d39241d594c489c61d5b310a.png java计算长度 https://i-blog.csdnimg.cn/direct/de0392ab46064ef5b5e4509c828b6d0a.png 2> 获取url https://i-blog.csdnimg.cn/direct/78841e05c20f4a6784f681ba953ee3c8.png 广告结果会根据点击计费 自然搜索结果,需要根据点击来优化用户体验 https://i-blog.csdnimg.cn/direct/ad150c97485c4efeaeede1d3d826e3f4.png Java API 文档,有俩份 1 线上文档 2 线下文档 我们希望用户点击线下文档的搜索结果,就能够跳转到线上的文档页面 https://i-blog.csdnimg.cn/direct/c3892814e4ca4e2ca243909a3a192a35.png具体代码实现 有/和,但是因为浏览器的容错能力很强 ,因此是每关系的 https://i-blog.csdnimg.cn/direct/36872345f2704ea4a16e50e8efc631b5.png https://i-blog.csdnimg.cn/direct/1983a207e92e40409d6e1ac503bca17e.png 3> 解析正文 我们只关心内容,不要html标签 我们需要去掉标签 此时我们可以用正则表达式来去标签,正则表达式是一种计算机中进行字符串处理/匹配 常用的字段 核心就是通过一些特殊符号来描述字符串的特征, 然后看某个字符串是否符合这些特征 https://i-blog.csdnimg.cn/direct/b7007463bda54f66b714a1f00b63220c.png 此时我们使用更加简单的方式来实现 html都是<>包裹起来的 如果找到<直到>为止,就不把它放在内容里面(使用StringBuilder) https://i-blog.csdnimg.cn/direct/00c7148a13a541f2bf97da1e7b3c63b1.png 关于读取字符的操作 https://i-blog.csdnimg.cn/direct/ffd295863bba4c16944c6f74dbf43877.png 针对这个,我们发现有很多的换行操作,目的是为了生成描述信息,不应该有很多的换行(内容本身不应该有换行,此时要去除换行 https://i-blog.csdnimg.cn/direct/7275537789ae4292a8a3688329ff773b.png 解决方法: 多加一个判断语句 https://i-blog.csdnimg.cn/direct/a3a134f96b584500b3412ab9bd3dc56e.png https://i-blog.csdnimg.cn/direct/312feec254db44aba33c62f31c0a5c08.png 小总结: https://i-blog.csdnimg.cn/direct/fbb5337edc6d4bacb4434f5d949b33a7.png 构建正排序索引和倒排索引 https://i-blog.csdnimg.cn/direct/8dcf10721fbf4e8cbd6f31bb0ed864ce.png https://i-blog.csdnimg.cn/direct/cf8bfc2f65eb48c189972e8bba5ffb01.png索引大小就是设置的DocId https://i-blog.csdnimg.cn/direct/e5cee8836dc8462e8925c4cca1ac500c.png 正排索引实现: id ->doc对象 https://i-blog.csdnimg.cn/direct/4678597355e442a28c245c707593aefe.png 倒排索引: 词->文档id 需要进行分词 https://i-blog.csdnimg.cn/direct/21a3948c16674066a1c9ec43e7bb92dd.png 权重值: 描述词和文档的相关性,我们此时通过词出现的次数表示相关性 https://i-blog.csdnimg.cn/direct/4f9323cb138f4e5b967f566524c98e30.png 计算权重的公式: 标题1次抵文章出现10次 https://i-blog.csdnimg.cn/direct/19b93f0c24094bd0873c24b0915b4022.png 迭代这些公式的方法 “小流量实验” https://i-blog.csdnimg.cn/direct/784435da3f694c3daee1f0def2389aa5.png 是否忽略大小 https://i-blog.csdnimg.cn/direct/c0acbf90f7414d55ac8d01665bfc8e89.png 梳理倒排索引的步骤 https://i-blog.csdnimg.cn/direct/f2a8ca0600a8444e847719f6063dc644.png 什么时候使用for each https://i-blog.csdnimg.cn/direct/91468eb48bef445288844c93c5fea730.png 构建文档出现的问题 https://i-blog.csdnimg.cn/direct/b56b1e55ab954ab18ce5bfe46c0ef334.png com.fasterxml.jackson.core jackson-databind 2.13.0 json格式 反序列化: 把串转为对象 https://i-blog.csdnimg.cn/direct/b7910dfb3bea4adc9e7f07aae49cf03b.png 创建匿名内部类的实例的目的 https://i-blog.csdnimg.cn/direct/a6bebc20766f416890c9c411a8d748ef.png 计算保存和加载的时间 https://i-blog.csdnimg.cn/direct/75ff7bb779d14d49be5e505aaab3b296.png paeser和Index的关系 https://i-blog.csdnimg.cn/direct/202431782fe84bfd87966b2fef8629a2.png 生成文件的数据 https://i-blog.csdnimg.cn/direct/8178bc7494834a0392152e68f20a1782.png 衡量加载时间 https://i-blog.csdnimg.cn/direct/d2115501a905420a9f3edf9d487af838.png 性能优化 找到性能瓶颈(那个部位耗时最高) 说明遍历的耗时最多 https://i-blog.csdnimg.cn/direct/fcd2d110144f4ef79c47d344b9abf8db.png https://i-blog.csdnimg.cn/direct/f9dde7447686456f9c49c5afe4779586.png 把刚刚单线程的代码改成多线程 单线程: 18秒 https://i-blog.csdnimg.cn/direct/ae9cc816f05d43a8abd0a22ebf3799eb.png 提交任务操作很快,保存所有可能会有线程安全的问题 https://i-blog.csdnimg.cn/direct/ff7b1200734447bdb44ffd38d9d1e478.png 分析过程 https://i-blog.csdnimg.cn/direct/06da89f16fba433e94fc706ea2e0af0e.png 保证在save之前把任务都处理完成 https://i-blog.csdnimg.cn/direct/5761ee5f811e49389221603656c32489.png 考虑线程安全: 多个线程是否同时使用公共的东西,尝试修改同一个对象, add里面有操作公共的对象 https://i-blog.csdnimg.cn/direct/73b1939835704a7895d432bdc24f70f8.png https://i-blog.csdnimg.cn/direct/1bcbae0e1fb4455bb87aea3601d64666.png 索引大小…. https://i-blog.csdnimg.cn/direct/0284debd5f4845648859ddbc6b309c52.png https://i-blog.csdnimg.cn/direct/bd1ad758094946e9b0c7e1f6e6659658.png 此处使用加锁的方式来解决线程安全问题 https://i-blog.csdnimg.cn/direct/c02d814ecc3a48a58e7d0ff32961d8c4.png 修改版本 https://i-blog.csdnimg.cn/direct/931785b3c0cc47809696ce680555a8a8.png 我们分别操作正排和倒排是不会产生锁竞争的,因此我们不需要对整个index对象进行枷锁,我们对各种的正排和倒排进行加锁即可,让并发最大化 https://i-blog.csdnimg.cn/direct/7b17b79adb994918864b6b141242024b.png 最终结果:使用八个线程来执行,只要7秒 https://i-blog.csdnimg.cn/direct/50a48ff59b46461d9f445000b5fbca9c.png 放多少个线程合适? https://i-blog.csdnimg.cn/direct/6b674e6d207d422f97b88ddebe96b3e7.png 细节问题 1> 守护线程 通过线程池创建的线程, 不是守护线程 当main方法执行完了,这些线程任然在工作(任然在等待) https://i-blog.csdnimg.cn/direct/4cc6ea73f38d47508ef727268ef7c379.png 程序依然在执行(进程没有退出) https://i-blog.csdnimg.cn/direct/ce46ea6e5f9f452fbe22965e5d80300e.png 进程顺利结束 https://i-blog.csdnimg.cn/direct/f3ac150c5a044473a28f2a3f80ec75ec.png 2> 第一次制作索引的时候,速度很慢,第二次很快.关机后再第一次制作, 又会很慢.. 主要是parsContent里面有读文件的操作,开销很大 https://i-blog.csdnimg.cn/direct/a12139297c0b49639ecd602d9133ddcd.png https://i-blog.csdnimg.cn/direct/30fb4d80fe204dce967db5ea89fb5ead.png 猜想,是否开机之后,首次运行读取文件速度特别慢? https://i-blog.csdnimg.cn/direct/aaaa0ca38ef043219ca53bb0a8dc98a0.png 我们第一次运行 https://i-blog.csdnimg.cn/direct/9224ed3140f7463b889fc2258983c543.png 后面再运行 https://i-blog.csdnimg.cn/direct/e51e07a75a344902b8ad77a14ed644c7.png 差距很大,主要是缓存的问题 https://i-blog.csdnimg.cn/direct/78f0401a4e5c4cd3a0e896f6177b5c18.png 解决从磁盘读文件速度慢的问题, https://i-blog.csdnimg.cn/direct/79805ad7e6304beea89bb0b73f7d036c.png https://i-blog.csdnimg.cn/direct/083a20e6db7942c293a187526beac27e.png 提前使用缓存区, 把数据进行缓存 https://i-blog.csdnimg.cn/direct/29ba80110e504bfbaec4c5233a164a78.png 验证索引加载逻辑 https://i-blog.csdnimg.cn/direct/805ecf848fb646b2a83b44ab9823c2ba.png 小结: 实现Parse类 https://i-blog.csdnimg.cn/direct/5ff52cab962f4f55af092bc2e4d818b4.png 实现Index类 https://i-blog.csdnimg.cn/direct/c5df8be19ff9465c81a4bfb33ad2b2e8.png 核心方法 https://i-blog.csdnimg.cn/direct/6530887f91994a9dbd745386586090b9.png 实现搜索模块 调用搜索模块,来搜索的核心操作 1 分词 2 触发 3 排序 4 包装结果 https://i-blog.csdnimg.cn/direct/e0eaa72eba964188b01bda65c53fc5f5.png 构造摘要: 包含正文的查询词或者查询词的一部分 生成的思路: 获取所有的查询词的分词结果, 遍历分词结果, 看哪个结果在正文中出现 https://i-blog.csdnimg.cn/direct/47edfaeae7034ae69796d7e69b07e1e7.png 正文转小写 https://i-blog.csdnimg.cn/direct/877d192d8c8f44979588e76f6d2d6e0d.png 还有个问题 https://i-blog.csdnimg.cn/direct/09e5b3d57bb44c4c849fb56fc0b314b1.png 因此在生成描述的时候,要找到整个词都是匹配的,而不是一个词的一部分,保证word在内容中是独立的一个词 https://i-blog.csdnimg.cn/direct/bff94b26284d4bf396b902055ae72058.png 实现 https://i-blog.csdnimg.cn/direct/33a3e36013d44601bed83a3cab4e1539.png 我们的HTML里面js代码,因此去标签后,js也在倒排索引里面了 https://i-blog.csdnimg.cn/direct/4bfb6ffe48604a05a920c3c39b66e8e3.png https://i-blog.csdnimg.cn/direct/8f6af353ae6f4edd9e7bdc6184b42727.png 去除js里面的内容,使用正则表达式, 特殊字符串 描述了一些匹配规则 . 表示匹配一个非换行字符 表示前面的字符可以出现若干次 https://i-blog.csdnimg.cn/direct/a71772c182524b479301e2c907efa43c.png去掉script标签和内容,去除普通的标签(不去掉内容)? 表示非贪婪匹配: 匹配到符合条件的最短结果 https://i-blog.csdnimg.cn/direct/9f8171342bdd403abcc8446b27822758.png 结果空格太多 了 https://i-blog.csdnimg.cn/direct/a31191e2bd3749b7bf68e133fc11f5c2.png 我们依然使用正则,把多个空格合并为一个空格 https://i-blog.csdnimg.cn/direct/0761445987684ad9a11c9160a2a9e121.png 然后进行搜索测试,发现没有js的代码了 https://i-blog.csdnimg.cn/direct/6ed86f4438ad4211a55199b82cfcee7d.png 小结: 实现了search方法 https://i-blog.csdnimg.cn/direct/49be5945249e40588d2820fcbc4b27cd.png 实现Web模块 提供一个web接口, 最终以网页的形式,把我们的程序呈现给用户 前端+后端 https://i-blog.csdnimg.cn/direct/21eaeecfd03f4daf9ce8ab20320d2229.png 约定前后端的接口 此处我们只需要实现搜索接口即可 https://i-blog.csdnimg.cn/direct/ff1026c892744f71a85c2cc4db87e29f.png 首先基于sevlet来进行实现 引入依赖 javax.servlet javax.servlet-api 3.1.0 provided https://i-blog.csdnimg.cn/direct/15742fadf27d45f580663c8533b2e7d2.png search里面是当前搜索引擎的核心代码逻辑, 构造索引, 实现搜索逻辑 api里面实现的是sevlet的代码 https://i-blog.csdnimg.cn/direct/630bb5936beb47cba3161181c446dedf.png 实现前端页面,实现搜索页 使用ajax前后交互的常用手段 https://i-blog.csdnimg.cn/direct/b172d8f43b3141e88c2bce636dfb2922.png 拼接传过来的数据 https://i-blog.csdnimg.cn/direct/201dcfceafed443596c00ced4586d183.png 对应进行拼接 https://i-blog.csdnimg.cn/direct/758e210c2ccf463288ecd478044dfb47.png 出现的一些问题 针对内容太多,超出了屏幕 https://i-blog.csdnimg.cn/direct/7acab346a5964553b84722ee998dbc55.png 点击后, 搜索结果页被目标页面替换问题:在a标签那里设置_blank属性 俩次搜索结果混合在一起的问题 https://i-blog.csdnimg.cn/direct/9736b45b898f459a807d0607accc8016.png https://i-blog.csdnimg.cn/direct/5bc50b779b764b74bb874a594acfec2c.png 把内容的分词进行标红 1 修改后端代码, 把查询词加上标记,套上标签 2 针对设置样式,比如给字体颜色设置红色 https://i-blog.csdnimg.cn/direct/0790ec0de25f495294c1cd0680186aec.png 500异常 https://i-blog.csdnimg.cn/direct/b7ca665a7f1b4b8091757388c086a0bc.png 当 array list的时候,分词结果还加上了空格,因此会搜出很多其他没有用的东西 https://i-blog.csdnimg.cn/direct/16b25521f68b4def84420151e7a855fe.png 此处使用暂停词: 高频,但是没有意义的内容 https://i-blog.csdnimg.cn/direct/ee2ad2a194964448a81060814a0d0ad4.png 停用词是哪些? https://i-blog.csdnimg.cn/direct/af0a96bad34c41eda431be08aceb3b4b.png 把暂停词表加载到内存里面 然后读取到hashset里面,然后遍历我们的分词,然后使用contains方法,判断在不在暂停词的表里面 https://i-blog.csdnimg.cn/direct/af7053cfea3c4142949819bf519b90fe.png 此刻其实还有问题, 就是边界问题,我们之前是 " “查询词” “来保证分词的独立性,但是 查询词. 并没有排除,此时还是使用正则表达式\b https://i-blog.csdnimg.cn/direct/f4ab87d67a94461380f184e667ea1b71.png https://i-blog.csdnimg.cn/direct/240b548ea9f1423eb86ad4cd5ab31acf.png indexOf 不支持正则,但是replaceAll支持, 我们把所有的\b都替换成空格,然后我们再进行查找 https://i-blog.csdnimg.cn/direct/5be1fd895a434b8382ef6387a9e0e7c0.png 然后package-use就成功把array)给标红了 https://i-blog.csdnimg.cn/direct/23fe28970eda4b29b9eeee1ea0f8c871.png https://i-blog.csdnimg.cn/direct/bd01c001368d4c44aa4ce638bb7fd92e.png 显示搜索结果的条数 https://i-blog.csdnimg.cn/direct/65f84bbb0e80472bad379eea1af709bb.png 我们修改前端 https://i-blog.csdnimg.cn/direct/6af6f5835e5649c792c0fea4b15ea516.png 然后我们发现,同一个文档可能会出现俩次(一个文档既有array又有list) https://i-blog.csdnimg.cn/direct/9c5e58d3de364a8180bb350692e0acb1.png 这个就要和之前算权重的公式有关 https://i-blog.csdnimg.cn/direct/307fd6545a844fb982d788fcd55c377c.png 去重,权重的合并步骤 分析俩路归并 https://i-blog.csdnimg.cn/direct/c722e86ff0c7476f9aa43164530ba091.png 分析多路归并 建立堆: 完全二叉树,小根堆 https://i-blog.csdnimg.cn/direct/be7c7d38ea394b9392692e82f4cf97cb.png 权重合并 先从若干行拿到最小的值,然后和上次插入的值一样,然后判断是不是一个文档,是一个文档就进行权重的合并,不是就进行插入 https://i-blog.csdnimg.cn/direct/0d195906136a4563828dffdf3727cef9.png 最后搜索结果 https://i-blog.csdnimg.cn/direct/3642c3dc3b1d4cc5bf154b6ec51f44d6.png 总结 https://i-blog.csdnimg.cn/direct/c6c2936b9e8c44d79ed18ca39b164b8b.png***