搭建內網搜索平台


本文是自己在公司發的文章,搭建公司內部的搜索平台

很早就有一個想法,我們公司大量業務知識,中心內部交流培訓和技術業務文章分享也不少,希望能有一個平台可以檢索它們並且很方便的搜索到它們。

檢索數據的方式可以像爬蟲一樣去抓取指定網站的內容,也可以通過任何人手工上傳自己的文章,並且能很及時的對上傳的文章建立索引並能搜索到它們。

要建立這樣的平台,肯定需要花費很多時間才能完成,因為是業余時間來做這個功能,為了能花費較少時間並且多了解一些框架和技術,我開發了部分代碼並用一些開源項目幫助搭建了一個這樣的平台。

爬蟲我用了Nutch1.5.1,通過訪問Solr3.6來建立Lucene索引,搜索過程通過Lucene3.6來獲取需要的數據,中文分詞用了IKAnalyzer2012_u6,搜索頁面的項目用的Struts2,一些數據用的Mongodb2.2.1來存儲,Nutch是通過Cygwin運行的。

搭建上述的框架,花費了我很多時間,遇到了很多問題,這些問題可能也和操作系統有關系,我是WIN7 64位的,有的問題通過網絡也沒有搜索到相關問題說明,是自己通過反復看日志猜出來的解決辦法。因此對於其他系統搭建這樣的框架,不一定完全具有參考性。

 

一、爬蟲和搭建數據中心

安裝過程:

首先需要在已經安裝JDK環境的機器下,把CygwinNutchSolr下載后分別解壓或安裝。

因為Nutch命令是shell腳本,Cygwin的目的是windows環境下模擬Linux環境執行,在http://www.cygwin.com/ 下載setup.exe文件,然后運行,我選擇的離線下載,因為安裝包比較大會下載很久,離線下載完畢后再安裝它,安裝目錄不要有空格和中文目錄。

Nutchhttp://nutch.apache.org/ 下載apache-nutch-1.5.1-bin.zip文件后,直接解壓即可,但我下載的內容bin文件夾里沒有nutch文件,我再單獨下載apache-nutch-1.5.1-src.zip文件,再把src里的nutch文件放到之前下載的bin文件夾里。然后把apache-nutch-1.5.1-bin.zip解壓后的文件復制到Cygwin文件夾的home/機器名/里。Nutch擅長做爬蟲,並且把爬取的數據按照特定結構存儲起來,由於大數量的文件存儲,Nutch發展起了一個現在很出名的頂級項目:Hadoop,它實現功能類試GoogleGFSMapduce算法,用來解決分布式的計算的問題,但我也沒用過,對它們不了解。把Nutch目錄放到Cygwin文件夾下后,需要配置環境變量NUTCH_HOME到該目錄。由於需要JDK環境,還需要配置NUTCH_JAVA_HOME環境變量到JDK的文件夾里,並且這里的JDK所在文件夾不能有中文名詞和空格。

Solrhttp://lucene.apache.org/solr/ 地方下載,下載非源碼壓縮文件后直接解壓就行,Solr是基於Lucene的一個項目,它擅長做數據索引,通過指定URL供其他系統調用,可以建立Lucene結構的索引。Solr3.6開源項目也有自己的頁面可以測試分詞,測試搜索功能等,可以簡單測試下中文分詞和搜索功能。同時需要創建一個環境變量SOLR_HOME指向Solr所在目錄,比如我指向的D:\solr3.6\

中文分詞用的IKAnalyzer2012_u6,在http://code.google.com/p/ik-analyzer/ 下載后,下載后就一個IKAnalyzer2012_u6.jar包和一個IKAnalyzer.cfg.xml文件,IKAnalyzer.cfg.xml文件內容如下:

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">  

<properties>  

<comment>IK Analyzer 擴展配置</comment>

<!--用戶可以在這里配置自己的擴展字典 

<entry key="ext_dict">ext.dic;</entry> 

-->

<!--用戶可以在這里配置自己的擴展停止詞字典-->

<entry key="ext_stopwords">stopword.dic;</entry> 

 

</properties>

為了讓Solr能用中文分詞,把中文分詞的配置文件IKAnalyzer.cfg.xml放到solr3.6\example\work\Jetty_0_0_0_0_8983_solr.war__solr__k1kf17\webapp\WEB-INF\classe目錄下,把IKAnalyzer2012_u6.jar包放到solr3.6\example\work\Jetty_0_0_0_0_8983_solr.war__solr__k1kf17\webapp\WEB-INF\lib目錄下,文件夾Jetty..每個機器可能會不一樣,IKAnalyzer.cfg.xml配置文件可以配置擴展的名詞和停止詞,分別用來被中文分詞識別的名詞和作為分詞中斷的標識。在solr3.6\example\work\Jetty_0_0_0_0_8983_solr.war__solr__k1kf17\webapp\WEB-INF\classes目錄增加擴展詞典:ext.dirext.dir內容里每一行表示一個新名稱,但第一行會被忽略,從第二行開始新增自己的新名詞,比如我新增如下圖:

 

