`
zcwfeng
  • 浏览: 98763 次
  • 性别: Icon_minigender_1
  • 来自: 吉林
社区版块
存档分类
最新评论

lucene第三步,分词

 
阅读更多

出自:http://blog.csdn.net/wxwzy738/article/details/8799656 的整理

1

2、语汇单元的结构解释


3、同义词的设计思路


4、分词器的比较和测试

  1. packageorg.lucene.test;
  2. importjava.io.File;
  3. importjava.io.IOException;
  4. importorg.apache.lucene.analysis.Analyzer;
  5. importorg.apache.lucene.analysis.SimpleAnalyzer;
  6. importorg.apache.lucene.analysis.StopAnalyzer;
  7. importorg.apache.lucene.analysis.WhitespaceAnalyzer;
  8. importorg.apache.lucene.analysis.standard.StandardAnalyzer;
  9. importorg.apache.lucene.document.Document;
  10. importorg.apache.lucene.document.Field;
  11. importorg.apache.lucene.index.CorruptIndexException;
  12. importorg.apache.lucene.index.IndexReader;
  13. importorg.apache.lucene.index.IndexWriter;
  14. importorg.apache.lucene.index.IndexWriterConfig;
  15. importorg.apache.lucene.index.Term;
  16. importorg.apache.lucene.search.IndexSearcher;
  17. importorg.apache.lucene.search.ScoreDoc;
  18. importorg.apache.lucene.search.TermQuery;
  19. importorg.apache.lucene.search.TopDocs;
  20. importorg.apache.lucene.store.Directory;
  21. importorg.apache.lucene.store.RAMDirectory;
  22. importorg.apache.lucene.util.Version;
  23. importorg.junit.Test;
  24. importorg.lucene.util.AnalyzerUtils;
  25. importorg.lucene.util.MySameAnalyzer;
  26. importorg.lucene.util.MyStopAnalyzer;
  27. importcom.chenlb.mmseg4j.analysis.MMSegAnalyzer;
  28. publicclassTestAnalyzer{
  29. /**
  30. *几种分词器在英文分词下面的比较
  31. */
  32. @Test
  33. publicvoidtest01(){
  34. //标准分词器
  35. Analyzera1=newStandardAnalyzer(Version.LUCENE_35);
  36. //停用词分词器
  37. Analyzera2=newStopAnalyzer(Version.LUCENE_35);
  38. //简单分词器
  39. Analyzera3=newSimpleAnalyzer(Version.LUCENE_35);
  40. //空格分词器
  41. Analyzera4=newWhitespaceAnalyzer(Version.LUCENE_35);
  42. Stringtxt="thisismyhouse,Iamcomefromyunnangzhaotong,"+
  43. "Myemailisynkonghao@gmail.com,MyQQis707807876";
  44. AnalyzerUtils.displayToken(txt,a1);
  45. //[my][house][i][am][come][from][yunnang][zhaotong][my][email][ynkonghao][gmail.com][my][qq][707807876]
  46. AnalyzerUtils.displayToken(txt,a2);
  47. //[my][house][i][am][come][from][yunnang][zhaotong][my][email][ynkonghao][gmail][com][my][qq]
  48. AnalyzerUtils.displayToken(txt,a3);
  49. //[this][is][my][house][i][am][come][from][yunnang][zhaotong][my][email][is][ynkonghao][gmail][com][my][qq][is]
  50. AnalyzerUtils.displayToken(txt,a4);
  51. //[this][is][my][house,I][am][come][from][yunnang][zhaotong,My][email][is][ynkonghao@gmail.com,My][QQ][is][707807876]
  52. }
  53. /**
  54. *几种分词器在中文分词下面的比较
  55. */
  56. @Test
  57. publicvoidtest02(){
  58. //标准分词器
  59. Analyzera1=newStandardAnalyzer(Version.LUCENE_35);
  60. //停用词分词器
  61. Analyzera2=newStopAnalyzer(Version.LUCENE_35);
  62. //简单分词器
  63. Analyzera3=newSimpleAnalyzer(Version.LUCENE_35);
  64. //空格分词器
  65. Analyzera4=newWhitespaceAnalyzer(Version.LUCENE_35);
  66. Stringtxt="我来自云南昭通昭阳区师专";
  67. AnalyzerUtils.displayToken(txt,a1);
  68. //[我][来][自][云][南][昭][通][昭][阳][区][师][专]
  69. AnalyzerUtils.displayToken(txt,a2);
  70. //[我来自云南昭通昭阳区师专]
  71. AnalyzerUtils.displayToken(txt,a3);
  72. //[我来自云南昭通昭阳区师专]
  73. AnalyzerUtils.displayToken(txt,a4);
  74. //[我来自云南昭通昭阳区师专]
  75. }
  76. /**
  77. *打印分词的详细信息
  78. */
  79. @Test
  80. publicvoidtest03(){
  81. //标准分词器
  82. Analyzera1=newStandardAnalyzer(Version.LUCENE_35);
  83. //停用词分词器
  84. Analyzera2=newStopAnalyzer(Version.LUCENE_35);
  85. //简单分词器
  86. Analyzera3=newSimpleAnalyzer(Version.LUCENE_35);
  87. //空格分词器
  88. Analyzera4=newWhitespaceAnalyzer(Version.LUCENE_35);
  89. Stringtxt="howareyouthankyou";
  90. AnalyzerUtils.displayAllToken(txt,a1);
  91. AnalyzerUtils.displayAllToken(txt,a2);
  92. AnalyzerUtils.displayAllToken(txt,a3);
  93. AnalyzerUtils.displayAllToken(txt,a4);
  94. }
  95. /**
  96. *停用词的测试
  97. */
  98. @Test
  99. publicvoidtest04(){
  100. Analyzera1=newMyStopAnalyzer(newString[]{"I","you","hate"});
  101. Analyzera2=newStopAnalyzer(Version.LUCENE_35);
  102. Stringtxt="howareYouthAnk'syouIhateyou";
  103. AnalyzerUtils.displayToken(txt,a1);
  104. AnalyzerUtils.displayToken(txt,a2);
  105. }
  106. /**
  107. *中文分词测试
  108. *使用词库分词,自己可扩展词库
  109. */
  110. @Test
  111. publicvoidtest05(){
  112. //Analyzera1=newMMSegAnalyzer();//未加入该分词器自带的词库
  113. //[我][来][自][云][南][昭][通][昭][阳][区][师][专]
  114. //导入分词的词典便有词库
  115. Analyzera1=newMMSegAnalyzer(newFile("D:\\Workspaces\\03_lucene_analyzer\\mmseg4j-1.8.4\\data"));
  116. //[我][来自][云南][昭][通][昭][阳][区][师专]
  117. //可以在data文件下面的words-my.dic扩展自己的词典,比如加了昭通,分词结果为:
  118. //[我][来自][云南][昭通][昭][阳][区][师专]
  119. Stringtxt="我来自云南昭通昭阳区师专";
  120. AnalyzerUtils.displayToken(txt,a1);
  121. }
  122. /**
  123. *同义词测试
  124. *@throwsIOException
  125. *@throwsCorruptIndexException
  126. */
  127. @Test
  128. publicvoidtest06()throwsCorruptIndexException,IOException{
  129. Analyzera1=newMySameAnalyzer();
  130. Stringtxt="我来自中国云南昭通昭阳区师专";
  131. AnalyzerUtils.displayAllToken(txt,a1);
  132. Stringkeyword="俺";
  133. Directorydire=newRAMDirectory();
  134. IndexWriterindexWriter=newIndexWriter(dire,newIndexWriterConfig(Version.LUCENE_35,a1));
  135. Documentdoc=newDocument();
  136. doc.add(newField("content",txt,Field.Store.YES,Field.Index.ANALYZED));
  137. indexWriter.addDocument(doc);
  138. indexWriter.close();
  139. IndexSearchersearch=newIndexSearcher(IndexReader.open(dire));
  140. TopDocstopDoc=search.search(newTermQuery(newTerm("content",keyword)),10);
  141. ScoreDoc[]scoreDoc=topDoc.scoreDocs;
  142. for(ScoreDocscore:scoreDoc){
  143. Documentdoc1=search.doc(score.doc);
  144. System.out.println(doc1.get("content"));
  145. }
  146. }
  147. }

5、扩展自己的停用词分词器
  1. packageorg.lucene.util;
  2. importjava.io.IOException;
  3. importjava.io.Reader;
  4. importjava.util.Set;
  5. importorg.apache.lucene.analysis.Analyzer;
  6. importorg.apache.lucene.analysis.LetterTokenizer;
  7. importorg.apache.lucene.analysis.LowerCaseFilter;
  8. importorg.apache.lucene.analysis.StopAnalyzer;
  9. importorg.apache.lucene.analysis.StopFilter;
  10. importorg.apache.lucene.analysis.TokenStream;
  11. importorg.apache.lucene.analysis.Tokenizer;
  12. importorg.apache.lucene.analysis.tokenattributes.CharTermAttribute;
  13. importorg.apache.lucene.util.Version;
  14. /**
  15. *扩展自己的停用词分词器
  16. *@authoruser
  17. *
  18. */
  19. publicclassMyStopAnalyzerextendsAnalyzer{
  20. privateSetstops;
  21. publicMyStopAnalyzer(String[]sws){
  22. //会自动将字符串数组转化为Set
  23. stops=StopFilter.makeStopSet(Version.LUCENE_35,sws,true);
  24. //把原来的停用词给加进来
  25. stops.addAll(StopAnalyzer.ENGLISH_STOP_WORDS_SET);
  26. }
  27. publicMyStopAnalyzer(){
  28. //获取原有的停用词
  29. stops.addAll(StopAnalyzer.ENGLISH_STOP_WORDS_SET);
  30. }
  31. @Override
  32. publicTokenStreamtokenStream(StringfieldName,Readerreader){
  33. System.out.println("//------------------------------------");
  34. Tokenizertokenizer=newLetterTokenizer(Version.LUCENE_35,reader);
  35. //Tokenizertokenizer=newStandardTokenizer(Version.LUCENE_35,reader);
  36. CharTermAttributecta=tokenizer.addAttribute(CharTermAttribute.class);
  37. try{
  38. while(tokenizer.incrementToken()){
  39. System.out.println(cta);
  40. }
  41. }catch(IOExceptione){
  42. e.printStackTrace();
  43. }
  44. System.out.println("------------------------------------\\");
  45. //为这个分词器设定过滤链和Tokenizer
  46. returnnewStopFilter(Version.LUCENE_35,
  47. newLowerCaseFilter(Version.LUCENE_35,
  48. newLetterTokenizer(Version.LUCENE_35,reader)),
  49. stops);
  50. }
  51. }
6、分词器的扩展,同义词分词器
  1. packageorg.lucene.util;
  2. importjava.io.Reader;
  3. importorg.apache.lucene.analysis.Analyzer;
  4. importorg.apache.lucene.analysis.TokenStream;
  5. importcom.chenlb.mmseg4j.Dictionary;
  6. importcom.chenlb.mmseg4j.MaxWordSeg;
  7. importcom.chenlb.mmseg4j.analysis.MMSegTokenizer;
  8. /**
  9. *分词器的扩展,同义词分词器
  10. *@authoruser
  11. *
  12. */
  13. publicclassMySameAnalyzerextendsAnalyzer{
  14. @Override
  15. publicTokenStreamtokenStream(StringfieldName,Readerreader){
  16. Dictionarydic=Dictionary.getInstance("D:\\Workspaces\\03_lucene_analyzer\\mmseg4j-1.8.4\\data");
  17. returnnewMySameTokenFilter(newMMSegTokenizer(newMaxWordSeg(dic),reader));
  18. }
  19. }

7、同义词过滤器的扩展
  1. packageorg.lucene.util;
  2. importjava.io.IOException;
  3. importjava.util.HashMap;
  4. importjava.util.Map;
  5. importjava.util.Stack;
  6. importorg.apache.lucene.analysis.TokenFilter;
  7. importorg.apache.lucene.analysis.TokenStream;
  8. importorg.apache.lucene.analysis.tokenattributes.CharTermAttribute;
  9. importorg.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;
  10. importorg.apache.lucene.util.AttributeSource;
  11. /**
  12. *同义词过滤器的扩展
  13. *@authoruser
  14. *
  15. */
  16. publicclassMySameTokenFilterextendsTokenFilter{
  17. privateCharTermAttributecta=null;
  18. privatePositionIncrementAttributepia=null;
  19. privateAttributeSource.Statecurrent=null;
  20. privateStack<String>sames=null;
  21. protectedMySameTokenFilter(TokenStreaminput){
  22. super(input);
  23. cta=this.addAttribute(CharTermAttribute.class);
  24. pia=this.addAttribute(PositionIncrementAttribute.class);
  25. sames=newStack<String>();
  26. }
  27. /**
  28. *思想如下:
  29. *其实每个同义词都要放在CharTermAttribute里面,但是如果直接cta.append("大陆");的话
  30. *那会直接把原来的词和同义词连接在同一个语汇单元里面[中国大陆],这样是不行的
  31. *要的是这样的效果[中国][大陆]
  32. *那么就要在遇到同义词的时候把当前的状态保存一份,并把同义词的数组放入栈中,
  33. *这样在下一个语汇单元的时候判断同义词数组是否为空,不为空的话把之前的保存的一份状态
  34. *还原,然后在修改之前状态的值cta.setEmpty(),然后在把同义词的值加入cta.append("大陆")
  35. *再把位置增量设为0,pia.setPositionIncrement(0),这样的话就表示是同义词,
  36. *接着把该同义词的语汇单元返回
  37. */
  38. @Override
  39. publicbooleanincrementToken()throwsIOException{
  40. while(sames.size()>0){
  41. //将元素出栈,并获取这个同义词
  42. Stringstr=sames.pop();
  43. //还原状态
  44. restoreState(current);
  45. cta.setEmpty();
  46. cta.append(str);
  47. //设置位置
  48. pia.setPositionIncrement(0);
  49. returntrue;
  50. }
  51. if(!input.incrementToken())returnfalse;
  52. if(getSameWords(cta.toString())){
  53. //如果有同义词将当前状态先保存
  54. current=captureState();
  55. }
  56. returntrue;
  57. }
  58. /*
  59. *使用这种方式是不行的,这种会把的结果是[中国]替换成了[大陆]
  60. *而不是变成了[中国][大陆]
  61. @Override
  62. publicbooleanincrementToken()throwsIOException{
  63. if(!input.incrementToken())returnfalse;
  64. if(cta.toString().equals("中国")){
  65. cta.setEmpty();
  66. cta.append("大陆");
  67. }
  68. returntrue;
  69. }
  70. */
  71. privatebooleangetSameWords(Stringname){
  72. Map<String,String[]>maps=newHashMap<String,String[]>();
  73. maps.put("中国",newString[]{"大陆","天朝"});
  74. maps.put("我",newString[]{"咱","俺"});
  75. String[]sws=maps.get(name);
  76. if(sws!=null){
  77. for(Strings:sws){
  78. sames.push(s);
  79. }
  80. returntrue;
  81. }
  82. returnfalse;
  83. }
  84. }

8、打印语汇单元的信息
  1. packageorg.lucene.util;
  2. importjava.io.IOException;
  3. importjava.io.StringReader;
  4. importorg.apache.lucene.analysis.Analyzer;
  5. importorg.apache.lucene.analysis.TokenStream;
  6. importorg.apache.lucene.analysis.tokenattributes.CharTermAttribute;
  7. importorg.apache.lucene.analysis.tokenattributes.OffsetAttribute;
  8. importorg.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;
  9. importorg.apache.lucene.analysis.tokenattributes.TypeAttribute;
  10. /**
  11. *打印语汇单元的信息
  12. *@authoruser
  13. *
  14. */
  15. publicclassAnalyzerUtils{
  16. publicstaticvoiddisplayToken(Stringstr,Analyzera){
  17. TokenStreamstream=a.tokenStream("content",newStringReader(str));
  18. /*
  19. *TokenStream相当于一条流
  20. *CharTermAttribute相当于一个碗
  21. *然后把碗丢进流里面,当碗得到一个元素后,碗又会自动流到了下
  22. *一个元素进行取值
  23. *这是一种设计模式:创建一个属性,这个属性会添加流中,
  24. *随着这个TokenStream增加
  25. */
  26. CharTermAttributecta=stream.addAttribute(CharTermAttribute.class);
  27. try{
  28. while(stream.incrementToken()){
  29. System.out.print("["+cta+"]");
  30. //System.out.println(stream);
  31. //如果直接打印Stream的话,toString打印如下:
  32. //(来,startOffset=1,endOffset=2,positionIncrement=1,type=<IDEOGRAPHIC>)
  33. }
  34. System.out.println();
  35. }catch(IOExceptione){
  36. e.printStackTrace();
  37. }
  38. }
  39. /**
  40. *打印详细信息的语汇单元
  41. *@paramstr
  42. *@parama
  43. */
  44. publicstaticvoiddisplayAllToken(Stringstr,Analyzera){
  45. TokenStreamstream=a.tokenStream("content",newStringReader(str));
  46. //位置增量
  47. PositionIncrementAttributepia=stream.addAttribute(PositionIncrementAttribute.class);
  48. //偏移量
  49. OffsetAttributeoa=stream.addAttribute(OffsetAttribute.class);
  50. //词元
  51. CharTermAttributecta=stream.addAttribute(CharTermAttribute.class);
  52. //分词的类型
  53. TypeAttributeta=stream.addAttribute(TypeAttribute.class);
  54. try{
  55. while(stream.incrementToken()){
  56. System.out.print(pia.getPositionIncrement()+":");
  57. System.out.print(cta+"["+oa.startOffset()+"-"+
  58. oa.endOffset()+"-"+ta.type());
  59. System.out.println();
  60. }
  61. System.out.println();
  62. }catch(IOExceptione){
  63. e.printStackTrace();
  64. }
  65. }
  66. }

工程下载路径:http://download.csdn.net/detail/wxwzy738/5284705
分享到:
评论

相关推荐

    lucene、solr中文分词器

    lucene默认自带的分词器对中文支持并不好,所以对于中文索引的分词器,建议使用第三方开源的中文分词器

    Lucene中文分词组件 JE-Analysis 1.4.0

    分词效率: 第一次分词需要1-2秒(读取词典),之后速度基本与Lucene自带分词持平 运行环境: Lucene 1.9+ 内存消耗: 30M+ 1.4.0 —— 2006-08-21 增加词典的动态扩展能力 1.3.3 —— 2006-07...

    Lucene 3.6 学习笔记

    第三章 搜索功能 8 3.1 简单搜索 8 (1) 创建IndexReader 8 (2) 创建IndexSearcher 8 (3) 创建Term和TermQuery 9 (5) 根据TopDocs获取ScoreDoc 9 (6) 根据ScoreDoc获取相应文档 9 3.2 其他搜索 9 (1) 范围查询...

    lucene学习

    Lucene的基础知识 1、案例分析:什么是全文检索,如何实现全文检索 ...b) 第三方中文分析器 7、索引库的维护 a) 添加文档 b) 删除文档 c) 修改文档 Lucene的高级查询、solr入门 solr在项目中的应用及电商搜索实现

    lucene-simple-pinyin:支持,Lucene,Solr5.x拼音分词插件

    lucene-simple-pinyin支持,Lucene,Solr5.x拼音分词插件这个分词插件,主要是为了解决有些中文的名称,需要一个对应的拼音名称而开发的,对拼音支持简拼,全拼,还可以过滤中文,对多音字可以选择只取一个,或者...

    lucene2.9.1所有最新开发包及源码及文档

    C) 第三方的中文分词器:如PaodingAnalyzer、IKAnalyzer 4) IndexWriter.MaxFieldLength: 指定域值的最大长度。 a) UNLIMITED 无限制的。 b) LIMITED 有限制的。值为10000 5) Document: 索引的组成单元. 一...

    深入理解Luncen搜索引擎开发

    Lucene是一个高性能、可伸缩的信息搜索(IR)库。...第3章 Lucene搜索实战 Lucene搜索实战 Lucene搜索深入实战 Lucene搜索深入实战进阶 第4章 Lucene高级 Lucene高级进阶 Lucene排序 Lucene过滤 Lucene分词器

    Lucene搜索引擎开发权威经典 光盘

    第3部分:索引的高级知识。介绍了Lucene建立索引的过程,索引的查看和删除,索引的同步,索引的合并和优化等内容。第4部分:搜索的高级知识。介绍使用不同的Query对象构建搜索请求,使用QueryParser解析用户的搜索...

    Ansj中文分词

    •增加了目前对最新版的Lucene、Solr、Elasticsearch开源第三方搜索框架的分词插件 效果测试——新词发现 引用 1. 未登陆词识别 example:NER:我要碎觉吊丝要小心!城西嘉南公寓 result:命名/v 实体/n ner/...

    solr6对应的IKAnalyzer分词器

    1. 添加分词器的jar文件:在文件夹`contrib`下新建一个文件夹`rd-lib`,并将`IKAnalyzer2012_u6.jar`拷贝进来,这个文件夹用来存放第三方jar文件,后面做数据导入时候,用到的数据库驱动也放到这个文件夹下。...

    【分享:lucene学习资料】---<下载不扣分,回帖加1分,欢迎下载,童叟无欺>

    7.2. 第三方过分析器 17 7.2.1. JE分词用法 17 8. 索引的合并 18 9. 各种Query 18 9.1. 概述 18 9.2. 使用特定的分析器搜索 18 9.3. 按词条搜索—TermQuery 19 9.4. 按“与或”搜索—BooleanQuery 19 9.5. 在某一...

    xmljava系统源码-IKAnalyzer2017_6_6_0:IK中文分词,兼容solr/lucene6.6.0,优化数字和英文搜索

    以下介绍solr的第三方分词器IKAnalyzer。 注:下面操作在Linux下执行,所添加的配置在windonws下依然有效。 运行环境 Solr:6.6.0 系统 : Linux 以下是设置solr中文分词器的方法。 注:开始之前,假定你已经成功登录...

    ShuzhenAnalyzer-1.1.3

    ShuzhenAnalyzer-1.1.3是一款用java写的基于字典的中文分词器,可以与Lucene(目前只测试了与Lucene2.2.0版本一起使用的情况,其他版本Lucene版本未测试过)一起使用来构建搜索引擎系统 其特性如下: 1、基于字典...

    javajava概要设计方案.doc

    javajava概要设计方案全文共5页,当前为第3页。 用户输入查询词 索引库 查询 返回结果 在返回的页面中显示结果 javajava概要设计方案全文共5页,当前为第4页。 javajava概要设计方案全文共5页,当前为第5页。

    淘特站内搜索引擎(C#版) v3.3

    淘特站内搜索引擎是由淘特JSP搜索引擎发展而来,系统基于Lucene.Net核心,通过高效的中文分词算法将数据库中内容进行分析、索引并保存至硬盘中。前台搜索时,通过读取索引文件查询,避免了传统数据库查询在高并发及...

    Java搜索引擎的研究与实现(含文档+源码)

    6 2.3搜索引擎的主要指标及分析 6 2.4小节 6 第三章 网络机器人 7 3.1什么是网络机器人 7 3.2网络机器人的结构分析 7 3.2.1如何解析HTML 7&lt;br&gt;3.2.2 Spider程序结构 8 3.2.3如何构造Spider程序...

    Tantivy 是受 Apache Lucene 启发并用 Rust 编写的全文搜索引擎库

    特征全文检索可配置的分词器(词干可用于 17 种拉丁语言,第三方支持中文(tantivy-jieba和cang-jie )、日语(lindera和tantivy-tokenizer-tiny-segmente )和韩语(lindera +lindera-ko-dic-builder )快速(查看:...

    自己动手写搜索引擎(罗刚著).doc

    第3章 获得海量数据 22 3.1 自己的网络蜘蛛 22 3.1.1 BerkeleyDB介绍 27 3.1.2 抓取网页 28 3.1.3 MP3 抓取 29 3.1.4 RSS 抓取 30 3.1.5 图片抓取 33 3.1.6 垂直行业抓取 34 3.2 抓取数据库中的内容 36 3.2.1 建立...

    搜索引擎的设计与实现(源码+数据库sql+lun文+视频).rar

    第一步我们先要实现一个布隆过滤器。 布隆过滤器是大数据领域的一个常见算法,它的目的是过滤掉那些不是目标的元素。也就是说如果一个要搜索的词并不存在于我的数据中,那么它可以以很快的速度返回目标不存在。 让...

    XML,XSLT,AJAX三大技术打造开源多用户博客X3BLOG

    自主研发的中文分词技术,速度超过3MB/s,准确率达到90%以上,大大超过网上各种开源中文分词技术,几乎可以和中科院的ICTCLAS相媲美,结合当前最成熟的Lucene的.net版本,实现了功能强大执行快速的全文检索引擎。...

Global site tag (gtag.js) - Google Analytics