java网络爬虫如何控制爬取的深度_Java 动手写爬虫: 二、 深度爬取
第二篇
前面实现了一个最基础的爬取单网页的爬虫,这一篇则着手解决深度爬取的问题
简单来讲,就是爬了一个网页之后,继续爬这个网页中的链接
1. 需求背景
背景比较简单和明确,当爬了一个网页之后,目标是不要就此打住,扫描这个网页中的链接,继续爬,所以有几个点需要考虑:
哪些链接可以继续爬 ?
是否要一直爬下去,要不要给一个终止符?
新的链接中,提取内容的规则和当前网页的规则不一致可以怎么办?
2. 设计
针对上面的几点,结合之前的实现结构,在执行 doFetchPage 方法获取网页之后,还得做一些其他的操作
扫描网页中的链接,根据过滤规则匹配满足要求的链接
记录一个depth,用于表示爬取的深度,即从最原始的网页出发,到当前页面中间转了几次(讲到这里就有个循环爬取的问题,后面说)
不同的页面提取内容规则不一样,因此可以考虑留一个接口出来,让适用方自己来实现解析网页内容
基本实现
开始依然是先把功能点实现,然后再考虑具体的优化细节
先加一个配置项,表示爬取页面深度; 其次就是保存的结果,得有个容器来暂存, 所以在 SimpleCrawlJob 会新增两个属性
/**
* 批量查询的结果
*/
private List crawlResults = new ArrayList<>();
/**
* 爬网页的深度, 默认为0, 即只爬取当前网页
*/
private int depth = 0;
因为有深度爬取的过程,所以需要修改一下爬取网页的代码,新增一个 doFetchNetxtPage方法,进行迭代爬取网页,这时,结果匹配处理方法也不能如之前的直接赋值了,稍微改一下即可, 改成返回一个接过实例
/**
* 执行抓取网页
*/
public void doFetchPage() throws Exception {
doFetchNextPage(0, this.crawlMeta.getUrl());
this.crawlResult = this.crawlResults.get(0);
}
private void doFetchNextPage(int currentDepth, String url) throws Exception {
HttpResponse response = HttpUtils.request(new CrawlMeta(url, this.crawlMeta.getSelectorRules()), httpConf);
String res = EntityUtils.toString(response.getEntity());
CrawlResult result;
if (response.getStatusLine().getStatusCode() != 200) { // 请求成功
result = new CrawlResult();
result.setStatus(response.getStatusLine().getStatusCode(), response.getStatusLine().getReasonPhrase());
result.setUrl(crawlMeta.getUrl());
this.crawlResults.add(result);
return;
}
result = doParse(res);
// 超过最大深度, 不继续爬
if (currentDepth > depth) {
return;
}
Elements elements = result.getHtmlDoc().select("a[href]");
for(Element element: elements) {
doFetchNextPage(currentDepth + 1, element.attr("href"));
}
}
private CrawlResult doParse(String html) {
Document doc = Jsoup.parse(html);
Map> map = new HashMap<>(crawlMeta.getSelectorRules().size());
for (String rule : crawlMeta.getSelectorRules()) {
List list = new ArrayList<>();
for (Element element : doc.select(rule)) {
list.add(element.text());
}
map.put(rule, list);
}
CrawlResult result = new CrawlResult();
result.setHtmlDoc(doc);
result.setUrl(crawlMeta.getUrl());
result.setResult(map);
result.setStatus(CrawlResult.SUCCESS);
return result;
}
说明
主要的关键代码在 doFetchNextPage 中,这里有两个参数,第一个表示当前url属于爬取的第几层,爬完之后,判断是否超过最大深度,如果没有,则获取出网页中的所有链接,迭代调用一遍
下面主要是获取网页中的跳转链接,直接从jsoup的源码中的example中获取,获取网页中链接的方法
// 未超过最大深度, 继续爬网页中的所有链接
result = doParse(res);
Elements elements = result.getHtmlDoc().select("a[href]");
for(Element element: elements) {
doFetchNextPage(currentDepth + 1, element.attr("href"));
}
测试case
测试代码和之前的差不多,唯一的区别就是指定了爬取的深度,返回结果就不截图了,实在是有点多
/**
* 深度爬
* @throws InterruptedException
*/
@Test
public void testDepthFetch() throws InterruptedException {
String url = "https://my.oschina.net/u/566591/blog/1031575";
CrawlMeta crawlMeta = new CrawlMeta();
crawlMeta.setUrl(url);
SimpleCrawlJob job = new SimpleCrawlJob(1);
job.setCrawlMeta(crawlMeta);
Thread thread = new Thread(job, "crawlerDepth-test");
thread.start();
thread.join();
List result = job.getCrawlResults();
System.out.println(result);
}
3. 改进
问题
上面虽然是实现了目标,但问题却有点多:
就比如上面的测试case,发现有122个跳转链接,顺序爬速度有点慢
链接中存在重复、页面内锚点、js等各种情况,并不是都满足需求
最后的结果塞到List中,深度较多时,链接较多时,list可能被撑暴
- 添加链接的过滤
过滤规则,可以划分为两种,正向的匹配,和逆向的排除
首先是修改配置类 CrawlMeta, 新增两个配置
/**
* 正向的过滤规则
*/
@Setter
@Getter
private Set positiveRegex = new HashSet<>();
/**
* 逆向的过滤规则
*/
@Setter
@Getter
private Set negativeRegex = new HashSet<>();
public Set addPositiveRegex(String regex) {
this.positiveRegex.add(Pattern.compile(regex));
return this.positiveRegex;
}
public Set addNegativeRegex(String regex) {
this.negativeRegex.add(Pattern.compile(regex));
return this.negativeRegex;
}
然后在遍历子链接时,判断一下是否满足需求
// doFetchNextPage 方法
Elements elements = result.getHtmlDoc().select("a[href]");
String src;
for(Element element: elements) {
src = element.attr("href");
if (matchRegex(src)) {
doFetchNextPage(currentDepth + 1, element.attr("href"));
}
}
// 规则匹配方法
private boolean matchRegex(String url) {
Matcher matcher;
for(Pattern pattern: crawlMeta.getPositiveRegex()) {
matcher = pattern.matcher(url);
if (matcher.find()) {
return true;
}
}
for(Pattern pattern: crawlMeta.getNegativeRegex()) {
matcher = pattern.matcher(url);
if(matcher.find()) {
return false;
}
}
return crawlMeta.getPositiveRegex().size() == 0;
}
上面主要是通过正则来进行过滤,暂不考虑正则带来的开销问题,至少是解决了一个过滤的问题
但是,但是,如果网页中的链接是相对路径的话,会怎么样
直接使用 Jsoup来测试一个网页,看获取的link地址为什么
// 获取网页中的所有链接
@Test
public void testGetLink() throws IOException {
String url = "http://chengyu.911cha.com/zishu_3_p1.html";
Connection httpConnection = HttpConnection.connect(url)
.header("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8")
.header("connection", "Keep-Alive")
.header("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36");
Document doc = httpConnection.get();
Elements links = doc.select("a[href]");
print("\nLinks: (%d)", links.size());
}
看下取出的链接
根据上面的测试,获取的链接如果是相对地址,则会有问题,需要有一个转化的过程,这个改动比较简单,jsoup本身是支持的
改一行即可
// 解析为documnet对象时,指定 baseUrl
// 上面的代码结构会做一点修改,后面会说到
Document doc = Jsoup.parse(html, url);
// 获取链接时,前面添加abs
src = element.attr("abs:href");
- 保存结果
当爬取的数据量较多时,将结果都保存在内存中,并不是一个好的选择,假色每个网页中,满足规则的是有10个,那么depth=n, 则从第一个网页出发,最终会得到
1 + 10 + ... + 10^n = (10^(n+1) - 1) / 9
显然在实际情况中是不可取的,因此可以改造一下,获取数据后给一个回调,让用户自己来选择如何处理结果,这时 SimpleCrawelJob 的结构基本上满足不了需求了
重新开始设计
1. AbstractJob 类中定义一个回调方法
/**
* 解析完网页后的回调方法
*
* @param crawlResult
*/
protected abstract void visit(CrawlResult crawlResult);
2. DefaultAbstractCrawlJob 实现爬取网页逻辑的抽象类
这个类实现爬取网页的主要逻辑,也就是将之前的SimpleCrwalJob的实现拷贝过来,区别是干掉了返回结果; 顺带修了一个小bug
java网络爬虫如何控制爬取的深度_Java 动手写爬虫: 二、 深度爬取相关推荐
- python爬表情包_【从零开始写爬虫一】批量下载表情包
序 打算写个关于node的爬虫菜鸟教程,接下来将带大家一步一步写一个表情包爬虫,从获取页面,解析表情包链接, 清洗脏数据,下载表情包到本地.开始之前你需要有对chrome调试工具和ES6有一定了解,包 ...
- php抓取dom处理后数据,写爬虫时PHP解析HTML最高效的方法那就是用DomCrawler!
需求来源,需要用PHP解析HTML提取我想要的数据 用PHP写网站爬虫的时候,需要把爬取的网页进行解析,提取里面想要的数据,这个过程叫做网页HTML中数据结构化. 很多人应该知道用phpQuery像J ...
- Java 反射取类中类_Java反射机制(二):通过反射取得类的结构
在反射运用过程中,如果你想得到一个类的完整结构,那么就要使用到java.lang.reflect包中的几个类: · Constructor 表示类中的构造方法 · Field 表示类中的属性 · ...
- java byte[] 文件流 转换成string是乱码_Java学习--IO(二)、多线程
1.标准输入流 标准输入流是指从标准输入设备流向程序的数据. Java利用http://System.in来得到一个InputStream字节输入流 public static void main(S ...
- 第三十六期:学 Java 网络爬虫,需要哪些基础知识?
说起网络爬虫,大家想起的估计都是 Python ,诚然爬虫已经是 Python 的代名词之一,相比 Java 来说就要逊色不少.有不少人都不知道 Java 可以做网络爬虫,其实 Java 也能做网络爬 ...
- python网络爬虫、Java 网络爬虫,哪个更好?
说起网络爬虫,大家想起的估计都是 Python ,诚然爬虫已经是 Python 的代名词之一,相比 Java 来说就要逊色不少.有不少人都不知道 Java 可以做网络爬虫,其实 Java 也能做网络爬 ...
- 学 Java 网络爬虫,需要哪些基础知识?
说起网络爬虫,大家想起的估计都是 Python ,诚然爬虫已经是 Python 的代名词之一,相比 Java 来说就要逊色不少.有不少人都不知道 Java 可以做网络爬虫,其实 Java 也能做网络爬 ...
- python爬虫怎么爬同一个网站的多页数据-如何用Python爬数据?(一)网页抓取
如何用Python爬数据?(一)网页抓取 你期待已久的Python网络数据爬虫教程来了.本文为你演示如何从网页里找到感兴趣的链接和说明文字,抓取并存储到Excel. 需求 我在公众号后台,经常可以收到 ...
- python高级—— 从趟过的坑中聊聊爬虫、反爬、反反爬,附送一套高级爬虫试题
前言: 时隔数月,我终于又更新博客了,然而,在这期间的粉丝数也就跟着我停更博客而涨停了,唉 是的,我改了博客名,不知道为什么要改,就感觉现在这个名字看起来要洋气一点. 那么最近到底咋不更新博客了呢?说 ...
- python高级—— 从趟过的坑中聊聊爬虫、反爬、反反爬,附送一套高级爬虫试题...
前言: 时隔数月,我终于又更新博客了,然而,在这期间的粉丝数也就跟着我停更博客而涨停了,唉 是的,我改了博客名,不知道为什么要改,就感觉现在这个名字看起来要洋气一点. 那么最近到底咋不更新博客了呢?说 ...
最新文章
- php实时股票,php获得股票数据
- 在CentOS 6.9 64bit上安装jdk1.8
- 用无人机打点作画,密集恐惧症患者慎入!
- 008 python接口 unittest
- 20应用统计考研复试要点(part16)--应用多元分析
- delphi7升级delphi2007可以互用马_马爹利鼎盛周末饭局暂停营业,未来再见
- postgresql数据库导入导出
- TokenInsight:反映区块链行业整体表现的TI指数较昨日同期下跌1.54%
- 在opencv3中的机器学习算法
- hdu 1251 统计难题 (字典树入门题)
- VS code gopls requires a module at the root of your workspace
- 使用FileWriter和BufferedWriter向文本文件中写信息
- 架构之美第三章-美丽架构之道
- ffplay播放器原理剖析
- 企业微信标签在哪?如何设置?
- h5阿里云播放器 常规使用
- 微信小程序如何更换头像
- 软件测试之第一章 软件测试和测试环境
- 基于Springboot的网上商城
- 华为电脑管家装到D盘_科普 | 电脑C盘满了,不想重新分区怎样扩容?我教你
热门文章
- 买房贷款月供怎么算?贷款利息是多少?
- C# 设置或验证 PDF中的文本域格式
- 什么是 A 轮融资?有 B轮 C轮么?
- 数显之家快讯:【SHIO世硕心语】2021,对你的老板好一点!
- 深入理解Plasma(四):Plasma Cash
- UISlider滑条
- Photoshop-选区的应用
- 张驰咨询:关于企业选择六西格玛绿带培训人员,你需要知道这些
- 软件License设计思路与实现方案
- o.redisson.client.handler.CommandsQueue : Exception occured.