然后需要把配置文件中的<entry key="ext_dict">ext.dic;</entry> 取消注釋,使ext.dic可以被識別,中文分詞Jar包和配置文件配置好后,需要把IK集成到Solr里。首先需要把Nutch下的conf文件夾里的schema.xml文件粘貼到solr3.6\example\solr\conf文件夾里,該Schema記錄了索引的字段類型和設置字段的存儲方式等,但默認字段類型沒有中文字段類型,需要新增一個fieldTypetext_cn的類型如下:

        <fieldType name="text_cn" class="solr.TextField"

            positionIncrementGap="100">

<analyzer type="index">

          <tokenizer class="org.wltea.analyzer.solr.IKTokenizerFactory"  isMaxWordLength="false"/>

          <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" enablePositionIncrements="true" />

          <filter class="solr.LowerCaseFilterFactory"/>

        </analyzer>

        <analyzer type="query">

           <tokenizer class="org.wltea.analyzer.solr.IKTokenizerFactory" isMaxWordLength="true"/>

           <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" enablePositionIncrements="true" />

           <filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>

           <filter class="solr.LowerCaseFilterFactory"/>

        </analyzer>  

        </fieldType>

然后把field為標題和內容的類型修改為text_cn,把默認為:

<field name="content" type="text" stored="false" indexed="true"/>

   <field name="title" type="text" stored="true" indexed="true"/> 

修改為:

    <field name="content" type="text_cn" stored="true" indexed="true"/>

    <field name="title" type="text_cn" stored="true" indexed="true"/>

把類型修改為新增的中文類型,並且由於搜索時需要顯示內容,把content字段設置為可存儲。通過上面從操作就把IK中文分詞集成到Solr里了。

配置完成后,可以用Solr來測試下中文分詞,把filed設置為type,並且type的值輸入剛才新增的類型text_cn,通過http://127.0.0.1:8983/solr/admin/analysis.jsp搜索:中國航信,如下:

 

上圖是還沒有新增自己的擴展字段ext.dic的結果,按照上面描述的方法增加了ext.dic字段后,航信作為了一個新名詞,然后再搜索后結果如下:

 

航信被識別出來了,作為一個單獨的名詞。現在中文分詞和自己擴展的新名詞就都可以用了。 lawson

Mongodbhttp://www.mongodb.org/下載后,直接解壓即可,把解壓后的Mongodb放到一個非中文目錄下,然后在控制台下,輸入Mongod即可開啟服務,一般要設置數據庫文件所在目錄,需要增加-dbpath參數,默認mongodb的端口是27017端口,可以通過-port修改其他端口,但啟動后無需任何密碼即可連接進來並查詢數據,因此開啟Mongodb服務時,需要增加-auth參數,這樣遠程就需要密碼才能連接起來查詢數據了。通過輸入Mongo,即可作為Mongodb客戶端訪問。下面列舉幾個客戶端常用命令:

1、Show dbs可以查看當前所有數據庫。

2、show collections可以查看當前數據庫的所有集合。

3、use searcher可以切換到searcher數據庫。

4、db.mginfo.find()可以查看當前數據庫的mginfo集合的數據。

5、db.addUser(‘user’,’pwd’);可以新增當前數據庫的用戶,服務端如果用-auth參數啟動后,客戶端需要db.auth(‘user’,’pwd’);鑒權后才能正常讀取Mongodb的數據。

因此正常情況下,服務端運行mongod -dbpath=D:\mongodb\data –auth

客戶端就可以通過用戶密碼訪問對應數據庫了,可視化查看界面可以用MongoVUE來查看mongodb的數據用戶信息。Java客戶端包我用的mongo-2.9.3.jar包,操作語句比如:

Mongo mongo = new Mongo("localhost", 27017);

DB db = mongo.getDB("searcher");

if (db.authenticate("user", "pwd".toCharArray())) {

DBCollection users = db.getCollection("mginfo");

users.insert(object);

}

通過客戶端mongo也同時需要鑒權才能查詢和操作數據了。

搭建爬蟲過程:

安裝完畢后,首先需要用Nutch去爬數據,到Cygwin的安裝目錄運行Cygwin.bat或者桌面快捷方式運行Cgywin,然后cd到Nutch的目錄,在Nutch目錄下先建一個txt文檔,用於保存需要爬的網站,每個網站一行,比如保存為url.txt,然后比如運行:bin/nutch crawl url.txt -dir crawlDir -depth 5 -topN 5000 -threads 100,如下圖: 

 

然后就可以爬取url.txt文檔里記錄的網站內容了,這里-depth表示爬取網站的深度,這里為5層,-topN表示每層最多個URL記錄,這里為5000個,-threads表示一共多少個線程執行,這里為開啟100個線程做抓取網站的工作,但實際每個網站是幾個線程來爬取,需要在單獨的配置節點配置,fetcher.threads.per.queue這個節點的值表示每個配置的網站用幾個線程來抓取。

然后開始等待爬取網站,爬取結束后,crawlDir文件夾下多了crawldblinkdbsegments文件夾,里面包括.data.crc,.index.crc,data,index文件。這些都是Nutch抓取后的數據文件。

Nutch抓取完畢后,需要把這些文件發送給solr建立索引,首先需要啟動solr,solr默認用jetty作為web服務器,進入solr的安裝目錄,比如我的是D:\solr3.6\,然后進入example目錄執行:java -jar start.jar,則可用jetty的方式啟動solr網站,默認端口是8983,如下圖:  lawson

Solr啟動后,就可以在Cgywin里通過命令把Nutch抓取的數據發送給Solr建立索引,通過命令:bin/nutch solrindex http://localhost:8983/solr/ crawlDir/crawldb -linkdb crawlDir/linkdb crawlDir/segments/*,如下圖:  

現在Solr里的D:\solr3.6\example\solr\data文件夾里已經保存有lucene格式的索引以及數據文件了。

然后可以用Solr測試下現在的搜索結果,通過訪問http://127.0.0.1:8983/solr/admin/,搜索標題為:航旅天空: 

搜索結果如下: 

Solr的結果,一共查找到491條記錄。現在說明Lucene正常建立了索引並能成功查詢出結果了。

 

遇到的問題:

我在部署上面環境和搭建過程中遇到很多問題,比如:

1Nutch爬取網站時,會報錯:Failed to set permissions of cygwin,最后經過大量資料查閱,問題應該是nutchlib文件夾下hadoop-core-1.0.3.jar文件有個權限判斷引起的,但由於對hadoopcygwin不夠熟悉內部細節,就下載了hadoop-core-1.0.3.jar的源代碼,把FileUtil類的checkReturnValue方法修改了,把里面的代碼全部注釋了,最后解決了這個權限問題。

2、還有報錯:No agents listed in 'http.agent.name' property,這是因為默認Nutch配置文件沒有設置爬取網站的爬蟲User-Agent頭,需要設置一個,修改conf/nutch-default.xmlproperty節點下的<name>http.agent.name</name>value值即可。

3、啟動Solr后,訪問http://127.0.0.1:8983/solr/admin/有時也有報錯:in solr.xml org.apache.solr.common.SolrException: Schema Parsing Failed: multiple points,這個問題是因為Solr下的conf配置文件schema.xml有問題導致的,網絡基本沒有搜索到這個問題,根據報錯內容,我發現該XML文件的根節點:<schema name="nutch" version="1.5.1">配置的1.5.1可能和報錯內容有關系,就修改成<schema name="nutch" version="1.5">,結果就沒有問題了。

4、除了上面3個會影響最基本爬取數據的問題,還遇到下面3個比較麻煩的問題:

l 有一個內部網站需要登錄才能訪問,Nutch不能爬取需要登錄后才能訪問的網頁內容。

l 有一個內部網站有robots.txt文件,並且里面限制了爬取所有頁面,Nutch會識別該robots.txt,並不爬取這個網站的內容。

l 有一個內部技術論壇用JForum搭建的,這個開源論壇有識別是否爬蟲的功能,Nutch默認被當做爬蟲,不能爬取了。

 

針對第一個問題,經過分析,發現這個網站實際就是通過設置cookie,並且可以設置cookie永久有效,因此只需要修改下抓取網站的源碼,設置好cookie就行了,Nutchjar包大多是通過插件的方式注入的,Nutch抓取網頁內容是用protocol-http.jar包的HttpResponse類的構造函數執行抓取操作,構造函數為HttpResponse(HttpBase http, URL url, CrawlDatum datum),內部用Socket的方式構造http請求協議頭和內容來獲取遠程網頁的內容,根據不同域名增加類試:

      reqStr.append("Cookie: ");

      reqStr.append("IS_NEED=1;...;");

      reqStr.append("\r\n");

