搜索引擎项目构建与解析(一)

news2024/9/17 3:25:21

这是源码,大家可以下载下来作为参考,一起食用效果更佳:SearchEngine · 王宇璇/submit - 码云 - 开源中国 (gitee.com)icon-default.png?t=N7T8https://gitee.com/yxuan-wang/submit/tree/master/SearchEngine搜索引擎项目总体来看内容比较简单,代码量也比较少,是一个易上手的项目。

建立这个项目的初衷就是个人认为搜索引擎在生活中比较实用的一个项目,可以依据这个简单的项目了解搜索引擎的大致工作原理,为以后处理学习或者工作中的项目提供一个最基本的流程模板做参考。

因为我们只是简单的建立一个小的项目来了解流程(像大型搜索应用实现起来还是很困难)建立的搜索引擎项目就是相当于一个站内搜索,比如说购物应用中搜索商品等。我们针对的搜索内容就是java文档。

我们首先需要准备好java文档

Java 下载 |神谕 (oracle.com)icon-default.png?t=N7T8https://www.oracle.com/java/technologies/downloads/

下载这个压缩包进行解压得到jdk  针对api文件夹中所有的html的文档构建搜索引擎,通过关键字搜索文档中的内容。

新建项目:

构建系统选择Maven,选好jdk版本。创建项目。

对于一个简单的浏览器,最重要的就是实现索引功能。从其功能考虑我们的项目如何创建:

用户输入一个词语或者一个句子,我们就要把和这个词语有关的所有文档全部展现出来。

分词

首先就是进行分词,我们搜索针对的是每一个词语,对于中文的分词比较困难,需要根据大数据来调查用语习惯,比如“但但丁丁真真是三个人,但但丁丁真真是两个人”诸如此类分词就很难搞,而英文就简单的多,“how are you?”我们只需要在空格处进行分词即可。此处我们使用ansj提供的ToAnalysis.parse方法进行分词。不要忘记引入依赖,刷新!!!

<!-- https://mvnrepository.com/artifact/org.ansj/ansj_seg -->
        <dependency>
            <groupId>org.ansj</groupId>
            <artifactId>ansj_seg</artifactId>
            <version>5.1.6</version>
        </dependency>

正排索引和倒排索引

搜索就需要考虑到正排索引和倒排索引。

举例说明,例如一个学校中,正排索引就是通过这个学生的id找到这个学生然后就可以得到这个学生的信息。倒排索引相当于这个学生违纪被校领导发现,但是他逃走了。校领导不知道他是谁,他的学生id,此时就通过他的长相,行动地点,等一系列特征进行索引,最终得到他的学生id。这个就是倒排索引。而找到这个人全部信息就再通过正排索引输入id进行查询

正排索引很简单,就是一个数组,我们通过文档在数组中的位置(也可以看作id)就可以查到这个文档。而倒排索引就是一个hash表,将所有文章中的所有内容进行解析分词,然后每个词语对应一个数组(储存的是含有这个词语的所有文章的id),然后再把这些词语作为key,将数组作为value储存于hashmap中去。

当用户进行查询,先进行分词,分出的词语作为key在hashmap中进行匹配,得到对应id数组,此数组中的所有id文章都包含此关键词。将数组中的id通过正排索引对应成文章进行展示就是这个项目的基本思路。

我们需要读取这个api文件夹中所有的html文件的内容,然后解析出其标题,正文,url

我们可以看到浏览器的大部分搜索结果都是由这三部分组成的。

代码实现:

1.先将所有的HTML文件读取出来保存到一个文件的数组当中去。

其中的enumFile方法就是将此路径下所有的文件加入到fileList数组当中去。

这是实现代码,主要的就是如果是directory就向下递归,如果是文件就进行判断,是html文件就放到数组当中去,如果不是就跳过不操作。这里判断是否是html用的是endWith判断文件扩展名。

将文件放置于数组中方便于接下来的解析操作。

2.对文件进行解析

