Solr搜索引擎开发初试(2)(接"Lucene搜索引擎开发初试(1)")

一、Solr描述
Solr是一种开放源码的、基于 Lucene Java 的搜索服务器,易于加入到 Web 应用程序中。Solr 支持XML、Json等多种格式的数据。也就是说,用户上传的文档集可以是整理成XML格式或者Json等格式的。Solr和Lucene的联系在于,Solr的底层核心技术是使用Lucene实现的。
在我的理解中, Solr在服务器上运行,用户预先爬好文档并且处理成XML格式(也就是说,Solr本身不包含数据爬取的部分),上传到服务器, Solr做的工作是将文档集建立索引。Solr建好索引之后,用户就可以输入词语进行查询了。

二、在Windows下配置Solr(基于jetty容器)

如果要将Solr安放在Tomcat服务器上,建议参考《跟益达学Solr5之使用Tomcat部署Solr》 传送→ http://iamyida.iteye.com/blog/2209106

但是按照我的经验来看,还是使用Solr安装包中自带的jetty容器比较方便。直接从官网下载Solr,解压。我选择的是4.10.0版本,解压后的文件夹为E:\ProgramFiles\solr-4.10.0。

运行cmd进入Windows下的命令行,进入以下文件夹:

E:\ProgramFiles\solr-4.10.0\example

执行java -jar start.jar,启动Solr,如图:


访问http://localhost:8983/solr/,出现如下图界面说明Solr启动成功:


第一次运行起solr之后里面是没有任何数据文件的,我们首先要做的是导入搜索基于的文件集。为了简便起见,首先用solr自带的测试例子尝试导入。

三、新建一个core并且导入数据文件集

在本地文件夹中进入E:\ProgramFiles\solr-4.10.0\example\solr,新建一个文件夹core2,将E:\ProgramFiles\solr-4.10.0\example\multicore\core0(这是solr文件夹里自带的内容)中的conf文件夹复制到E:\ProgramFiles\solr-4.10.0\example\solr\core2中,同时在E:\ProgramFiles\solr-4.10.0\example\solr\core2中新建一个空文件夹data


进入http://localhost:8983/solr/,也就是solr的管理界面,点击管理页面的Core Admin, 添加一个名为core2的新core,注意要改掉name和instanceDir为core2,dataDir就不必改了,因为我们已经在本地文件夹中新建好data文件夹了。点击Add Core,如图:


注意,在管理界面Add core这一步骤必须在本地新建文件夹之后进行,否则Add core 会报错。

此时再打开本地文件夹,进入E:\ProgramFiles\solr-4.10.0\example\solr\core2,发现发生了一些变化,例如该文件夹下多了core.properties,并且data文件夹中多了index文件夹和tlog文件夹。


我们要准备好用于上传的数据文件集。这个文件集是通过对某个网站进行爬取得到的并且输出到一个xml文件(或者json文件中)。上传的数据文档集的格式必须与core2的配置文件E:\ProgramFiles\solr-4.10.0\example\solr\core2\conf\schema.xml相匹配。我们要首先观察准备好的要上传的文件,并且相应地修改schema.xml。

我准备的数据文件是个xml文档(已经做完了爬取网页并处理的部分),名为final.xml,内容大致如下:


我们可以看到,这个数据文档中有3个字段,url,title,p,分别对应爬下来的网页的url,标题,内容。

修改schema.xml,如图,原始的schema.xml为:


修改为如下图:


增加了<fieldType name="text_ik" class="solr.TextField">,是一个中文分词的设置。此时需要进入本地文件夹,将中文分词的包IKAnalyzer2012FF_u1.jar放在E:\ProgramFiles\solr-4.10.0\example\solr-webapp\webapp\WEB-INF\lib文件夹下。然后修改<!-- general -->下的前三项,name分别修改为数据文件中的三个字段,第二个和第三个因为会出现中文,因此type也需要修改为“text_ik”。<uniqueKey>设置为url,这是主键。<defaultSearchField>是要日后要被搜索的内容。

修改完schema.xml之后要重启一下solr,也就是要关掉当前solr运行的控制台窗口,重新进入E:\ProgramFiles\solr-4.10.0\example,运行java -jar start.jar。重启solr才能使刚才的配置生效。

理论上说使用solr文件夹中的post.jar 就可以用命令行上传数据集文件了。不过为了简便起见我使用了Solr 的管理界面。在管理界面中选中core2进行操作,进入Documents,将Document Type设置为xml,将final.xml中的文件复制到Document(s)中,然后点击Submit Document。如果文件比较大,这一步花费的时间可能会很长,耐心等待。如图:


至此,上传数据文件集的步骤就完成了。此时可以在管理界面进行搜索了。点击Query,在q中输入你要搜索的单词,先不用管下面的各种选项,点击最下方的Execute Query,就会显示搜索结果(以json的形式):


注意,网页上方的http://localhost:8983/solr/core2/select?q=%E4%BC%9A&wt=json&indent=true就是向服务器提交的查询语句。

至此,基本的solr的使用就结束了。


四、Solr在java中的使用

在Java中运用solr需要一个叫做solrj的jar包,但是最终我并没有采取这种方式,对于我的小项目,用了更为简单轻量的直接利用爬虫的原理向服务器提交请求。这里仅展示代码以显示这样做是可行的。

核心代码:

importjava.io.IOException;

importjava.util.Iterator;

 

import org.apache.solr.client.solrj.SolrQuery;

import org.apache.solr.client.solrj.SolrServerException;

import org.apache.solr.client.solrj.impl.HttpSolrClient;

import org.apache.solr.client.solrj.response.QueryResponse;

import org.apache.solr.common.SolrDocument;

import org.apache.solr.common.SolrDocumentList;

 

public class SolrJTest {

    public void query(){

        String url="http://127.0.0.1:8983/solr/";

       

   

        HttpSolrClient hsc = new HttpSolrClient.Builder(url).build();

     

        SolrQuery sq = new SolrQuery();

 

        sq.set("q", "p:\" \"");

 

        sq.setStart(0);

 

        sq.setRows(10);

        

        try {

   

            QueryResponse resp = hsc.query("core0",sq);

           

            SolrDocumentList res = resp.getResults();

            

            System.out.println(res.getNumFound());

            System.out.println(resp.getQTime());

            

            for(Iterator<SolrDocument> i=res.listIterator();i.hasNext();){

                SolrDocument sd=i.next();

                System.out.print("url:"+sd.getFieldValue("url"));

                System.out.print("\ntitle:"+sd.getFieldValue("title"));

                System.out.println("\np:"+sd.getFieldValue("p"));

            }

        } catch (SolrServerException | IOException e) {

            // TODO Auto-generated catch block

            e.printStackTrace();

        }

    }

   

    public static void main(String[] args) {

    SolrJTesttest=new SolrJTest();

        test.query();

 

    }

}

五、构建一个web搜索引擎


利用管理平台使用solr仅仅是第一步,更重要的是我们要在java代码中实现一些内容,从而在web开发中利用jsp使用solr。jsp要做的工作就是提交关于搜索内容的请求到solr的服务器,并且将solr服务器返回的内容(json格式)解析,得到想要的结果以呈现在前段上。

这一部分的原理实际上和爬虫的原理是一样的。都是向服务器发送请求,然后服务器返回结果。

 

核心代码:

建立连接:

importjava.io.BufferedReader;

importjava.io.InputStreamReader;

importjava.net.URL;

importjava.net.URLConnection;

 

public class pachong {

 

    public String paqu(String url0) {

        // 定义即将访问的链接

        String url = url0;

        // 定义一个字符串用来存储网页内容

        String result = "";

        // 定义一个缓冲字符输入流

        BufferedReader in = null;

        try {

            // string转成url对象

            URL realUrl = new URL(url);

            // 初始化一个链接到那个url的连接

            URLConnection connection = realUrl.openConnection();

            // 开始实际的连接

            connection.connect();

            // 初始化BufferedReader输入流来读取URL的相应

            in = newBufferedReader(new InputStreamReader(connection.getInputStream(), "utf-8"));

            // 用来临时存储抓取到的每一行数据

            String line;

            while ((line = in.readLine()) != null) {

                // 遍历抓取到的每一行并将其存储到result里面

                result += line + "\n";

            }

        } catch (Exception e) {

            System.out.println("发送GET请求出现异常!" + e);

            e.printStackTrace();

        } // 使用finally来关闭输入流

        finally {

            try {

                if (in != null) {

                   in.close();

                }

            } catch (Exception e2) {

                e2.printStackTrace();

            }

        }

        return result;

    }

}

向服务器提出请求并获取结果到String中:

newpachong().paqu("http://localhost:8983/solr/core1/spellcheck?spellcheck.build=true");

        String XmlContext = new pachong().paqu("http://localhost:8983/solr/core1/spellcheck?q=" + t + "&rows=0");

 

六、搜索引擎具体功能

