java课程设计团队博客《基于学院的搜索引擎》

时间:2022-10-12 04:33:51

JAVA课程设计

基于学院网站的搜索引擎

对学院网站用爬虫进行抓取、建索(需要中文分词)、排序(可选)、搜索、数据摘要高亮、分页显示。Web界面。

一、团队介绍


二、项目git地址

码云地址

三、项目git提交记录截图

java课程设计团队博客《基于学院的搜索引擎》

四、项目主要使用技术

  • Httplcient
  • Jsoup
  • 多线程
  • 数据库dao模式
  • Lucene+IKAnanyzer
  • Javascript /jQuery
  • Bootstrap
  • Web

五、项目其余特点

  • 容错处理完善
  • 界面美观
  • 有配置文件
  • 数据量大的时候查询速度依旧快

六、项目功能架构图与主要功能流程图

java课程设计团队博客《基于学院的搜索引擎》

七、项目运行截图

用爬虫把数据爬取解析后存到数据库里面

java课程设计团队博客《基于学院的搜索引擎》

把数据库里面的内容建索生成的索引文件

java课程设计团队博客《基于学院的搜索引擎》

设计的前端界面

java课程设计团队博客《基于学院的搜索引擎》

设计的logo,现在是2018年,也是狗年,然后就把2018变成狗。 这个是网站上直接下载下来的,不过也是找了好久

附上链接

java课程设计团队博客《基于学院的搜索引擎》

我们再搜索框输入我们要查询的东西

java课程设计团队博客《基于学院的搜索引擎》

然后展示搜索得到的结果

java课程设计团队博客《基于学院的搜索引擎》

这个是gif动图,因为色彩太丰富了,导致录制的时候,看起来颜色有点变

java课程设计团队博客《基于学院的搜索引擎》

八、项目整体流程

1.爬虫+数据库

对爬虫了解也不是很深,粗略的讲一下大体思路。

由于知识积累尚浅,平时一般采用这3种方式爬取基本的网页。

①观察url的规律,有些url可能是id=xxx,这个xxx是从1开始递增的,这个时候我们就是可以去遍历。具体规律看实际,这边只是一个思路

②查看网页源码,看看其属性class什么的,看看是否有规律,如果有,那就按照这个来,这个用Beautifulsoul4的时候经常用,叫做CSS 选择器。我们在写 CSS 时,标签名不加任何修饰,类名前加点.,id名前加 #,在这里我们也可以利用类似的方法来筛选元素。因为感觉写正则匹配太麻烦(其实是不太精通),喜欢这种懒人式的操作。

还有的话就是用xpath语法来获取你想要的东西,以前有用过python中的scrapy框架,里面就有xpath语法,刚刚查了一下,用java写的爬虫中也有。反正现在浏览器xpath路径已经给你弄好,复制粘贴修改一下就ok。

③获取属性为<a href="xxx">的链接,可以用jsoup或者其他语言的其他解析器,也可以用正则去匹配。最后进行筛选,再去请求,再去匹配....



首先观察网站结构,先大体翻看url结构,发现每篇正文都是info/xxxx/xxxx.htm,于是有了第一个思路,对xxxx进行遍历,得到每一个url。再利用jsoup去进行解析,存进数据库。

java课程设计团队博客《基于学院的搜索引擎》



然后又查看一下导航栏的源码,发现有惊喜,就是主干url的class都是menu0_0_

java课程设计团队博客《基于学院的搜索引擎》

想到这就是继续深入,猜测副干那些也有这种规律。果不其然

java课程设计团队博客《基于学院的搜索引擎》

综合上述方法,权衡利弊后,选择第二种方法。



观察学院网站发现,学院网站有基本的三层,第一层就是导航栏,请求menu0_0_,大概有几十个url

java课程设计团队博客《基于学院的搜索引擎》

然后点击进去,到第二层,类似于文章的目录一样,这边的话上第一步的主干url请求以后,我们用选择器选择class="c124907"的链接

java课程设计团队博客《基于学院的搜索引擎》

请求第二层的url,点击,到达第三层,这个时候我们就是在正文中提取自己想要的信息了,比如选择title,正文内容啊,.contentstyle124904等等

java课程设计团队博客《基于学院的搜索引擎》

java课程设计团队博客《基于学院的搜索引擎》

java课程设计团队博客《基于学院的搜索引擎》

java课程设计团队博客《基于学院的搜索引擎》



