整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

用 Java 拿下 HTML 分分钟写个小爬虫

文适合有 Java 基础知识的人群

本文作者:HelloGitHub-秦人

HelloGitHub 推出的《讲解开源项目》系列,今天给大家带来一款开源 Java 版一款网页元素解析框架——jsoup,通过程序自动获取网页数据。

项目源码地址:https://github.com/jhy/jsoup

一、项目介绍

jsoup 是一款 Java 的 HTML 解析器。可直接解析某个 URL 地址的 HTML 文本内容。它提供了一套很省力的 API,可通过 DOM、CSS 以及类似于 jQuery 选择器的操作方法来取出和操作数据。

jsoup 主要功能:

  1. 从一个 URL、文件或字符串中解析 HTML。
  2. 使用 DOM 或 CSS 选择器来查找、取出数据。
  3. 可操作 HTML 元素、属性、文本。

二、使用框架

2.1 准备工作

  • 掌握 HTML 语法
  • Chrome 浏览器调试技巧
  • 掌握开发工具 idea 的基本操作

2.2 学习源码

将项目导入 idea 开发工具,会自动下载 maven 项目需要的依赖。源码的项目结构如下:

快速学习源码是每个程序员必备的技能,我总结了以下几点:

  1. 阅读项目 ReadMe 文件,可以快速知道项目是做什么的。
  2. 概览项目 pom.xml 文件,了解项目引用了哪些依赖。
  3. 查看项目结构、源码目录、测试用例目录,好的项目结构清晰,层次明确。
  4. 运行测试用例,快速体验项目。

2.3 下载项目

git clone https://github.com/jhy/jsoup

2.4 运行项目测试代码

通过上面的方法,我们很快可知 example 目录是测试代码,那我们直接来运行。注:有些测试代码需要稍微改造一下才可以运行。

例如,jsoup 的 Wikipedia 测试代码:

public class Wikipedia {
    public static void main(String[] args) throws IOException {
        Document doc = Jsoup.connect("http://en.wikipedia.org/").get();
        log(doc.title());

        Elements newsHeadlines = doc.select("#mp-itn b a");
        for (Element headline : newsHeadlines) {
            log("%s\n\t%s", headline.attr("title"), headline.absUrl("href"));
        }
    }

    private static void log(String msg, String... vals) {
        System.out.println(String.format(msg, vals));
    }
}

