目标
编写一个 Java 的爬虫程序,并行爬取小说网站上的小说。代码已上传至 GitHub。Python 编写的爬虫请见50 行代码实现一个并发的 Python 爬虫程序。本文主要关注:
- 爬虫程序思路
- 使用 Gradle 构建项目时遇到的问题以及解决办法
- 使用 JDK8 的特性如何简便地进行并行化开发
爬虫思路
先说一说自己写这个爬虫时的思路,也算是解决问题的入手点。主要的解析类为 HtmlParser ,负责解析 Html。其实这个程序就是通过 Html 源码解析小说名 Title、小说所含所有章节的 Url、小说每一章的内容,最后将内容按顺序写入文件。
Title
解析小说章节目录网址 Html 源码的 <h1>...</h1>
标签中的值
1 | // 章节目录网址 |
Chapter URIs
解析小说章节目录网址 Html 源码的 <div id="list">...</div>
中包含的 <a href="...">
1 | // 章节目录网址 |
Content
根据 Chapter URIs 解析每一章的内容,最后拼接在一起写入文件。根据 <div id="content">...</div>
解析,
替换为空格,<br />
替换为换行符
1 | // 某一章的网址 |
Gradle
不得不说,Gradle 让我眼前一亮,相比 Maven,它抛弃了冗余繁琐的 XML 格式,使用语法精炼的 Groovy,以下就是我的项目构建文件 build.gradle 的全部内容,相当于 Maven 的 pom.xml,清爽简短但重要的信息一点不少。
1 | plugins { |
在上面的配置文件中我们可以发现,Gradle 沿用了 Maven 的中央仓库,同时还可以设置 Maven 本地仓库,同时 Gradle 项目生成的构件也可以发布到 Maven 仓库供他人使用,这一点也是支撑 Gradle 快速发展的重要一点。
安装
关于安装,我使用的 IDE 是 IntelliJ IDEA,使用内置的 Gradle 也可,但最好是下载安装全局的 Gradle,这样可以随时随地跑 Gradle 命令。Mac 下安装十分方便。其他系统如何安装可以在 Gradle官网 上找到。
1 | brew install gradle |
JUnit5
在使用 Gradle 的时候我遇到了一个问题:明明添加了 JUnit5 也就是 jupiter 的依赖,为什么使用 gradle test
命令没用任何输出。这个问题在我将依赖换回 Junit4 时得到解决,也就是说果然是配置的问题。在 build.gradle 中添加如下配置即可。
1 | dependencies { |
还可以添加 testLogging
。再运行命令有如下输出
1 | ➜ web-crawler git:(master) ✗ gradle clean test |
并行化
Java 多线程编程是越来越容易了,从最早的 Thread,Runnable 到 JDK5 的ExecutorService 到 JDK7 的 ForkJoin 框架。现在 JDK8 又提供了并行流(parallelStream)来简化这一过程。
1 | List<Integer> cost = Lists.newArrayList(1, 3, 7, 9, 34); |
在 parallelStream 中默认线程池大小是机器的 CPU 核数。默认情况下,所有的 Fork/Join 任务都会共用同一个线程池,线程的数量等于CPU的核数。所以在未手动设置线程池数量的情况下我的电脑会启动 4 个线程,而爬虫的瓶颈在于网络 I/O 不在 CPU,所以果断自定义线程数。效果也十分显著,爬取两千多章的时间从原先的两分多钟变为 20s。
1 | final ForkJoinPool pool = new ForkJoinPool(PARALLELISM_LEVEL); |
需要注意两点:
pool.submit()
该方法为懒加载,如果不调用它的结果则实际不会执行,最后.get()
获取执行结果。.reduce((x, y) -> x + y)
这一步貌似会有很多字符串拼接影响效率,实则底层会使用StringBuilder
或StringBuffer
帮你做了性能优化。
性能
关于并行数对爬虫程序的性能影响,实际测试时受网络波动的影响,测试数据可能波动较大,连接每个 Url 的耗时在 50ms ~ 1000ms 波动区间。为减少网络不稳定带来的影响,以下测试分为 100 章、1000 章两个数量级,进行不同并行等级的循环测试,测试 3 轮,时间单位为秒。
数量级:103 章
PARALLELISM_LEVEL | 1 | 4 | 16 | 64 | 256 |
---|---|---|---|---|---|
第1次测试 | 9 | 5 | 2 | 2 | 2 |
第2次测试 | 8 | 4 | 2 | 2 | 2 |
第3次测试 | 9 | 3 | 2 | 2 | 3 |
数量级:1180 章
PARALLELISM_LEVEL | 4 | 8 | 16 | 32 | 64 | 128 | 256 |
---|---|---|---|---|---|---|---|
第1次测试 | 28 | 16 | 10 | 13 | 12 | 12 | 11 |
第2次测试 | 23 | 16 | 14 | 11 | 12 | 12 | 14 |
第3次测试 | 25 | 29 | 14 | 13 | 29 | 20 | 20 |