这边的话还碰到个小坑,刚刚开始爬取的时候,大概只是爬取到了300多条链接,想想觉得有点少,再翻看目录的话发现,还有分页的没有考虑到,于是,把分页的给弄下来

java课程设计团队博客《基于学院的搜索引擎》,分页的弄下来后,发现url数还是有点少,继续看,发现从当前页开始只显示7页的链接,其他剩余的这个从源码又爬取不到

java课程设计团队博客《基于学院的搜索引擎》

观察这些页数的链接后,可以发现?a2t=19&a2p=3&a2c=10&urltype=tree.TreeTempUrl&wbtreeid=1114 ,a2t代表的是总页数, a2p代表的是当前页数,webtreeid这个不同页面id不同

于是正则去匹配,当a2t大于7的时候,把这个a2p里面的参数自己弄一个循环来添加。

java课程设计团队博客《基于学院的搜索引擎》

到最后的话里面有重复的,要去一下重

java课程设计团队博客《基于学院的搜索引擎》

以上的操作都是基于多线程来完成的。



获取到了全部url后(带有info正文的,一些无关紧要的url去掉)。

然后去请求这些url,里面也是有些可能是无效的,出现404 not found 啊什么的,利用jsoup,如果解析不到特定的字段,舍弃这个url。

这边的话利用多线程去请求正文标题等内容,请求完成以后就直接插入数据库了

java课程设计团队博客《基于学院的搜索引擎》

数据库的话采用dao模式

java课程设计团队博客《基于学院的搜索引擎》

建立一个爬虫的类

java课程设计团队博客《基于学院的搜索引擎》

2 检索部分

刚刚开始做这个搜索引擎的时候是想直接用select * from xxx where xxxxx like '%xx%' 这个语法的来返回查询结果的,和老师交流了一下,改用Lucene+IkAnalyzer进行分词索引,然后查询。如果用 like语法的话,这个搜索引擎就失去了大半部分意义了。因为搜索引擎讲究的是高效,在数据量小的时候采用sql语法查询和在索引中进行查询差别不是太大,但数据量大的时候差别就出来了。在网上看到过一个案例, lucene在查找100W 数据的时间 控制在0.02秒左右,相反的在Sql中100W的全文检索的话需要10秒左右。有一个正采用lucene开发小型的搜索引擎的人测试,sql 100w以内的数据 还是勉强能OK . lucene 就不限制了.(目前 正在做的一个搜索引擎 数据量5T 将近5亿数据 时间维持在0.4秒以内)

首先,我们要来理解lucene是什么?能干什么?

Lucene 是一个高效的,基于Java 的全文检索库

全文检索的定义:全文检索就是先创建索引,然后根据索引来进行搜索的过程,就叫全文检索。

全文检索首先对要搜索的数据或者是文档进行分词,然后形成索引,通过查询索引来查询文档。原理和查字典一样,对于一个不认识的字,我们先在偏旁部首表中找到偏旁,然后跳到所有带这个偏旁的字里面,再来搜寻这个字。再跳到这个词指定的页数,得到我们想要的结果。建立索引的话就是把查字典的过程给逆过来。对于一大段内容,先利用分词器IKAnalyzer进行分词(拆分成单独的字或词,去除标点,停词等),然后建立索引。搜索的话就和查字典一样了



这边用processon弄了一个简单的图

java课程设计团队博客《基于学院的搜索引擎》

java课程设计团队博客《基于学院的搜索引擎》

我们来看一下索引的内部结构,比如我们有10条数据库的记录,建成索引文档后docid编号为1-10

java课程设计团队博客《基于学院的搜索引擎》

将其里面的内容进行建成索引文档

每个字符串都指向包含此字符串的文档(Document)链表,此文档链表称为倒排表

java课程设计团队博客《基于学院的搜索引擎》

比如我们输入”计算机学院毕业生”进行查询,

这个时候IKAnalyzer会对其进行分词,拆分成计算机 算 学 学院 院 ..etc.

取出包含毕业、计算机、学院的文档链表进行合并,得到包含三者的文档,然后包含俩个的,一个的文档链表。

那么返回了那么多的结果,如何判断哪些是和我们的想要的结果最接近的呢??

Term Frequency (tf):这个词在文档中出现的次数

Document Frequency (df):有多少文档包含这个Term

一个文档中包含了很多的词(Term),其中找出词在文档中重要性的过程称为计算出词的权重(Weight),如果一个词在一个文档中出现次数越多说明越重要,但在其他的文档中出现的次数也越多,那么重要性就会降低,比如