的Http請求頭,則可對該域名下的所有網站都帶cookie去獲取遠程網頁數據了。

第二個問題Nutch內部默認會判斷robots.txt文件,為了修改更簡單,我直接修改了apache-nutch-1.5.1.jar包的org.apache.nutch.fetcher.Fetcher下的私有類:FetcherThread的run方法,代碼如下:

              RobotRules rules = protocol.getRobotRules(fit.url, fit.datum);

/*              if (!rules.isAllowed(fit.u)) {

                // unblock

                fetchQueues.finishFetchItem(fit, true);

                if (LOG.isDebugEnabled()) {

                  LOG.debug("Denied by robots.txt: " + fit.url);

                }

                output(fit.url, fit.datum, null, ProtocolStatus.STATUS_ROBOTS_DENIED, CrawlDatum.STATUS_FETCH_GONE);

                reporter.incrCounter("FetcherStatus", "robots_denied", 1);

                continue;

              }*/

把判斷當前robots.txt內容是否允許爬取網站的邏輯注釋掉了,即它還是去分析robots.txt文件,但分析完成后不判斷它是否禁止了爬取該網站。

第三個問題是因為該網站以前掛的公網,雖然現在掛內網了,但robots.txt一直存在,這個程序以代碼的方式判斷是否爬蟲,並判斷是否屏蔽它的訪問,我下載了JForum的源碼,發現它主要是通過資源文件:clickstream-jforum.xml配置的Host和user-agent的value值作為爬蟲黑名單,Host我肯定不滿足,只要修改User-agent頭即可,Nutch可以修改nutch-default.xml配置文件,把  <name>http.agent.name</name>節點的值修改下即可,修改后爬取的記錄如:

2012-12-05 05:40:37 127.0.0.1 GET /robots.txt - 88 - 127.0.0.1 MozillaLiu/Nutch-1.5.1 404 0 2 0

User-agent里的Nutch-1.5.1從哪里來的呢?通過源碼,我才發現這個是另外一個配置節點的值:

<property>

  <name>http.agent.version</name>

  <value>liu1</value>

  <description>A version string to advertise in the User-Agent 

   header.</description>

</property>

通過這樣配置后,爬蟲爬取記錄就變為:

2012-12-05 05:54:11 127.0.0.1 GET /robots.txt - 88 - 127.0.0.1 MozillaLiu/liu1 404 0 2 0

現在訪問記錄就沒有任何異樣的名稱了。最終解決了JForum搭建的這個技術論壇,爬蟲能正常爬取這個網站的數據了。

 http://lawson.cnblogs.com

通過上述方法解決了我搭建搜索平台的主要問題,但是比如需要登錄才能抓取的網頁、有robots.txt寫明禁止爬蟲爬取的問題,雖然讓我的爬蟲爬取了,但感覺還是抓取這樣的數據還是比較暴力,但因為是內網數據,只是用於內部搜索方便大家,因此就讓它暴力一點把。

 

二、平台搭建

平台數據有兩個來源:1、來自爬蟲的數據和建立的索引數據;2、用戶手工上傳的文檔,因為主要是上傳分享的知識,因此上傳的文檔支持doc,docx,ppt,pptx,pdf。通過上述的介紹,爬蟲的數據已經有了,現在需要編寫支持用戶上傳文檔的邏輯,並建立Lucene索引和用戶搜索的平台。

要實現對用戶手工上傳文檔進行索引並可查詢,需要做下面三步:

1、首選需要處理上傳文檔的解析工作,解析成可以識別的文字文檔

2、然后對解析后的文檔建立索引,並通過數據庫持久化保存一些必要的信息。

3、開發前台頁面,能通過用戶的搜索信息查詢出結果。

  http://lawson.cnblogs.com

首先對Office文檔的操作,可以用開源項目POI來讀取文檔內容,在http://poi.apache.org/下載后,解壓即可,我是用的3.8版本,比如讀取.doc文檔代碼比如:

org.apache.poi.hwpf.extractor.WordExtractor doc = null;

try {

doc = new WordExtractor(new FileInputStream(filePath));

} catch (Exception e) {

e.printStackTrace();

}

if (null != doc) {

result = doc.getText();

}

讀取.docx文檔代碼比如:

XWPFWordExtractor docx = null;

try {

OPCPackage packages = POIXMLDocument.openPackage(filePath);

docx = new XWPFWordExtractor(packages);

} catch (XmlException e) {

e.printStackTrace();

} catch (OpenXML4JException e) {

e.printStackTrace();

}

if (null != docx) {

result = docx.getText();

}

讀取.ppt文檔代碼比如:

StringBuffer content = new StringBuffer("");

