爬虫(二):Lucene

  • 搜索引擎:
    * 什么是搜索引擎
    * 搜索引擎基本运行原理
    * 原始数据库做搜索有什么弊端
    * 倒排索引(敲黑板)
  • lucene
    • lucene相关的概念
    • lucene和solr的关系
    • lucene入门程序(写入索引的操作代码)
    • lucene相关写入索引的api的解释
    • ik分词器
    • lucene的搜索: 基础的搜索(2个), 多样化的搜索(5个)
  • lucene的高级(理解)
    • lucene的高亮
    • lucene的分页
    • lucene的排序
    • lucene的激励因子

1. 搜索引擎

1.1 什么是搜索引擎

搜索引擎是指根据一定的策略、运用特定的计算机程序从互联网上搜集信息,在对信息进行组织和处理后,为用户提供检索服务,将用户检索相关的信息展示给用户的系统。搜索引擎包括全文索引、目录索引、元搜索引擎、垂直搜索引擎、集合式搜索引擎、门户搜索引擎与免费链接列表等。

代表性搜索引擎: 谷歌(Google) 百度(baidu)

1.2 搜索引擎的运行原理

  • 爬虫程序–>爬取网页–将爬取到的网页保存到临时库中
  • 利用规则将数据网页数据处理并保存到索引库中
  • 调用查询索引的程序,从索引库中查询相关信息

1.3 原始数据库做搜索的弊端

    1. 慢, 当数据库中保存了大量的数据以后, 查询的效率非常的低
    1. 如果使用的原始的sql完成搜索, 那么只能匹配首尾的内容,和用户的结果有可能是不同的
    1. 如果用户真的输错了, 难道就啥也搜不到了吗? 如果用户输入错误, 那么可以导致其结果和用户想的完全不同

1.4 倒排索引

​ 倒排索引 . 称为反向索引, 可以将一句话, 一个词, 一段话把他进行拆分(分词). 将分好词的数据建立索引, 将其保存的索引库中, 这样当用户来访问的索引库的时候, 将用户输入的内容也进行分词, 将分好词的内容到索引库中查询, 将查询到的内容返回用户

为什么说倒排索引可以提高查询的效率?

​ 假设有一个用户: 输入"谷歌跳槽离开", 先将用户这个输入的内容进行一个分词, (谷歌, 跳槽, 离开),然后将这个三个词语到索引库中进行分开查询, 然后将三个的词语查询的id值返回, 1,2,3,4,5,根据id查询数据库

2. lucene

lucene 是Apache提供的全文检索的工具包, 其本质就是一个工具包, 只不过可以使用lucene帮助我们构建一个全文搜索的引擎

官方网址: http://lucene.apache.org/

2.1 lucene和solr的关系

lucene: 是一个底层的API, 本质是一个工具包

solr: 是一个企业级的搜索引擎, 其底层就是lucene

2.2 lucene的基本的入门

  • 第一步: 导包
 
        <dependency>
            <groupId>org.apache.lucenegroupId>
            <artifactId>lucene-coreartifactId>
            <version>4.10.2version>
        dependency>
        
        <dependency>
            <groupId>org.apache.lucenegroupId>
            <artifactId>lucene-queriesartifactId>
            <version>4.10.2version>
        dependency>
        
        <dependency>
            <groupId>org.apache.lucenegroupId>
            <artifactId>lucene-test-frameworkartifactId>
            <version>4.10.2version>
        dependency>
        
        <dependency>
            <groupId>org.apache.lucenegroupId>
            <artifactId>lucene-analyzers-commonartifactId>
            <version>4.10.2version>
        dependency>
        
        <dependency>
            <groupId>org.apache.lucenegroupId>
            <artifactId>lucene-queryparserartifactId>
            <version>4.10.2version>
        dependency>
        
        <dependency>
            <groupId>org.apache.lucenegroupId>
            <artifactId>lucene-highlighterartifactId>
            <version>4.10.2version>
        dependency>	
  • 编写代码(写入索引的代码)