说明:上面代码是获取页面(http://en.wikipedia.org/)包含(#mp-itn b a)选择器的所有元素,并打印这些元素的 title , herf 属性。维基百科 国内无法访问,所以上面这段代码运行会报错。

改造后可运行的代码如下:

public static void main(String[] args) throws IOException {
    Document doc = Jsoup.connect("https://www.baidu.com/").get();
    Elements newsHeadlines = doc.select("a[href]");
    for (Element headline : newsHeadlines) {
        System.out.println("href: " +headline.absUrl("href") );
    }
}

三、工作原理

Jsoup 的工作原理,首先需要指定一个 URL,框架发送 HTTP 请求,然后获取响应页面内容,然后通过各种选择器获取页面数据。整个工作流程如下图:

以上面为例:

3.1 发请求

Document doc = Jsoup.connect("https://www.baidu.com/").get();

这行代码就是发送 HTTP 请求,并获取页面响应数据。

3.2 数据筛选

Elements newsHeadlines = doc.select("a[href]");

定义选择器,获取匹配选择器的数据。

3.3 数据处理

for (Element headline : newsHeadlines) {
        System.out.println("href: " +headline.absUrl("href") );
    }

这里对数据只做了一个简单的数据打印,当然这些数据可写入文件或数据的。

四、实战

获取豆瓣读书 -> 新书速递中每本新书的基本信息。包括:书名、书图片链接、作者、内容简介(详情页面)、作者简介(详情页面)、当当网书的价格(详情页面),最后将获取的数据保存到 Excel 文件。

目标链接:https://book.douban.com/latest?icn=index-latestbook-all

4.1 项目 pom.xml 文件

项目引入 jsoup、lombok、easyexcel 三个库。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>JsoupTest</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <maven.compiler.target>1.8</maven.compiler.target>
        <maven.compiler.source>1.8</maven.compiler.source>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.jsoup</groupId>
            <artifactId>jsoup</artifactId>
            <version>1.13.1</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>2.2.6</version>
        </dependency>
    </dependencies>
</project>

4.2 解析页面数据

public class BookInfoUtils {

    public static List<BookEntity> getBookInfoList(String url) throws IOException {
        List<BookEntity>  bookEntities=new ArrayList<>();
        Document doc = Jsoup.connect(url).get();
        Elements liDiv = doc.select("#content > div > div.article > ul > li");
        for (Element li : liDiv) {
            Elements urls = li.select("a[href]");
            Elements imgUrl = li.select("a > img");
            Elements bookName = li.select(" div > h2 > a");
            Elements starsCount = li.select(" div > p.rating > span.font-small.color-lightgray");
            Elements author = li.select("div > p.color-gray");
            Elements description = li.select(" div > p.detail");

            String bookDetailUrl = urls.get(0).attr("href");
            BookDetailInfo detailInfo = getDetailInfo(bookDetailUrl);
            BookEntity bookEntity = BookEntity.builder()
                    .detailPageUrl(bookDetailUrl)
                    .bookImgUrl(imgUrl.attr("src"))
                    .bookName(bookName.html())
                    .starsCount(starsCount.html())
                    .author(author.text())
                    .bookDetailInfo(detailInfo)
                    .description(description.html())
                    .build();
//            System.out.println(bookEntity);
            bookEntities.add(bookEntity);
        }
        return bookEntities;
    }
    /**
     *
     * @param detailUrl
     * @return
     * @throws IOException
     */
    public static BookDetailInfo getDetailInfo(String detailUrl)throws IOException{

        Document doc = Jsoup.connect(detailUrl).get();
        Elements content = doc.select("body");

        Elements price = content.select("#buyinfo-printed > ul.bs.current-version-list > li:nth-child(2) > div.cell.price-btn-wrapper > div.cell.impression_track_mod_buyinfo > div.cell.price-wrapper > a > span");
        Elements author = content.select("#info > span:nth-child(1) > a");
        BookDetailInfo bookDetailInfo = BookDetailInfo.builder()
                .author(author.html())
                .authorUrl(author.attr("href"))
                .price(price.html())
                .build();
        return bookDetailInfo;
    }
}

这里的重点是要获取网页对应元素的选择器

例如:获取 li.select("div > p.color-gray") 中 div > p.color-gray 是怎么知道的。

使用 chrome 的小伙伴应该都猜到了。打开 chrome 浏览器 Debug 模式,Ctrl + Shift +C 选择一个元素,然后在 html 右键选择 Copy ->Copy selector,这样就可以获取当前元素的选择器。如下图:

4.3 存储数据到 Excel

为了数据更好查看,我将通过 jsoup 抓取的数据存储的 Excel 文件,这里我使用的 easyexcel 快速生成 Excel 文件。

Excel 表头信息

@Data
@Builder
public class ColumnData {

    @ExcelProperty("书名称")
    private String bookName;

    @ExcelProperty("评分")
    private String starsCount;

    @ExcelProperty("作者")
    private String author;

    @ExcelProperty("封面图片")
    private String bookImgUrl;

    @ExcelProperty("简介")
    private String description;

    @ExcelProperty("单价")
    private String price;
}

生成 Excel 文件

public class EasyExcelUtils {

    public static void simpleWrite(List<BookEntity> bookEntityList) {
        String fileName = "D:\\devEnv\\JsoupTest\\bookList" + System.currentTimeMillis() + ".xlsx";
        EasyExcel.write(fileName, ColumnData.class).sheet("书本详情").doWrite(data(bookEntityList));
        System.out.println("excel文件生成完毕...");
    }
    private static List<ColumnData> data(List<BookEntity> bookEntityList) {
        List<ColumnData> list = new ArrayList<>();
        bookEntityList.forEach(b -> {
            ColumnData data = ColumnData.builder()
                    .bookName(b.getBookName())
                    .starsCount(b.getStarsCount())
                    .author(b.getBookDetailInfo().getAuthor())
                    .bookImgUrl(b.getBookImgUrl())
                    .description(b.getDescription())
                    .price(b.getBookDetailInfo().getPrice())
                    .build();
            list.add(data);
        });
        return list;
    }
}

4.4 最终展示效果

最终的效果如下图:

以上就是从想法到实践,我们就在实战中使用了 jsoup 的基本操作。

完整代码地址:https://github.com/hellowHuaairen/JsoupTest

五、最后

Java HTML Parser 库:jsoup,把它当成简单的爬虫用起来还是很方便的吧?

为什么会讲爬虫?大数据,人工智能时代玩的就是数据,数据很重要。作为懂点技术的我们,也需要掌握一种获取网络数据的技能。当然也有一些工具 Fiddler、webscraper 等也可以抓取你想要的数据。

教程至此,你应该也能对 jsoup 有一些感觉了吧。编程是不是也特别有意思呢?参考我上面的实战案例,有好多网站可以实践一下啦~欢迎在评论区晒你的实战。

容简介

《Java Web开发实战1200例》包括第一卷、第二卷共计1200个例子,每卷各计600个例子。

本书以开发人员在项目开发中经常遇到的问题和必须掌握的技术为中心,介绍了应用Java Web进行程序开发的各个方面的知识和技巧,主要包括Java Web编程基础、文件操作管理、图形图像与多谋体技术、窗体应用技术、JSP操作Word与Excel等。全书分6篇23章,共计600个实例和600个经验技巧。每个实例都是经过精心筛选的,具有很强的实用性,其中一些实例是开发人员难于寻觅的解决方案。

本书适合Java Web的初学者,如高校学生、求职人员作为练习、速查、学习使用,也适合Java Web程序员参考、查阅。

支持书签目录,方便查阅

第一卷:

基础篇

  1. 开发环境搭建
  2. Java语言基础
  3. HTML/CSS技术
  4. JSP基础与内置对象
  5. JavaBean技术
  6. Servlet技术
  7. 过滤器与监听器技术
  8. JSTL标签库
  9. JavaScript技术
  10. Ajax技术

文件管理编

  1. 文件基本操作及文件上传下载
  2. 文件的批量管理

图像与多媒体篇

  1. 图像生成
  2. 图像操作
  3. 多媒体应用

窗体应用篇

  1. 窗口的应用
  2. 导航条的应用
  3. 表单的应用
  4. 表格的操作

操作Word、Excel、报表打印篇

  1. jsp操作word
  2. jsp操作excel
  3. 报表与打印

综合应用篇

  1. 综合应用

第二卷:

流行组件应用篇

...

Java Web数据库应用篇

...

图表分析篇

...

Ajax框架应用篇

...

流行框架篇

...

网站安全与架构模式篇

...

综合应用篇

...

点赞+转发+评论,关注后私信回复关键词:实战,即可获取链接!

领取PDF的小伙伴别忘记在评论区回应下,感谢大家支持!

前言】

你是否也曾羡慕过有些 phython 大神有着如下的神操作:

他们就轻轻的执行一串代码,就能循环的抓取很多自己想要的数据。

其实不用太羡慕他们,因为不光 phython 能实现,我们用 Java 同样也能够轻松实现。

闲话不多说,下面我们直接开始实战:

【1】创建项目

(1.1)我们用 IDEA(Eclipse同理) 创建一个全新的maven工程,我这里取名工程名 zyqok,各位随意。

(1.2)在 pom.xml 里面加上 <dependencies>

(1.3)创建 Test 类,好了工程就已经搭好了。

【2】Httpclient 实现网络请求

(2.1)什么是 httpclient ?

Httpclient 是 Apache 的一个子项目,它是一个为 Java 可以实现网络请求的客户端工具包。

简单的说,他是一个 Jar 包,有了他,我们通过 Java 程序就可以实现网络请求。

(2.2) 复制下面的 httpclient 依赖,加入到 pom.xml 文件中。

<!-- httpclient 核心包 -->
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.2</version>
</dependency>

(2.3)创建一个 HttpTool 的类,这个类我们专门用来实现网络请求相关方法。

(2.4) 为了避免其他网站侵权问题,下面以我个人网站一个页面为例(http://www.zyqok.cn/material/index),我们来抓取这个页面上的所有图片。

(2.5) 可以看得出,这是一个 get 请求,并且返回的是一个 Html 页面。所以我们在 HttpTool 类中加入一个如下方法体:

  /**
   * 实现Get请求
   * @param url 请求地址
   * @return 页面内容
   */
  public static String doGet(String url) {
    
    return null;
  }

(2.6)复制代码,添加 get 实现方法:

    // 构建get请求
    HttpGet get = new HttpGet(url);
    // 创建客户端
    CloseableHttpClient client = HttpClients.createDefault();
    try {
      // 客户端执行请求,获取响应
      HttpResponse response = client.execute(get);
      // 获取响应的页面内容
      InputStream in = response.getEntity().getContent();
      StringBuilder sb = new StringBuilder();
      byte[]b = new byte[102400];
      int length;
      while ((length = in.read(b)) != -1) {
        sb.append(new String(b, 0, length, "utf-8"));
      }
      // 返回页面内容
      return sb.toString();
    } catch (Exception e) {
      e.printStackTrace();
      return null;
    }

(2.7)OK,网络请求相关实现类我们已经写好了,我们接下来测试下,我们在 Test 类的 main 方法里加入如下代码:

    String html = HttpTool.doGet("http://www.zyqok.cn/material/index");
    System.out.println(html);

(2.8)执行程序,查看结果。可以看到我们确实已经通过请求,获取到网页的返回内容了。

【3】Jsoup 解析网页

在整个【2】的实现过程中,我们已经拿到网页返回的数据,但我们要的是整个网页中的图片,并不是这种杂乱无章的网页页面数据,那么我们该怎么办呢?简单,接下来我们需要用到另外一种技术了 ---- Jsoup。

(3.1)什么是 Jsoup 技术?

下面是度娘给出的一个官方解释:Jsoup 是一款Java 的HTML解析器,可直接解析某个URL地址、HTML文本内容。它提供了一套非常省力的API,可通过DOM,CSS以及类似于jQuery的操作方法来取出和操作数据(摘自百度)。

下面再用我个人语言简单的总结下:Jsoup 技术就是用来处理各种 html 页面 和 xml 数据。我们这里可以通过 Jsoup 来处理【2】中返回的 html 页面。

(3.2)加入 Jsoup 依赖

我们在 pom.xml 加入如下依赖:

<!-- Jsoup 核心包 -->
<dependency>
	<groupId>org.jsoup</groupId>
	<artifactId>jsoup</artifactId>
	<version>1.11.3</version>
</dependency>

(3.3)当然,使用 Jsoup 之前,我们需要对响应的 HTML 页面进行分析,分析主要作用是:如何定位筛选出我们需要的数据?

我们把【2】中获取到的页面响应拷贝到 txt 文本中,然后可以发现:每个图片它都包含在一个 div 中,且该div 有一个名为 material-div 的 class。

(3.4)按照上面分析:首先我们要获取到包含图片的所有 div,于是我们修改main方法中代码为如下:

    String html = HttpTool.doGet("http://www.zyqok.cn/material/index");
    // 将 html 页面解析为 Document 对象
    Document doc = Jsoup.parse(html);
    // 获取所有包含 class = material-div 的 div 元素
    Elements elements = doc.select("div.material-div");
    for(Element div: elements){
        System.out.println(div.toString());
    }

注意:doc.select() 括号中的参数为过滤条件,基本等同于 Jquery 的过滤条件,所以会Jquery的同学,如何筛选条件基本就得心应手的,当然不会写筛选条件的也不要怕,这里有一份 Jsoup 使用指南,阁下不妨收下(传送门:Jsoup 官方使用指南)。

(3.5)我们执行代码,将输出结果继续拷贝到文本中。

可以看到,本次确实只有图片相关的div元素了,但这并不是我们想要的最终结果,我们最终的结果是获取到所有图片。

所以我们还需要继续分析:如何获取所有图片的链接和名字。

(3.6)由于每个图片所在的div元素结构都一样,所以我们可以取随机取一个div元素进行分析,于是我们可以取第一个div来进行分析,结构如下:

<div align="center" style="padding: 10px;" class="material-div"> 
    <div style="width: 80px; height: 80px; margin-bottom: 3px; display: flex; align-items: center; justify-content: center"> 
        <img class="fangda image" src="https://zyqok.oss-cn-chengdu.aliyuncs.com/20200414220946131_大树夕阳.jpg"> 
        <input type="hidden" class="materialId" value="121"> 
    </div> 
    <font style="font-size: 5px">大树夕阳.jpg</font><br> 
    <font style="font-size: 5px">2020-04-14 22:09:46</font> 
</div>

3.7)我们可以看到,整个结构内,就一个 img 元素标签,于是我们可以取第1个img标签的 src 属性为图片链接;同理,我们取第1个 font 元素的文本内容为图片名称。

(3.8)于是我们可以修改循环中的代码内容如下:

// 获取第1个 img 元素Element img = div.selectFirst("img");// 获取第1个 font 元素Element font = div.selectFirst("font");// 获取img元素src属性,即为图片链接String url = img.attr("src");// 获取name元素文本,即为图片名称String name = font.text();System.out.println(name + ": " + url);

(3.9)我们执行上面代码,可以得出如下结果。

可以看到,这个页面上的所有图片地址和名称已经被我们成功抓下来了。

【4】获取图片到本地

在第【3】步中,我们获取到的只是所有图片的链接,并没有将所有图片下载到我们本地,那么接下来,我们要将这个图片下载到我们本地才算完成。

(4.1)既然要下载到本地,我们首先在本地找个地方,用于存放这些图片。

比如:我将这图片全部下载到 D:\imgs(D 盘的 imgs 文件夹)中。

(4.2)我们在 HttpTool 类中增加保存图片到本地的方法,代码如下:

  /**
   * 保存图片到本地
   * @param src 图片地址
   * @param name 图片名称
   */
  public static void saveImg(String src, String name) {
    // 构建get请求
    HttpGet get = new HttpGet(src);
    // 创建客户端
    CloseableHttpClient client = HttpClients.createDefault();
    try {
      // 客户端执行请求,获取响应
      HttpResponse response = client.execute(get);
      // 获取响应的页面内容
      InputStream in = response.getEntity().getContent();
      int length;
      byte[] bytes = new byte[1024];
      FileOutputStream fos = new FileOutputStream("D:\\imgs\\" + name);
      while ((length = in.read(bytes)) != -1) {
        fos.write(bytes, 0, length);
        fos.flush();
      }
      in.close();
      fos.close();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

(4.3)修改 Test 类 main 方法最终代码如下:


  public static void main(String args[]) throws Exception {
    String html = HttpTool.doGet("http://www.zyqok.cn/material/index");
    // 将 html 页面解析为 Document 对象
    Document doc = Jsoup.parse(html);
    // 获取所有包含 class = material-div 的 div 元素
    Elements elements = doc.select("div.material-div");
    for (int i = 0; i<elements.size(); i++) {
      Element div = elements.get(i);
      // 获取第1个 img 元素
      Element img = div.selectFirst("img");
      // 获取第1个 font 元素
      Element font = div.selectFirst("font");
      // 获取img元素src属性,即为图片链接
      String src = img.attr("src");
      // 获取name元素文本,即为图片名称
      String name = font.text();
      if (!name.contains(".")) {
        name += ".jpg";
      }
      HttpTool.saveImg(src, i + name);
      System.out.println("抓取第 " + i + " 张图片成功! 图片名称 : " + name);
    }
    System.out.println("所有图片抓取完成 !!");
  }

(4.4)执行代码,打印如下图,看到这个结果,是不是感觉有点文章开头的展示味道了。

最后,我们只需要去本地文件夹下看看,所有图片是否成功保存到了本地?如果有图片,则我们就成功了。

(4.5)我们打开D盘imgs文件夹,可以看到网站上的图片确实已经全部保存到本地了。

【5】结尾语

通过我们 [批量抓取网络图片] 这一实战案例,我们可以感受到:通过 Httopclient 和 Jsoup 这两种技术,不仅仅可以批量抓取数据,其实还可以实现很多功能。

比如:网站登录,分布式服务器之间的数据传递,三方平台的API对接,有效数据的筛选和保存,数据的二次加工等等。