java课程设计团队博客《基于学院的搜索引擎》

java课程设计团队博客《基于学院的搜索引擎》

判断Term之间的关系从而得到文档相关性的过程,也即向量空间模型的算法(VSM)

java课程设计团队博客《基于学院的搜索引擎》

我们把文档看作一系列词(Term),每一个词(Term)都有一个权重(Term weight),不同的词(Term)根据自己在文档中的权重来影响文档相关性的打分计算。于是我们把所有此文档中词(term)的权重(term weight) 看作一个向量。

Document = {term1, term2, …… ,term N}

Document Vector = {weight1, weight2, …… ,weight N}

同样我们把查询语句看作一个简单的文档,也用向量来表示。

Query = {term1, term 2, …… , term N}

Query Vector = {weight1, weight2, …… , weight N}

我们把所有搜索出的文档向量及查询向量放到一个N维空间中,每个词(term)是一维。

我们一般认为两个向量之间的夹角越小,相关性越大。

所以我们计算夹角的余弦值作为相关性的打分,夹角越小,余弦值越大,打分越高,相关性越大。

打分的计算表达式为

java课程设计团队博客《基于学院的搜索引擎》

3. 前后端

前后端也没有太多东西,就是一个美化后的表单+jquery进行分页和bootstrap+jquery修饰美化和数据交互,用表单接收到用户的数据后传到后端,然后再根据关键字从索引文件中查询出来,在前端进行展示

第一次用eclipsee,出现的问题就是eclipsee启动tomcat访问不到主页