1. 爬虫

爬虫利用了一个叫做Scrapy的Python爬虫框架,并且使用XPath来从页面的HTML源码中选择需要提取的数据。

文件目录如下:




Scrapy爬取下来的内容是以json格式的存储的,为了方便后面,我往Scrapy里添加了一段将json格式的文档转换成xml格式的文档的代码,爬下来的内容主要有url、标题、内容、发布时间、评论数。

核心Python代码如下:

#coding=utf-8

import scrapy

import time

import random

fromscrapy.spiders import Spider

fromscrapy.selector import Selector

from bcexo.itemsimport BcexoItem

 

classSportsSpider(scrapy.Spider):

    name = "sina"

    allowed_domains = ["sina.com.cn"]

    start_urls = [

         #"http://sports.sina.com.cn/g/premierleague/"

      "http://sports.sina.com.cn/"

       #"http://sports.sina.com.cn/basketball/nba/2017-05-20/doc-ifyfkqiv6579321.shtml"

    ]

 

    def parse(self, response):

        cur_url = response.url

        sel = Selector(response)

        links = sel.xpath('//a[@href]')

       

        if "/doc" in cur_url:

            item = BcexoItem()

            item['link'] = cur_url

            item['title'] =sel.css('.article-a__title::text').extract()[0]#xpath('//div[@class="article-a__title"]//text()').extract()[0]#

            item['desc'] = ""

            content =sel.css('.article-a__content p::text').extract()

            for ct in content:

                item['desc'] = item['desc'] +ct

            item['date'] =sel.css('.article-a__time::text').extract()[0]

            item['count'] =str(random.randint(0,1000))

                          #print("link:\n%s\ntitle:\n%s\ndesc:\n%s\ntime:\n%s\ncount:\n%s\n"%(item['link'],item['title'],item['desc'],item['date'],item['count']))

            #time.sleep(30)

            yield item

       

        for href in links:

            link =str(href.re('href="(.*?)"')[0])

            try:

                if link:

                    if notlink.startswith('http'):  # 处理相对URL

                        link = cur_url + link

                    yield scrapy.Request(link,callback=self.parse)

            except Exception as e:

                print("!!")

 

搜索引擎的核心部分利用了solr框架。最前面已经讲过,Solr主要的作用是建立帮助建立索引。在jetty容器中运行起来solr,需要自己添加中文分词。将刚才得到的文档集xml传给solr就会建立倒排表索引。Solr的倒排表索引包括文档频次(多少文档出现过这个Term)和词频(某个文档中该Term出现过几次)。查询的时候向solr所在的服务器发送一个请求,solr就会返回搜索结果。Solr返回的结果是json格式(当然也可以是xml格式)的,需要自己写代码对json进行解析。

 

2. 文档排序

我写的给文档打分参考的字段依次是文章内容、标题、发布时间、评论数。这相当于不同的优先级。只有基于文章内容打分获得了平局的情况下才会考虑依据标题打分,依据标题平局了的情况下才会依据发布时间。事实上,如果修改代码将发布时间放在第一个,搜索结果的排序几乎是严格的时间序。

 

核心代码:

String JsonContext = new pachong().paqu("http://localhost:8983/solr/core1/select?q=" + t

                + "&sort=desc+desc%2Ctitle+desc%2Cdate+desc%2Ccount+desc&wt=json&indent=true");

        String Initialpart = JsonContext.substring(JsonContext.indexOf("docs") + 6, JsonContext.length() - 4);

        System.out.println(Initialpart);

        String Highlight = JsonContext.substring(JsonContext.indexOf("highlighting") + 14, JsonContext.length() - 2);

        JSONObject highlight = JSONObject.fromObject(Highlight);

        JSONArray jsonArray = JSONArray.fromObject(Initialpart);

 

 

3. 高亮显示

Solr可以标记你搜索的单词。利用这个实现高亮。这个标记功能就是除了正常返回的结果,再多返回一些结果,这些结果就是我们所要的包含高亮搜索词语的内容。搜索词语被用特殊的标签标记了一下。也就是说,这部分内容和正常的结果是分别返回的,需要对利用这部分内容对正常结果进行替换拼接,还要自己写代码设置一下高亮的形式,是加粗还是红色。

参数说明:

l  hl.fl: 用空格或逗号隔开的字段列表。要启用某个字段的highlight功能,就得保证该字段在schema中是stored。如果该参数未被给出,那么就会高亮默认字段 standard handler会用df参数,dismax字段用qf参数。你可以使用星号去方便的高亮所有字段。如果你使用了通配符,那么要考虑启用hl.requiredFieldMatch选项。