主要是解析出标题,内容以及url。

private void parseHTML(File file) {
        //一条搜索结果:标题 , 描述 , url
        //1.解析标题
        String title = parseTitle(file);
        //2.解析出url
        String url = parseUrl(file);
        //3.解析出正文
        //String content = parseContent(file);
        String content = parseContentByRegex(file);
        //将解析后的内容添加到Index的数组当中去。
        index.addDoc(title , url , content);
    }

    private String parseTitle(File file) {
        return file.getName().substring(0,file.getName().lastIndexOf("."));
        //substring截取字符串,也可以使用f.getName().length() - ".html".length();
    }

    private String parseUrl(File file) {
        String part1 = file.getAbsolutePath().substring(INPUT_PATH.length());//后面的部分
        String part2 = "https://docs.oracle.com/javase/8/docs/api/";//前缀
        String url = part2 + part1;//此时的url中既包含正斜杠也包含反斜杠
        //替换也可以,不替换也可以,浏览器都可以识别。
        return url;
    }

    public String parseContent(File file) {
        //按字符读取文件。按字符而不是按字节。
        //java标准库中既提供了字符读取类 FileReader,也提供了字节读取类  FileInputStream
        try (BufferedReader bufferedReader = new BufferedReader(new FileReader(file),1024 * 1024)){
            //拷贝开关
            boolean isCopy = true;
            //保存读取结果的容器
            StringBuilder content = new StringBuilder();
            while(true){
                int ret = bufferedReader.read();//此处的read返回值为int表示一些非法情况,读到末尾没有数据返回-1
                if(ret == -1){
                    //文件读完了
                    break;
                }
                //如果ret不是-1,此时的返回结果就是正常的字符了。
                char c = (char) ret;
                if(isCopy){
                    //开关是打开的状态,普通字符就应该拷贝到stringBuilder中
                    if(c == '<'){
                        isCopy = false;
                        continue;
                    }
                    //文档中的换行太多,不美观,可以换成空格
                    if(c == '\n'||c == '\r'){
                        c = ' ';
                    }
                    content.append(c);
                }else{
                    if(c == '>'){
                        isCopy = true;
                    }
                }
            }
            //fileReader.close();
            return content.toString();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public String readFile(File file){
        try(BufferedReader bufferedReader = new BufferedReader(new FileReader(file) , 1024 * 1024)){
            StringBuilder content = new StringBuilder();
            while(true){
                int ret = bufferedReader.read();
                if(ret == -1){
                    break;
                }
                char c = (char) ret;
                if(c == '\n'||c == '\r'){
                    c = ' ';
                }
                content.append(c);
            }
            return content.toString();
        }catch (IOException e){
            e.printStackTrace();
        }
        return null;
    }

    //正则表达式解析内容
    public String parseContentByRegex(File file) {
        //1.先把整个文件读到String里面
        String content = readFile(file);
        //2.替换掉script标签
        content = content.replaceAll("<script.*?>(.*?)</script>", " ");
        //3.不要忘记其他普通Html标签
        content = content.replaceAll("<.*?>", " ");
        //4.替换空格符,多个替换为一个
        //+需要替换的字符必须出现一次及以上才能够替换,*则是不需要出现此内容就可以进行替换
        content = content.replaceAll("\\s+" , " ");
        return content;
        //第三步和第二步顺序不能调换,如果调换第三步会直接用空格替换两个script标签,此时再进行第二部没有意义了,标签中的内容就无法替换了。

    }

1.解析标题

文件名出去文件扩展名就是标题,所以直接截取文件名到".",此时得到标题。

2.解析url

url就是我们在网站中需要跳转到这篇文档的网址。举个例子,

ArrayList文档在此电脑中的位置:D:\beginning\homework\project\jdk-8u411-docs-all\docs\api\java\util\ArrayList.html

这是我们可以得到的。

而他在网页中的位置:https://docs.oracle.com/javase/8/docs/api//java/util/ArrayList.html

而用户访问只能在网页中访问,此时我们就需要用此电脑中的位置解析出其url

根据规律,我们可以发现这个url就是固定前缀:https://docs.oracle.com/javase/8/再加上在此电脑中的docs\api\java\util\ArrayList.html此时就能用我们在此电脑中文件的位置得到其url,可以在网络中进行访问。

3.解析内容

首先我们用记事本打开文件看文件的内容,发现HTML用<>作为标签的格式,而标签中的内容是设定字体,位置等一系列的特点,我们读取内容是不需要的。所以我们需要将里面的内容排除。

1.parseContent

根据以上内容得出第一种方法,用BufferedRead来作为输入流对象读取文件中的字符,如果返回-1,就是读取完毕,其他数字就是读取成功,此时我们就要进行筛选,如果读取到“<”说明就是标签中的内容,此时停止读取。如果读取到">"就说明读取的是页面显示内容,继续读取。

之后将解析出的内容进行规整,发现有太多换行看起来非常丑,再去掉换行。

2.parseContentByRegex

之后我们把解析出的文档打印出来发现其中包含了<Script>content</Script>标签,其中的内容于与正文是无关的。但是我们第一个方法就会保留这些content,这是不合理的。

优化解析逻辑,此时我们需要将此标签中的内容也替换掉,就需要用到正则表达式。

 此时用<.*?>表示匹配所有字符,且是非贪婪匹配,非贪婪匹配与贪婪匹配的区别:

举例: 

源字符串:aa<div>test1</div>bb<div>test2</div>cc 

正则表达式一:<div>.*</div> 

匹配结果一:<div>test1</div>bb<div>test2</div> 

正则表达式二:<div>.*?</div> 

匹配结果二:<div>test1</div>(这里指的是一次匹配结果,所以没包括<div>test2</div>) 

引用于:https://www.cnblogs.com/admans/p/11955614.html

也就是说贪婪匹配会尽可能多的匹配,此时很可能将我们需要的内容匹配到,所以用非贪婪匹配。

匹配的内容就包括:1.标签中的内容替换为空格 2.标签替换为空格

3.此时就会造成空格过多不美观,将多个空格替换为一个空格。

注意:1和2不能够调换,也就是说如果先匹配了标签,那么第一步就无法识别标签中的内容替换也就无从说起了。

多线程

至此我们的解析就已经完成,但是在之后执行代码进行解析的过程中我发现解析的速度很慢,所以想到了多线程进行优化,而既然使用多线程,多线程后续写代码中就不可避免的会提高难度,因为要考虑到锁的问题。

创建线程池,给每一个线程都分配解析的任务,这里需要注意的是我使用了

CountDownLatch latch = new CountDownLatch(files.size());

来来保存文件的数量并且计数,每解析一个文件latch.countDown();就会自动减一,然后用await方法进行阻塞,到所有文件都被解析之后再向下进行。

 public void runByThread(){
        System.out.println("索引制作开始。");
        long start = System.currentTimeMillis();
        ArrayList<File> files = new ArrayList<>();
        enumFile(INPUT_PATH , files);
        //多线程完成循环遍历,此处为了能够通过多线程制作索引,就直接引入线程池
        CountDownLatch latch = new CountDownLatch(files.size());
        ExecutorService executorService = Executors.newFixedThreadPool(4);
        for(File file: files){
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("解析"+file.getAbsolutePath());
                    parseHTML(file);
                    latch.countDown();
                }
            });
        }
        try {
            latch.await(60 , TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        executorService.shutdown();
        index.save();
        long end = System.currentTimeMillis();
        System.out.println("索引制作完毕"+(end-start)+"ms");
    }

解析过程就介绍到这里,后续的内容点击头像进入空间进行查看。

感谢大家的支持!!!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1951941.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【计算机方向】五本“三区水刊”重磅推荐!几乎不拒收,国人发文友好!

本期将为您带来五本计算机SCI 妥妥毕业神刊&#xff01; AUTONOMOUS AGENTS AND MULTI-AGENT SYSTEMS International Journal on Document Analysis and Recognition COMPUTATIONAL INTELLIGENCE IET Biometrics ACM Transactions on Asian and Low-Resource L…

linux系统安装pytorch_中文地址命名实体识别案例

命名实体有关文章参考这篇文章 中文地址命名实体识别训练和预测 win10系统安装cuda环境参考这篇文章 搭建Pytorch的GPU环境超详细 1、下载python https://www.python.org/downloads/release/python-368/ 2、下载python包 https://pypi.org/search/?q=transformers 1、搜…

物联网主机 E6000:智慧应急领域的创新力量

在当今瞬息万变的世界中&#xff0c;突发事件和紧急情况时有发生。如何迅速、准确地应对这些挑战&#xff0c;保障人民生命财产安全&#xff0c;成为了社会发展的重要课题。而物联网主机 E6000 的出现&#xff0c;为智慧应急领域带来了全新的解决方案。 一、强大的性能与功能 物…

opencv使用KCF算法跟踪目标,给出目标中心位置

效果图 代码 import cv2class VideoTracker:def __init__(self, video_path: str):self.video_path video_pathself.cap cv2.VideoCapture(video_path)self.tracker cv2.legacy.TrackerKCF_create()self.initBB Noneself.tracker_initialized Falseself.selecting Fals…

如何进行小程序的调试

Errno错误码 在使用部分小程序 API / 组件时&#xff0c;抛出的异常&#xff08;fail 回调 / Promise reject&#xff09;Error 对象中除了带有 errMsg&#xff0c;还会带有通用错误码 errno。 代码示例 wx.openBluetoothAdapter({success (res) {console.log(res)}fail (er…

时间序列分析方法之 -- 指数平滑(Exponential Smoothing)

目录 原理 适用情况 Python 示例代码 结论 原理 指数平滑&#xff08;Exponential Smoothing&#xff09;是一种用于时间序列分析和预测的平滑技术。与简单移动平均&#xff08;SMA&#xff09;和加权移动平均&#xff08;WMA&#xff09;不同&#xff0c;指数平滑通过对最…

VS+opencv+环境配置

下载opencv库。 版本 - OpenCV 下载完了是一个exe文件&#xff0c;&#xff08;可以更换目录&#xff09;直接双击&#xff0c;也就是压缩。 vs配置&#xff1a; 调试-调试属性 点编辑&#xff0c;加入这两个&#xff0c;路径根据自己的opencv库 3、链接器 测试&#xff1a;…

《Java初阶数据结构》----8.<java对象的比较总结>

目录 前言 一、Java对象的比较 1.1基本类型的比较 1.2 对象比较的问题&#xff08;与equals&#xff09; 1.3对象的比较 &#xff08;三种常用方式&#xff09; 1.重写equals方法 2.基于Comparble接口类的比较 3.基于比较器比较&#xff08;Comparator接口&#xff09; …

如何在网站嵌入可填写的PDF表单:2024巴黎奥运会赛程

如何将可填写的 PDF 表单嵌入您的网页&#xff1f;访问者无需下载或注册即可查看并填写。 简单&#xff01;本文以2024巴黎奥运会赛程表单为例&#xff0c;演示如何将其嵌入网页中。您可以在 ONLYOFFICE 表单库免费获取该模板&#xff0c;有白色和紫色两种背景设计。 如何在网站…

uniapp中出现图片过小会与盒子偏离

结论&#xff1a;在image的父盒子中加上display: flex&#xff0c;原因不清楚 出问题的代码和图片如下&#xff1a; <template><view style" background-color: greenyellow; height: 10rpx;width: 10rpx;"><image :src"imgSrc.seatnull" …

UCOS-III 任务调度锁定/解锁接口OSSchedLock/Unlock详解

在实时操作系统中&#xff0c;任务调度是系统实现多任务并发执行的核心机制。但在某些场景下&#xff0c;我们需要临时关闭任务调度&#xff0c;以确保某段代码在执行过程中不被打断。下面将详细介绍这两个接口的应用场景及实现原理。 1. 关闭调度的应用场景 关闭任务调度的主要…

Windows环境下部署本地大模型教程

自ChatGPT在2022年爆发式出圈以来&#xff0c;大模型对人类世界产生了重大影响&#xff0c;无论是在生产、工作、写作方面还是自动驾驶领域&#xff0c;大模型发挥了非常重要的作用。 现各大厂&#xff0c;无论是国内大厂还是国外大厂&#xff0c;都纷纷推出了自己的对话大模型…

服务暴露 traefik

一。traefik 部署 前置资源 还是那个网站&#xff0c;这里复制 entryPoints&#xff0c;进入traefik的大门 可选在哪台机器上部署 traefik 部署 用 Daemonset 的方式是为了&#xff0c;加机器到集群后&#xff0c;能自动部署traefik 到目标机器 注意 8084端口 必须加…

【JVM基础06】——组成-直接内存详解

目录 1- 引言&#xff1a;直接内存概述1-1 直接内存是什么&#xff1f;直接内存的定义(What)1-2 为什么用直接内存&#xff1f;Java程序对直接内存的使用 (Why) 2- ⭐核心&#xff1a;详解直接内存(How)2-1 文件拷贝案例介绍对比常规 IO(BIO) 和 NIO常规 IO 的操作流程NIO 的操…

C++ - char*、const char*、char[]、string

const char* const char* 用来定义字符串常量。 char[ ] char型的字符数组是一种定长的数组&#xff0c;存储指定长度的字符序列&#xff0c;数组中的每个元素都是一个char类型的变量&#xff0c;如&#xff1a; char arr[] {h, a, l, l, o, \0}; char c arr[0]; // 访问…

Spring Boot的Web开发

目录 Spring Boot的Web开发 1.静态资源映射规则 第一种静态资源映射规则 2.enjoy模板引擎 3.springMVC 3.1请求处理 RequestMapping DeleteMapping 删除 PutMapping 修改 GetMapping 查询 PostMapping 新增 3.2参数绑定 一.支持数据类型: 3.3常用注解 一.Request…

[Vulnhub] Raven2 PHPMailer-RCE+MSQP:Mysql权限提升

信息收集 IP AddressOpening Ports192.168.101.160TCP:22,80,111,46606 $ nmap -p- 192.168.101.160 --min-rate 1000 -sC -sV PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 6.7p1 Debian 5deb8u4 (protocol 2.0) | ssh-hostkey: | 1024 26:81:c1:f…

拉提查合创5步玩转git工具协作代码开发

1 工具使用场景 开发团队使用git版本管理工具&#xff0c;进行协作代码开发过程中&#xff0c;最常用的场景为&#xff1a; &#xff08;1&#xff09;拉取代码 将git远端仓库最新代码拉取到本地。 &#xff08;2&#xff09;提交代码 将本地新增修改的代码提交至git远端仓库中…

Spring Cloud微服务项目统一封装数据响应体

在微服务架构下&#xff0c;处理服务之间的通信和数据一致性是一个重要的挑战。为了提高开发效率、保证数据的一致性及简化前端开发&#xff0c;统一封装数据响应体是一种非常有效的实践。本文博主将介绍如何在 Spring Cloud 微服务项目中统一封装数据响应体&#xff0c;并分享…

【优秀python算法毕设】基于python时间序列模型分析气温变化趋势的设计与实现

1 绪论 1.1 研究背景与意义 在气候变化日益受到全球关注的背景下&#xff0c;天气气温的变化已经对人们的生活各方面都产生了影响&#xff0c;人们在外出时大多都会在手机上看看天气如何&#xff0c;根据天气的变化来决定衣物的穿着和出行的安排。[1]如今手机能提供的信息已经…