Java 网络爬虫

栏目: 编程工具 · 发布时间: 5年前

内容简介:很早之前写过一个 Java 的爬虫程序,可以爬取笔趣阁上的小说。最近突然有爬点书看看的想法,遂翻到原先的代码,简直不忍直视。好在现在编码能力有所提升,而且团队内一直强调重构的重要性,所以重新拾起这个爬虫程序,好好重构一番。代码已上传至本文主要讲讲:不得不说,Gradle 让我眼前一亮,相比 Maven,它抛弃了冗余繁琐的 XML 格式,使用语法精炼的

Preface

很早之前写过一个 Java 的爬虫程序,可以爬取笔趣阁上的小说。最近突然有爬点书看看的想法,遂翻到原先的代码,简直不忍直视。好在现在编码能力有所提升,而且团队内一直强调重构的重要性,所以重新拾起这个爬虫程序,好好重构一番。代码已上传至 GitHub

本文主要讲讲:

  1. 使用 Gradle 构建项目时遇到的问题以及解决办法
  2. 使用 JDK8 的特性如何简便地进行 并行开发
  3. 写爬虫时的思路

Gradle

不得不说,Gradle 让我眼前一亮,相比 Maven,它抛弃了冗余繁琐的 XML 格式,使用语法精炼的 Groovy ,以下就是我的项目构建文件 build.gradle 的全部内容,相当于 Maven 的 pom.xml,清爽简短但重要的信息一点不少。

plugins {
    id 'java'
}

group 'xyz.s1mple.crawler'
version '1.0-SNAPSHOT'

sourceCompatibility = 1.8

repositories {
    mavenLocal()
    mavenCentral()
}

dependencies {
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.1'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.1'
    testCompile 'org.assertj:assertj-core:3.11.1'
    compile 'org.slf4j:slf4j-api:1.7.25'
    runtime 'org.slf4j:slf4j-simple:1.7.25'
}

test {
    useJUnitPlatform()

    testLogging {
        events "passed", "skipped", "failed"
    }
}

在上面的配置文件中我们可以发现,Gradle 沿用了 Maven 的中央仓库,同时还可以设置 Maven 本地仓库,同时 Gradle 项目生成的构件也可以发布到 Maven 仓库供他人使用,这一点也是支撑 Gradle 快速发展的重要一点。

安装

关于安装,我使用的 IDE 是 IntelliJ IDEA,使用内置的 Gradle 也可,但最好是下载安装全局的 Gradle,这样可以随时随地跑 Gradle 命令。Mac 下安装十分方便。其他系统如何安装可以在 Gradle官网 上找到。

brew install gradle

JUnit5

在使用 Gradle 的时候我遇到了一个问题:明明添加了 JUnit5 也就是 jupiter 的依赖,为什么使用 gradle test 命令没用任何输出。这个问题在我将依赖换回 Junit4 时得到解决,也就是说果然是配置的问题。在 build.gradle 中添加如下配置即可。

dependencies {
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.1'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.1'
}
    
test {
    useJUnitPlatform()
}

还可以添加 testLogging 。再运行命令有如下输出

➜  web-crawler git:(master) ✗ gradle clean test

> Task :test

xyz.s1mple.crawler.HtmlParserTest > should_parse_novel_content_from_html() PASSED

xyz.s1mple.crawler.HtmlParserTest > should_parse_novel_title_from_html() PASSED

xyz.s1mple.crawler.HtmlParserTest > should_parse_chapter_uris_from_html() PASSED

BUILD SUCCESSFUL in 1s

并行

Java 多线程编程是越来越容易了,从最早的 Thread,Runnable 到 JDK5 的ExecutorService 到 JDK7 的 ForkJoin 框架。现在 JDK8 又提供了 并行流parallelStream )来简化这一过程。

List<Integer> cost = Lists.newArrayList(1, 3, 7, 9, 34);
long total = cost.parallelStream()
                    .map(x -> x += 1)
                    .reduce((y, z) -> y + z)
                    .get();

在 parallelStream 中默认线程池大小是机器的 CPU 核数。 默认情况下,所有的 Fork/Join 任务都会共用同一个线程池,线程的数量等于CPU的核数 。所以在未手动设置线程池数量的情况下我的电脑会启动 4 个线程,而爬虫的瓶颈在于网络 I/O 不在 CPU,所以果断自定义线程数。效果也十分显著,爬取两千多章的时间从原先的两分多钟变为 20s。

final ForkJoinPool pool = new ForkJoinPool(PARALLELISM_LEVEL);
pool.submit(() -> urls.parallelStream()
        .map(url -> htmlParser.parseContent(urlReader.read(url)))
        .reduce((x, y) -> x + y)
        .orElse(""))
        .get();

需要注意两点:

  1. pool.submit() 该方法为 懒加载 ,如果 不调用它的结果则实际不会执行 ,最后 .get() 获取执行结果。
  2. .reduce((x, y) -> x + y) 这一步貌似会有很多字符串拼接影响效率,实则底层会使用 StringBuilderStringBuffer 帮你做了性能优化

测试

关于并行数对爬虫程序的性能影响,实际测试时受网络波动的影响,测试数据可能波动较大,连接每个 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

爬虫思路

最后说一说自己写这个爬虫时的思路,也算是解决问题的入手点。主要的解析类为 HtmlParser ,负责解析 Html。其实这个程序就是通过 Html 源码解析小说名 Title、小说所含所有章节的 Url、小说每一章的内容,最后将内容 reduce 写入文件。

Title

解析小说章节目录网址 Html 源码的 <h1>...</h1> 标签中的值

// 章节目录网址
http://www.biquge.cm/9/9422/

// html
<h1>大道朝天</h1>

// after parse
大道朝天

Chapter URIs

解析小说章节目录网址 Html 源码的 <div id="list">...</div> 中包含的 <a href="...">

// 章节目录网址
http://www.biquge.cm/9/9422/

// html
<div id="list">
<dl><dd><a href="/9/9422/6927857.html">第一章 三千里禁</a></dd>
<dd><a href="/9/9422/6927858.html">第二章 斩天一剑</a></dd>
<dd><a href="/9/9422/6927859.html">第三章 再次踏进那条河的白衣少年</a></dd>
...
</dl></div>

// after parse
["/9/9422/6927859.html", "/9/9422/6927858.html", "/9/9422/6927857.html" ...]

Content

根据 Chapter URIs 解析每一章的内容,最后拼接在一起写入文件。根据 <div id="content">...</div> 解析, &nbsp; 替换为空格, <br /> 替换为换行符

// 某一章的网址
http://www.biquge.cm/9/9422/6927857.html

// html
<div id="content">    朝天大陆南方,一片青山绵延数千里,数百秀峰终年隐在云雾中。<br />
<br />
    天下第一修行大派青山宗便在此间,普通人极难一睹真容。<br />
<br />
...
</div>

// after parse

    朝天大陆南方,一片青山绵延数千里,数百秀峰终年隐在云雾中。
    
    天下第一修行大派青山宗便在此间,普通人极难一睹真容。

References


以上所述就是小编给大家介绍的《Java 网络爬虫》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Little Typer

The Little Typer

Daniel P. Friedman、David Thrane Christiansen、Duane Bibby、Robert Harper、Conor Mcbride / MIT Press / 2018-10-16 / GBP 30.00

An introduction to dependent types, demonstrating the most beautiful aspects, one step at a time. A program's type describes its behavior. Dependent types are a first-class part of a language, and are......一起来看看 《The Little Typer》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

MD5 加密
MD5 加密

MD5 加密工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具