java課程設計團隊博客《基於學院的搜索引擎》


JAVA課程設計

基於學院網站的搜索引擎

對學院網站用爬蟲進行抓取、建索(需要中文分詞)、排序(可選)、搜索、數據摘要高亮、分頁顯示。Web界面。

一、團隊介紹


二、項目git地址

碼雲地址

三、項目git提交記錄截圖

四、項目主要使用技術

  • Httplcient
  • Jsoup
  • 多線程
  • 數據庫dao模式
  • Lucene+IKAnanyzer
  • Javascript /jQuery
  • Bootstrap
  • Web

五、項目其余特點

  • 容錯處理完善
  • 界面美觀
  • 有配置文件
  • 數據量大的時候查詢速度依舊快

六、項目功能架構圖與主要功能流程圖

七、項目運行截圖

用爬蟲把數據爬取解析后存到數據庫里面



把數據庫里面的內容建索生成的索引文件



設計的前端界面

設計的logo,現在是2018年,也是狗年,然后就把2018變成狗。 這個是網站上直接下載下來的,不過也是找了好久

附上鏈接

我們再搜索框輸入我們要查詢的東西

然后展示搜索得到的結果

這個是gif動圖,因為色彩太豐富了,導致錄制的時候,看起來顏色有點變

八、項目整體流程

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去進行解析,存進數據庫。



然后又查看一下導航欄的源碼,發現有驚喜,就是主干url的class都是menu0_0_

想到這就是繼續深入,猜測副干那些也有這種規律。果不其然

綜合上述方法,權衡利弊后,選擇第二種方法。


觀察學院網站發現,學院網站有基本的三層,第一層就是導航欄,請求menu0_0_,大概有幾十個url

然后點擊進去,到第二層,類似於文章的目錄一樣,這邊的話上第一步的主干url請求以后,我們用選擇器選擇class="c124907"的鏈接

請求第二層的url,點擊,到達第三層,這個時候我們就是在正文中提取自己想要的信息了,比如選擇title,正文內容啊,.contentstyle124904等等




這邊的話還碰到個小坑,剛剛開始爬取的時候,大概只是爬取到了300多條鏈接,想想覺得有點少,再翻看目錄的話發現,還有分頁的沒有考慮到,於是,把分頁的給弄下來
,分頁的弄下來后,發現url數還是有點少,繼續看,發現從當前頁開始只顯示7頁的鏈接,其他剩余的這個從源碼又爬取不到

觀察這些頁數的鏈接后,可以發現?a2t=19&a2p=3&a2c=10&urltype=tree.TreeTempUrl&wbtreeid=1114 ,a2t代表的是總頁數, a2p代表的是當前頁數,webtreeid這個不同頁面id不同
於是正則去匹配,當a2t大於7的時候,把這個a2p里面的參數自己弄一個循環來添加。

到最后的話里面有重復的,要去一下重

以上的操作都是基於多線程來完成的。


獲取到了全部url后(帶有info正文的,一些無關緊要的url去掉)。
然后去請求這些url,里面也是有些可能是無效的,出現404 not found 啊什么的,利用jsoup,如果解析不到特定的字段,舍棄這個url。
這邊的話利用多線程去請求正文標題等內容,請求完成以后就直接插入數據庫了

數據庫的話采用dao模式

建立一個爬蟲的類

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弄了一個簡單的圖

我們來看一下索引的內部結構,比如我們有10條數據庫的記錄,建成索引文檔后docid編號為1-10

將其里面的內容進行建成索引文檔
每個字符串都指向包含此字符串的文檔(Document)鏈表,此文檔鏈表稱為倒排表

比如我們輸入”計算機學院畢業生”進行查詢,
這個時候IKAnalyzer會對其進行分詞,拆分成計算機 算 學 學院 院 ..etc.
取出包含畢業、計算機、學院的文檔鏈表進行合並,得到包含三者的文檔,然后包含倆個的,一個的文檔鏈表。

那么返回了那么多的結果,如何判斷哪些是和我們的想要的結果最接近的呢??
Term Frequency (tf):這個詞在文檔中出現的次數
Document Frequency (df):有多少文檔包含這個Term

一個文檔中包含了很多的詞(Term),其中找出詞在文檔中重要性的過程稱為計算出詞的權重(Weight),如果一個詞在一個文檔中出現次數越多說明越重要,但在其他的文檔中出現的次數也越多,那么重要性就會降低,比如

判斷Term之間的關系從而得到文檔相關性的過程,也即向量空間模型的算法(VSM)

我們把文檔看作一系列詞(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)是一維。

我們一般認為兩個向量之間的夾角越小,相關性越大。
所以我們計算夾角的余弦值作為相關性的打分,夾角越小,余弦值越大,打分越高,相關性越大。

打分的計算表達式為

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>

<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)


##或者微信搜索:凡哥共享


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM