第二篇文章探讨的是定制爬取的信息,之前的分析我们得到,爬取的框架主要包括:
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的,请参考我的上一篇文章
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
/***
* 从一个网页得到几块内容
* @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的信息,之后再考虑信息和索引的更新。