跟益达学Solr5之玩转post.jar

   为了方便用户往solr中添加索引,Solr为用户提供了一个post.jar工具,用户只需要在命令行下运行post.jar并传入一些参数就可以完成索引的增删改操作,对,它仅仅是一个供用户进行Solr测试的工具而已,有关post.jar的使用说明如下:

Txt代码  

  1. SimplePostTool version 5.1.0  
  2. Usage: java [SystemProperties] -jar post.jar [-h|-] [<file|folder|url|arg> [<file|folder|url|arg>...]]  
  3.   
  4. Supported System Properties and their defaults:  
  5.   -Dc=<core/collection>  
  6.   -Durl=<base Solr update URL> (overrides -Dc option if specified)  
  7.   -Ddata=files|web|args|stdin (default=files)  
  8.   -Dtype=<content-type> (default=application/xml)  
  9.   -Dhost=<host> (default: localhost)  
  10.   -Dport=<port> (default: 8983)  
  11.   -Dauto=yes|no (default=no)  
  12.   -Drecursive=yes|no|<depth> (default=0)  
  13.   -Ddelay=<seconds> (default=0 for files, 10 for web)  
  14.   -Dfiletypes=<type>[,<type>,...] (default=xml,json,csv,pdf,doc,docx,ppt,pptx,xls,xlsx,odt,odp,ods,ott,otp,ots,rtf,htm,html,txt,log)  
  15.   -Dparams="<key>=<value>[&<key>=<value>...]" (values must be URL-encoded)  
  16.   -Dcommit=yes|no (default=yes)  
  17.   -Doptimize=yes|no (default=no)  
  18.   -Dout=yes|no (default=no)  
  19.   
  20. This is a simple command line tool for POSTing raw data to a Solr port.  
  21. NOTE: Specifying the url/core/collection name is mandatory.  
  22. Data can be read from files specified as commandline args,  
  23. URLs specified as args, as raw commandline arg strings or via STDIN.  
  24. Examples:  
  25.   java -Dc=gettingstarted -jar post.jar *.xml  
  26.   java -Ddata=args -Dc=gettingstarted -jar post.jar '<delete><id>42</id></delete>'  
  27.   java -Ddata=stdin -Dc=gettingstarted -jar post.jar < hd.xml  
  28.   java -Ddata=web -Dc=gettingstarted -jar post.jar http://example.com/  
  29.   java -Dtype=text/csv -Dc=gettingstarted -jar post.jar *.csv  
  30.   java -Dtype=application/json -Dc=gettingstarted -jar post.jar *.json  
  31.   java -Durl=http://localhost:8983/solr/techproducts/update/extract -Dparams=literal.id=pdf1 -jar post.jar solr-word.pdf  
  32.   java -Dauto -Dc=gettingstarted -jar post.jar *  
  33.   java -Dauto -Dc=gettingstarted -Drecursive -jar post.jar afolder  
  34.   java -Dauto -Dc=gettingstarted -Dfiletypes=ppt,html -jar post.jar afolder  
  35. The options controlled by System Properties include the Solr  
  36. URL to POST to, the Content-Type of the data, whether a commit  
  37. or optimize should be executed, and whether the response should  
  38. be written to STDOUT. If auto=yes the tool will try to set type  
  39. automatically from file name. When posting rich documents the  
  40. file name will be propagated as "resource.name" and also used  
  41. as "literal.id". You may override these or any other request parameter  
  42. through the -Dparams property. To do a commit only, use "-" as argument.  
  43. The web mode is a simple crawler following links within domain, default delay=10s.  

   重点在这里:

Java代码  

  1. java [SystemProperties] -jar post.jar [-h|-] [<file|folder|url|arg> [<file|folder|url|arg>...]]  

   要看懂这个post.jar使用命令规范,你首先需要知道,被中括号包住的参数表示可选参数即这个参数可有可有,| 表示或者,SystemProperties表示系统属性,什么叫系统属性呢?即你通过System.setProperty();设置的参数,比如:

Java代码  

  1. System.setProperty(key,value);  

  这里的key,value值都是随便定义的,没什么特别要求,这样你随后通过System.getProperty(key)通过key就能在任意时刻获取到该key对应的参数值,如果是在dos命令行下,你也可以通过Java -Dkey=value这种方式指定,至此java [SystemProperties]这部分你应该理解了,至于后面的-jar是java命令的参数,即执行一个jar文件,-jar后面指定一个jar包路径,默认是相对于当前所在路径,-h即表示添加了这个即会打印命令提示信息,就好比你敲java -h是类似的,后面的file,folder,url,args分别表示你要提交的数据的几种不同表示形式,file即表示你要提交的数据是存在于文件中,而folder即表示你要提交的存在于文件夹中,url即表示你要提交的数据是存在于互联网上的一个URL地址表示的资源,它可能是一个HTML页面,可能是一个PDF文件,可能是一个图片等等,args即表示你要提交的数据直接在命令行敲出来,但arges并不是随随便便一个字符串就行的,它需要有固定的格式,solr才能解析,至于args的输入格式后面会说到。

   Supported System Properties and their defaults:

   这句下面列出了post.jar支持的几个自定义系统属性,下面我会对每个自定义系统属性一一做个说明:

Java代码  

  1.  -Dc=<core/collection>  
  2. -Durl=<base Solr update URL> (overrides -Dc option if specified)  
  3. -Ddata=files|web|args|stdin (default=files)  
  4. -Dtype=<content-type> (default=application/xml)  
  5. -Dhost=<host> (default: localhost)  
  6. -Dport=<port> (default: 8983)  
  7. -Dauto=yes|no (default=no)  
  8. -Drecursive=yes|no|<depth> (default=0)  
  9. -Ddelay=<seconds> (default=0 for files, 10 for web)  
  10. -Dfiletypes=<type>[,<type>,...] (default=xml,json,csv,pdf,doc,docx,ppt,pptx,xls,xlsx,odt,odp,ods,ott,otp,ots,rtf,htm,html,txt,log)  
  11. -Dparams="<key>=<value>[&<key>=<value>...]" (values must be URL-encoded)  
  12. -Dcommit=yes|no (default=yes)  
  13. -Doptimize=yes|no (default=no)  
  14. -Dout=yes|no (default=no)  

   -D是命令行下指定系统属性的固定前缀,

  c表示core名称,你需要对solr admin里的哪个core进行索引数据添加/修改/删除

 

  url表示solr admin后台索引更新的请求URL,这个URL是固定的,一般格式是http://host:port/solr/${coreName}/update,这里的${coreName}和上面的c属性值保持一致

 

  data表示你要提交数据的几种模式,files模式表示你要提交的数据在文件里

  web表示你要提交的数据在互联网上的一个URL表示的资源文件里

  args表示你要提交的数据你会直接在post.jar命令后面直接输入

  stdin表示你要提交的数据需要在dos命令行下通过System.in输入流临时接收,跟args有点类似,

  但不同的是,stdin模式下,post.jar后面不需要指定任何参数,直接回车即可,然后程序会等待用户输入,

  用户输入完毕再回车,post.jar会接收到用户输入,post.jar重新被唤醒继续执行。而args是直接在post.jar后面

  输入参数,没有一个中断过程,而stdin模式下如果用户一直没有输入,那post.jar就会一直阻塞在那里等待用户输入为止。

  

  type表示你要提交数据的MIME类型,默认是application/xml即默认会当作是XML来处理

 

  host表示你要链接的SOlr Admin部署服务器的主机名或者IP地址,默认是localhost

 

  port表示你要链接的Solr Admin部署的Web容器监听的端口号,默认post.jar里设置为8983

  port具体值取决于你实际部署环境而定

 

  auto表示是否自动猜测文件类型

 

  recursive表示是否递归,这里递归有两种情况,比如你data=folder即表示是否递归查找文件夹下的

  所有文件,如果你data=web即表示是否递归抓取URL,设置为no即表示不递归操作,设置为一个数字,

  即表示递归深度

 

  delay:这里的时间延迟也分两种,如果你post的是file,那么每个file的post间隔为0,即不做延迟处理,

  而如果你是post的是网络上的一个url资源,因为需要收到对方服务器的访问限制,所以必须要做一个抓取

  频率限制即每抓一个睡眠一会儿,否则抓取太快太频率容易被对方封IP。

 

  filetypes表示post.jar支持提交哪些文件类型,后面有列出默认支持的文件类型,如果你想覆盖默认值,那么

  请指定此参数

 

  params表示需要追加到Solr Admin的请求URL后面的请求参数如id=1&name=yida之类的

 

  commit表示是否提交到solr admin后台进行索引写入,设置为false表示不提交至sor admin,但设置为true也不一定

  就意味着就一定会把索引写入磁盘,这取决于solrconfig中directory配置的实现是什么,如果配置的是RAMDirectory,就仅仅只在内存中操作了。

 

  optimize表示是否需要对索引进行优化操作,默认为no即表示不对索引进行优化

   

  out即OutputStream表示输出流,这个参数作用就是,你请求Solr Admin添加索引数据,Solr Admin后台会返回数据给你,Solr Admin后台返回的数据你拿什么输出流来接收,默认是System.out即表示把后台返回的信息输出打印到控制台

 

  理解上面的相关说明,再来看看官方提供的几个post.jar使用命令示例,是不是感觉so easy了?

Java代码  

  1. Examples:  
  2.   java -Dc=gettingstarted -jar post.jar *.xml  
  3.   java -Ddata=args -Dc=gettingstarted -jar post.jar '<delete><id>42</id></delete>'  
  4.   java -Ddata=stdin -Dc=gettingstarted -jar post.jar < hd.xml  
  5.   java -Ddata=web -Dc=gettingstarted -jar post.jar http://example.com/  
  6.   java -Dtype=text/csv -Dc=gettingstarted -jar post.jar *.csv  
  7.   java -Dtype=application/json -Dc=gettingstarted -jar post.jar *.json  
  8.   java -Durl=http://localhost:8983/solr/techproducts/update/extract -Dparams=literal.id=pdf1 -jar post.jar solr-word.pdf  
  9.   java -Dauto -Dc=gettingstarted -jar post.jar *  
  10.   java -Dauto -Dc=gettingstarted -Drecursive -jar post.jar afolder  
  11.   java -Dauto -Dc=gettingstarted -Dfiletypes=ppt,html -jar post.jar afolder  

  

  OK,post.jar知道怎么玩了,那是不是该来实践一把?要想往solr admin后台添加索引数据,你首先需要添加一个core,添加一个core你可以通过Solr Admin的web UI来创建,如图:

 

   

   instanceDir就是你的core根目录,solr-hone就是你的SOLR_HOME,你可以在SOLR_HOME下创建多个core目录,dataDir表示你core的数据目录,当前core的索引数据会存放在dataDir下的data\index目录下,上述所有文件夹需要你手动创建(除了data\index这里的index目录,solr会自动创建),如图:

 solr_home目录下需要一个solr.xml,这个配置文件可以从solr的zip包里获取,如图:

 如图找到solr.xml复制到你自己的solr-home根目录下,然后你的core目录下需要一个conf目录,用来存放当前core的solr配置,这些配置文件可以从solr的examples里找到,如图:

   

   solrconfig.xml配置文件是每个core必须的一个配置文件,只对当前core有效,sechma.xml配置文件是用来定义索引的每个域的,比如域的名称啊,域的类型,域是否索引,是否存储,是否分词,是否存储项向量,使用什么分词器,指定同义词字典文件在哪儿,指定停用词字典文件在哪儿等等,这些信息都是是sechma.xml中定义的,如果你有点Lucene基础,那编写schema.xml就没什么压力了,只不过以前在Lucene中是直接使用Lucene API来定义域的这些信息的,现在改用XML形式表达同样的意思。注意里面还有个protwords.txt字典文件,这在Lucene中还没接触过。下面是一段有关protwords.txt字典文件的解释说明:

Txt代码  

  1. Protwords are the words which you do not want to be stemmed (In stemming  
  2. case manager/managing/managed/manageable all are indexed as ---> manag. Same  
  3. thing goes in case of searching. In case you do not want a particular word  
  4. to be stemmed at index/search time just put it in protwords.txt of SOLR.  

   大意就是Protwords表示那些你不想被还原的单词,比如manager/managing/managed/manageable这些单词,

在stemming模式下,他们全都被索引为manag,如果你不希望某个单词被stemming(转换成原型),那么你就可以把他们放入protwords.txt字典文件中,这样他们就不会被还原成原型了。

prot即protected缩写,即受保护的意思,只有英文才存在单词还原的情况。

      这样你的core目录结构就创建好了,如果你不按这种规范去创建目录结构,那么你在创建core的时候会报错,比如你可能会遇到这样的异常:

 Core创建成功后,你会在solr admin 后台看到这样的界面:

     

     当然你也可以直接通过在浏览器输入URL的方式来创建,

      http://localhost:8080/solr/admin/cores?action=CREATE&name=core2&instanceDir=/opt/solr/core2&config=solrconfig.xml&schema=schema.xml&dataDir=data

   name:就是你的core名称,

   instanceDir就是你的core根目录,举个例子,linux下可能是/opt/solr/core2,windows下可能是C:/solr/core2

   config,schema即core的两个重要的配置文件的名称,只要你core目录结构按规范创建好了,就会按照你指定的配置文件名称去conf目录下去找,dataDir表示你的core的数据目录,该用户主要用来存放你当前core的索引数据

 

    core创建好了,那就可以在命令行下执行post.jar往solr admin中添加索引了,首先你需要在dos下切到post.jar所在目录,如图:

 

 在运行post.jar命令之前,我们需要找一个测试用的xml文件,这里我以solr的examples目录下提供的xml为例,如图:


  

 

   然后到Solr Admin web后台界面刷新页面,查看core-c的索引数量是否有变化,如图:


     但是要注意,不是任何xml文件都可以被索引的,提交的XML内容是有固定的编写格式的,打开我们刚刚提交的xml文件,如图:


   <add>表示添加索引,一对<doc></doc>表示Lucene中的一个Document,field表示域,name毫无疑问就是域名,field标签之间的值就是域值,<add>标签只有有一个,<add>标签下可以有多个<doc>标签,多个<doc>即表示批量添加多个document.

   <add>标签还有2个可选属性,

   overwrite: "true" | "false" ,默认为false,表示对于拥有相同uniqueKey的document是否需要覆盖,uniqueKey表示document的唯一主键,类似数据库表的主键,

  commitWithin:表示document必须在指定的毫秒数内提交成功,否则就放弃提交。

  

  你还可以为某个document设置权重,比如:

 

Java代码  

  1. <add>  
  2.   <doc boost="2.5">  
  3.     <field name="employeeId">05991</field>  
  4.     <field name="office" boost="2.0">Bridgewater</field>  
  5.   </doc>  
  6. </add>  

   

 

  如何添加多值域?

 

Java代码  

  1. <add>  
  2.   <doc>  
  3.     <field name="employeeId">05991</field>  
  4.     <field name="skills" update="set">Python</field>  
  5.     <field name="skills" update="set">Java</field>  
  6.     <field name="skills" update="set">Jython</field>  
  7.   </doc>  
  8. </add>  

  如何将某个域的值设为null?

Java代码  

  1. <add>  
  2.   <doc>  
  3.     <field name="employeeId">05991</field>  
  4.     <field name="skills" update="set" null="true" />  
  5.   </doc>  
  6. </add>  

  你还可以在<add>标签下添加

 

Java代码  

  1. <commit/>  
  2. <optimize/>  

  类似于你在Lucene里显式的调用writer.commit();writer.optimize();

  

  如何根据ID删除document?(注意这里说的id指的是uniqueKey指定的域,uniqueKey是在schema.xml中定义的,不要与document的文档ID混为一谈)

 

Java代码  

  1. <delete><id>05991</id></delete>  

 

  如何根据一个Query删除一个Document呢?

 

Java代码  

  1. <delete><query>office:Bridgewater</query></delete>  

  office表示域名,bridgewater表示域值,默认创建的是TermQuery,域值可以有通配符,可以是正则表达式,可以使用QueryParser表达式表示,你懂的。

 

  上面说的都是在命令行下操作,如果你觉得在命令行下操作有点蛋疼,那我们也可以在eclipse中操作,通过反编译post.jar我发现post.jar包里面就是一个SimplePostTool类,我花了点时间阅读了SimplePostTool类的源码并对其关键位置加了一些注释,源码如下:

Java代码  

  1. package com.yida.framework.solr5.test;  
  2.   
  3. import java.io.BufferedReader;  
  4. import java.io.ByteArrayInputStream;  
  5. import java.io.ByteArrayOutputStream;  
  6. import java.io.File;  
  7. import java.io.FileFilter;  
  8. import java.io.FileInputStream;  
  9. import java.io.IOException;  
  10. import java.io.InputStream;  
  11. import java.io.InputStreamReader;  
  12. import java.io.OutputStream;  
  13. import java.net.HttpURLConnection;  
  14. import java.net.MalformedURLException;  
  15. import java.net.ProtocolException;  
  16. import java.net.URL;  
  17. import java.net.URLEncoder;  
  18. import java.nio.BufferOverflowException;  
  19. import java.nio.ByteBuffer;  
  20. import java.nio.charset.Charset;  
  21. import java.nio.charset.StandardCharsets;  
  22. import java.text.SimpleDateFormat;  
  23. import java.util.ArrayList;  
  24. import java.util.Date;  
  25. import java.util.HashMap;  
  26. import java.util.HashSet;  
  27. import java.util.LinkedHashSet;  
  28. import java.util.List;  
  29. import java.util.Locale;  
  30. import java.util.Map;  
  31. import java.util.Set;  
  32. import java.util.TimeZone;  
  33. import java.util.regex.Pattern;  
  34. import java.util.regex.PatternSyntaxException;  
  35. import java.util.zip.GZIPInputStream;  
  36. import java.util.zip.Inflater;  
  37. import java.util.zip.InflaterInputStream;  
  38.   
  39. import javax.xml.bind.DatatypeConverter;  
  40. import javax.xml.parsers.DocumentBuilderFactory;  
  41. import javax.xml.parsers.ParserConfigurationException;  
  42. import javax.xml.xpath.XPath;  
  43. import javax.xml.xpath.XPathConstants;  
  44. import javax.xml.xpath.XPathExpression;  
  45. import javax.xml.xpath.XPathExpressionException;  
  46. import javax.xml.xpath.XPathFactory;  
  47.   
  48. import org.w3c.dom.Document;  
  49. import org.w3c.dom.Node;  
  50. import org.w3c.dom.NodeList;  
  51. import org.xml.sax.SAXException;  
  52. /** 
  53.  * 往Solr Admin后台提交索引数据的一个小测试工具 
  54.  * @author Lanxiaowei 
  55.  * 
  56.  */  
  57. @SuppressWarnings("unused")  
  58. public class SimplePostTool {  
  59.     /**Solr Admin后台部署服务器的主机名或IP地址,默认为localhost即本地*/  
  60.     private static final String DEFAULT_POST_HOST = "localhost";  
  61.     /**Solr Admin后台部署容器监听的端口号,默认为8983*/  
  62.     private static final String DEFAULT_POST_PORT = "8983";  
  63.     /**当前工具的版本号*/  
  64.     private static final String VERSION_OF_THIS_TOOL = "5.1.0";  
  65.     /**是否提交索引*/  
  66.     private static final String DEFAULT_COMMIT = "yes";  
  67.     /**是否需要优化索引*/  
  68.     private static final String DEFAULT_OPTIMIZE = "no";  
  69.     /**是否将输出流设置为System.out即控制台输出流*/  
  70.     private static final String DEFAULT_OUT = "no";  
  71.     /**是否自动猜测文件MIME类型,默认是按照文件后缀名进行判定*/  
  72.     private static final String DEFAULT_AUTO = "no";  
  73.     /**是否递归抓取,0表示不递归抓取,1表示递归抓取*/  
  74.     private static final String DEFAULT_RECURSIVE = "0";  
  75.     /**抓取时间间隔即每抓取一个URL后睡眠多少秒,单位:秒*/  
  76.     private static final int DEFAULT_WEB_DELAY = 10;  
  77.     /**默认索引提交时间间隔即每提交一个睡眠多少毫秒,单位:毫秒*/  
  78.     private static final int DEFAULT_POST_DELAY = 10;  
  79.     /**对于URL就是抓取深度,对于文件夹就是目录深度,当前深度为0*/  
  80.     private static final int MAX_WEB_DEPTH = 10;  
  81.     /**默认文件MIME类型*/  
  82.     private static final String DEFAULT_CONTENT_TYPE = "application/xml";  
  83.     /**默认支持提交的文件类型*/  
  84.     private static final String DEFAULT_FILE_TYPES = "xml,json,csv,pdf,doc,docx,ppt,pptx,xls,xlsx,odt,odp,ods,ott,otp,ots,rtf,htm,html,txt,log";  
  85.     /**文件提交模式*/  
  86.     static final String DATA_MODE_FILES = "files";  
  87.     /**URL后面挂参数形式提交*/  
  88.     static final String DATA_MODE_ARGS = "args";  
  89.     /**标准输入模式,选择了这种模式的话,程序会中断,等待用户输入作为提交数据*/  
  90.     static final String DATA_MODE_STDIN = "stdin";  
  91.     /**爬虫抓取模式提交索引即需要用户提供一个待抓的页面链接,内部去抓取页面内容然后提交*/  
  92.     static final String DATA_MODE_WEB = "web";  
  93.     /**默认提交模式为files即提交文件*/  
  94.     static final String DEFAULT_DATA_MODE = "files";  
  95.     boolean auto = false;  
  96.     int recursive = 0;  
  97.     int delay = 0;  
  98.     String fileTypes;  
  99.     URL solrUrl;  
  100.     OutputStream out = null;  
  101.     String type;  
  102.     String mode;  
  103.     boolean commit;  
  104.     boolean optimize;  
  105.     String[] args;  
  106.     private int currentDepth;  
  107.     static HashMap<String, String> mimeMap;  
  108.     GlobFileFilter globFileFilter;  
  109.     //每个深度的URL集合,这里的list索引即抓取深度  
  110.     List<LinkedHashSet<URL>> backlog = new ArrayList<LinkedHashSet<URL>>();  
  111.     //已抓取过的URL集合  
  112.     Set<URL> visited = new HashSet<URL>();  
  113.   
  114.     static final Set<String> DATA_MODES = new HashSet<String>();  
  115.     static final String USAGE_STRING_SHORT = "Usage: java [SystemProperties] -jar post.jar [-h|-] [<file|folder|url|arg> [<file|folder|url|arg>...]]";  
  116.     static boolean mockMode = false;  
  117.     static PageFetcher pageFetcher;  
  118.   
  119.     public static void main(String[] args) {  
  120.         String coreName = "core-test";  
  121.         System.setProperty("c",coreName);  
  122.           
  123.         info("SimplePostTool version 5.1.0");  
  124.         if ((0 < args.length)  
  125.                 && (("-help".equals(args[0])) || ("--help".equals(args[0])) || ("-h"  
  126.                         .equals(args[0])))) {  
  127.             //打印post.jar命令提示信息  
  128.             usage();  
  129.         } else {  
  130.             SimplePostTool t = parseArgsAndInit(args);  
  131.             t.execute();  
  132.         }  
  133.     }  
  134.   
  135.     public void execute() {  
  136.         long startTime = System.currentTimeMillis();  
  137.         if (("files".equals(this.mode)) && (this.args.length > 0)) {  
  138.             doFilesMode();  
  139.         } else if (("args".equals(this.mode)) && (this.args.length > 0)) {  
  140.             doArgsMode();  
  141.         } else if (("web".equals(this.mode)) && (this.args.length > 0)) {  
  142.             doWebMode();  
  143.         } else if ("stdin".equals(this.mode)) {  
  144.             doStdinMode();  
  145.         } else {  
  146.             usageShort();  
  147.             return;  
  148.         }  
  149.   
  150.         if (this.commit)  
  151.             commit();  
  152.         if (this.optimize)  
  153.             optimize();  
  154.         long endTime = System.currentTimeMillis();  
  155.         displayTiming(endTime - startTime);  
  156.     }  
  157.   
  158.     private void displayTiming(long millis) {  
  159.         SimpleDateFormat df = new SimpleDateFormat("H:mm:ss.SSS",  
  160.                 Locale.getDefault());  
  161.         df.setTimeZone(TimeZone.getTimeZone("UTC"));  
  162.         System.out.println(new StringBuilder().append("Time spent: ")  
  163.                 .append(df.format(new Date(millis))).toString());  
  164.     }  
  165.   
  166.     protected static SimplePostTool parseArgsAndInit(String[] args) {  
  167.         String urlStr = null;  
  168.         try {  
  169.             String mode = System.getProperty("data", "files");  
  170.             if (!DATA_MODES.contains(mode)) {  
  171.                 fatal(new StringBuilder()  
  172.                         .append("System Property 'data' is not valid for this tool: ")  
  173.                         .append(mode).toString());  
  174.             }  
  175.   
  176.             //需要追加到Solr请求URL后面的请求参数  
  177.             String params = System.getProperty("params", "");  
  178.   
  179.             String host = System.getProperty("host", DEFAULT_POST_HOST);  
  180.             String port = System.getProperty("port", DEFAULT_POST_PORT);  
  181.             String core = System.getProperty("c");  
  182.   
  183.             urlStr = System.getProperty("url");  
  184.   
  185.             if ((urlStr == null) && (core == null)) {  
  186.                 fatal("Specifying either url or core/collection is mandatory.\nUsage: java [SystemProperties] -jar post.jar [-h|-] [<file|folder|url|arg> [<file|folder|url|arg>...]]");  
  187.             }  
  188.   
  189.             //若没有指定Solr请求URL,则生成默认的SOLR请求URL  
  190.             if (urlStr == null) {  
  191.                 urlStr = String.format(Locale.ROOT,  
  192.                         "http://%s:%s/solr/%s/update", new Object[] { host,  
  193.                                 port, core });  
  194.             }  
  195.             urlStr = appendParam(urlStr, params);  
  196.             URL url = new URL(urlStr);  
  197.             boolean auto = isOn(System.getProperty("auto", DEFAULT_AUTO));  
  198.             String type = System.getProperty("type");  
  199.   
  200.             int recursive = 0;  
  201.             String r = System.getProperty("recursive", DEFAULT_RECURSIVE);  
  202.             try {  
  203.                 recursive = Integer.parseInt(r);  
  204.             } catch (Exception e) {  
  205.                 if (isOn(r)) {  
  206.                     recursive = "web".equals(mode) ? 1 : 999;  
  207.                 }  
  208.             }  
  209.             int delay = "web".equals(mode) ? DEFAULT_WEB_DELAY : 0;  
  210.             try {  
  211.                 delay = Integer.parseInt(System  
  212.                         .getProperty("delay", delay + ""));  
  213.             } catch (Exception e) {  
  214.             }  
  215.             OutputStream out = isOn(System.getProperty("out", DEFAULT_OUT)) ? System.out  
  216.                     : null;  
  217.             String fileTypes = System.getProperty("filetypes",DEFAULT_FILE_TYPES);  
  218.             boolean commit = isOn(System.getProperty("commit", DEFAULT_COMMIT));  
  219.             boolean optimize = isOn(System.getProperty("optimize", DEFAULT_OPTIMIZE));  
  220.   
  221.             return new SimplePostTool(mode, url, auto, type, recursive, delay,  
  222.                     fileTypes, out, commit, optimize, args);  
  223.         } catch (MalformedURLException e) {  
  224.             fatal(new StringBuilder()  
  225.                     .append("System Property 'url' is not a valid URL: ")  
  226.                     .append(urlStr).toString());  
  227.         }  
  228.         return null;  
  229.     }  
  230.   
  231.     public SimplePostTool(String mode, URL url, boolean auto, String type,  
  232.             int recursive, int delay, String fileTypes, OutputStream out,  
  233.             boolean commit, boolean optimize, String[] args) {  
  234.         this.mode = mode;  
  235.         this.solrUrl = url;  
  236.         this.auto = auto;  
  237.         this.type = type;  
  238.         this.recursive = recursive;  
  239.         this.delay = delay;  
  240.         this.fileTypes = fileTypes;  
  241.         this.globFileFilter = getFileFilterFromFileTypes(fileTypes);  
  242.         this.out = out;  
  243.         this.commit = commit;  
  244.         this.optimize = optimize;  
  245.         this.args = args;  
  246.         pageFetcher = new PageFetcher();  
  247.     }  
  248.   
  249.     public SimplePostTool() {  
  250.     }  
  251.   
  252.     /** 
  253.      * 要提交的索引数据存在于文件中,你可以通过args指定一个文件目录或者一个文件路径或者xxxx\*.xml这种通配符形式 
  254.      */  
  255.     private void doFilesMode() {  
  256.         this.currentDepth = 0;  
  257.   
  258.         if (!this.args[0].equals("-")) {  
  259.             info(new StringBuilder()  
  260.                     .append("Posting files to [base] url ")  
  261.                     .append(this.solrUrl)  
  262.                     .append(!this.auto ? new StringBuilder()  
  263.                             .append(" using content-type ")  
  264.                             .append(this.type == null ? DEFAULT_CONTENT_TYPE  
  265.                                     : this.type).toString() : "").append("...")  
  266.                     .toString());  
  267.             if (this.auto)  
  268.                 info(new StringBuilder()  
  269.                         .append("Entering auto mode. File endings considered are ")  
  270.                         .append(this.fileTypes).toString());  
  271.             if (this.recursive > 0)  
  272.                 info(new StringBuilder()  
  273.                         .append("Entering recursive mode, max depth=")  
  274.                         .append(this.recursive).append(", delay=")  
  275.                         .append(this.delay).append("s").toString());  
  276.             int numFilesPosted = postFiles(this.args, 0, this.out, this.type);  
  277.             info(new StringBuilder().append(numFilesPosted)  
  278.                     .append(" files indexed.").toString());  
  279.         }  
  280.     }  
  281.   
  282.     /** 
  283.      * 要提交的索引数据直接通过args post方式提交到Solr Admin后台 
  284.      */  
  285.     private void doArgsMode() {  
  286.         info(new StringBuilder().append("POSTing args to ")  
  287.                 .append(this.solrUrl).append("...").toString());  
  288.         for (String a : this.args) {  
  289.             postData(stringToStream(a), null, this.out, this.type, this.solrUrl);  
  290.         }  
  291.     }  
  292.   
  293.     /** 
  294.      * 要提交的数据存在于互联网,需要即时去抓取网页内容,然后提交 
  295.      * @return 
  296.      */  
  297.     private int doWebMode() {  
  298.         reset();  
  299.         int numPagesPosted = 0;  
  300.         try {  
  301.             if (this.type != null) {  
  302.                 fatal("Specifying content-type with \"-Ddata=web\" is not supported");  
  303.             }  
  304.             if (this.args[0].equals("-")) {  
  305.                 return 0;  
  306.             }  
  307.   
  308.             this.solrUrl = appendUrlPath(this.solrUrl, "/extract");  
  309.   
  310.             info(new StringBuilder().append("Posting web pages to Solr url ")  
  311.                     .append(this.solrUrl).toString());  
  312.             this.auto = true;  
  313.             info(new StringBuilder()  
  314.                     .append("Entering auto mode. Indexing pages with content-types corresponding to file endings ")  
  315.                     .append(this.fileTypes).toString());  
  316.             if (this.recursive > 0) {  
  317.                 if (this.recursive > MAX_WEB_DEPTH) {  
  318.                     this.recursive = MAX_WEB_DEPTH;  
  319.                     warn("Too large recursion depth for web mode, limiting to 10...");  
  320.                 }  
  321.                 if (this.delay < DEFAULT_WEB_DELAY)  
  322.                     warn("Never crawl an external web site faster than every "+DEFAULT_WEB_DELAY+" seconds, your IP will probably be blocked");  
  323.                 info(new StringBuilder()  
  324.                         .append("Entering recursive mode, depth=")  
  325.                         .append(this.recursive).append(", delay=")  
  326.                         .append(this.delay).append("s").toString());  
  327.             }  
  328.             numPagesPosted = postWebPages(this.args, 0, this.out);  
  329.             info(new StringBuilder().append(numPagesPosted)  
  330.                     .append(" web pages indexed.").toString());  
  331.         } catch (MalformedURLException e) {  
  332.             fatal(new StringBuilder()  
  333.                     .append("Wrong URL trying to append /extract to ")  
  334.                     .append(this.solrUrl).toString());  
  335.         }  
  336.         return numPagesPosted;  
  337.     }  
  338.   
  339.     private void doStdinMode() {  
  340.         info(new StringBuilder().append("POSTing stdin to ")  
  341.                 .append(this.solrUrl).append("...").toString());  
  342.         postData(System.in, null, this.out, this.type, this.solrUrl);  
  343.     }  
  344.   
  345.     private void reset() {  
  346.         this.fileTypes = "xml,json,csv,pdf,doc,docx,ppt,pptx,xls,xlsx,odt,odp,ods,ott,otp,ots,rtf,htm,html,txt,log";  
  347.         this.globFileFilter = getFileFilterFromFileTypes(this.fileTypes);  
  348.         this.backlog = new ArrayList<LinkedHashSet<URL>>();  
  349.         this.visited = new HashSet<URL>();  
  350.     }  
  351.   
  352.     /** 
  353.      * 打印post.jar命令使用示例 
  354.      */  
  355.     private static void usageShort() {  
  356.         System.out  
  357.                 .println("Usage: java [SystemProperties] -jar post.jar [-h|-] [<file|folder|url|arg> [<file|folder|url|arg>...]]\n       Please invoke with -h option for extended usage help.");  
  358.     }  
  359.   
  360.     /** 
  361.      * 打印post.jar命令提示信息 
  362.      */  
  363.     private static void usage() {  
  364.         System.out  
  365.                 .println("Usage: java [SystemProperties] -jar post.jar [-h|-] [<file|folder|url|arg> [<file|folder|url|arg>...]]\n\nSupported System Properties and their defaults:\n  -Dc=<core/collection>\n  -Durl=<base Solr update URL> (overrides -Dc option if specified)\n  -Ddata=files|web|args|stdin (default=files)\n  -Dtype=<content-type> (default=application/xml)\n  -Dhost=<host> (default: localhost)\n  -Dport=<port> (default: "+DEFAULT_POST_PORT+")\n  -Dauto=yes|no (default=no)\n  -Drecursive=yes|no|<depth> (default=0)\n  -Ddelay=<seconds> (default=0 for files, 10 for web)\n  -Dfiletypes=<type>[,<type>,...] (default=xml,json,csv,pdf,doc,docx,ppt,pptx,xls,xlsx,odt,odp,ods,ott,otp,ots,rtf,htm,html,txt,log)\n  -Dparams=\"<key>=<value>[&<key>=<value>...]\" (values must be URL-encoded)\n  -Dcommit=yes|no (default=yes)\n  -Doptimize=yes|no (default=no)\n  -Dout=yes|no (default=no)\n\nThis is a simple command line tool for POSTing raw data to a Solr port.\nNOTE: Specifying the url/core/collection name is mandatory.\nData can be read from files specified as commandline args,\nURLs specified as args, as raw commandline arg strings or via STDIN.\nExamples:\n  java -Dc=gettingstarted -jar post.jar *.xml\n  java -Ddata=args -Dc=gettingstarted -jar post.jar '<delete><id>42</id></delete>'\n  java -Ddata=stdin -Dc=gettingstarted -jar post.jar < hd.xml\n  java -Ddata=web -Dc=gettingstarted -jar post.jar http://example.com/\n  java -Dtype=text/csv -Dc=gettingstarted -jar post.jar *.csv\n  java -Dtype=application/json -Dc=gettingstarted -jar post.jar *.json\n  java -Durl=http://localhost:8983/solr/techproducts/update/extract -Dparams=literal.id=pdf1 -jar post.jar solr-word.pdf\n  java -Dauto -Dc=gettingstarted -jar post.jar *\n  java -Dauto -Dc=gettingstarted -Drecursive -jar post.jar afolder\n  java -Dauto -Dc=gettingstarted -Dfiletypes=ppt,html -jar post.jar afolder\nThe options controlled by System Properties include the Solr\nURL to POST to, the Content-Type of the data, whether a commit\nor optimize should be executed, and whether the response should\nbe written to STDOUT. If auto=yes the tool will try to set type\nautomatically from file name. When posting rich documents the\nfile name will be propagated as \"resource.name\" and also used\nas \"literal.id\". You may override these or any other request parameter\nthrough the -Dparams property. To do a commit only, use \"-\" as argument.\nThe web mode is a simple crawler following links within domain, default delay="+DEFAULT_WEB_DELAY+"s.");  
  366.     }  
  367.   
  368.     /** 
  369.      * 提交文件 
  370.      * @param args 
  371.      * @param startIndexInArgs 
  372.      * @param out 
  373.      * @param type 
  374.      * @return 
  375.      */  
  376.     public int postFiles(String[] args, int startIndexInArgs, OutputStream out,  
  377.             String type) {  
  378.         reset();  
  379.         int filesPosted = 0;  
  380.         for (int j = startIndexInArgs; j < args.length; j++) {  
  381.             File srcFile = new File(args[j]);  
  382.             if ((srcFile.isDirectory()) && (srcFile.canRead())) {  
  383.                 filesPosted += postDirectory(srcFile, out, type);  
  384.             } else if ((srcFile.isFile()) && (srcFile.canRead())) {  
  385.                 filesPosted += postFiles(new File[] { srcFile }, out, type);  
  386.             } else {  
  387.                 File parent = srcFile.getParentFile();  
  388.                 if (parent == null)  
  389.                     parent = new File(".");  
  390.                 String fileGlob = srcFile.getName();  
  391.                 GlobFileFilter ff = new GlobFileFilter(fileGlob, false);  
  392.                 File[] files = parent.listFiles(ff);  
  393.                 if ((files == null) || (files.length == 0)) {  
  394.                     warn(new StringBuilder()  
  395.                             .append("No files or directories matching ")  
  396.                             .append(srcFile).toString());  
  397.                 } else  
  398.                     filesPosted += postFiles(parent.listFiles(ff), out, type);  
  399.             }  
  400.         }  
  401.         return filesPosted;  
  402.     }  
  403.   
  404.     /** 
  405.      * 提交文件 
  406.      * @param files 
  407.      * @param startIndexInArgs 
  408.      * @param out 
  409.      * @param type 
  410.      * @return 
  411.      */  
  412.     public int postFiles(File[] files, int startIndexInArgs, OutputStream out,  
  413.             String type) {  
  414.         reset();  
  415.         int filesPosted = 0;  
  416.         for (File srcFile : files) {  
  417.             if ((srcFile.isDirectory()) && (srcFile.canRead())) {  
  418.                 filesPosted += postDirectory(srcFile, out, type);  
  419.             } else if ((srcFile.isFile()) && (srcFile.canRead())) {  
  420.                 filesPosted += postFiles(new File[] { srcFile }, out, type);  
  421.             } else {  
  422.                 File parent = srcFile.getParentFile();  
  423.                 if (parent == null)  
  424.                     parent = new File(".");  
  425.                 String fileGlob = srcFile.getName();  
  426.                 GlobFileFilter ff = new GlobFileFilter(fileGlob, false);  
  427.                 File[] fileList = parent.listFiles(ff);  
  428.                 if ((fileList == null) || (fileList.length == 0)) {  
  429.                     warn(new StringBuilder()  
  430.                             .append("No files or directories matching ")  
  431.                             .append(srcFile).toString());  
  432.                 } else  
  433.                     filesPosted += postFiles(fileList, out, type);  
  434.             }  
  435.         }  
  436.         return filesPosted;  
  437.     }  
  438.   
  439.     /** 
  440.      * 提交目录下所有文件 
  441.      * @param dir 
  442.      * @param out 
  443.      * @param type 
  444.      * @return  返回提交的文件数量 
  445.      */  
  446.     private int postDirectory(File dir, OutputStream out, String type) {  
  447.         if ((dir.isHidden()) && (!dir.getName().equals(".")))  
  448.             return 0;  
  449.         info(new StringBuilder().append("Indexing directory ")  
  450.                 .append(dir.getPath()).append(" (")  
  451.                 .append(dir.listFiles(this.globFileFilter).length)  
  452.                 .append(" files, depth=").append(this.currentDepth).append(")")  
  453.                 .toString());  
  454.         int posted = 0;  
  455.         posted += postFiles(dir.listFiles(this.globFileFilter), out, type);  
  456.         if (this.recursive > this.currentDepth) {  
  457.             for (File d : dir.listFiles()) {  
  458.                 if (d.isDirectory()) {  
  459.                     this.currentDepth += 1;  
  460.                     posted += postDirectory(d, out, type);  
  461.                     this.currentDepth -= 1;  
  462.                 }  
  463.             }  
  464.         }  
  465.         return posted;  
  466.     }  
  467.   
  468.     /** 
  469.      * 提交文件 
  470.      * @param files 
  471.      * @param out 
  472.      * @param type 
  473.      * @return 
  474.      */  
  475.     public int postFiles(File[] files, OutputStream out, String type) {  
  476.         int filesPosted = 0;  
  477.         for (File srcFile : files) {  
  478.             try {  
  479.                 if ((!srcFile.isFile()) || (!srcFile.isHidden())) {  
  480.                     postFile(srcFile, out, type);  
  481.                     Thread.sleep(DEFAULT_POST_DELAY);  
  482.                     filesPosted++;  
  483.                 }  
  484.             } catch (InterruptedException e) {  
  485.                 throw new RuntimeException();  
  486.             }  
  487.   
  488.         }  
  489.         return filesPosted;  
  490.     }  
  491.   
  492.     /** 
  493.      * 根据用户提供的url进行web模式提交索引数据 
  494.      * @param args 
  495.      * @param startIndexInArgs 
  496.      * @param out 
  497.      * @return 
  498.      */  
  499.     public int postWebPages(String[] args, int startIndexInArgs,  
  500.             OutputStream out) {  
  501.         reset();  
  502.         LinkedHashSet<URL> s = new LinkedHashSet<URL>();  
  503.         for (int j = startIndexInArgs; j < args.length; j++) {  
  504.             try {  
  505.                 URL u = new URL(normalizeUrlEnding(args[j]));  
  506.                 s.add(u);  
  507.             } catch (MalformedURLException e) {  
  508.                 warn(new StringBuilder()  
  509.                         .append("Skipping malformed input URL: ")  
  510.                         .append(args[j]).toString());  
  511.             }  
  512.         }  
  513.         //将URL集合存入backlog  
  514.         this.backlog.add(s);  
  515.         //这里0表示抓取深度,刚开始抓取深度为0  
  516.         return webCrawl(0, out);  
  517.     }  
  518.   
  519.     /** 
  520.      * 将不规范的URL标准化 
  521.      * @param link 
  522.      * @return 
  523.      */  
  524.     protected static String normalizeUrlEnding(String link) {  
  525.         //如果URL中包含#号,则直接截图开头至#号位置出,#后面部分丢弃  
  526.         if (link.indexOf("#") > -1) {  
  527.             link = link.substring(0, link.indexOf("#"));  
  528.         }  
  529.         //如果URL以问号结尾,则删除结尾的问号  
  530.         if (link.endsWith("?")) {  
  531.             link = link.substring(0, link.length() - 1);  
  532.         }  
  533.         //如果URL以/结尾,则删除结尾的/  
  534.         if (link.endsWith("/")) {  
  535.             link = link.substring(0, link.length() - 1);  
  536.         }  
  537.         return link;  
  538.     }  
  539.   
  540.     /** 
  541.      * 页面抓取 
  542.      * @param level  当前抓取深度 
  543.      * @param out 
  544.      * @return 
  545.      */  
  546.     protected int webCrawl(int level, OutputStream out) {  
  547.         int numPages = 0;  
  548.         LinkedHashSet<URL> stack = (LinkedHashSet<URL>) this.backlog.get(level);  
  549.         int rawStackSize = stack.size();  
  550.         stack.removeAll(this.visited);  
  551.         int stackSize = stack.size();  
  552.         LinkedHashSet<URL> subStack = new LinkedHashSet<URL>();  
  553.         info(new StringBuilder().append("Entering crawl at level ")  
  554.                 .append(level).append(" (").append(rawStackSize)  
  555.                 .append(" links total, ").append(stackSize).append(" new)")  
  556.                 .toString());  
  557.         for (URL u : stack) {  
  558.             try {  
  559.                 //当前URL存入已访问列表,避免同一URL重复抓取  
  560.                 this.visited.add(u);  
  561.                 //获取到页面内容PageFetcherResult  
  562.                 PageFetcherResult result = pageFetcher.readPageFromUrl(u);  
  563.                 //状态码200表示页面抓取成功  
  564.                 if (result.httpStatus == 200) {  
  565.                     u = result.redirectUrl != null ? result.redirectUrl : u;  
  566.                     //如果有页面重定向,则抓取重定向后的页面内容  
  567.                     URL postUrl = new URL(appendParam(  
  568.                             this.solrUrl.toString(),  
  569.                             new StringBuilder()  
  570.                                     .append("literal.id=")  
  571.                                     .append(URLEncoder.encode(u.toString(),  
  572.                                             "UTF-8"))  
  573.                                     .append("&literal.url=")  
  574.                                     .append(URLEncoder.encode(u.toString(),  
  575.                                             "UTF-8")).toString()));  
  576.   
  577.                     boolean success = postData(  
  578.                             new ByteArrayInputStream(result.content.array(),  
  579.                                     result.content.arrayOffset(),  
  580.                                     result.content.limit()), null, out,  
  581.                             result.contentType, postUrl);  
  582.                     if (success) {  
  583.                         info(new StringBuilder().append("POSTed web resource ")  
  584.                                 .append(u).append(" (depth: ").append(level)  
  585.                                 .append(")").toString());  
  586.                         Thread.sleep(this.delay * 1000);  
  587.                         numPages++;  
  588.   
  589.                         //如果抓取深度还没超过限制  
  590.                         if ((this.recursive > level)  
  591.                                 && (result.contentType.equals("text/html"))) {  
  592.                             //从抓取的页面中提取出URL  
  593.                             Set<URL> children = pageFetcher.getLinksFromWebPage(  
  594.                                     u,  
  595.                                     new ByteArrayInputStream(result.content  
  596.                                             .array(), result.content  
  597.                                             .arrayOffset(), result.content  
  598.                                             .limit()), result.contentType,  
  599.                                     postUrl);  
  600.                             //把提取出来的URL存入stack中  
  601.                             subStack.addAll(children);  
  602.                         }  
  603.                     } else {  
  604.                         warn(new StringBuilder()  
  605.                                 .append("An error occurred while posting ")  
  606.                                 .append(u).toString());  
  607.                     }  
  608.                 } else {  
  609.                     warn(new StringBuilder().append("The URL ").append(u)  
  610.                             .append(" returned a HTTP result status of ")  
  611.                             .append(result.httpStatus).toString());  
  612.                 }  
  613.             } catch (IOException e) {  
  614.                 warn(new StringBuilder()  
  615.                         .append("Caught exception when trying to open connection to ")  
  616.                         .append(u).append(": ").append(e.getMessage())  
  617.                         .toString());  
  618.             } catch (InterruptedException e) {  
  619.                 throw new RuntimeException();  
  620.             }  
  621.         }  
  622.         if (!subStack.isEmpty()) {  
  623.             this.backlog.add(subStack);  
  624.             numPages += webCrawl(level + 1, out);  
  625.         }  
  626.         return numPages;  
  627.     }  
  628.   
  629.     public static ByteBuffer inputStreamToByteArray(BAOS bos,InputStream is)  
  630.             throws IOException {  
  631.         return inputStreamToByteArray(bos,is, 2147483647L);  
  632.     }  
  633.   
  634.     /** 
  635.      * 页面输入流转换到输出流,然后输出流将接收到的字节数据存入ByteBuffer字节缓冲区 
  636.      * @param bos 
  637.      * @param is 
  638.      * @param maxSize 
  639.      * @return 
  640.      * @throws IOException 
  641.      */  
  642.     public static ByteBuffer inputStreamToByteArray(BAOS bos,InputStream is, long maxSize)  
  643.             throws IOException {  
  644.         long sz = 0L;  
  645.         int next = is.read();  
  646.         while (next > -1) {  
  647.             if (++sz > maxSize) {  
  648.                 throw new BufferOverflowException();  
  649.             }  
  650.             bos.write(next);  
  651.             next = is.read();  
  652.         }  
  653.         bos.flush();  
  654.         is.close();  
  655.         return bos.getByteBuffer();  
  656.     }  
  657.   
  658.     /** 
  659.      * 计算完整的URL,因为页面上的A标签的href属性值可能是相对路径,所以这里需要拼接上baseUrl,你懂的 
  660.      * @param baseUrl 网站根路径 
  661.      * @param link    从A标签属性值上提取出来的值 
  662.      * @return 
  663.      */  
  664.     protected String computeFullUrl(URL baseUrl, String link) {  
  665.         if ((link == null) || (link.length() == 0)) {  
  666.             return null;  
  667.         }  
  668.         if (!link.startsWith("http")) {  
  669.             if (link.startsWith("/")) {  
  670.                 link = new StringBuilder().append(baseUrl.getProtocol())  
  671.                         .append("://").append(baseUrl.getAuthority())  
  672.                         .append(link).toString();  
  673.             } else {  
  674.                 if (link.contains(":")) {  
  675.                     return null;  
  676.                 }  
  677.                 String path = baseUrl.getPath();  
  678.                 if (!path.endsWith("/")) {  
  679.                     int sep = path.lastIndexOf("/");  
  680.                     String file = path.substring(sep + 1);  
  681.                     if ((file.contains(".")) || (file.contains("?")))  
  682.                         path = path.substring(0, sep);  
  683.                 }  
  684.                 link = new StringBuilder().append(baseUrl.getProtocol())  
  685.                         .append("://").append(baseUrl.getAuthority())  
  686.                         .append(path).append("/").append(link).toString();  
  687.             }  
  688.         }  
  689.         link = normalizeUrlEnding(link);  
  690.         String l = link.toLowerCase(Locale.ROOT);  
  691.   
  692.         //过滤调图片链接  
  693.         if ((l.endsWith(".jpg")) || (l.endsWith(".jpeg"))  
  694.                 || (l.endsWith(".png")) || (l.endsWith(".gif"))) {  
  695.             return null;  
  696.         }  
  697.         return link;  
  698.     }  
  699.   
  700.     /** 
  701.      * 判断某个文件类型是否在程序支持范围内,支持范围由mimeMap变量定义 
  702.      * @param type 
  703.      * @return 
  704.      */  
  705.     protected boolean typeSupported(String type) {  
  706.         for (String key : mimeMap.keySet()) {  
  707.             if ((((String) mimeMap.get(key)).equals(type))  
  708.                     && (this.fileTypes.contains(key))) {  
  709.                 return true;  
  710.             }  
  711.         }  
  712.         return false;  
  713.     }  
  714.   
  715.     /** 
  716.      * 只要输入的是true,on,yes,1都返回true 
  717.      * @param property 
  718.      * @return 
  719.      */  
  720.     protected static boolean isOn(String property) {  
  721.         return "true,on,yes,1".indexOf(property) > -1;  
  722.     }  
  723.   
  724.     /** 
  725.      * 打印警告信息 
  726.      * @param msg 
  727.      */  
  728.     static void warn(String msg) {  
  729.         System.err.println(new StringBuilder()  
  730.                 .append("SimplePostTool: WARNING: ").append(msg).toString());  
  731.     }  
  732.   
  733.     /** 
  734.      * 打印提示信息 
  735.      * @param msg 
  736.      */  
  737.     static void info(String msg) {  
  738.         System.out.println(msg);  
  739.     }  
  740.   
  741.     /** 
  742.      * 打印比较严重致命性的信息 
  743.      * @param msg 
  744.      */  
  745.     static void fatal(String msg) {  
  746.         System.err.println(new StringBuilder()  
  747.                 .append("SimplePostTool: FATAL: ").append(msg).toString());  
  748.         System.exit(2);  
  749.     }  
  750.   
  751.     /** 
  752.      * 提交索引数据至Solr Admin 
  753.      */  
  754.     public void commit() {  
  755.         info(new StringBuilder().append("COMMITting Solr index changes to ")  
  756.                 .append(this.solrUrl).append("...").toString());  
  757.         doGet(appendParam(this.solrUrl.toString(), "commit=true"));  
  758.     }  
  759.   
  760.     /** 
  761.      * 发送索引优化请求至Solr Admin后台 
  762.      */  
  763.     public void optimize() {  
  764.         info(new StringBuilder().append("Performing an OPTIMIZE to ")  
  765.                 .append(this.solrUrl).append("...").toString());  
  766.         doGet(appendParam(this.solrUrl.toString(), "optimize=true"));  
  767.     }  
  768.   
  769.     /** 
  770.      * 在URL后面追加参数即id=1&mode=files格式 
  771.      * @param url 
  772.      * @param param 
  773.      * @return 
  774.      */  
  775.     public static String appendParam(String url, String param) {  
  776.         String[] pa = param.split("&");  
  777.         for (String p : pa) {  
  778.             if (p.trim().length() != 0) {  
  779.                 String[] kv = p.split("=");  
  780.                 if (kv.length == 2) {  
  781.                     url = new StringBuilder().append(url)  
  782.                             .append(url.indexOf(63) > 0 ? "&" : "?")  
  783.                             .append(kv[0]).append("=").append(kv[1]).toString();  
  784.                 } else {  
  785.                     warn(new StringBuilder().append("Skipping param ")  
  786.                             .append(p)  
  787.                             .append(" which is not on form key=value")  
  788.                             .toString());  
  789.                 }  
  790.             }  
  791.         }  
  792.         return url;  
  793.     }  
  794.   
  795.     public void postFile(File file, OutputStream output, String type) {  
  796.         InputStream is = null;  
  797.         try {  
  798.             URL url = this.solrUrl;  
  799.             String suffix = "";  
  800.             if (this.auto) {  
  801.                 if (type == null) {  
  802.                     type = guessType(file);  
  803.                 }  
  804.                 if (type != null) {  
  805.                     if ((!type.equals("application/xml"))  
  806.                             && (!type.equals("text/csv"))  
  807.                             && (!type.equals("application/json"))) {  
  808.                         suffix = "/extract";  
  809.                         String urlStr = appendUrlPath(this.solrUrl, suffix)  
  810.                                 .toString();  
  811.                         if (urlStr.indexOf("resource.name") == -1) {  
  812.                             //往提交URL后面追加resource.name参数即文件的绝对路径  
  813.                             urlStr = appendParam(  
  814.                                     urlStr,  
  815.                                     new StringBuilder()  
  816.                                             .append("resource.name=")  
  817.                                             .append(URLEncoder.encode(  
  818.                                                     file.getAbsolutePath(),  
  819.                                                     "UTF-8")).toString());  
  820.                         }  
  821.                         if (urlStr.indexOf("literal.id") == -1) {  
  822.                             //往提交URL后面追加literal.id参数即文件的绝对路径  
  823.                             urlStr = appendParam(  
  824.                                     urlStr,  
  825.                                     new StringBuilder()  
  826.                                             .append("literal.id=")  
  827.                                             .append(URLEncoder.encode(  
  828.                                                     file.getAbsolutePath(),  
  829.                                                     "UTF-8")).toString());  
  830.                         }  
  831.                         url = new URL(urlStr);  
  832.                     }  
  833.                 } else  
  834.                     //未知的文件类型则直接跳过,仅仅是打印下警告信息  
  835.                     warn(new StringBuilder().append("Skipping ")  
  836.                             .append(file.getName())  
  837.                             .append(". Unsupported file type for auto mode.")  
  838.                             .toString());  
  839.   
  840.             } else if (type == null) {  
  841.                 //如果自动猜测文件类型关闭了,而文件类型又为Null,那只好设置为默认值DEFAULT_CONTENT_TYPE  
  842.                 type = DEFAULT_CONTENT_TYPE;  
  843.             }  
  844.   
  845.             info(new StringBuilder()  
  846.                     .append("POSTing file ")  
  847.                     .append(file.getName())  
  848.                     .append(this.auto ? new StringBuilder().append(" (")  
  849.                             .append(type).append(")").toString() : "")  
  850.                     .append(" to [base]").append(suffix).toString());  
  851.             is = new FileInputStream(file);  
  852.             //开始提交文件  
  853.             postData(is, Integer.valueOf((int) file.length()), output, type,  
  854.                     url);  
  855.         } catch (IOException e) {  
  856.             e.printStackTrace();  
  857.             warn(new StringBuilder().append("Can't open/read file: ")  
  858.                     .append(file).toString());  
  859.         } finally {  
  860.             try {  
  861.                 if (is != null) {  
  862.                     is.close();  
  863.                 }  
  864.             } catch (IOException e) {  
  865.                 fatal(new StringBuilder()  
  866.                         .append("IOException while closing file: ").append(e)  
  867.                         .toString());  
  868.             }  
  869.   
  870.         }  
  871.     }  
  872.   
  873.     /** 
  874.      * 往请求URL追加内容, 
  875.      * 如http://localhost:8080/solr/core1?param1=value1&param2=value2 追加一个/update后 
  876.      *   http://localhost:8080/solr/core1/update?param1=value1&param2=value2 
  877.      * @param url 
  878.      * @param append 
  879.      * @return 
  880.      * @throws MalformedURLException 
  881.      */  
  882.     protected static URL appendUrlPath(URL url, String append)  
  883.             throws MalformedURLException {  
  884.         return new URL(new StringBuilder()  
  885.                 .append(url.getProtocol())  
  886.                 .append("://")  
  887.                 .append(url.getAuthority())  
  888.                 .append(url.getPath())  
  889.                 .append(append)  
  890.                 .append(url.getQuery() != null ? new StringBuilder()  
  891.                         .append("?").append(url.getQuery()).toString() : "")  
  892.                 .toString());  
  893.     }  
  894.   
  895.     /** 
  896.      * 根据文件后缀名猜测文件MIME类型 
  897.      * @param file 
  898.      * @return 
  899.      */  
  900.     protected static String guessType(File file) {  
  901.         String name = file.getName();  
  902.         String suffix = name.substring(name.lastIndexOf(".") + 1);  
  903.         return (String) mimeMap.get(suffix.toLowerCase(Locale.ROOT));  
  904.     }  
  905.   
  906.     /** 
  907.      * 发送get请求 
  908.      * @param url 
  909.      */  
  910.     public static void doGet(String url) {  
  911.         try {  
  912.             doGet(new URL(url));  
  913.         } catch (MalformedURLException e) {  
  914.             warn(new StringBuilder().append("The specified URL ").append(url)  
  915.                     .append(" is not a valid URL. Please check").toString());  
  916.         }  
  917.     }  
  918.     /** 
  919.      * 发送get请求 
  920.      * @param url 
  921.      */  
  922.     public static void doGet(URL url) {  
  923.         try {  
  924.             if (mockMode) {  
  925.                 return;  
  926.             }  
  927.             HttpURLConnection urlc = (HttpURLConnection) url.openConnection();  
  928.             if (url.getUserInfo() != null) {  
  929.                 String encoding = DatatypeConverter.printBase64Binary(url  
  930.                         .getUserInfo().getBytes(StandardCharsets.US_ASCII));  
  931.                 urlc.setRequestProperty("Authorization", new StringBuilder()  
  932.                         .append("Basic ").append(encoding).toString());  
  933.             }  
  934.             //开始请求Solr Admin后台  
  935.             urlc.connect();  
  936.             //验证是否请求成功  
  937.             checkResponseCode(urlc);  
  938.         } catch (IOException e) {  
  939.             warn(new StringBuilder()  
  940.                     .append("An error occurred posting data to ").append(url)  
  941.                     .append(". Please check that Solr is running.").toString());  
  942.         }  
  943.     }  
  944.   
  945.     /** 
  946.      * POST方式提交 
  947.      * @param data 
  948.      * @param length 
  949.      * @param output 
  950.      * @param type 
  951.      * @param url 
  952.      * @return 
  953.      */  
  954.     public boolean postData(InputStream data, Integer length,  
  955.             OutputStream output, String type, URL url) {  
  956.         if (mockMode) {  
  957.             return true;  
  958.         }  
  959.         boolean success = true;  
  960.         if (type == null)  
  961.             type = DEFAULT_CONTENT_TYPE;  
  962.         HttpURLConnection urlc = null;  
  963.         try {  
  964.             try {  
  965.                 urlc = (HttpURLConnection) url.openConnection();  
  966.                 try {  
  967.                     //设置Http Method为 post  
  968.                     urlc.setRequestMethod("POST");  
  969.                 } catch (ProtocolException e) {  
  970.                     //如果Solr Admin端服务不支持POST请求,则打印异常信息  
  971.                     fatal(new StringBuilder()  
  972.                             .append("Shouldn't happen: HttpURLConnection doesn't support POST??")  
  973.                             .append(e).toString());  
  974.                 }  
  975.                 urlc.setDoOutput(true);  
  976.                 urlc.setDoInput(true);  
  977.                 urlc.setUseCaches(false);  
  978.                 urlc.setAllowUserInteraction(false);  
  979.                 urlc.setRequestProperty("Content-type", type);  
  980.                 if (url.getUserInfo() != null) {  
  981.                     String encoding = DatatypeConverter.printBase64Binary(url  
  982.                             .getUserInfo().getBytes(StandardCharsets.US_ASCII));  
  983.                     urlc.setRequestProperty(  
  984.                             "Authorization",  
  985.                             new StringBuilder().append("Basic ")  
  986.                                     .append(encoding).toString());  
  987.                 }  
  988.                 if (null != length)  
  989.                     urlc.setFixedLengthStreamingMode(length.intValue());  
  990.                 urlc.connect();  
  991.             } catch (IOException e) {  
  992.                 fatal(new StringBuilder()  
  993.                         .append("Connection error (is Solr running at ")  
  994.                         .append(this.solrUrl).append(" ?): ").append(e)  
  995.                         .toString());  
  996.                 success = false;  
  997.             }  
  998.             Throwable localThrowable3;  
  999.             try {  
  1000.                 OutputStream out = urlc.getOutputStream();  
  1001.                 localThrowable3 = null;  
  1002.                 try {  
  1003.                     pipe(data, out);  
  1004.                 } catch (Throwable localThrowable1) {  
  1005.                     localThrowable3 = localThrowable1;  
  1006.                     throw localThrowable1;  
  1007.                 } finally {  
  1008.                     if (out != null)  
  1009.                         if (localThrowable3 != null) {  
  1010.                             try {  
  1011.                                 out.close();  
  1012.                             } catch (Throwable x2) {  
  1013.                                 localThrowable3.addSuppressed(x2);  
  1014.                             }  
  1015.                         } else {  
  1016.                             out.close();  
  1017.                         }  
  1018.                 }  
  1019.             } catch (IOException e) {  
  1020.                 fatal(new StringBuilder()  
  1021.                         .append("IOException while posting data: ").append(e)  
  1022.                         .toString());  
  1023.                 success = false;  
  1024.             }  
  1025.             try {  
  1026.                 success &= checkResponseCode(urlc);  
  1027.                 InputStream in = urlc.getInputStream();  
  1028.                 localThrowable3 = null;  
  1029.                 try {  
  1030.                     pipe(in, output);  
  1031.                 } catch (Throwable localThrowable2) {  
  1032.                     localThrowable3 = localThrowable2;  
  1033.                     throw localThrowable2;  
  1034.                 } finally {  
  1035.                     if (in != null)  
  1036.                         if (localThrowable3 != null)  
  1037.                             try {  
  1038.                                 in.close();  
  1039.                             } catch (Throwable x2) {  
  1040.                                 localThrowable3.addSuppressed(x2);  
  1041.                             }  
  1042.                         else  
  1043.                             in.close();  
  1044.                 }  
  1045.             } catch (IOException e) {  
  1046.                 warn(new StringBuilder()  
  1047.                         .append("IOException while reading response: ")  
  1048.                         .append(e).toString());  
  1049.                 success = false;  
  1050.             }  
  1051.         } finally {  
  1052.             if (urlc != null) {  
  1053.                 urlc.disconnect();  
  1054.             }  
  1055.         }  
  1056.         return success;  
  1057.     }  
  1058.   
  1059.     /** 
  1060.      * 根据响应状态码判断是否提交成功了 
  1061.      * @param urlc 
  1062.      * @return 
  1063.      * @throws IOException 
  1064.      */  
  1065.     private static boolean checkResponseCode(HttpURLConnection urlc)  
  1066.             throws IOException {  
  1067.         //响应状态码如果大于等于400,表示请求失败了  
  1068.         if (urlc.getResponseCode() >= 400) {  
  1069.             warn(new StringBuilder().append("Solr returned an error #")  
  1070.                     .append(urlc.getResponseCode()).append(" (")  
  1071.                     .append(urlc.getResponseMessage()).append(") for url: ")  
  1072.                     .append(urlc.getURL()).toString());  
  1073.   
  1074.             Charset charset = StandardCharsets.ISO_8859_1;  
  1075.             String contentType = urlc.getContentType();  
  1076.   
  1077.             if (contentType != null) {  
  1078.                 int idx = contentType.toLowerCase(Locale.ROOT).indexOf(  
  1079.                         "charset=");  
  1080.                 if (idx > 0) {  
  1081.                     charset = Charset.forName(contentType.substring(  
  1082.                             idx + "charset=".length()).trim());  
  1083.                 }  
  1084.             }  
  1085.   
  1086.             InputStream errStream = urlc.getErrorStream();  
  1087.             Throwable localThrowable2 = null;  
  1088.             try {  
  1089.                 if (errStream != null) {  
  1090.                     BufferedReader br = new BufferedReader(  
  1091.                             new InputStreamReader(errStream, charset));  
  1092.                     StringBuilder response = new StringBuilder("Response: ");  
  1093.                     int ch;  
  1094.                     while ((ch = br.read()) != -1) {  
  1095.                         response.append((char) ch);  
  1096.                     }  
  1097.                     warn(response.toString().trim());  
  1098.                 }  
  1099.             } catch (Throwable localThrowable1) {  
  1100.                 localThrowable2 = localThrowable1;  
  1101.                 throw localThrowable1;  
  1102.             } finally {  
  1103.                 if (errStream != null)  
  1104.                     if (localThrowable2 != null)  
  1105.                         try {  
  1106.                             errStream.close();  
  1107.                         } catch (Throwable x2) {  
  1108.                             localThrowable2.addSuppressed(x2);  
  1109.                         }  
  1110.                     else  
  1111.                         errStream.close();  
  1112.             }  
  1113.             return false;  
  1114.         }  
  1115.         return true;  
  1116.     }  
  1117.   
  1118.     /** 
  1119.      * 字符串转换成字节输入流 
  1120.      * @param s 
  1121.      * @return 
  1122.      */  
  1123.     public static InputStream stringToStream(String s) {  
  1124.         return new ByteArrayInputStream(s.getBytes(StandardCharsets.UTF_8));  
  1125.     }  
  1126.   
  1127.     /** 
  1128.      * 把输入流传输到输出流上 
  1129.      * @param source 
  1130.      * @param dest 
  1131.      * @throws IOException 
  1132.      */  
  1133.     private static void pipe(InputStream source, OutputStream dest)  
  1134.             throws IOException {  
  1135.         byte[] buf = new byte[1024];  
  1136.         int read = 0;  
  1137.         while ((read = source.read(buf)) >= 0) {  
  1138.             if (null != dest) {  
  1139.                 dest.write(buf, 0, read);  
  1140.             }  
  1141.         }  
  1142.         if (null != dest) {  
  1143.             dest.flush();  
  1144.         }  
  1145.     }  
  1146.   
  1147.     /** 
  1148.      * 根据传入的fileType构建文件过滤器 
  1149.      * @param fileTypes 
  1150.      * @return 
  1151.      */  
  1152.     public GlobFileFilter getFileFilterFromFileTypes(String fileTypes) {  
  1153.         String glob;  
  1154.         if (fileTypes.equals("*")) {  
  1155.             glob = ".*";  
  1156.         } else {  
  1157.             glob = new StringBuilder().append("^.*\\.(")  
  1158.                     .append(fileTypes.replace(",", "|")).append(")$")  
  1159.                     .toString();  
  1160.         }  
  1161.         return new GlobFileFilter(glob, true);  
  1162.     }  
  1163.   
  1164.     /** 
  1165.      * 根据XPath表达式获取XML节点 
  1166.      * @param n 
  1167.      * @param xpath 
  1168.      * @return 
  1169.      * @throws XPathExpressionException 
  1170.      */  
  1171.     public static NodeList getNodesFromXP(Node n, String xpath)  
  1172.             throws XPathExpressionException {  
  1173.         XPathFactory factory = XPathFactory.newInstance();  
  1174.         XPath xp = factory.newXPath();  
  1175.         XPathExpression expr = xp.compile(xpath);  
  1176.         return (NodeList) expr.evaluate(n, XPathConstants.NODESET);  
  1177.     }  
  1178.   
  1179.     /** 
  1180.      * 根据XPath表达式获取XML节点 
  1181.      * @param n 
  1182.      * @param xpath 
  1183.      * @param concatAll  是否包含所有子节点,否则只取第一个 
  1184.      * @return 
  1185.      * @throws XPathExpressionException 
  1186.      */  
  1187.     public static String getXP(Node n, String xpath, boolean concatAll)  
  1188.             throws XPathExpressionException {  
  1189.         NodeList nodes = getNodesFromXP(n, xpath);  
  1190.         StringBuilder sb = new StringBuilder();  
  1191.         if (nodes.getLength() > 0) {  
  1192.             for (int i = 0; i < nodes.getLength(); i++) {  
  1193.                 sb.append(new StringBuilder()  
  1194.                         .append(nodes.item(i).getNodeValue()).append(" ")  
  1195.                         .toString());  
  1196.                 if (!concatAll) {  
  1197.                     break;  
  1198.                 }  
  1199.             }  
  1200.             return sb.toString().trim();  
  1201.         }  
  1202.         return "";  
  1203.     }  
  1204.   
  1205.     /** 
  1206.      * 把字节数据转换为Document对象,为XML解析做准备 
  1207.      * @param in 
  1208.      * @return 
  1209.      * @throws SAXException 
  1210.      * @throws IOException 
  1211.      * @throws ParserConfigurationException 
  1212.      */  
  1213.     public static Document makeDom(byte[] in) throws SAXException, IOException,  
  1214.             ParserConfigurationException {  
  1215.         InputStream is = new ByteArrayInputStream(in);  
  1216.         Document dom = DocumentBuilderFactory.newInstance()  
  1217.                 .newDocumentBuilder().parse(is);  
  1218.         return dom;  
  1219.     }  
  1220.   
  1221.     static {  
  1222.         DATA_MODES.add("files");  
  1223.         DATA_MODES.add("args");  
  1224.         DATA_MODES.add("stdin");  
  1225.         DATA_MODES.add("web");  
  1226.   
  1227.         mimeMap = new HashMap<String, String>();  
  1228.         mimeMap.put("xml", "application/xml");  
  1229.         mimeMap.put("csv", "text/csv");  
  1230.         mimeMap.put("json", "application/json");  
  1231.         mimeMap.put("pdf", "application/pdf");  
  1232.         mimeMap.put("rtf", "text/rtf");  
  1233.         mimeMap.put("html", "text/html");  
  1234.         mimeMap.put("htm", "text/html");  
  1235.         mimeMap.put("doc", "application/msword");  
  1236.         mimeMap.put("docx",  
  1237.                 "application/vnd.openxmlformats-officedocument.wordprocessingml.document");  
  1238.         mimeMap.put("ppt", "application/vnd.ms-powerpoint");  
  1239.         mimeMap.put("pptx",  
  1240.                 "application/vnd.openxmlformats-officedocument.presentationml.presentation");  
  1241.         mimeMap.put("xls", "application/vnd.ms-excel");  
  1242.         mimeMap.put("xlsx",  
  1243.                 "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");  
  1244.         mimeMap.put("odt", "application/vnd.oasis.opendocument.text");  
  1245.         mimeMap.put("ott", "application/vnd.oasis.opendocument.text");  
  1246.         mimeMap.put("odp", "application/vnd.oasis.opendocument.presentation");  
  1247.         mimeMap.put("otp", "application/vnd.oasis.opendocument.presentation");  
  1248.         mimeMap.put("ods", "application/vnd.oasis.opendocument.spreadsheet");  
  1249.         mimeMap.put("ots", "application/vnd.oasis.opendocument.spreadsheet");  
  1250.         mimeMap.put("txt", "text/plain");  
  1251.         mimeMap.put("log", "text/plain");  
  1252.     }  
  1253.   
  1254.     public class PageFetcherResult {  
  1255.         int httpStatus = 200;  
  1256.         String contentType = "text/html";  
  1257.         URL redirectUrl = null;  
  1258.         ByteBuffer content;  
  1259.   
  1260.         public PageFetcherResult() {  
  1261.         }  
  1262.     }  
  1263.   
  1264.     /** 
  1265.      * 页面抓取类 
  1266.      * @author Lanxiaowei 
  1267.      * 
  1268.      */  
  1269.     class PageFetcher {  
  1270.         Map<String, List<String>> robotsCache;  
  1271.         final String DISALLOW = "Disallow:";  
  1272.   
  1273.         public PageFetcher() {  
  1274.             this.robotsCache = new HashMap<String, List<String>>();  
  1275.         }  
  1276.   
  1277.         /** 
  1278.          * 根据指定的URL去抓取页面,页面内容包装在PageFetcherResult对象中 
  1279.          * @param u 
  1280.          * @return 
  1281.          */  
  1282.         public PageFetcherResult readPageFromUrl(URL u) {  
  1283.             PageFetcherResult res = new PageFetcherResult();  
  1284.             try {  
  1285.                 /** 
  1286.                  * 如果当前URL在roots.txt的禁止爬取列表中,则直接跳过 
  1287.                  */  
  1288.                 if (isDisallowedByRobots(u)) {  
  1289.                     SimplePostTool  
  1290.                             .warn("The URL "  
  1291.                                     + u  
  1292.                                     + " is disallowed by robots.txt and will not be crawled.");  
  1293.                     res.httpStatus = 403;  
  1294.                     SimplePostTool.this.visited.add(u);  
  1295.                     return res;  
  1296.                 }  
  1297.                 res.httpStatus = 404;  
  1298.                 HttpURLConnection conn = (HttpURLConnection) u.openConnection();  
  1299.                 conn.setRequestProperty("User-Agent",  
  1300.                         "SimplePostTool-crawler/5.1.0 (http://lucene.apache.org/solr/)");  
  1301.                 conn.setRequestProperty("Accept-Encoding", "gzip, deflate");  
  1302.                 conn.connect();  
  1303.                 res.httpStatus = conn.getResponseCode();  
  1304.                 if (!SimplePostTool  
  1305.                         .normalizeUrlEnding(conn.getURL().toString())  
  1306.                         .equals(SimplePostTool.normalizeUrlEnding(u.toString()))) {  
  1307.                     SimplePostTool.info("The URL " + u  
  1308.                             + " caused a redirect to " + conn.getURL());  
  1309.                     u = conn.getURL();  
  1310.                     res.redirectUrl = u;  
  1311.                     SimplePostTool.this.visited.add(u);  
  1312.                 }  
  1313.                 if (res.httpStatus == 200) {  
  1314.                     String rawContentType = conn.getContentType();  
  1315.                     String type = rawContentType.split(";")[0];  
  1316.                     if (SimplePostTool.this.typeSupported(type)) {  
  1317.                         String encoding = conn.getContentEncoding();  
  1318.                         InputStream is = null;  
  1319.                         if ((encoding != null)  
  1320.                                 && (encoding.equalsIgnoreCase("gzip"))) {  
  1321.                             is = new GZIPInputStream(conn.getInputStream());  
  1322.                         } else {  
  1323.                             if ((encoding != null)  
  1324.                                     && (encoding.equalsIgnoreCase("deflate")))  
  1325.                                 is = new InflaterInputStream(  
  1326.                                         conn.getInputStream(), new Inflater(  
  1327.                                                 true));  
  1328.                             else {  
  1329.                                 is = conn.getInputStream();  
  1330.                             }  
  1331.                         }  
  1332.                         BAOS bos = new BAOS();  
  1333.                         res.content = SimplePostTool.inputStreamToByteArray(bos,is);  
  1334.                         is.close();  
  1335.                         bos.close();  
  1336.                     } else {  
  1337.                         SimplePostTool  
  1338.                                 .warn("Skipping URL with unsupported type "  
  1339.                                         + type);  
  1340.                         res.httpStatus = 415;  
  1341.                     }  
  1342.                 }  
  1343.             } catch (IOException e) {  
  1344.                 SimplePostTool.warn("IOException when reading page from url "  
  1345.                         + u + ": " + e.getMessage());  
  1346.             }  
  1347.             return res;  
  1348.         }  
  1349.   
  1350.         /** 
  1351.          * 根据roots.txt信息判断指定URL是否可以抓取 
  1352.          * @param url 
  1353.          * @return 
  1354.          */  
  1355.         public boolean isDisallowedByRobots(URL url) {  
  1356.             String host = url.getHost();  
  1357.             //拼接网站的roots.txt访问地址  
  1358.             String strRobot = url.getProtocol() + "://" + host + "/robots.txt";  
  1359.             //先从缓存中获取当前网站的roots信息  
  1360.             List<String> disallows = (List<String>) this.robotsCache.get(host);  
  1361.             //若缓存中没有  
  1362.             if (disallows == null) {  
  1363.                 disallows = new ArrayList<String>();  
  1364.                 try {  
  1365.                     //则根据拼接的roots.txt访问地址去解析获取  
  1366.                     URL urlRobot = new URL(strRobot);  
  1367.                     //解析roots信息  
  1368.                     disallows = parseRobotsTxt(urlRobot.openStream());  
  1369.                 } catch (MalformedURLException e) {  
  1370.                     return true;  
  1371.                 } catch (IOException e) {  
  1372.                 }  
  1373.             }  
  1374.             //缓存到 map中  
  1375.             this.robotsCache.put(host, disallows);  
  1376.               
  1377.             //判断是否存在于roots的禁爬列表中  
  1378.             String strURL = url.getFile();  
  1379.             for (String path : disallows) {  
  1380.                 if ((path.equals("/")) || (strURL.indexOf(path) == 0)) {  
  1381.                     return true;  
  1382.                 }  
  1383.             }  
  1384.             //return false即表示不是禁爬URL  
  1385.             return false;  
  1386.         }  
  1387.   
  1388.         /** 
  1389.          * 根据roots.txt输入流解析roots信息,存入list中,一般是一行一条url 
  1390.          * @param is 
  1391.          * @return 
  1392.          * @throws IOException 
  1393.          */  
  1394.         protected List<String> parseRobotsTxt(InputStream is)  
  1395.                 throws IOException {  
  1396.             List<String> disallows = new ArrayList<String>();  
  1397.             BufferedReader r = new BufferedReader(new InputStreamReader(is,  
  1398.                     StandardCharsets.UTF_8));  
  1399.             String l;  
  1400.             while ((l = r.readLine()) != null) {  
  1401.                 String[] arr = l.split("#");  
  1402.                 if (arr.length != 0) {  
  1403.                     l = arr[0].trim();  
  1404.                     //我们只关心禁爬URL信息,Disallow不允许的意思即禁爬  
  1405.                     if (l.startsWith("Disallow:")) {  
  1406.                         l = l.substring("Disallow:".length()).trim();  
  1407.                         if (l.length() != 0) {  
  1408.                             disallows.add(l);  
  1409.                         }  
  1410.                     }  
  1411.                 }  
  1412.             }  
  1413.             is.close();  
  1414.             return disallows;  
  1415.         }  
  1416.   
  1417.         /** 
  1418.          * 从抓取到的页面内容中提取出URL 
  1419.          * @param u 
  1420.          * @param is 
  1421.          * @param type 
  1422.          * @param postUrl 
  1423.          * @return 
  1424.          */  
  1425.         protected Set<URL> getLinksFromWebPage(URL u, InputStream is,  
  1426.                 String type, URL postUrl) {  
  1427.             Set<URL> l = new HashSet<URL>();  
  1428.             URL url = null;  
  1429.             try {  
  1430.                 ByteArrayOutputStream os = new ByteArrayOutputStream();  
  1431.                 URL extractUrl = new URL(SimplePostTool.appendParam(  
  1432.                         postUrl.toString(), "extractOnly=true"));  
  1433.                 boolean success = SimplePostTool.this.postData(is, null, os,  
  1434.                         type, extractUrl);  
  1435.                 if (success) {  
  1436.                     Document d = SimplePostTool.makeDom(os.toByteArray());  
  1437.                     String innerXml = SimplePostTool.getXP(d,  
  1438.                             "/response/str/text()[1]", false);  
  1439.                     d = SimplePostTool.makeDom(innerXml  
  1440.                             .getBytes(StandardCharsets.UTF_8));  
  1441.                     //这个XPath表达式表示:获取html标签下的body标签下的所有a标签的href属性值  
  1442.                     NodeList links = SimplePostTool.getNodesFromXP(d,  
  1443.                             "/html/body//a/@href");  
  1444.                     for (int i = 0; i < links.getLength(); i++) {  
  1445.                         String link = links.item(i).getTextContent();  
  1446.                         link = SimplePostTool.this.computeFullUrl(u, link);  
  1447.                         if (link != null) {  
  1448.                             url = new URL(link);  
  1449.                             if ((url.getAuthority() != null)  
  1450.                                     && (url.getAuthority().equals(u  
  1451.                                             .getAuthority()))) {  
  1452.                                 l.add(url);  
  1453.                             }  
  1454.                         }  
  1455.                     }  
  1456.                 }  
  1457.             } catch (MalformedURLException e) {  
  1458.                 SimplePostTool.warn("Malformed URL " + url);  
  1459.             } catch (IOException e) {  
  1460.                 SimplePostTool.warn("IOException opening URL " + url + ": "  
  1461.                         + e.getMessage());  
  1462.             } catch (Exception e) {  
  1463.                 throw new RuntimeException();  
  1464.             }  
  1465.             return l;  
  1466.         }  
  1467.     }  
  1468.   
  1469.     /** 
  1470.      * 自定义文件过滤器 
  1471.      * @author Lanxiaowei 
  1472.      * 
  1473.      */  
  1474.     class GlobFileFilter implements FileFilter {  
  1475.         private String _pattern;  
  1476.         private Pattern p;  
  1477.   
  1478.         /** 
  1479.          * isRegex用来表示第一个参数pattern是否为一个正则表达式 
  1480.          * @param pattern 
  1481.          * @param isRegex 
  1482.          */  
  1483.         public GlobFileFilter(String pattern, boolean isRegex) {  
  1484.             this._pattern = pattern;  
  1485.             //如果pattern参数不是一个正则表达式  
  1486.             if (!isRegex) {  
  1487.                 //不是正则表达式的话,则需要对正则表达式里的特殊字符进行转义,所以这里的处理就不言自明了  
  1488.                 this._pattern = this._pattern.replace("^", "\\^")  
  1489.                         .replace("$", "\\$").replace(".", "\\.")  
  1490.                         .replace("(", "\\(").replace(")", "\\)")  
  1491.                         .replace("+", "\\+").replace("*", ".*")  
  1492.                         .replace("?", ".");  
  1493.                 //经过上一步处理后this._pattern参数就被当作一个普通的文件名了,  
  1494.                 //再在开头加^结尾加$转换成正则表达式  
  1495.                 this._pattern = ("^" + this._pattern + "$");  
  1496.             }  
  1497.             try {  
  1498.                 //这里的2即Pattern.CASE_INSENSITIVE即忽略大小写的意思  
  1499.                 this.p = Pattern.compile(this._pattern, 2);  
  1500.             } catch (PatternSyntaxException e) {  
  1501.                 SimplePostTool.fatal("Invalid type list " + pattern + ". "  
  1502.                         + e.getDescription());  
  1503.             }  
  1504.         }  
  1505.         /**根据正则表达式匹配结果判断是否返回这个文件*/  
  1506.         public boolean accept(File file) {  
  1507.             return this.p.matcher(file.getName()).find();  
  1508.         }  
  1509.     }  
  1510.   
  1511.     /** 
  1512.      * 自定义字节输出流 
  1513.      * @author Administrator 
  1514.      * 
  1515.      */  
  1516.     public static class BAOS extends ByteArrayOutputStream {  
  1517.         //把输出流存入ByteBuffer字节缓冲区中,因为ByteBuffer比byte[]读写效率要高  
  1518.         public ByteBuffer getByteBuffer() {  
  1519.             return ByteBuffer.wrap(this.buf, 0, this.count);  
  1520.         }  
  1521.     }  
  1522. }  

    看懂了post.jar的源码,有助于你更熟练使用post.jar来进行索引的添加删除等操作,下面截图演示如何在eclipse下运行SimplePostTool类进行索引测试操作,如图:


 

 

        

       如果你还有什么问题请加我Q-Q:7-3-6-0-3-1-3-0-5,

或者加裙
一起交流学习!

转载:http://iamyida.iteye.com/blog/2207920

时间: 2024-10-04 19:32:16

跟益达学Solr5之玩转post.jar的相关文章

跟益达学Solr5之批量索引JSON数据

     假定你有这样一堆JSON数据,   Json代码   [     {"id":"1", "name":"Red Lobster", "city":"San Francisco, CA", "type":"Sit-down Chain", "state":"California", "tag

跟益达学Solr5之从MySQL数据库导入数据并索引

  最近有小伙伴跟我抱怨说:益达,最近博客更新的有点慢呐.其实不是我变懒了,我是不想因为数量而降低了博客的质量,我需要抱着对你们负责的态度来写每一篇博客,绝不能含糊啊,所以,还望大家多多包涵呐. 今天群里一朋友问我Solr如何对数据库表里的数据进行索引,为此,今晚特地更新此篇博客,这里我以Solr当前最新版本5.1.0,数据库MySQL为例进行讲解说明. 首先我们需要准备一张测试表,如图:      测试表创建好了之后,我们需要让表里插入几条测试数据,如图:  然后需要把E:\solr-5.1.

跟益达学Solr5之使用Tomcat部署Solr

  最近忙着面试以及生活琐事把时间都霸占了,博客拖了4天没更新了,让各位久等了,望多多包涵!不过还好,工作已经敲定了,终于可以安心的学习Solr并分享我学习的点点滴滴啦!         上回我们在Jetty下部署了,不过我想小伙伴们使用Tomcat还是要多点,所以这回我们就来试试把Solr5部署到Tomcat下,这里以Win7 64bit Tomcat7.0.55为例,linux环境下同理,没太大区别:         首先你要去Solr官网下载Solr5.x的zip压缩包,至于怎么下载我这里

跟益达学Solr5之增量索引MySQL数据库表数据

   Solr5中如何增量索引MySQL数据库表中的数据,这个问题之前有某个童鞋问过我,今天午休时间就腾空更新篇博客,希望能帮助到你们.        为了测试方便,我首先从京东网站弄了点测试数据,如图:    这里要声明下,我不是在给京东商城打广告哈,仅仅是随便找个网站弄点测试数据,这部分工作全是我无聊手动插入MySQL数据库中的,如图:  建表SQL以及测试数据,我待会儿会上传到底下的附件里.然后你需要在solrconfig.xml配置文件中启用全量导入和增量导入请求处理器,如图:  然后分

跟益达学Solr5之使用IK分词器

   在Solr中该如何使用IK分词器呢,这是小伙伴们问的频率比较高的一个问题,今晚特此更新此篇博客.其实之前我在其他博客里已经使用了IK分词器,只是我没做详细说明.        在schema.xml配置中其实有很多关于分词器的配置示例,我从中摘录一段配置示例,比如: Xml代码   <fieldType name="text_general" class="solr.TextField" positionIncrementGap="100&qu

跟益达学Solr5之在Eclipse下编译Solr5源码

    2015年6月7日,Solr已经更新到5.2.0,所以这里我以5.2.0版本为例,你使用Solr5.x都是类似的区别不大.开始之前,首先你需要去Solr官网去下载Solr5源码,如图:          taz包其实是可以使用Winrar解压的,解压后如图:  我是解压到F:\javazipfile目录下,官方默认提供的源码包并不是一个标准的Eclipse Java Project,需要使用ivy进行构建,通过ivy的构建可以将下载下来的源码包转换成一个标准的Java Project,然

跟益达学Solr5之使用MMSeg4J分词器

   要想在Sor中使用MMSeg4J分词器,首先你需要自定义一个TokenizerFactory实现类,虽然直接配置Analyzer类也可以,但那样无法配置Analyzer构造函数的参数,不够灵活,存在弊端,所以我一直都是以扩展TokenizerFactory的方式来讲解类似MMSeg4J这样的中文分词器在Solr中的使用.       MMSegTokenizerFactory类我花了3个多小时修改了源码并经过N多测试,表示已经可以使用,我主要的是针对Lucene5 API对MMSegTok

跟益达学Solr5之拼音分词

 应群友强烈要求,特此更新此篇博客.其实在我的Lucene5系列博客里我已经介绍了拼音分词,遗憾的是,大家不能举一反三,好吧,还是我亲自上马吧!         首先我们来看看我当初使用Lucene5是如何实现的,  在Solr5中,我们只需要为IKTokenizer扩展一个IKTokenizerFactory,为PinyinTokenFilter扩展一个PinyinTokenFilterFactory,为PinyinNGramTokenFilter扩展一个PinyinNGramTokenFil

跟益达学Solr5之使用Ansj分词器

        OK,直接开门见山,不绕弯子啦!基于上篇博客,我们知道了在Solr中配置分词器有两种方式,一种是直接配置分词器类,比如: Xml代码   <fieldType name="text_ik" class="solr.TextField">                 <analyzer class="org.wltea.analyzer.lucene.IKAnalyzer" />         <