try {

SlideShow ss = new SlideShow(new HSLFSlideShow(path));

Slide[] slides = ss.getSlides();

for (int i = 0; i < slides.length; i++) {

TextRun[] t = slides[i].getTextRuns();

for (int j = 0; j < t.length; j++) {

content.append(t[j].getText());

}

content.append(slides[i].getTitle());

}

} catch (Exception e) {

System.out.println(e.toString());

}

讀取.pptx文檔代碼比如:

OPCPackage slideShow;

String reusltString = null;

try {

slideShow = POIXMLDocument.openPackage(path);

XMLSlideShow xmlSlideShow = new XMLSlideShow(slideShow);

XSLFSlide[] slides = xmlSlideShow.getSlides();

StringBuilder sb = new StringBuilder();

for (XSLFSlide slide : slides) {

CTSlide rawSlide = slide.getXmlObject();

CTGroupShape gs = rawSlide.getCSld().getSpTree();

@SuppressWarnings("deprecation")

CTShape[] shapes = gs.getSpArray();

for (CTShape shape : shapes) {

CTTextBody tb = shape.getTxBody();

if (null == tb)

continue;

CTTextParagraph[] paras = tb.getPArray();

for (CTTextParagraph textParagraph : paras) {

CTRegularTextRun[] textRuns = textParagraph.getRArray();

for (CTRegularTextRun textRun : textRuns) {

sb.append(textRun.getT());

}

sb.append("\r\n");

}

}

}

reusltString = sb.toString();

} catch (IOException e) {

e.printStackTrace();

}

上面就完成了常見的幻燈片培訓和技術分享文檔的讀取了。

對PDF文件的讀取可以通過開源項目PDFBox來處理,在http://pdfbox.apache.org/下載后解壓即可,我是用的1.7.1版本,在官網下載下來只有pdfbox-1.7.1.jar包,但它還依賴了很多其他開源Jar包,需要下載的有bcprov-jdk15on-147.jar,commons-logging.jar,fontbox-1.6.0.jar,icu4j-50rc.jar,JempBox-0.2.0.jar,才能正常讀取PDF文檔,當然這些jar包可能其他版本也是可以用的。讀取PDF文檔代碼比如:

FileInputStream fis = new FileInputStream(filePath);

String result = "";

try {

PDFParser p = new PDFParser(fis);

p.parse();

PDFTextStripper ts = new PDFTextStripper();

result = ts.getText(p.getPDDocument());

System.out.println(result);

fis.close();

} catch (Exception e) {

e.printStackTrace();

}

這樣就能讀取出PDF的文檔內容了。

 

文檔內容獲取后,需要對它們建立索引,通過Lucene的API可以很方便的為這些內容建立索引數據文件,需要注意的是,需要對文檔內容進行存儲,代碼如下:

Field fieldcontent = new Field("content", info.getContentString(), Store.YES, Index.ANALYZED);

doc.add(fieldcontent);

並且IndexWriter寫索引文件時,需要用IKAnalyzer作為分析器。

 

對於用戶搜索信息,最好能像百度一樣可以對搜索的關鍵詞進行高亮顯示,Lucene提供了lucene-highlighter-3.6.0.jar包,來對搜索高亮效果等進行處理,處理語句如下:

SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter("<b><font color='red'>", "</font></b>");

Highlighter highlighter = new Highlighter(simpleHTMLFormatter, new QueryScorer(query));

TokenStream tokenStream = analyzer.tokenStream("content", new StringReader(showContentString));

String str = highlighter.getBestFragment(tokenStream, showContentString);

這樣根據highlighter的getBestFragment方法獲取到首先找到的文檔內容里符合搜索條件的文檔內容,並且符合搜索條件的數據用font color為red的標簽框起來了。默認str的長度只為100,即返回100長度的文檔內容,可以通過下面的方法修改:

Fragmenter fragmenter = new SimpleFragmenter(150);

highlighter.setTextFragmenter(fragmenter);

這樣返回的就是150字符長度的內容了。

通過上面搭建的工作和代碼編寫的工作,網站功能已經基本開發完畢,搜索“航旅天空”的效果如下: 

搜索到的數據都是爬蟲爬取的結果,如果是別人主動分享的文檔,搜索“B2B 自動出票 匯付 本票通”效果如下: 

查詢的第一條結果是人工上傳的分享文檔,鏈接直接是一個分享PPT的下載地址並且如果上傳人填寫了名字,查詢時會顯示上傳人的姓名。

 

現在一個簡易版的搜索平台就搭建好了,有相關問題歡迎溝通!

 轉載請注明來自: http://lawson.cnblogs.com 


免責聲明!

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



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