2.7 Lucene新闻高频词提取

2.7.1 问题提出

给出一篇新闻文档,统计出现频率最高的有哪些词语。

2.7.2 需求分析

关于文本关键词提取的算法有很多,开源工具也不止一种。这里只介绍如何从Lucene索引中提取词项频率的Top N。索引过程的本质是一个词条化的生存倒排索引的过程,词条化会从文本中去除标点符号、停用词等,最后生成词项。在代码中实现的思路是使用IndexReader的getTermVector获取文档的某一个字段的Terms,从terms中获取tf(term frequency),拿到词项的tf以后放到map中降序排序,取出Top-N。

2.7.3 编程实现

在百度新闻上随机找了一篇新闻《李开复:无人驾驶进入黄金时代AI有巨大投资机会》,新闻内容为李开复关于人工智能的主题演讲。把新闻的文本内容放在testfile/news.txt文件中,索引文档代码见代码清单2-13。

代码清单2-13

    import java.io.BufferedReader;
    import java.io.File;
    import java.io.FileReader;
    import java.io.IOException;
    import java.nio.file.Paths;
    import org.apache.lucene.analysis.Analyzer;
    import org.apache.lucene.document.Document;
    import org.apache.lucene.document.Field;
    import org.apache.lucene.document.FieldType;
    import org.apache.lucene.index.IndexOptions;
    import org.apache.lucene.index.IndexWriter;
    import org.apache.lucene.index.IndexWriterConfig;
    import org.apache.lucene.index.IndexWriterConfig.OpenMode;
    import org.apache.lucene.store.Directory;
    import org.apache.lucene.store.FSDirectory;
    import lucene.ik.IKAnalyzer6x;
    public class IndexDocs {
        public static void main(String[] args)throws IOException {
            File newsfile = new File("testfile/lucene.txt");
            String text1 = textToString(newsfile);
            Analyzer smcAnalyzer = new IKAnalyzer6x(true);
            IndexWriterConfig indexWriterConfig = new IndexWriterConfig(
                        smcAnalyzer);
            indexWriterConfig.setOpenMode(OpenMode.CREATE);
            // 索引的存储路径
            Directory directory = null;
            // 索引的增删改由indexWriter创建
            IndexWriter indexWriter = null;
            directory = FSDirectory.open(Paths.get("indexdir"));
            indexWriter = new IndexWriter(directory, indexWriterConfig);
            // 新建FieldType,用于指定字段索引时的信息
            FieldType type = new FieldType();
            // 索引时保存文档、词项频率、位置信息、偏移信息
            type.setIndexOptions(IndexOptions.DOCS_AND_FREQS_AND_
                                    POSITIONS_AND_  OFFSETS);
            type.setStored(true); // 原始字符串全部被保存在索引中
            type.setStoreTermVectors(true); // 存储词项量
            type.setTokenized(true); // 词条化
            Document doc1 = new Document();
            Field field1 = new Field("content", text1, type);
            doc1.add(field1);
            indexWriter.addDocument(doc1);
            indexWriter.close();
            directory.close();
    }
    public static String textToString(File file){
        StringBuilder result = new StringBuilder();
        try {
        // 构造一个BufferedReader类来读取文件
        BufferedReader br = new BufferedReader(new FileReader(file));
        String str = null;
        // 使用readLine方法,一次读一行
        while ((str = br.readLine()) != null) {
            result.append(System.lineSeparator()+ str);
        }
            br.close();
        } catch(Exception e){
            e.printStackTrace();
        }
        return result.toString();
    }
    }

获取新闻热词的代码见代码清单2-14。

代码清单2-14

        import java.io.IOException;
        import java.nio.file.Paths;
        import java.util.ArrayList;
        import java.util.Collections;
        import java.util.Comparator;
        import java.util.HashMap;
        import java.util.List;
        import java.util.Map;
        import java.util.Map.Entry;
        import org.apache.lucene.index.DirectoryReader;
        import org.apache.lucene.index.IndexReader;
        import org.apache.lucene.index.Terms;
        import org.apache.lucene.index.TermsEnum;
        import org.apache.lucene.store.Directory;
        import org.apache.lucene.store.FSDirectory;
        import org.apache.lucene.util.BytesRef;
        public class GetTopTerms {
        public static void main(String[] args)throws IOException {
        Directory directory =FSDirectory. open(Paths. get("indexdir"));
        IndexReader reader = DirectoryReader.open(directory);
          //因为只索引了一个文档,所以DocID为0
          //通过getTermVector获取content字段的词项
          Terms terms = reader.getTermVector(0, "content");
                // 遍历词项
         TermsEnum termsEnum = terms.iterator();
         Map<String, Integer> map = new HashMap<String, Integer>();
                BytesRef thisTerm;
                while((thisTerm=termsEnum.next())! = null){
                    String termText = thisTerm.utf8ToString(); // 词项
                    // 通过totalTermFreq()方法获取词项频率
                    map.put(termText,(int)termsEnum.totalTermFreq());
                }
                // 按value排序
                List<Map.Entry<String, Integer>> sortedMap = new
                ArrayList<Map.Entry<String, Integer>>(map.entrySet());
                Collections.sort(sortedMap, new Comparator<Map.Entry<
                    String, Integer>>(){
                    public int compare(Map.Entry<String, Integer> o1,
                    Map. Entry<String, Integer> o2)   {
                        return(o2.getValue()- o1.getValue());
                    }
                });
                getTopN(sortedMap, 10);
            }
            // 获取top-n
            public static void getTopN(List<Entry<String, Integer>>
                               sortedMap, int N){
                for(int i = 0; i < N; i++){
                    System.out.println(sortedMap.get(i).getKey()+ ":" +
                                sortedMap.get(i).getValue());
                }
            }
        }

运行结果:

        人工智能:61
        领域:34
        公司:23
        无人驾驶:20
        投资:20
        互联网:18
        创业:17
        中国:16
        特别:16
        技术:15