1 抓取任务中定制 URL 队列分配策略
2 主题抓取
3 增量爬虫
4 Heritrix工具化
1 优化Heritrix爬取速度
1.1 优化Frointier(边界控制器)
默认情况下,Heritrix使用域名分配策略(HostnameQueueAssignmentPolicy)将爬取的URL分配到不同的队列抓取。该策略的特点是:根据域名作为分组依据, 即同一域名下的URL将被分配进同一个抓取队列。这样在抓取时会单线程抓取。所以需要将要爬取的多个URL分散至多个队列中并行处理, 以提高爬取速度。
大部分解决该问题的方法都是使用ELFHash散列函数。
//代码如下
public long ELFHash(String str) {
long hash = 0;
long x = 0;
for (int i = 0; i < str.length(); i++) {
hash = (hash << 4) + str.charAt(i);
if ((x = hash & 0xF0000000L) != 0) {
hash ^= (x >> 24);
hash &= ~x;
}
}
return (hash & 0x7FFFFFFF);
}
但通过实际测试(文献), 发现效果并不理想。而使用Hflp散列函数可以提高Heritrix的抓取效率, 实现负载均衡。
public static long Hflp (String str) {
byte[] url = str.getBytes();
long n = 0;
byte[] bytes = intTobyte(n);
for (int i= 0; i < url.length; i++) {
bytes[i % 4] ^= url[i];
}
n = byteToInt(bytes);
return n % 0x7FFFFFFF;
}
public static byte[] intTobyte (long i) {
byte[] bytes = new byte[4];
bytes[0] = (byte) (i & 0xFF);
bytes[1] = (byte) ((i >> 8) & 0xFF);
bytes[2] = (byte) ((i >> 16) & 0xFF);
bytes[3] = (byte) ((i >> 24) & 0xFF);
return bytes;
}
public static int byteToInt (byte[] bytes) {
int a = bytes[0] & 0xFF;
int b = (bytes[1] & 0xFF) << 8;
int c = (bytes[2] & 0xFF) << 16;
int d = (bytes[3] & 0xFF) << 24;
return a+b+c+d;
}
上面代码要生效,还要进行如下配置:
- 新建HflpQueueAssignmentPolicy,继承QueueAssignmentPolicy,实现其getClassKey方法。
public class HflpQueueAssignmentPolicy extends QueueAssignmentPolicy {
public String getClassKey(CrawlController controller, CandidateURI cauri) {
String scheme = cauri.getUURI().getScheme();
String candidate = null;
try {
if (scheme.equals(DNS)){
if (cauri.getVia() != null) {
// Special handling for DNS: treat as being
// of the same class as the triggering URI.
// When a URI includes a port, this ensures
// the DNS lookup goes atop the host:port
// queue that triggered it, rather than
// some other host queue
UURI viaUuri = UURIFactory.getInstance(cauri.flattenVia());
candidate = viaUuri.getAuthorityMinusUserinfo();
// adopt scheme of triggering URI
scheme = viaUuri.getScheme();
} else {
candidate= cauri.getUURI().getReferencedHost();
}
} else {
String uri = cauri.getUURI().toString();
long hash = Hflp(uri);
candidate = Long.toString(hash % 100);
// candidate = cauri.getUURI().getAuthorityMinusUserinfo();
}
if(candidate == null || candidate.length() == 0) {
candidate = DEFAULT_CLASS_KEY;
}
} catch (URIException e) {
logger.log(Level.INFO,
"unable to extract class key; using default", e);
candidate = DEFAULT_CLASS_KEY;
}
if (scheme != null && scheme.equals(UURIFactory.HTTPS)) {
// If https and no port specified, add default https port to
// distinguish https from http server without a port.
if (!candidate.matches(".+:[0-9]+")) {
candidate += UURIFactory.HTTPS_PORT;
}
}
// Ensure classKeys are safe as filenames on NTFS
return candidate.replace(':','#');
}
}
- 代码需要写在新类中,然后在Heritrix.proprities文件中修改默认调用的 HostnameQueueAssignmentPolicy,而使用新的 HflpQueueAssignmentPolicy 配置如下:
# List here all queue assignment policies you'd have show as a
# queue-assignment-policy choice in AbstractFrontier derived Frontiers
# (e.g. BdbFrontier).
# HostnameQueueAssignmentPolicy
org.archive.crawler.frontier.AbstractFrontier.queue-assignment-policy = org.archive.crawler.frontier.ELFHashQueueAssignmentPolicy org.archive.crawler.frontier.IPQueueAssignmentPolicy org.archive.crawler.frontier.BucketQueueAssignmentPolicy org.archive.crawler.frontier.SurtAuthorityQueueAssignmentPolicy org.archive.crawler.frontier.TopmostAssignedSurtQueueAssignmentPolicy
org.archive.crawler.frontier.ELFHashQueueAssignmentPolicy \
org.archive.crawler.frontier.IPQueueAssignmentPolicy \
org.archive.crawler.frontier.BucketQueueAssignmentPolicy \
org.archive.crawler.frontier.SurtAuthorityQueueAssignmentPolicy \
org.archive.crawler.frontier.TopmostAssignedSurtQueueAssignmentPolicy
org.archive.crawler.frontier.BdbFrontier.level = INFO
1.2 优化Extractor(抽取链)
这一步的优化是通过实现抽象类Extractor,覆写其extract方法,完成对于抓取URL的过滤。主要分为两步:
(1)新建类,实现Extractor抽象类中的extract方法
public class Extractor_Cnpc extends Extractor{
public static final String patternString1 = ".*href\\s*=\\s*(\"|'|)http://.*";
public static final String patternString2 = "<\\s*[aA][\\s\\S]+(href\\s*=\\s*(\"|'|)(http|https)://[^>]+\\s*)>";
public static final String patternString3 = "<\\s*[aA][\\s\\S]+(href\\s*=\\s*(\"|'|)((http|https)://[^\"]+(\")))";
public static final String patternString4 = "";//Extractor_Cnpc-row "])(.*)>"
public static final String patternString5 = "(http://|https://)+((\\w|\\.|\\/|-|=|\\?|&)+)+";//抽取特征
public Extractor_Cnpc(String name, String description) {
super(name, description);
}
public Extractor_Cnpc(String name) {
super(name, "Cnpc news extractor");
}
private static String URL_REGEX = null;
public static Pattern pattern1 = Pattern.compile(patternString1,
Pattern.DOTALL);
public static Pattern pattern2 = Pattern.compile(patternString2,
Pattern.DOTALL);
public static Pattern pattern3 = Pattern.compile(patternString3,
Pattern.DOTALL);
public static Pattern pattern4 = Pattern.compile(patternString4,
Pattern.CASE_INSENSITIVE);
public static Pattern pattern5 = Pattern.compile(patternString5,
Pattern.DOTALL);
@Override
protected void extract(CrawlURI curi) {
String url = "";
try {
HttpRecorder hr = curi.getHttpRecorder();
if(hr == null){
throw new IOException("HttpRecorder is null");
}
ReplayCharSequence cs = hr.getReplayCharSequence();
if(cs == null){
return;
}
String context = cs.toString();
Pattern pattern = Pattern.compile(patternString4, Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(context);
while(matcher.find()){
url = matcher.group();//先将url截取出来
writeUriIntoTxt(url,"F:\\data\\test\\output.txt");
url = url.replace("\"", "");//替换",清除前后双引号
URL_REGEX = extractor_Feature(readCrawlUrl());
if(url.matches(URL_REGEX)){//和正则进行匹配
curi.createAndAddLinkRelativeToBase(url, context, Link.NAVLINK_HOP);
writeUriIntoTxt(url,getWriteUrlPath());
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
(2)通过正则表达式过滤,抽取出符合规则的URL,加入抓取队列。
(3)其他抽取的方法
- 使用正则表达式配合HTMLParser的方式抽取url
- 配置模版
- 抽取出JavaScript代码中的信息
http://www.cnblogs.com/cczw/archive/2012/07/16/2593957.html - 抽取PDF文件
Apache PDFbox是一个开源的、基于Java的、支持PDF文档生成的工具库,它可以用于创建新的PDF文档,修改现有的PDF文档,还可以从PDF文档中提取所需的内容。Apache PDFBox还包含了数个命令行工具。
http://blog.csdn.net/chszs/article/details/9026439 - 使用POI抽取office系列
这个我也有写一些:https://www.jianshu.com/p/0a32d8bd6878 - 区域控制
纯真ip解析出ip地址,然后利用正则判断出指定区域的ip,如中国湖北、中国北京等,进而确定地址。
1.3 在Prefetcher中取消robots.txt的限制
Robots.txt是一种专门用于搜索引擎网 络爬虫的文件,当构造一个网站时,如果作者希望该网站的内容被搜索引擎收录,就可以在网站中创建一个纯文本文件robots.txt,在这个文件中,声明 该网站不想被robot访问的部分。这样,该网站的部分或全部内容就可以不被搜索引擎收录了,或者指定搜索引擎只收录指定的内容。
Heritrix在其说明文档中,表明它是一个完全遵守robots.txt协议的网络爬虫。这一点固然在宣传上起到了一定的作用。但是,在实际的网页采集过程中,这并不是一种最好的作法。因为大部分 的网站并不会放置一个robots.txt文件以供搜索引擎读取,在互联网信息以几何级数增长的今天,网站总是在希望自己的内容不被人所利用的同时,又希望自己能够被更多的用户从搜索引擎上检索到。
如果当一个网站没有放置robots.txt文件时,Heritrix总是要花上大量的时间试图去访问这样一个文件,甚至可能retry很多次。这 无疑很大的降低了抓取效率。因此为了提高抓取的效率,可以试着将对robots.txt的访问部分去除。
方法如下:
(1)修改PreconditionEnforcer
在Heritrix中,对 robots.txt 文件的处理是处于PreconditionEnforcer这个Processor中的。PreconditionEnforcer是一个 Prefetcher,当处理时,总是需要考虑一下当前这个链接是否有什么先决条件要先被满足的,而对robots.txt的访问则正好是其中之一。在 PreconditionEnforcer中,有一个private类型的方法,它的方法签名为:
private boolean considerRobotsPreconditions(CrawlURI curi)
该方法的含义为:在进行对参数所表示的链接的抓取前,看一下是否存在一个由robots.txt所决定的先决条件。很显然,如果对每个链接都有这样的处理。那么,很有可能导致整个抓取任务的失败。因此,需要对它进行调整。
这个方法返回true时的含义为需要考虑 robots.txt文件,返回false时则表示不需要考虑robots.txt文件,可以继续将链接传递给后面的处理器。所以,最简单的修改办法就是 将这个方法整个注释掉,只留下一个false的返回值。这种方法完全可行,抓取的速度提高了至少一半以上!
2 主题抓取(增量爬虫)
- URL:判断抓取的URL和种子URL是否属于一个主题(增量,则重复);
- 内容:将爬取下来的网页抽取出内容,计算与种子网页的相似度,在阈值之内的则认为是一个主题的网页(增量,则重复);
- Heritrix自带方法
(1)在Heritrix爬取结果文件夹log文件中的recover.gz文件中,自动记录了上一次已抓取的URL信息。所以可以对该文件中记录的URL不断过滤累加, 每次爬取时, 将这些URL写入到已抓取队列, 避免重复信息的二次爬取。
(2)将Heritrix的配置文件order.xml中的属性值表示的是当前以抓取的URL,Heritrix每次启动之前都会读取这个路径下的文件中的URL,不会抓取。所以,可以将这个地址设置为recover.gz文件地址, 在对recover.gz进行累加,从而实现对网站长时间监测过程中的增量爬取。
3 Heritrix工具化
将Heritrix做成一个工具包直接用。方法如下:
-
将Heritrix做成一个jar包,或者是一个.bat文件。
(1)将Heritrix代码写好,例如前面说的抓取优化。
(2)使用固定的order.xml(爬取某网站的order.xml)覆盖原始的order.xml,这样每次进入爬取参数就已经配置好了,可以直接进入抓取。
(3)修改war包(admin.war),修改web.xml中的拦截器,不进入login页面。替换原有。
(4)统一输出路径:修改mirror文件输出地址。如下图,
(5)生成jar包,并通过.bat文件启动jar文件。
- 通过作业调度框架Quartz实现该功能
P.S. Heritrix三部曲真难写,不过总结、分享知识很开心。
END