public class IndexWriterLucene {
    //演示: 写入索引的操作
    public static void main(String[] args) throws Exception {
        //1. 需要创建写入索引器对象
        //1.1 指定索引库的目录
        //此处这个一定要是一个目录, 千万别写文件
        Directory directory = FSDirectory.open(new File("F:\\index"));
        //1.2 创建索引写入器的配置对象
        //参数1: 指定lucene的版本
        //参数2: 指定的分词器,目前采用标准分词器
        IndexWriterConfig indexWriterConfig = new IndexWriterConfig(Version.LATEST,new StandardAnalyzer());
        IndexWriter indexWriter = new IndexWriter(directory,indexWriterConfig);

        //2. 添加文档的操作
        //2.1 创建文档对象
        Document doc = new Document();
        //2.1.1 添加文档的内容
        doc.add(new LongField("id",1L, Field.Store.YES));
        doc.add(new StringField("title","lucene的简介", Field.Store.YES));
        doc.add(new TextField("content","lucene是Apache提供的一个全文检索的工具包, 使用lucene可以构建一个搜索引擎", Field.Store.YES));

        indexWriter.addDocument(doc);

        //3. 写入索引(提交索引)
        indexWriter.commit();

        //4. 将索引写入器关闭
        indexWriter.close();

    }
}
  • 写入多条索引数据
 //演示: 写入多条索引的操作
    public static void main(String[] args) throws Exception {
        //1. 创建写入索引器对象
        //1.1 指定索引库的目录位置
        Directory directory = FSDirectory.open(new File("F:\\index"));
        //1.2 创建索引写入器的配置对象
        IndexWriterConfig indexWriterConfig = new IndexWriterConfig(Version.LATEST,new IKAnalyzer());
        //CREATE: 创建, 不管索引库存在不存在, 都是每次执行重新 新的索引库
        //APPEND: 追加, 如果索引库存在, 那么就会追加数据, 如果索引库不存在, 直接报错
        //CREATE_OR_APPEND: 创建或者追加, 如果索引库不存在, 那么就是创建, 如果索引库存在, 就是追加
        indexWriterConfig.setOpenMode(IndexWriterConfig.OpenMode.CREATE_OR_APPEND);

        IndexWriter indexWriter = new IndexWriter(directory,indexWriterConfig);

        //2. 添加文档内容
        List<Document> list = new ArrayList<Document>();

        for(int i = 0 ; i<5 ; i++){
            //2.1 创建文档对象
            Document doc = new Document();
            //2.1.1 添加文档的内容
            doc.add(new LongField("id",i, Field.Store.YES));
            doc.add(new StringField("title","花花", Field.Store.YES));
            doc.add(new TextField("content","自定义词汇"+i, Field.Store.YES));

            list.add(doc);
        }

        indexWriter.addDocuments(list);

        //3. 提交文档
        indexWriter.commit();

        //4. 关闭写入器对象
        indexWriter.close();



    }

2.3 索引查看工具

  • 设置索引库的位置

  • 视图窗口的解释

  • 文档视图的内容

2.4 索引写入相关的api

  • IndexWriter: 索引写入器对象

    其主要的作用, 添加索引, 修改索引和删除索引

    • 创建此对象的时候, 需要传入Directory和indexWriterConfig对象
  • Directory: 目录类, 用来指定索引库的目录

    • 常用的实现类:
      • FSDirectory: 用来指定文件系统的目录, 将索引信息保存到磁盘上
        • 优点: 索引可以进行长期保存, 安全系数高
        • 缺点: 读取略慢
      • RAMDriectory: 内存目录, 将索引库信息存放到内存中
        • 优点: 读取速度快
        • 缺点: 不安全, 无法长期保存, 关机后就消失了
  • IndexWriterConfig: 索引写入器的配置类

    • 创建此对象, 需要传递Lucene的版本和分词器
    • 作用:
      • 作用1 : 指定Lucene的版本和需要使用的分词器
      • 作用2: 设置Lucene的打开索引库的方式: setOpenMode();
		//参数值: APPEND CREATE   CREATE_OR_APPEND
        /**
         * APPEND: 表示追加, 如果索引库存在, 就会向索引库中追加数据, 如果索引库不存在, 直接报错
         * 
         * CREATE: 表示创建, 不管索引库有没有, 每一次都是重新创建一个新的索引库
         * 
         * CREATE_OR_APPEND: 如果索引库有, 就会追加, 如果没有 就会创建索引库
         		默认值也是 CREATE_OR_APPEND
         */
        config.setOpenMode(IndexWriterConfig.OpenMode.CREATE_OR_APPEND);
  • Document: 文档

