Nutch二次开发之定制爬取网站信息

第二篇文章探讨的是定制爬取的信息,之前的分析我们得到,爬取的框架主要包括:

1)inject把自己写的url文件中的url经过过滤和正规化注入crawldb中,保存到crawldb目录下

2)generate从crawldb中把url提取出来经过过滤正规化生成fetchlist队列,保存到segments的crawl_generate文件夹下

3)fetch根据fetchlist队列将url对应的网页信息提取下来保存在segments下的crawl_fetch和crawl_content文件夹下

4)parse根据crawl_content内容的信息对网页进行解析,生成parse_data,parse_text,crawl_parse文件夹

5)update根据如上解析的内容更新crawldb

6)invertlinks计算反向链接

这里我想把定制的内容建立索引,提高搜索的精度。那建立索引的文本内容是在parse阶段解析出来,保存到parse_text的,请参考我的上一篇文章

Nutch 二次开发之parse正文内容。

由于基于boilerpipe工具提取正文信息的方案提取精度不能不高,所以基于定制的方案将具有优势。为了对不同网站不同domain进行爬取,为了提高爬取的效率,我们把定制信息记录到配置文件里。首先来看一下配置文件site-contentfilter.properties如下:

isCustomized = true

site = 163|qq
regex.163 = http://[a-z0-9]*\.163\..*?
regex.qq = http://[a-z0-9]*\.qq\..*?

regex.163.news = http://news\.163\.com/\\d{2}/\\d{4}/\\d{2}/.*\.html
filter.163.news = |
regex.163.tech = http://tech\.163\.com/\\d{2}/\\d{4}/\\d{2}/.*\.html
filter.163.tech = 
regex.qq.news = http://news\.qq\.com/.*?
filter.qq.news = 
regex.qq.tech = http://tech\.qq\.com/.*?
filter.qq.tech = 

将配置文件放到conf目录下,第一行代表是否采用定制方式,还是采用boilerpipe通用方式,第二行代表爬取的host,对url进行host的判断,再缩小匹配url的host-domain范围,为了方便提高效率的作用。第三块我们看到有163的news板块和l63的tech板块的url正则表达式和filter定制tagnode信息。

在此我们开始编写定制网页信息工具类的代码:MainContentUtils.java,首先我们来看类成员变量(静态成员变量)和实例成员变量

private static InputStream in = null;
	private static Properties p = null;
	private static boolean isCustomized = true;
	private static String[] sites = null;//网站过滤
	private static String[] sitesRegex = null;//站点对应的url用于过滤
	private static Parser parser = null;//htmlParser 用于解析网页
	
	private StringBuffer sb = null;
	private byte[] contentBytes = null;
	private String encoding = null;
	private String url = null; //url用来正则表示式检测 
	private int blockCount = 0; //提取块数
	private String desFilterString = null; //目标filter 
	private String desSite = null; //目标站点
	private String desRegex = null; //目标正则表达式
	private String[] desFilters = null;//目标filter
	private ArrayList regexKeyCandidates = null;//对应站点的候选regex在properties文件中的key


在类成员变量中,主要是完成配置文件的读取工作,并初始化相应的属性,而实例成员变量则是对于每一个输入的网页进行提取的具体的变量。下面来看静态初始化块:

static{
			try {
				in = new BufferedInputStream(
						new FileInputStream("conf/site-contentfilter.properties"));//读入配置文件
				p = new Properties();//新建properties对象
				p.load(in);配置文件load入properties对象
			} catch (IOException e) {
				e.printStackTrace();
			}
			String isCustomizedString = p.getProperty("isCustomized");//是否使用定制方案
			System.out.println("isCustomizedString:" + isCustomizedString);
			try{
				if (isCustomizedString.equals("true")) {
					isCustomized = true;
					System.out.println("isCustomized:" + isCustomized);
					String sitesString = p.getProperty("site");//获得站点,在如上配置文件中返回 "193|qq"
					sites = sitesString.split("\\|");
					String regexKey = null;
					sitesRegex = new String[sites.length];//获取站点对应的url
					for(int i = 0; i < sites.length; i++) {
						regexKey =  "regex." + sites[i];
						sitesRegex[i] = p.getProperty(regexKey);
					}
				}
				else
					isCustomized = false;
			}catch (Exception e) {
				e.printStackTrace();
			}
	}
如下是构造器
public MainContentUtils(StringBuffer sb, String url, byte[] content, String encoding) {
  this.sb = sb;//返回的正文部分信息的StringBuffer
  this.url = url;//对此url进行内容提取
  this.contentBytes = content;//对应词url的内容源码byte[]
  this.encoding = encoding;//此内容源码的编码方案
 }

下面来看一下主要的接口来获得正文部分的信息:

/**
	 * 获得mainContent的主要接口
	 * @param sb 返回的string
	 * @param url 根据url判断定制的filter
	 * @param content 输入网页byte[]
	 * @param encoding 网页的编码方式
	 * @return 获得text成功 true
	 */
	public boolean getMainContent(){
		if(isCustomized) {
			try {
				parser = new Parser(new String(contentBytes, encoding));//创建parser对象
				parser.setEncoding(encoding);
			} catch (ParserException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
				
			
			if(!analyzeURL(url)){//判断此url是否是应该解析的url,目录页被过滤掉,返回false
				return false;
			}
			analyzeFilterString();//分析定制tagnode,可能是一块tagnode,也可能是两块tagnode:摘要一块,正文一块
			for(int i = 0 ; i < blockCount; i++) {
				if (!getMainContentBlock(sb, i))//对于每块不同的tagnode,获取信息String
					return false;
			}
			return true;
		}else {
			try {
				if(!analyzeURL(url)){
					return false;
				}
				sb.append(BoilerpipeUtils.getMainbodyTextByBoilerpipe(//使用boilerpipe进行内容解析
						new InputSource(new ByteArrayInputStream(contentBytes))));
				return true;
			} catch (BoilerpipeProcessingException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (SAXException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
		}
		return false;
	}

从上面的代码我们看到的流程是:先对url进行过滤判断,过滤掉目录页的提取,之后对分块的内容进行提取,代码分别如下:

	private void analyzeFilterString() {
		// TODO Auto-generated method stub
		desFilters = desFilterString.split("\\|");
		blockCount = desFilters.length;
	}

	/***
	 * 根据输入的url找到对应的NodeFilter
	 * @param url 输入的url
	 * @return
	 */
	private boolean analyzeURL(String url) {
		// TODO Auto-generated method stub
		this.url = url;
		Pattern pattern = null;
		for(int i = 0; i < sites.length; i++) {
			pattern = Pattern.compile(sitesRegex[i]);
			Matcher matcher = pattern.matcher(url);
			if (matcher.matches()) {
				desSite = sites[i];
				getRegexBySite(desSite);
				return getFilterByUrl(url);
			}
		}
		return false;//没有此站点
	}

	/***
	 * 
	 * @return 查找到此站点下的分类filter 返回true
	 */
	private boolean getFilterByUrl(String url) {
		// TODO Auto-generated method stub
		String regex = null, key = null;
		Iterator it = regexKeyCandidates.iterator();
		while(it.hasNext()) {
			key = it.next();
			regex = p.getProperty(key);
			Pattern pattern = Pattern.compile(regex);
			Matcher matcher = pattern.matcher(url);
			if(matcher.matches()) {
				desRegex = regex;
				key = "filter." + key.substring(key.indexOf(".")+1, key.length());
				desFilterString = p.getProperty(key);
				return true;//匹配到此站点此分类
			}
		}
		return false;//此站点没有此分类
	}

	/***
	 * 根据url找到对应的候选正则表达式数组
	 * @param site url对应的站点
	 */
	private void getRegexBySite(String site){
		regexKeyCandidates = new ArrayList();
		Enumeration e = p.keys();
		String key, value;
		while(e.hasMoreElements()){
			key = (String) e.nextElement();
			if (key.startsWith("regex." + site)
					&& (key.indexOf(".")!= key.lastIndexOf("."))) {
				regexKeyCandidates.add(key);
			} 
		}
		
	}
	
/***
	 * 从一个网页得到几块内容
	 * @param sb 回传的文本字符串
	 * @param index 第几块block
	 * @return true获取成功
	 */
	private boolean getMainContentBlock(StringBuffer sb, int index) {
	    String filter = desFilters[index].trim();
	    filter = filter.substring(filter.indexOf("<")+1, filter.indexOf(">"));
	    String[]  s = filter.split("@");
	    String nodeTag = s[0];
	    String attribute = s[1]; 
	    NodeFilter nodeFilter = null;
	    NodeList nodelist = null;
	    String attributeKey = attribute.substring(0,attribute.indexOf("="));
	    String attributeValue = attribute.substring(
				attribute.indexOf("\"")+1, attribute.lastIndexOf("\""));
	    if(nodeTag.equals("p") || nodeTag.equals("div")) {
	    		
	    		nodeFilter = new AndFilter(
	    				new TagNameFilter(nodeTag),
	    				new HasAttributeFilter(attributeKey, attributeValue));
	    }else {
	    	//留给扩展
	    }
	    try {
			nodelist = parser.parse(nodeFilter);
			if(nodelist.size() != 0){
				for(int i = 0; i 

至此真个工具类的代码分析完毕,在parse-html插件中的htmlParser类中获取text的位置加入如下代码:

MainContentUtils mct = new MainContentUtils(
        sb, content.getUrl(), contentInOctets, encoding);
      mct.getMainContent();
      text = sb.toString();
      FileWriter fw;
 try {
  fw = new FileWriter("E://mainbodypage//URLText.txt",true);//用于测试
  fw.write("url::" + content.getUrl() + "\n");
  fw.write("text::" + text + "\n");
  fw.close();
 } catch (IOException e) {
  // TODO Auto-generated catch block
  e.printStackTrace();
 }
      sb.setLength(0);
完成了信息采集的代码工具,下一步就是要控制url的队列,爬取所有url的信息,之后再考虑信息和索引的更新。


 
  

                            
                        
                    
                    
                    

你可能感兴趣的:(nutch插件,nutch,二次开发,parse-html,parsr,nutch二次开发,搜索引擎,string,url,properties,encoding,正则表达式,filter)