l  hl.requireFieldMatch: 如果置为true,除非用hl.fl指定了该字段,查询结果才会被高亮。它的默认值是false。

l  hl.usePhraseHighlighter: 如果一个查询中含有短语(引号框起来的)那么会保证一定要完全匹配短语的才会被高亮。

l  hl.highlightMultiTerm :如果使用通配符和模糊搜索,那么会确保与通配符匹配的term会高亮。默认为false,同时hl.usePhraseHighlighter要为true。

l  hl.fragsize: 返回的最大字符数。默认是100.如果为0,那么该字段不会被fragmented且整个字段的值会被返回。

注意事项:

一定要设置主键!

solr对高亮的设计是,高亮部分跟结果集部分是分开返回的,如果没有配主键,那么高亮部分返回的结果是这样的,如下图所示,可以看出高亮部分没有带主键,这个时候,你就与上面的结果集匹配不上,那么这样的高亮就没有任何意义,因为不能够确定高亮的是哪条记录。

因为solr的结果集跟高亮是分开返回的,而且高亮是不会排序的,所以我把我的接口设计成,将高亮部分替换结果集的部分。

 

核心配置文件:



核心代码:

StringJsonContext = new pachong().paqu("http://localhost:8983/solr/core1/select?q=" + t

                + "&sort=desc+desc%2Ctitle+desc%2Cdate+desc%2Ccount+desc&wt=json&indent=true");

        String Initialpart = JsonContext.substring(JsonContext.indexOf("docs") + 6, JsonContext.length() - 4);

        System.out.println(Initialpart);

        String Highlight = JsonContext.substring(JsonContext.indexOf("highlighting") + 14, JsonContext.length() - 2);

        JSONObject highlight = JSONObject.fromObject(Highlight);

        JSONArray jsonArray = JSONArray.fromObject(Initialpart);

   

        int size = jsonArray.size();

        String ans = "";

        for (int i = 0; i < size; i++) {

            JSONObject jsonObject = jsonArray.getJSONObject(i);

            String jsonurl = "" + jsonObject.get("link");

            Object high_object = highlight.get(jsonurl);

            JSONArray array1 = JSONArray.fromObject(JSONObject.fromObject(high_object).get("desc"));

            JSONArray array2 = JSONArray.fromObject(JSONObject.fromObject(high_object).get("title"));

            String high_content1 = array1.getString(0);

            String high_content2 = array2.getString(0);

            ans += "[" + i + "]link=" + jsonObject.get("link") + "\n&";

            ans += "[" + i + "]desc=" + high_content1 + "\n&";

            Result result;

            if (high_content2 == "null") {

                ans += "[" + i + "]title=" + jsonObject.get("title") + "\n&";

                result = new Result("" + high_content1, "" + jsonObject.get("link"), "" + jsonObject.get("title"),

                       suggestion);

            } else {

                ans += "[" + i + "]title=" + high_content2 + "\n&";

                result = new Result("" + high_content1, "" + jsonObject.get("link"), "" + high_content2, suggestion);

            }

            r.add(result);

        }

        return ans;

4. 相关搜索

这一部分并不是爬网页的时候从网页上爬下来的。每当我搜索一个词在文档集中查找匹配的文档时,同时还做另一个工作:将搜索词与索引进行比对,找到那些与搜索词相近的索引中的词。具体是调用了一个计算搜索词与索引词距离的方法,从而找到“相关搜索”。

很好的参考文献:

http://www.cnblogs.com/HD/p/3993424.html

 

核心配置文件:


核心代码:

// 相关搜索

        String[] suggestion = null;

        new pachong().paqu("http://localhost:8983/solr/core1/spellcheck?spellcheck.build=true");

        String XmlContext = new pachong().paqu("http://localhost:8983/solr/core1/spellcheck?q=" + t + "&rows=0");

        XmlContext = XmlContext.substring(XmlContext.indexOf("suggestions"), XmlContext.indexOf("</response>"));

        if (XmlContext.length() >= 25) {

            // System.out.println(XmlContext);

            String numFound = XmlContext.substring(XmlContext.indexOf("numFound") + 10, XmlContext.indexOf("</int>"));

            // System.out.println(numFound);

            suggestion = new String[Integer.parseInt(numFound)];

            for (int i = 0; i < Integer.parseInt(numFound); i++) {

                XmlContext = XmlContext.substring(XmlContext.indexOf("<str>") + 5, XmlContext.length());

                suggestion[i] = XmlContext.substring(0, XmlContext.indexOf("</str>"));

                // System.out.println(suggestion[i]);

            }

        }