​ 在Lucene中, 每一条数据以文档的形式进行存储, 文档中也有其对应的属性和值, Lucene中一个文档类似数据库的一个表, 表中的字段类似于文档中的字段,只不过这个文档只能保存一条数据

​ Document看做是一个文件, 文件的属性就是文档的属性, 文件对应属性的值就是文档的属性的值 content

  • 一个文档中可以有多个字段, 每一个字段就是一个field对象,不同的文档可以有不同的属性
  • 字段也有其对应数据类型, 故Field类也提供了各种数据类型的实现类
Field类 数据类型 Analyzed是否分析 Indexed是否索引 Stored是否存储 说明
StringField(FieldName, FieldValue,Store.YES)) 字符串 N Y Y或N 这个Field用来构建一个字符串Field,但是不会进行分析,会将整个串存储在索引中,比如(订单号,姓名等)是否存储在文档中用Store.YES或Store.NO决定
LongField(FieldName, FieldValue,Store.YES) Long型 Y Y Y或N 这个Field用来构建一个Long数字型Field,进行分析和索引,比如(价格)是否存储在文档中用Store.YES或Store.NO决定
StoredField(FieldName, FieldValue) 重载方法,支持多种类型 N N Y 这个Field用来构建不同类型Field不分析,不索引,但要Field存储在文档中
TextField(FieldName, FieldValue, Store.NO)或TextField(FieldName, reader) 字符串或流 Y Y Y或N 如果是一个Reader, lucene猜测内容比较多,会采用Unstored的策略.

名称解释:

​ 分析: 是否将字段的值进行分词

​ 索引: 指的是能否被搜索

​ 是否保存: 指的的原始值是否需要保存

​ 如果一个字段中的值可以被分词, 那么必然是支持搜索的

  • Analyzer: 分词器:

    ​ 用于对文档中的数据进行分词, 其分词的效果取决于分词器的选择, Lucene中根据各个国家制定了各种语言的分词器,对中文有一个ChineseAnalyzer 但是其分词的效果, 是将中文进行一个一个字的分开

    针对中文分词一般只能使用第三方的分词词:

2.5 集成IK

说明: ik分词器官方版本并不支持Lucene4.x版本, 有人基本官方版本做了改进, 使其支持Lucene4.x

  • 基本使用
导包:

<dependency>
    <groupId>com.janeluogroupId>
    <artifactId>ikanalyzerartifactId>
    <version>2012_u6version>
dependency>

  • ik的高级使用
    • 引入ik的扩展词典相关的内容

2.6 基本查询

  • 基本入门案例(单字段的解析器)
//单字段的查询解析器
    @Test
    public  void queryParseToLucene() throws Exception {
        //1. 需要创建查询索引的对象
        IndexReader indexReader = DirectoryReader.open(FSDirectory.open(new File("F:\\index")));
        IndexSearcher indexSearcher = new IndexSearcher(indexReader);

        //2. 执行查询, 返回结果集
        //2.1 封装查询条件
        // 注意: 此处指定的分词器一定要和写入索引库的分词器一致
        QueryParser queryParser = new QueryParser("content",new IKAnalyzer());
        //此处的参数, 其实就是用户的输入的内容
        Query query = queryParser.parse("程序员是最厉害的人");
        TopDocs topDocs = indexSearcher.search(query, Integer.MAX_VALUE);

        int totalHits = topDocs.totalHits;//一共查询到了多少条数据
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;// 返回的得分文档的数组
        System.out.println("一共返回了"+totalHits+"条数据");
        //2.2 遍历得分文档的数组
        for (ScoreDoc scoreDoc : scoreDocs) {
            float score = scoreDoc.score;//获取文档分数
            int docID = scoreDoc.doc; //返回文档的id
            Document document = indexSearcher.doc(docID);
            String id = document.get("id");
            String title = document.get("title");
            String content = document.get("content");
            System.out.println("得分:"+score+" "+id+" "+title+" "+content);

        }

    }
  • 多字段的查询解析器
