前言
关于本文的一些背景知识,请移步该系列的前序文章。
简单爬虫设计(一)——基本模型
简单爬虫设计(二)——爬取范围
简单爬虫设计(三)——需要处理的网页范围
简单爬虫设计(四)——管理爬虫内部状态
待重构的代码和坏味道
在本系列第一篇文章中,展示了爬虫的控制逻辑代码,现在重新把它放在下面。这部分代码虽然利用了领域模型基本模块,但是职责不清,层次不够清晰,很多语句并不在相同的抽象层次上。
包含的代码坏味道有:
- 同一个函数有多个职责:fetchAndProcess方法
- 同一个函数中的语句不在一个抽象层次:crawl方法
- 没有一个地方可以清楚地展现完整处理流程
public class CrawlerImpl implements Crawler {
public void crawl() {
Link target = null;
while (null != (target = targetLinks.next())) {
try {
fetchAndProcess(target);
if (this.fetchedLinks.total() >= crawlingScope.maxToCrawl()) {
return;
}
TimeUnit.MILLISECONDS.sleep(this.crawlDelay);
} catch (Exception e) {
}
}
}
}
private void fetchAndProcess(Link target) {
if (!this.crawlingScope.contains(target)) {
return;
}
if (target.getDepth() > 0 && this.fetchedLinks.contains(target)) {
return;
}
Webpage webpage = fetch(target);
if (processingScope.contains(webpage)) {
webpageRepository.add(webpage);
}
Links allLinks = webpage.links();
for (Link link : allLinks) {
if (this.crawlingScope.contains(link) &&
!this.fetchedLinks.contains(link)) {
targetLinks.add(link);
}
}
this.fetchedLinks.add(target);
}
重构过程
第一步,拆分fetchAndProcess方法。
这个方法中包含了太多职责,有判断是否需要爬取的逻辑,有下载网页的逻辑,还有处理网页的逻辑和抽取链接的逻辑,需要依次提取成独立的方法。
- 提取fetch方法,单纯的下载网页,并返回Webpage对象。
private Webpage fetch(Link target) {
Webpage webpage = downloader.download(target);
return webpage;
}
- 提取shouldFetch方法,判断一个目标是否需要采集,返回布尔值。
private boolean shouldFetch(Link target) {
if (!this.crawlingScope.contains(target)) {
return false;
}
if (target.getDepth() > 0 && this.fetchedLinks.contains(target)) {
return false;
}
return true;
}
- 提取process方法,仅处理网页,保存或者抽取详细信息。
private void process(Webpage webpage) {
if (processingScope.contains(webpage)) {
webpageRepository.add(webpage);
logger.info("Saved html {}", webpage.getUrl());
}
if (this.webExtractor != null) {
Gson gson = new GsonBuilder().setPrettyPrinting().create();
System.out.println(gson.toJson(extract(webpage)));
}
}
- 提取harvestLinks方法,从网页中收集需要继续爬取的链接。
private Collection<Link> harvestLinks(Links allLinks) {
List<Link> result = new LinkedList<>();
for (Link link : allLinks) {
if (this.crawlingScope.contains(link) &&
!this.fetchedLinks.contains(link)) {
result.add(link);
}
}
return result;
}
- 提取shouldStop方法,判断是否应该停止采集。
private boolean shouldStop() {
if (this.fetchedLinks.total() >= crawlingScope.maxToCrawl()) {
return true;
}
return false;
}
第二步,重新整理crawl方法。
可以更加清晰展现爬虫的控制逻辑,尽量让每个语句在同一个抽象层次。
public void crawl() {
Link target;
while (null != (target = this.targetLinks.next())) {
try {
if (!shouldFetch(target)) {
continue;
}
Webpage webpage = fetch(target);
process(webpage);
this.targetLinks.addAll(this.harvestLinks(webpage.links()));
this.fetchedLinks.add(target);
if (shouldStop()) {
break;
}
TimeUnit.MILLISECONDS.sleep(this.crawlDelay);
} catch (Exception e) {
e.printStackTrace();
}
}
}
小结
通过重构,学到了两点:
一是要通过深入理解领域逻辑,抽象好的领域模型;
二是用好领域模型,需要重构加深对应用逻辑的理解。
有了这两点,才能让应用逻辑更加清晰,方便后续扩展功能。
如果喜欢这系列文章,欢迎点赞+关注+收藏!
|