JSP后台与前端的交互:

    request.setCharacterEncoding("utf-8");

                               Stringsearchcontent = request.getParameter("search_text").trim();

                               ArrayList<Result>r;

 

                               //searchProcess sp = newsearchProcess();

                               //sp.setSentence(searchcontent);

                               //r = sp.getResult();

 

                               String str =Test.getURLEncoderString(searchcontent);

                               //out.print("abc");

                               Test test = new Test();

                               if(searchcontent.indexOf("time_search")!=-1){

                                   Stringsearchword=searchcontent.substring(0,searchcontent.indexOf("time_search")-1);

                                   Stringsearchtime=searchcontent.substring(searchcontent.indexOf("time_search")+12,searchcontent.indexOf("time_search")+20);

                                   //out.print(searchword);

                                   //out.print(searchtime);  

                               searchcontent=searchcontent.substring(0,searchcontent.indexOf("time_search")-1);

                                   str =Test.getURLEncoderString(searchcontent);

                                   Stringtime_str = Test.getURLEncoderString(searchtime);

                                   Stringtime_information = test.time_display(str,time_str);

                               }else{

                                   Stringinformation = test.display(str);

                               }

                               r =test.getResult();

 

                               if (r.get(0).getSuggest() != null) {

 

                                   String[]suggestion = r.get(0).getSuggest();

                                   out.print("相关搜索: ");

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

 

                                       out.print("<a href='/SportsSearch/main.jsp?search_text=" + suggestion[j]

                                               +"'style='font-size:13px;''>" + suggestion[j] + "</a>");

                                       out.print("   ");

                                   }

                               }


5. 按时间group搜索结果并返回

实际就是在向solr服务器提出请求的时候多加一个时间的限制,solr会在满足这个限制的条件下返回排序前10位的文档。比如我只想看到2017年的所有新闻。

核心代码:

StringJsonContext = new pachong().paqu("http://localhost:8983/solr/core1/select?q=" + t

                + "&fq=date:"+time+"*&wt=json&indent=true");

        String Initialpart = JsonContext.substring(JsonContext.indexOf("docs") + 6, JsonContext.length() - 4);

        //System.out.println(JsonContext);

        String Highlight = JsonContext.substring(JsonContext.indexOf("highlighting") + 14, JsonContext.length() - 2);

        JSONObject highlight = JSONObject.fromObject(Highlight);

        JSONArray jsonArray = JSONArray.fromObject(Initialpart);

        int size = jsonArray.size();

        // System.out.println("Size: " +size);

        String ans = "";

        for (int i = 0; i < size; i++) {

            JSONObject jsonObject = jsonArray.getJSONObject(i);

            String jsonurl = "" + jsonObject.get("link");

            Objecthigh_object = highlight.get(jsonurl);

            JSONArray array1 = JSONArray.fromObject(JSONObject.fromObject(high_object).get("desc"));

            JSONArray array2 = JSONArray.fromObject(JSONObject.fromObject(high_object).get("title"));

            String high_content1 = array1.getString(0);

            String high_content2 = array2.getString(0);

            // System.out.println(high_content2);

            ans += "[" + i + "]link=" + jsonObject.get("link") + "\n&";

            // ans += "[" + i +"]title=" + jsonObject.get("title")+"\n&";

            // ans += "[" + i +"]p=" + jsonObject.get("p")+"\n&";

            ans += "[" + i + "]desc=" + high_content1 + "\n&";

            Result result;

            if (high_content2 == "null") {

                ans += "[" + i + "]title=" + jsonObject.get("title") + "\n&";

                result = new Result("" + high_content1, "" + jsonObject.get("link"), "" + jsonObject.get("title"),

                       suggestion);

            } else {

                ans += "[" + i + "]title=" + high_content2 + "\n&";

                result = new Result("" + high_content1, "" + jsonObject.get("link"), "" + high_content2, suggestion);

                //System.out.println(high_content2);

            }

            r.add(result);

        }

        return ans;

 

JSP后台与前端交互的代码处理:

 

    out.print("时间: ");

                               for(int k=0;k<5;k++){

                                       out.print("<ahref='/SportsSearch/main.jsp?search_text=" + str

                                               +"%20time_search=20170"+(k+1)+" 'style='font-size:13px;''>2017" + (k+1) + "</a>");

                               out.print("   ");

                             }



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值