//多字段的查询解析器
    @Test
    public  void multiFieldQueryParserToLucene() throws Exception {
        //1. 创建查询索引的对象
        IndexReader reader  =DirectoryReader.open(FSDirectory.open(new File("F:\\index")));
        IndexSearcher indexSearcher = new IndexSearcher(reader);

        //2. 执行查询
        MultiFieldQueryParser queryParser = new MultiFieldQueryParser(new String[]{"title","content"},new IKAnalyzer());
        Query query = queryParser.parse("程序员是最厉害的");
        TopDocs topDocs = indexSearcher.search(query, Integer.MAX_VALUE);

        //2.1 获取结果集中内容
        int totalHits = topDocs.totalHits;//共计查询到多少条数据
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;//得分文档的数组
        //2.2 遍历数组, 获取文档的id值
        for (ScoreDoc scoreDoc : scoreDocs) {
            float score = scoreDoc.score;//文档的得分
            int docID = scoreDoc.doc;//文档的id值

            Document document = indexSearcher.doc(docID);
            String id = document.get("id");
            String title = document.get("title");
            String content = document.get("content");
            System.out.println("得分:"+score+" "+id+" "+title+" "+content);
        }

    }

2.7 索引查询的相关api

2.8 多样化查询

2.8.1 词条查询 termQuery

 /**
     * 词条: 是一个不可在分割的内容, 如果写错了, 或者跟索引库中的词条不匹配, 那么都是查询不出来的
     *      词条可以是一个字, 是一个词, 甚至可以是一句话或者一段话
     *  主要适用于那些不能够进行分词的字段, 比如: id
     * @throws Exception
     */
    @Test
    public  void termQueryToLucene() throws Exception {
        //1. 创建查询索引的对象
        IndexSearcher indexSearcher = new IndexSearcher(DirectoryReader.open(FSDirectory.open(new File("f:\\index"))));
        //2. 执行查询
        TermQuery query = new TermQuery(new Term("title","lucene的简介"));

        TopDocs topDocs = indexSearcher.search(query, Integer.MAX_VALUE);

//2.1 获取结果集中内容
        int totalHits = topDocs.totalHits;//共计查询到多少条数据
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;//得分文档的数组
        //2.2 遍历数组, 获取文档的id值
        for (ScoreDoc scoreDoc : scoreDocs) {
            float score = scoreDoc.score;//文档的得分
            int docID = scoreDoc.doc;//文档的id值

            Document document = indexSearcher.doc(docID);
            String id = document.get("id");
            String title = document.get("title");
            String content = document.get("content");
            System.out.println("得分:"+score+" "+id+" "+title+" "+content);
        }

    }

2.8.2 通配符查询: wildCardQuery

//通配符查询
    //?: 表示的是一个占位符
    //*: 表示的占位0到多个
    @Test
    public void wildCardQueryToLucene() throws IOException {

        WildcardQuery query = new WildcardQuery(new Term("content","蓝瘦香菇*"));

        baseQuery(query);
    }

2.8.3 模糊查询 fuzzyQuery

 /**
     * 模糊查询: 最大的编辑次数 2
     *      编辑: 替换, 删除, 修改  只要在2次之内能够还原成词条中的内容就可以被查询到
     *      编辑的次数: 0~2之间, 默认就是2
     *      
     * @throws IOException
     */
    @Test
    public void fuzzyQueryToLucene() throws IOException {
        FuzzyQuery query = new FuzzyQuery(new Term("content","lucen"),2);
        baseQuery(query);
    }

2.8.4 数字范围查询 NumericRangeQuery

    //数字范围查询

    /**
     * 参数1: 查询的字段
     *      注意: 此处字段的数据类型必须要和NumericRangeQuery的泛型保持一致
     * 参数2: 最小值
     * 参数3: 最大值
     * 参数4: 是否包含最小值
     * 参数5: 是否包含最大值
     * @throws IOException
     */
    @Test
    public void numQueryToLucene() throws IOException {
        NumericRangeQuery<Long> numericRangeQuery = NumericRangeQuery.newLongRange("id",0L,3L,false,false);

        baseQuery(numericRangeQuery);
    }

2.8.5 组合查询: BooleanQuery

    //组合查询: BooleanQuery

    /**BooleanQuery: 本身自己没有任何的条件的
     *
     * MUST: 必须, 此条件必须存在的  求交集
     * SHOULD: 可以存在也可以不存在, 如果有数据, 就显示, 如果没数据, 就不显示  多个条件的并集
     * MUST_NOT: 表示的查询的结果中必须不包含此条件的内容  求的差集
     * @throws IOException
     */
    @Test
    public void booleanQueryToLucene() throws IOException {
        BooleanQuery booleanQuery = new BooleanQuery();

        NumericRangeQuery<Long> numericRangeQuery = NumericRangeQuery.newLongRange("id",0L,3L,true,true);
        booleanQuery.add(numericRangeQuery, BooleanClause.Occur.MUST);//第一个条件, 建议使用必须的条件

        TermQuery query = new TermQuery(new Term("content","程序员"));
        booleanQuery.add(query, BooleanClause.Occur.MUST_NOT);
        baseQuery(booleanQuery);
    }

3. lucene的高级

3.1 lucene的高亮

高亮: 本质上就是给关键词的两边添加了高亮的标签,类似于css样式,或者html

public class HighlighterToLucene {

    public static void main(String[] args) throws Exception {
        //1. 创建查询对象
        IndexSearcher indexSearcher = new IndexSearcher(DirectoryReader.open(FSDirectory.open(new File("f:\\index"))));

        //2. 执行查询
        TermQuery termQuery = new TermQuery(new Term("content","程序员"));
        //高亮设置 --------------start-----------------
        SimpleHTMLFormatter formatter = new SimpleHTMLFormatter("","");
        QueryScorer scorer = new QueryScorer(termQuery);// 执行用户输入的关键字
        Highlighter highlighter = new Highlighter(formatter,scorer);

        //高亮设置 --------------end-----------------
        TopDocs topDocs = indexSearcher.search(termQuery, Integer.MAX_VALUE);
        //2.1 获取结果集中内容
        int totalHits = topDocs.totalHits;//共计查询到多少条数据
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;//得分文档的数组
        //2.2 遍历数组, 获取文档的id值
        for (ScoreDoc scoreDoc : scoreDocs) {
            float score = scoreDoc.score;//文档的得分
            int docID = scoreDoc.doc;//文档的id值

            Document document = indexSearcher.doc(docID);
            String id = document.get("id");
            String title = document.get("title");
            String content = document.get("content");

            //高亮的获取--------------start--------------

            content = highlighter.getBestFragment(new IKAnalyzer(), "content", content);
            title = highlighter.getBestFragment(new IKAnalyzer(), "title", title);
            //id = highlighter.getBestFragment(new IKAnalyzer(), "id", id);

            //高亮的获取--------------end--------------
            System.out.println("得分:"+score+" "+id+" "+title+" "+content);
        }

    }

}

3.2 lucene的排序

public class SortToLucene {
    public static void main(String[] args) throws IOException {
        //1. 创建查询对象
        IndexSearcher indexSearcher = new IndexSearcher(DirectoryReader.open(FSDirectory.open(new File("f:\\index"))));

        //2. 执行查询
        FuzzyQuery fuzzyQuery = new FuzzyQuery(new Term("content","程序员"));
        //sort默认的排序方式是从小到大
        //如果想要设置从大到小: 设置true即可. 默认是false
        Sort sort = new Sort(new SortField("id", SortField.Type.LONG,true));
        TopFieldDocs topFieldDocs = indexSearcher.search(fuzzyQuery, Integer.MAX_VALUE, sort);

        int totalHits = topFieldDocs.totalHits;//查询到多少条数据
        ScoreDoc[] scoreDocs = topFieldDocs.scoreDocs;//得分文档

        for (ScoreDoc scoreDoc : scoreDocs) {
            float score = scoreDoc.score;//当前文档的得分
            int docId = scoreDoc.doc;
            Document doc = indexSearcher.doc(docId);

            String id = doc.get("id");
            String title = doc.get("title");
            System.out.println(id+"  "+ title);
        }
    }
}

3.3 lucene的分页

lucene中压根就不支持的分页, 但是可以使用代码进行模拟出一个的样式来

public class LimitToLucene {

    public static void main(String[] args) throws IOException {

        //分页的参数
        int page = 3; //当前的页数
        int pageNumber = 2;//每页显示的条数

        //1. 创建查询对象
        IndexSearcher indexSearcher = new IndexSearcher(DirectoryReader.open(FSDirectory.open(new File("f:\\index"))));

        //2. 执行查询
        TermQuery query  =  new TermQuery(new Term("content","程序员"));
        TopDocs topDocs = indexSearcher.search(query, page*pageNumber);

        //2.1 获取结果集中内容
        int totalHits = topDocs.totalHits;//共计查询到多少条数据
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;//得分文档的数组
        //2.2 遍历数组, 获取文档的id值
        for (int i = (page-1)*pageNumber ; i<totalHits ; i++) {
            ScoreDoc scoreDoc = scoreDocs[i];
            float score = scoreDoc.score;//文档的得分
            int docID = scoreDoc.doc;//文档的id值

            Document document = indexSearcher.doc(docID);
            String id = document.get("id");
            String title = document.get("title");
            String content = document.get("content");
            System.out.println("得分:"+score+" "+id+" "+title+" "+content);
        }

    }
}

3.4 lucene激励因子

激励因子: 用来影响文档的打分, 如果在写入索引的时候, 指定了激励因子, 如果激励设置的越大, 那么其对应的文档的在进行查询的时候, 得分越高

public class FolatToLucene {

    @Test
    public void  indexWriterToLucene() throws IOException {
        //1. 创建索引写入器对象
        Directory directory = FSDirectory.open(new File("f:\\index"));
        IndexWriterConfig indexWriterConfig = new IndexWriterConfig(Version.LATEST,new IKAnalyzer());
        IndexWriter indexWriter = new IndexWriter(directory,indexWriterConfig);

        //2. 添加文档

        Document doc = new Document();
        LongField longField = new LongField("id",20L, Field.Store.YES);
        doc.add(longField);
        TextField textField = new TextField("content","程序员是全宇宙最累的人",Field.Store.YES);
        //默认, 激励因子是 1
        textField.setBoost(10);
        doc.add(textField);
        indexWriter.addDocument(doc);
        //3. 提交文档
        indexWriter.commit();

        //4. 关闭写入器
        indexWriter.close();
    }

    @Test
    public  void queryToLucene() throws IOException {
        //1. 创建查询对象
        IndexSearcher indexSearcher = new IndexSearcher(DirectoryReader.open(FSDirectory.open(new File("f:\\index"))));

        //2. 执行查询
        TermQuery termQuery = new TermQuery(new Term("content","程序员"));
        TopDocs topDocs = indexSearcher.search(termQuery, Integer.MAX_VALUE);
        //2.1 获取结果集中内容
        int totalHits = topDocs.totalHits;//共计查询到多少条数据
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;//得分文档的数组
        //2.2 遍历数组, 获取文档的id值
        for (ScoreDoc scoreDoc : scoreDocs) {
            float score = scoreDoc.score;//文档的得分
            int docID = scoreDoc.doc;//文档的id值

            Document document = indexSearcher.doc(docID);
            String id = document.get("id");
            String title = document.get("title");
            String content = document.get("content");
            System.out.println("得分:"+score+" "+id+" "+title+" "+content);
        }
    }
}

你可能感兴趣的:(所学即所得)