查阅了相关资料后得知 在eclipsee中启动tomacat后,它去启动的web项目并不是tomcat文件夹下的webapp下web工程,而是eclipsee中自己的一个文件夹下的web工程。```
[附上解决链接](http://blog.csdn.net/guitk/article/details/8306987) 然后第二个问题是,如何把我查询到的数据传到jsp中,第一次用jsp,不太熟,后面查询到jsp里面也是可以写java代码的,这个就是非常6了 ![](http://images2017.cnblogs.com/blog/1121221/201801/1121221-20180129141859015-1066215907.png) 直接把获取到的数据存到crawl里面去,然后再弄个list<Crawl>存储crawl对象,return返回
![](http://images2017.cnblogs.com/blog/1121221/201801/1121221-20180129142139953-476945471.png)
然后jsp页面接收
![](http://images2017.cnblogs.com/blog/1121221/201801/1121221-20180129142312765-1676400951.png) 还有一个就是采用jquery进行分页,大体思路就是,从查询得到的结果里面统计获取到几条结果,然后自定义每条显示几个结果,向上取整得到分页的数目
,如果分页数目为1就不分页,不为1的话就是采用append方法添加链接,我们定义一个参数来接受,比如用户点倒第二页,那么参数分别为用户要查询的字段和要看的第几页,比如word=xxx&page=2,类推。 ![](http://images2017.cnblogs.com/blog/1121221/201801/1121221-20180129142840390-241575100.png) 字段的话name就是相当于word,a就是相当于page=,变量命名是刚刚开始测试用的,也没有去改。
效果如前图。 # 九、项目关键代码
	try {
Document doc=Jsoup.connect("http://cec.jmu.edu.cn/").get();
Elements links = doc.select(".menu0_0_");
for (Element link : links) {
lis1.add(oriurl+link.attr("href"));
}
} catch (IOException e1) {
e1.printStackTrace();
}
</br>

  try {
CloseableHttpResponse response = httpClient.execute(httpget, context);
try {
HttpEntity entity = response.getEntity();
Document doc=Jsoup.parse(EntityUtils.toString(entity,"utf8"));
Elements links=doc.select(".c124907");
for (Element link : links) {
lis1.add(url +link.attr("href"));
}
String pattern ="\\?a2t=([0-9]{1,})&a2p=[0-9]{1,}&a2c=10&urltype=tree.TreeTempUrl&wbtreeid=([0-9]{1,})";
Elements links1=doc.select("a[href]");
for (Element link1 : links1) {
String line=link1.attr("href");
Pattern r = Pattern.compile(pattern);
Matcher m = r.matcher(line);
int i=0;
if (m.find( )) {

// System.out.println("Found value: " + m.group(0) );

int j=Integer.parseInt(m.group(1));

if(j>7){

for(int k=1;k<j+1;k++){

lis.add("?a2t="+String.valueOf(j)+"&a2p="+String.valueOf(k)+"&a2c=10&urltype=tree.TreeTempUrl&wbtreeid="+m.group(2));

}

}

else{

lis.add(m.group(0));

}

</br>
   CloseableHttpResponse response = httpClient.execute(httpget, context);
try {
HttpEntity entity = response.getEntity();
Document doc=Jsoup.parse(EntityUtils.toString(entity,"utf8"));
Elements links=doc.select(".c124907");
for (Element link : links) {
lis.add(link.attr("href")); }
</br>

        try {
HttpEntity entity = response.getEntity();
Document doc=Jsoup.parse(EntityUtils.toString(entity,"utf8"));
String title = doc.select(".contentstyle124904").text();
</br>

Crawl crawl=new Crawl(httpget.getURI().toString(),doc.title().toString(),title);

CrawlDaoImpl test=new CrawlDaoImpl();

try {

if(bool){

test.add(crawl);

System.out.println(httpget.toString()+"添加成功");

}

            	else{
System.out.println("添加失败");
</br>

jdbc.url=jdbc:mysql://localhost:3306/test

jdbc.username=root

jdbc.password=root

jdbc.driver=com.mysql.jdbc.Driver

</br>
@Override
public Crawl findById(int id) throws SQLException {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
Crawl p = null;
String sql = "select url,abs,description from crawl where id=?";
try{
conn = DBUtils.getConnection();
ps = conn.prepareStatement(sql);
ps.setInt(1, id);
rs = ps.executeQuery();
if(rs.next()){
p = new Crawl();
p.setId(id);
p.setUrl(rs.getString(1));
p.setAbs(rs.getString(2));
p.setDescription(rs.getString(3));
}
}catch(SQLException e){
e.printStackTrace();
throw new SQLException("*");
}finally{
DBUtils.close(rs, ps, conn);
}
return p;
}
</br>

public class IndexManager {

@Test

public void createIndex() throws Exception {

// 采集数据

CrawlDao dao = new CrawlDaoImpl();

List list = dao.findAll();

// 将采集到的数据封装到Document对象中

List docList = new ArrayList();

Document document;

for (Crawl crawl : list) {

document = new Document();

// store:如果是yes,则说明存储到文档域中

Field id = new IntField("id", crawl.getId(), Store.YES);

Field url = new StoredField("url", crawl.getUrl());

Field abs = new StoredField("abs", crawl.getAbs());

Field description = new TextField("description",

crawl.getDescription(), Store.YES);

document.add(id);

document.add(url);

document.add(abs);

document.add(description);

docList.add(document);

}

// 创建分词器,标准分词器

// Analyzer analyzer = new StandardAnalyzer();

// 使用ikanalyzer

Analyzer analyzer = new IKAnalyzer();

// 创建IndexWriter

IndexWriterConfig cfg = new IndexWriterConfig(Version.LUCENE_4_10_3,

analyzer);

// 指定索引库的地址

File indexFile = new File("C:\test1\aaa\");

Directory directory = FSDirectory.open(indexFile);

IndexWriter writer = new IndexWriter(directory, cfg);

// 通过IndexWriter对象将Document写入到索引库中

for (Document doc : docList) {

writer.addDocument(doc);

}

writer.close();

}

</br>

public class IndexSearch {

List<Crawl> lis1=new ArrayList();
public List doSearch(Query query) throws InvalidTokenOffsetsException {
// 创建IndexSearcher
// 指定索引库的地址
try {
File indexFile = new File("C:\\test1\\aaa\\");
Directory directory = FSDirectory.open(indexFile);
IndexReader reader = DirectoryReader.open(directory);
IndexSearcher searcher = new IndexSearcher(reader);
// 通过searcher来搜索索引库
// 第二个参数:指定需要显示的顶部记录的N条
TopDocs topDocs = searcher.search(query, 20);
// 根据查询条件匹配出的记录总数
int count = topDocs.totalHits;

// ScoreDoc[] scoreDocs = topDocs.scoreDocs;

String filed="description";

// TopDocs top=searcher.search(query, 100);

QueryScorer score=new QueryScorer(query,filed);//传入评分

SimpleHTMLFormatter fors=new SimpleHTMLFormatter("<span style="color:red;">", "");//定制高亮标签

Highlighter highlighter=new Highlighter(fors,score);//高亮分析器

// highlighter.setMaxDocCharsToAnalyze(10);//设置高亮处理的字符个数

for(ScoreDoc sd:topDocs.scoreDocs){

Document doc=searcher.doc(sd.doc);

String description=doc.get(filed);

//Lucene中分词的所有信息我们都可以从TokenStream流中获取.

TokenStream token=TokenSources.getAnyTokenStream(searcher.getIndexReader(), sd.doc, "description", new IKAnalyzer(true));//获取tokenstream

Fragmenter fragment=new SimpleSpanFragmenter(score); //根据这个评分新建一个对象

highlighter.setTextFragmenter(fragment); //必须选取最合适的

highlighter.setTextFragmenter(new SimpleFragmenter());//设置每次返回的字符数

String str=highlighter.getBestFragment(token, description);//获取高亮的片段,可以对其数量进行限制

Crawl crawl = new Crawl();

crawl.setDescription(str);

crawl.setAbs(doc.get("abs"));

crawl.setUrl(doc.get("url"));

lis1.add(crawl);

}

reader.close();

} catch (IOException e) {

e.printStackTrace();

}

return lis1;

}
</br>

java课程设计团队博客《基于学院的搜索引擎》
<form action="./query2.jsp" method="GET">
<div class="search-wrapper">
<div class="input-holder">
<input type="text" class="search-input" placeholder="" name="name"/>
<button class="search-icon" onclick="searchToggle(this, event);"><span></span></button>
</div>
<span class="close" onclick="searchToggle(this, event);"></span>
<div class="result-container"> </div>
</div>
</form>
</br>

</br>
</br>

<%

String d =request.getParameter("a");

//out.print(d+"
");

int b=0;

int k=0;

if(i!=0&&d==null){

for(Crawl crawl: lis){

if(5>k&&k>=0){

out.print("

<p class="text-center"><a href=""+crawl.getUrl()+"">"+crawl.getAbs()+"

");

out.print("<p class="text-center">"+crawl.getDescription()+"
");

out.print("
");

}

k=k+1;

}

}

else{

if(d!=null){

int c=Integer.valueOf(d);

//out.print(c);

for(Crawl crawl: lis){

if(c5>b&&b>=(c-1)5){

if(crawl.getDescription()==null){

out.print("");

}

else{

out.print("

<p class="text-center"><a href=""+crawl.getUrl()+"">"+crawl.getAbs()+"

");

out.print("<p class="text-center">"+crawl.getDescription()+"
");

out.print("
");

	}
}
b=b+1;

}

}

}

%>

</br>

#十、尚待改进或者新的想法
##变量的命名不太规范
##可以尝试着去做一个只有修改部分参数,就可以去爬取别的网站的搜索引擎,甚至更大
</br> #团队成员任务分配
|<h2>姓名</h2>|<h2>任务</h2>|
|:----------:|:--------------------------:|
|<h2>袁德兴</h2> | <h2>利用Lucene和IKanalyzer进行检索,部分前后端内容与模块衔接</h2>|
| <h2>陈芳毅</h2> |<h2>采用httpclient和jsoup,进行爬取和解析,部分数据库内容</h2>|
| <h2>韩烨 </h2>|<h2>采用数据库的dao模式将jsoup解析后的内容进行存储,部分前端和logo的设计</h2>|
| <h2>刘兵 </h2>|<h2>采用bootstrap和jsp等进行前端界面的设计和后端代码实现</h2>|
| <h2>张晨曦</h2>| <h2>采用jquery和jsp等进行前端界面的设计和后端代码的实现</h2>| # 十一、本次课设中大佬们博客内容提供的帮助非常大,衷心的感谢。
[httpclient官方文档](http://ifeve.com/httpclient-2-4/)
[lucene学习教程](https://www.cnblogs.com/jeremy-blog/p/5008717.html)
[lucene学习5分钟](http://www.importnew.com/12715.html)
[lucene学习](https://www.cnblogs.com/guochunguang/articles/3641008.html)
[lucene4入门实例](http://iluoxuan.iteye.com/blog/1708695#)
[lucene高亮](http://blog.csdn.net/u014449866/article/details/45848693)
[jsp教程](http://www.runoob.com/jsp/jsp-tutorial.html)
[jquery教程](http://www.runoob.com/jquery/jquery-tutorial.html)
[bootstrap教程](http://www.runoob.com/bootstrap/bootstrap-tutorial.html) ##扫描下方二维码关注我公众号 ![](https://img2018.cnblogs.com/blog/1121221/201909/1121221-20190903163340807-1389378986.jpg) ##或者微信搜索:凡哥共享