protected int status; //运行状态
public final static int RUNNING = 1;// 运行状态 运行中
public final static int STOPED = 2; // 运行状态 停止
protected boolean resumable = false;// 是否中断可恢复,即程序中断是否重新爬取已爬取的网址
protected int threads = 50;// 爬取的线程树
protected CrawlDatums seeds = new CrawlDatums(); // 爬区的种子URL集合
protected CrawlDatums forcedSeeds = new CrawlDatums();// 强制注入 爬区的种子URL集合
protected Fetcher fetcher;// 爬取任务执行器,管理爬虫线程,调度
protected int maxExecuteCount = -1; // 设置每个爬取任务的最大执行次数,爬取或解析失败都会导致执行失败。 当一个任务执行失败时,爬虫会在后面的迭代中重新执行该任务,当该任务执行失败的次数超过最大执行次数时,任务生成器会忽略该任务
protected Executor executor = null;// 实际获得内容并处理的执行器
protected NextFilter nextFilter = null;// URL爬取后获得的url 过滤器
protected DBManager dbManager;// 爬取的url存储管理器
其中最重要的方法 开始爬取过程
/**
* 开始爬取,迭代次数为depth
*
* @param depth 迭代次数
* @throws Exception 异常
*/
public void start(int depth) throws Exception {
LOG.info(this.toString());
//register dbmanager conf
ConfigurationUtils.setTo(this, dbManager, executor, nextFilter);
registerOtherConfigurations();
// 这里 如果不是可恢复的 清理数据管理器
if (!resumable) {
if (dbManager.isDBExists()) {
dbManager.clear();
}
if (seeds.isEmpty() && forcedSeeds.isEmpty()) {
LOG.info("error:Please add at least one seed");
return;
}
}
dbManager.open();
// 向dbManager 注入 种子URL
if (!seeds.isEmpty()) {
inject();
}
// 向dbManager 强制注入种子URL
if (!forcedSeeds.isEmpty()) {
injectForcedSeeds();
}
// 状态修改为运行
status = RUNNING;
// 开始运行
for (int i = 0; i < depth; i++) {
if (status == STOPED) {
break;
}
LOG.info("start depth " + (i + 1));
long startTime = System.currentTimeMillis();
// 新建爬取线程执行器
fetcher = new Fetcher();
// 配置执行器
//register fetcher conf
ConfigurationUtils.setTo(this,fetcher);
fetcher.setDBManager(dbManager);
fetcher.setExecutor(executor);
fetcher.setNextFilter(nextFilter);
fetcher.setThreads(threads);
// 开始爬取
int totalGenerate = fetcher.fetchAll(generatorFilter);
long endTime = System.currentTimeMillis();
long costTime = (endTime - startTime) / 1000;
LOG.info("depth " + (i + 1) + " finish: \n\ttotal urls:\t" + totalGenerate + "\n\ttotal time:\t" + costTime + " seconds");
if (totalGenerate == 0) {
break;
}
}
// 关闭(这里有一个bug,不知道作者怎么想的, 状态未更改为停止,也未更新为初始值0)
dbManager.close();
afterStop();
}
/**
* 是否自动抽取符合正则的链接并加入后续任务
*/
protected boolean autoParse = true;
// 爬取到的页面内容 精细化处理, 未实现(个人实现,拿到内容后个人处理)
protected Visitor visitor;
// 请求处理器,从URL中获取内容, 默认通过jdk httpConnection
protected Requester requester;
// 父类属性复制
public AutoParseCrawler(boolean autoParse) {
this.autoParse = autoParse;
this.requester = this;
this.visitor = this;
this.executor = this;
}
// Requester 的实现 从URL中获取内容, 默认通过jdk httpConnection
@Override
public Page getResponse(CrawlDatum crawlDatum) throws Exception {
HttpRequest request = new HttpRequest(crawlDatum);
return request.responsePage();
}
/**
* URL正则约束
*/
protected RegexRule regexRule = new RegexRule();
/**
* Executor 实现,冲datum中url获取内容并处理, 增加到下一个执行任务中
*/
@Override
public void execute(CrawlDatum datum, CrawlDatums next) throws Exception {
// 获取 datum URL 中的内容,
Page page = requester.getResponse(datum);
// 用户处理内容
visitor.visit(page, next);
// 如果自动动抽取符合正则的链接并加入后续任务
if (autoParse && !regexRule.isEmpty()) {
parseLink(page, next);
}
afterParse(page, next);
}
protected void parseLink(Page page, CrawlDatums next) {
String conteType = page.contentType();
if (conteType != null && conteType.contains("text/html")) {
Document doc = page.doc();
if (doc != null) {
Links links = new Links().addByRegex(doc, regexRule, getConf().getAutoDetectImg());
next.add(links);
}
}
}
/**
* BreadthCrawler是基于Berkeley DB的插件,于2.20版重新设计
* BreadthCrawler可以设置正则规律,让遍历器自动根据URL的正则遍历网站,可以关闭这个功能,自定义遍历
* 如果autoParse设置为true,遍历器会自动解析页面中符合正则的链接,加入后续爬取任务,否则不自动解析链接。
* 注意,爬虫会保证爬取任务的唯一性,也就是会自动根据CrawlDatum的key进行去重,默认情况下key就是URL,
* 所以用户在编写爬虫时完全不必考虑生成重复URL的问题。
* 断点爬取中,爬虫仍然会保证爬取任务的唯一性。
*
* @author hu
*/
public abstract class BreadthCrawler extends AutoParseCrawler {
/**
* 构造一个基于伯克利DB的爬虫
* 伯克利DB文件夹为crawlPath,crawlPath中维护了历史URL等信息
* 不同任务不要使用相同的crawlPath
* 两个使用相同crawlPath的爬虫并行爬取会产生错误
*
* @param crawlPath 伯克利DB使用的文件夹
* @param autoParse 是否根据设置的正则自动探测新URL
*/
public BreadthCrawler(String crawlPath,boolean autoParse) {
super(autoParse);
this.dbManager=new BerkeleyDBManager(crawlPath);
}
}
RamCrawler
/**
* 基于内存的Crawler插件,适合一次性爬取,并不具有断点爬取功能
* 长期任务请使用BreadthCrawler
*
* @author hu
*/
public abstract class RamCrawler extends AutoParseCrawler {
public RamCrawler(){
this(true);
}
public RamCrawler(boolean autoParse) {
super(autoParse);
RamDB ramDB = new RamDB();
this.dbManager = new RamDBManager(ramDB);
}
public void start() throws Exception{
start(Integer.MAX_VALUE);
}
}