Java-网络爬虫(一)

文章目录

  • 前言
  • 一、网络爬虫
    • 1. 介绍
    • 2. 爬虫协议
    • 3. 法律法规
  • 二、相关知识
    • 1. HttpClient
    • 2. Jsoup
  • 三、综合案例
    • 1. 案例一
    • 2. 案例二
  • 四、总结


前言

在大数据时代,信息采集是一项重要的工作,而互联网中的数据是海量的,如果单纯靠人力进行信息获取,不仅低效繁琐,而且搜集的成本也会提高,如何自动高效地获取互联网中的数据是一个重要的问题,而爬虫技术就是针对这些问题而生的。

一、网络爬虫

1. 介绍

网络爬虫(Web crawler)又称为网络蜘蛛或网络机器人,是一种自动化程序,用于在互联网上浏览和抓取信息,是互联网时代一项普遍运用的网络信息搜集技术。

在这里插入图片描述

该项技术最早应用于搜索引擎领域,是搜索引擎获取数据来源的支撑性技术之一。随着数据资源的爆炸式增长,网络爬虫的应用场景和商业模式变得更加广泛和多样,较为常见的有新闻平台的内容汇聚和生成、电子商务平台的价格对比功能、基于气象数据的天气预报应用等等。

一个出色的网络爬虫工具能够处理大量的数据,大大节省了人类在该类工作上所花费的时间。网络爬虫作为数据抓取的实践工具,构成了互联网开放和信息资源共享理念的基石,如同互联网世界的一群工蜂,不断地推动网络空间的建设和发展。

原理:

传统爬虫从一个或者若干个初始网页的 URL 开始,通过模拟浏览器行为,自动访问并解析网页。它们可以跟踪链接,从一个网页到另一个网页,逐层遍历整个互联网。通过取网页的HTML源代码,并从中提取有用的信息,如文本、图像、链接等。

功能与价值:

网络爬虫技术是互联网开放共享精神的重要实现工具。允许收集者通过爬虫技术收集数据是数据开放共享的重要措施,网络爬虫能够通过聚合信息、提供链接,为数据所有者的网站带来更多的访问量,这些善意、适量的数据抓取行为,符合数据所有者开放共享数据的预期。

从功能上来讲,爬虫一般分为数据采集、处理、存储三个部分。

爬虫的应用:

  • 实现和优化搜索引擎
  • 获取更多的数据源

2. 爬虫协议

爬虫的功能十分强大,但是我们并不能为所欲为的使用爬虫,爬虫需要遵循 robots 协议,该协议是国际互联网界通行的道德规范,每一个爬虫都应该遵守。

Robots 协议(也称为爬虫协议、机器人协议等)的全称是 “网络爬虫排除标准”(Robots Exclusion Protocol),网站通过 Robots 协议告诉搜索引擎哪些页面可以抓取,哪些页面不能抓取,该协议属于一个规范,并不能保证网站的隐私。

Robots 协议是国际互联网界通行的道德规范,基于以下原则:

  1. 搜索技术应服务于人类,同时尊重信息提供者的意愿,并维护其隐私权。

  2. 网站有义务保证其使用者的个人信息和隐私不被侵犯。

在使用爬虫的时候我们应当注意一下几点:

  1. 拒绝访问和抓取有关不良信息的网站。

  2. 注意版权意识,对于原创内容,未经允许不要将信息用于其他用途,特别是商业方面。

  3. 严格遵循 robots.txt 协议。

  4. 爬虫协议查看方式

大部分网站都会提供自己的 robots.txt 文件,这个文件会告诉我们该网站的爬取准则,查看方式是在域名加 /robots.txt 并回车。

例如百度的爬虫协议:https://www.baidu.com/robots.txt

在这里插入图片描述

  • User-agent:为访问用户
  • Allow:允许爬行的目录
  • Disallow:不允许爬行的目录
  • Sitemap:网站地图,告诉爬虫这个页面是网站地图

从上述协议可以看到百度对于普通使用者为:

User-agent: *
Disallow: /

则表示禁止所有搜索引擎访问网站的任何部分。

而对于 Baiduspider 这类用户

User-agent: Baiduspider
Disallow: /baidu
Disallow: /s?
Disallow: /ulink?
Disallow: /link?
Disallow: /home/news/data/
Disallow: /bh

则不能爬取 /baidu、/s?、/ulink?... 下面目录的数据。


3. 法律法规

网络爬虫规制的必要性:

  • (一)恶意抓取侵害他人权益和经营自由通过网络爬虫访问和收集网站数据行为本身已经产生了相当规模的网络流量,但是,有分析表明其中三分之二的数据抓取行为是恶意的,并且这一比例还在不断上升:恶意机器人可以掠夺资源、削弱竞争对手。恶意机器人往往被滥用于从一个站点抓取内容,然后将该内容发布至另一个站点,而不显示数据源或链接,这一不当手段将帮助非法组织建立虚假网站,产生欺诈风险,以及对知识产权、商业秘密的窃取行为。
  • (二)恶意爬虫危及网络安全从行为本身来讲,恶意爬虫会对目标网站产生 DDOS 攻击的效果,当有成百上千的爬虫机器人与同一网站进行交互,网站将会失去对真实目标的判断,其很难确定哪些流量来自真实用户,哪些流量来自机器人。若平台使用了掺杂虚假访问行为的缺陷数据,做出相关的营销决策,可能会导致大量时间和金钱的损失。尽管 robots 协议作为国际通行的行业规范,能够帮助网站在 robot.txt文件中明确列出限制抓取的信息范围,但并不能从根本上阻止机器人的恶意爬虫行为,其协议本身无法为网站提供任何技术层面的保护。目前恶意的网络爬虫行为已经给互联网平台带来了一定的商业和技术风险,影响了其正常的平台运营和业务开展。
  • (三)现行法律规制方式及其不足之处网络爬虫的不当访问、收集、干扰行为应当受到法律规制。目前,我国已有法律对网络爬虫进行规制主要集中在刑法有关计算机信息系统犯罪的相关条文上。从刑法所追求的法益来看,刑法规范的是对目标网站造成严重影响并具有社会危害性的数据抓取行为。若行为人违反刑法的相关规定,通过网络爬虫访问收集一般网站所存储、处理或传输的数据,可能构成刑法中的非法获取计算机信息系统数据罪;如果在数据抓取过程中实施了非法控制行为,可能构成非法控制计算机信息系统罪。此外,由于使用网络爬虫造成对目标网站的功能干扰,导致其访问流量增大、系统响应变缓,影响正常运营的,也可能构成破坏计算机信息系统罪。

由于刑法的谦抑性,其只能在网络爬虫行为产生严重社会危害而无刑罚以外手段进行规制的情形下起到惩治效果,而对于网络爬虫妨碍其他网站正常运行、过量访问收集数据等一般性危害行为很难起到规制作用,因此我国需要建立在刑法以外的行政规制手段,构建完善的刑事责任、行政责任乃至民事责任体系,以保护互联网平台的合法权益,维护网络空间的正常秩序。

完善网络爬虫规制方式的建议:

从网络爬虫的相关案例来看,其使用者往往有充分的理由做出可能涉嫌违法的数据抓取行为,其辩护理由通常包括:“我可以用公开访问的数据做任何事”“这是合理使用行为”“这与搜索引擎行为类似”“只是使用了自动脚本,而未使用在建立网站上”“我已经遵守了它们的 robots 协议”“该网站没有 robots 协议”“这些数据我只是个人研究使用,并没有商业目的”。由此可见,依托行为是否具有恶意或者通过主观层面来判断爬虫行为违法与否是具有难度的。网络爬虫规制的目标是在数据资源开放共享与互联网平台经营自由、网站安全之间取得平衡,遵循技术中立性原则,对网络爬虫进行规制应当基于客观结果,即是否妨碍网站的正常运行或者对他人合法权益造成严重危害。

数字时代,在数据利用成为网络产业中心的背景下,亟待确立数据访问、获取的规则。在技术手段、市场手段之外,需要采用法律手段规制爬虫技术的应用,对特定的数据访问场景进行规范。通过数据安全立法设置爬虫技术严重影响网站正常运行的判断标准,对具有危害性的网络爬虫行为进行适当规制,是我国安全与发展并重互联网治理根本准则在数据治理领域的体现,其目标是在数据活动各方主体中找到平衡点,兼顾数据开放共享与数据所有者经营自由和安全、社会公共利益,确保数据依法有序自由流动。

谨慎使用的技术:

  1. 爬虫访问频次要控制,别把对方服务器搞崩溃
  2. 涉及个人隐私的信息不能爬
  3. 突破网站的反爬措施,后果很严重
  4. 不要把爬取的数据做不正当竞争
  5. 付费内容,不要抓
  6. 突破网络反爬措施的代码,最好不要上传到网络上

二、相关知识

1. HttpClient

因为爬虫技术是模仿游览器行为,那么必然是需要发送 HTTP 请求,在 Javaapache 有提供支持 HTTP 协议的客户端编程工具包 HttpClient,可以使用 HttpClient 来发送请求,

例如:使用 HttpClient 请求 https://www.rgbku.com/chaxun.html(rgb颜色查询器)

在这里插入图片描述

那么代码可以这样写:

import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import java.io.IOException;
import java.util.Objects;

public class HttpClientDemo {
    public static void main(String[] args) {
        // 创建 httpClient 对象
        CloseableHttpClient httpClient = HttpClients.createDefault();
        // 创建 httpGet 对象,设置访问 URL
        HttpGet httpGet = new HttpGet("https://www.rgbku.com/chaxun.html");
        CloseableHttpResponse response = null;
        try {
            // 发送请求
            response = httpClient.execute(httpGet);
            // 根据状态码判断是否响应成功(一般是 200)
            if (response.getStatusLine().getStatusCode() == 200) {
                // 解析响应
                HttpEntity entity = response.getEntity();
                String html = EntityUtils.toString(entity, Consts.UTF_8);
                // 打印响应内容
                System.out.println(html);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭资源
            try {
                httpClient.close();
                if (Objects.nonNull(response)) {
                    response.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

可以从打印信息中就能看出已获取到该网站的 HTML 信息了

在这里插入图片描述

HttpClient 不仅可以发送 GET 请求,还能够发起 POSTPUTDELETE 等等各种请求,同时还能携带参数、tokencookie 和设置 User-Agent 等功能,可以做到很好的模拟用户在游览器上面访问网站。

GET 请求:

    /**
     * 发送不带参数的 GET 请求
     */
    public static void sendGet() throws Exception{
        // 创建 httpClient 对象
        CloseableHttpClient httpClient = HttpClients.createDefault();
        // 创建 httpGet 对象,设置访问 URL
        HttpGet httpGet = new HttpGet("https://www.xxxx.com");
        CloseableHttpResponse response = httpClient.execute(httpGet);
        // 对响应信息进行处理 ...

        // 关闭资源
        response.close();
        httpClient.close();
    }

    /**
     * 发送带参数的 GET 请求
     */
    public static void sendGetHasParam() throws Exception{
        // 创建 httpClient 对象
        CloseableHttpClient httpClient = HttpClients.createDefault();
        // 创建 URIBuilder
        URIBuilder uriBuilder = new URIBuilder("https://www.xxxx.com");
        // 设置参数
        uriBuilder
                .setParameter("param1", "value1")
                .setParameter("param2", "value2");
        // 创建 httpGet 对象,设置 URI
        HttpGet httpGet = new HttpGet(uriBuilder.build());
        CloseableHttpResponse response = httpClient.execute(httpGet);
        // 对响应信息进行处理 ...

        // 关闭资源
        response.close();
        httpClient.close();
    }

POST 请求:

    /**
     * 发送不带参数的 POST 请求
     */
    public static void sendPost() throws Exception{
        // 创建 httpClient 对象
        CloseableHttpClient httpClient = HttpClients.createDefault();
        // 创建 httpPost 对象,设置访问 URL
        HttpPost httpPost = new HttpPost("https://www.xxxx.com");
        CloseableHttpResponse response = httpClient.execute(httpPost);
        // 对响应信息进行处理 ...

        // 关闭资源
        response.close();
        httpClient.close();
    }

    /**
     * 发送带参数的 POST 请求
     */
    public static void sendPostHasParam() throws Exception{
        // 创建 httpClient 对象
        CloseableHttpClient httpClient = HttpClients.createDefault();
        // 创建 httpPost 对象,设置访问 URL
        HttpPost httpPost = new HttpPost("https://www.xxxx.com");
        // 封装表单中的参数
        List<NameValuePair> params = new ArrayList<>();
        params.add(new BasicNameValuePair("param1", "value1"));
        params.add(new BasicNameValuePair("param2", "value2"));
        /*
         * 创建表单的 entity 对象
         *      parameters:表单数据
         *      charset:编码
         */
        UrlEncodedFormEntity entity = new UrlEncodedFormEntity(params, Consts.UTF_8);
        // 设置表单的 entity 对象到 Post 请求中
        httpPost.setEntity(entity);
        CloseableHttpResponse response = httpClient.execute(httpPost);
        // 对响应信息进行处理 ...

        // 关闭资源
        response.close();
        httpClient.close();
    }

连接池:

每次发送请求时都需要创建 HttpClient,会有频繁创建和销毁的问题,对性能会有一定的影响,可以使用连接池来解决这个问题

    /**
     * 连接池
     */
    public static void poolManager() throws Exception {
        // 创建连接池管理器
        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
        // 设置最大连接数
        connectionManager.setMaxTotal(100);
        // 设置每个主机的最大连接数:因为在爬取数据的时候可通会访问多个主机,如果不设置可能会导致连接不均衡
        connectionManager.setDefaultMaxPerRoute(10);
        // 使用连接池管理器发起请求
        doGet(connectionManager);
        doGet(connectionManager);
    }

    /**
     * 通过连接池发送 http 请求
     * @param connectionManager 连接池
     */
    private static void doGet(PoolingHttpClientConnectionManager connectionManager) throws Exception {
        // 从连接池中获取 HttpClient 对象
        CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(connectionManager).build();
        // 创建 httpGet 对象,设置访问 URL
        HttpGet httpGet = new HttpGet("https://www.xxxx.com");
        CloseableHttpResponse response = httpClient.execute(httpGet);
        // 对响应信息进行处理 ...

        // 关闭资源
        response.close();
        // 这里要注意的是 httpClient 不需要再关闭了,因为是连接池管理的
        // httpClient.close();
    }

设置请求信息:

有时候因为网络或者目标服务器的原因,请求需要更长的时间才能完成,或者需要改变 User-Agent 的设置才能正常发起请求时,这个时候就需要自定义设置这些参数

    /**
     * 配置请求信息
     */
    private static void setRequestInfo() throws Exception {
        // 创建 httpClient 对象
        CloseableHttpClient httpClient = HttpClients.createDefault();
        // 创建 httpGet 对象,设置访问 URL
        HttpGet httpGet = new HttpGet("https://www.xxxx.com");
        
        // 配置请求信息
        RequestConfig requestConfig = RequestConfig.custom()
                // 创建连接的最长时间,单位是毫秒
                .setConnectTimeout(1000)
                // 设置获取连接的最长时间,单位是毫秒
                .setConnectionRequestTimeout(500)
                // 设置数据传输的最长时间
                .setSocketTimeout(10 * 1000)
                // 还可以设置其它的设置 ...
                .build();
        
        // 设置请求信息
        httpGet.setConfig(requestConfig);

        CloseableHttpResponse response = httpClient.execute(httpGet);
        // 对响应信息进行处理 ...

        // 关闭资源
        response.close();
        httpClient.close();
    }

虽然说 HttpClient 已经具备了爬数据的功能,但是使用 HttpClient 得到的响应信息比较难解析其中的内容,要对 html 进行进行大量的字符串处理,编写正则表达式去匹配想要获取的信息,所以通常情况下我们并不会使用这种方式进行数据分析。


2. Jsoup

Jsoup 是一款 JavaHTML 解析器,可直接解析某个 URL 地址、HTML 文本内容,它提供了一套非常省力的 API,可通过 DOMCSS 以及类似于 jQuery 的操作方法来取出操作数据。

主要功能如下:

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

引入依赖:

<!-- jsoup -->
<dependency>
    <groupId>org.jsoup</groupId>
    <artifactId>jsoup</artifactId>
    <version>1.15.3</version>
</dependency>

示例:还是以 https://www.rgbku.com/chaxun.html(rgb颜色查询器) 这个网址为例,获取该网址的 title 内容

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;

import java.net.URL;

public class JsoupDemo {
    public static void main(String[] args) throws Exception {
        // 解析 URL
        Document document = Jsoup.parse(new URL("https://www.rgbku.com/chaxun.html"), 1000);
        // 比如我想要获取 html 文件中 <title> 部分的内容
        String title = document
                // 获取所有的 title 标签
                .getElementsByTag("title")
                // 拿到第一个
                .first()
                // 获取标签中的文本内容
                .text();
        // 打印
        System.out.println("title = " + title);
    }
}

日志信息:

在这里插入图片描述

虽然使用 Jsoup 可以替代 HttpClient 直接发起请求解析数据,但是往往不会这样使用,因为实际的开发过程中,需要使用到多线程、连接池、代理等等方式,而 Jsoup 对这些的支持不是很友好,所以一般把 Jsoup 仅仅作为 Html 解析工具使用

(一)加载文档:

    /**
     * 通过 URL 加载文档
     */
    public static void loadingUrl() throws Exception {
        /*
         * 解析 URL:
         *      spec:访问问的 url
         *      timeoutMillis:超时时间
         */
        Document document = Jsoup.parse(new URL("https://www.rgbku.com/chaxun.html"), 1000);
        // 解析 document

    }

    /**
     * 通过字符串加载文档
     */
    public static void loadingString() throws Exception {
        String html = "html-content";
        // 解析字符串
        Document document = Jsoup.parse(html);
        // 解析 document

    }

    /**
     * 通过文件架子啊文档
     */
    public static void loadingFile() throws Exception {
        // html 文件
        File file = new File("D:\\demo.html");
        // 解析字符串
        Document document = Jsoup.parse(file, "utf8");
        // 解析 document

    }

(二)提取数据:

获取元素

  • 方式一:使用 DOM 方法提取文档数据
    • getElementById(String id):根据 id 获取元素
    • getElementsByTag(String tag):根据标签获取元素
    • getElementsByClass(String className):根据 class 获取元素
    • getElementsByAttribute(String key):根据属性获取元素
    • getElementsByAttributeValue(String key, String value):根据属性和属性值获取元素

示例:

    /**
     * 使用DOM方法获取元素
     */
    public static void getElementByDom() throws Exception {
        // 加载 document
        Document document = Jsoup.parse(new URL("https://www.xxxx.com"), 1000);

        // 根据 id 获取元素
        Element idElement = document.getElementById("id");
        // 根据标签获取元素
        Elements tagElements = document.getElementsByTag("tag_name");
        // 根据 class 获取元素
        Elements classElements = document.getElementsByClass("class_name");
        // 根据属性获取元素
        Elements attributeElements = document.getElementsByAttribute("attribute");
        // 通过属性值获取元素
        Elements attributeValueElements = document.getElementsByAttributeValue("attribute", "value");
    }
  • 方式二:使用选择器获取元素
    • select(String cssQuery):通过选择器获取元素

示例:

    /**
     * 使用选择器获取元素
     */
    public static void getElementBySelector() throws Exception {
        // 加载 document
        Document document = Jsoup.parse(new URL("https://www.xxxx.com"), 1000);

        // 通过 id 查找元素
        Element idElement = document.select("#id").first();
        // 通过标签名称查找元素
        Elements tagElements = document.select("tag_name");
        // 通过 class 名称查找元素
        Elements classElements = document.select(".class_name");
        // 通过属性获取元素
        Elements attributeElements = document.select("[attribute]");
        // 通过属性值获取元素
        Elements attributeValueElements = document.select("[attribute=value]");

        /*
         * 选择器可以任意的组合使用
         */

        // tag#id:标签+ID
        Elements tagIdElements = document.select("tag_name#id");
        // tag.class:标签+class
        Elements tagClassElements = document.select("tag_name.class_name");
        // tag[attribute]:标签+属性名
        Elements tagAttributeElements = document.select("tag_name[attribute]");
        // tag[attribute].class:标签+属性名+class
        Elements tagAttributeClassElements = document.select("tag_name[attribute].class_name");
        // ancestor child:查询某个元素下的子元素
        Elements ancestorChildElements = document.select("ancestor child");
        // parent > child:查询直接子元素
        Elements parentChildElements = document.select("parent > child");
        // parent > *:查找所有子元素
        Elements allChildElements = document.select("parent > *");
    }

处理元素数据

  • attr(String key):获取属性
  • attr(String key, String value):设置属性
  • attributes():获取所有属性
  • id():获取 id
  • className():获取类名
  • classNames():获取类名集
  • text():获取文本内容
  • text(String value):设置文本内容
  • html():获取内部 HTML 内容
  • html(String value):设置内部 HTML 内容
  • outerHtml():获取外部 HTML 值
  • data():获取数据内容(例如 script 和 style 标签)
  • tag():获取标签
  • tagName():获取标签名称

三、综合案例

爬虫的工作流程通常包括以下几个步骤:

  1. 确定起始点:需要指定一个或多个起始 URL 作为抓取的入口点。

  2. 下载网页:使用 HTTPHTTPS 协议向服务器发送请求,下载网页的 HTML 源代码。

  3. 解析网页:解析 HTML 源代码,提取出所需的信息。

  4. 处理数据:对提取的数据进行处理和清洗,以便后续分析和存储。

  5. 跟踪链接:从当前网页中提取所有链接,并将它们添加到待抓取的 URL 队列中,以便进一步遍历。

  6. 控制抓取速度:为了避免给服务器带来过大的负载,爬虫通常会设置抓取速度限制,包括请求间隔时间和并发请求数量。

  7. 存储数据:将提取的数据保存到数据库、文件或其他存储介质中,以便后续使用和分析。

以下案例我会将抓取到的数据存放在 excel 文件中,会使用到 EasyExcel,对于 EasyExcel 的使用可参考博客: Java-easyExcel入门教程

1. 案例一

将 RBG 颜色查询器页面中的数据抓取出来之后保存到 excel 表格中

在这里插入图片描述

分析:

在这里插入图片描述

从该网址的 HTML 源码分析可得,RGB 的数据来源于 <table> 表格中,<tbody> 定义了表格主题,用于存放数据,每个单元格的数据存放在 <td> 标签中,所以只要拿到 中的文本数据,再剔除掉表头相关的数据即可。

代码实现:

RgbEntity.java

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.*;
import com.alibaba.excel.enums.poi.BorderStyleEnum;
import com.alibaba.excel.enums.poi.FillPatternTypeEnum;
import com.alibaba.excel.enums.poi.HorizontalAlignmentEnum;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * RGB 实体类
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
// 头背景设置
@HeadStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, horizontalAlignment = HorizontalAlignmentEnum.CENTER, borderLeft = BorderStyleEnum.THIN, borderTop = BorderStyleEnum.THIN, borderRight = BorderStyleEnum.THIN, borderBottom = BorderStyleEnum.THIN)
//标题高度
@HeadRowHeight(40)
//内容高度
@ContentRowHeight(30)
//内容居中,左、上、右、下的边框显示
@ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, borderLeft = BorderStyleEnum.THIN, borderTop = BorderStyleEnum.THIN, borderRight = BorderStyleEnum.THIN, borderBottom = BorderStyleEnum.THIN)
public class RgbEntity {

    @ApiModelProperty(value = "英文代码")
    @ExcelProperty("英文代码")
    @ColumnWidth(15)
    private String engName;

    @ApiModelProperty(value = "中文名")
    @ExcelProperty("中文名")
    @ColumnWidth(15)
    private String zhName;

    @ApiModelProperty(value = "十六进制")
    @ExcelProperty("十六进制")
    @ColumnWidth(15)
    private String code;

    @ApiModelProperty(value = "RGB颜色值")
    @ExcelProperty("RGB颜色值")
    @ColumnWidth(15)
    private String value;
}

ReptileDemo.class

import com.alibaba.excel.EasyExcel;
import com.mike.server.system.domain.excel.RgbEntity;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

public class ReptileDemo {
    public static void main(String[] args) throws Exception {
        demo01();
    }

    /**
     * 案例一:将 RBG 颜色查询器页面中的数据抓取出来之后保存到 excel 表格中
     */
    public static void demo01() throws Exception {
        // 获取 document 文档
        Document document = Jsoup.parse(new URL("https://www.rgbku.com/chaxun.html"), 1000);
        // 通过 id = color 获取 tbody 元素
        Element tbodyElement = document.getElementById("color");
        // 获取 tbody 下所有的 <tr> 标签
        assert tbodyElement != null;
        Elements childrenElements = tbodyElement.children();
        // 创建 RgbEntity 集合存放数据
        List<RgbEntity> list = new ArrayList<>();
        // 遍历
        for (Element childrenElement : childrenElements) {
            String tagName = childrenElement.tagName();
            String align = childrenElement.attr("align");
            // 筛选出每一行的数据,剔除表头
            if ("tr".equals(tagName) && !"center".equals(align)) {
                // 获取每个单元格的数据
                Elements tdElements = childrenElement.children();
                /*
                 * 根据 html 源码分析可得:
                 *      (1)每个 <tr> 标签下都有 5 个 <td> 标签
                 *      (2)这五个 <td> 标签中的内容分别对应:颜色 英文代码 中文名 十六进制 RGB颜色值
                 */
                String engName = tdElements.get(1).text();
                String zhName = tdElements.get(2).text();
                String code = tdElements.get(3).text();
                String value = tdElements.get(4).text();
				// 添加到集合中
                list.add(RgbEntity.builder()
                        .engName(engName)
                        .zhName(zhName)
                        .code(code)
                        .value(value)
                        .build());
            }
        }

        // 写入到 excel 中
        File file = new File("D:\\rgb.xlsx");
        OutputStream os = new FileOutputStream(file);
        EasyExcel.write(os, RgbEntity.class)
                .sheet("Sheet1").doWrite(list);
    }
}

运行代码生成 excel 文件:

在这里插入图片描述


2. 案例二

通过食品营养成分查询平台获取所有食品营养成分数据,并持久化到 excel 文件中

在这里插入图片描述

在这里插入图片描述

这个案例的代码就不太方便展示了,我就简单的说下实现的逻辑:

在这里插入图片描述

通过分析 HTML 源码可知,从大类(一级分类)列表页面中可以获取到大类的名称和图片以及进入到类别(二级分类)列表页的 URL,在类别(二级分类)的页面中又可以获取到类别的名称和进入到食品列表页的 URL,在食品列表页中可以获取到食品的名称和进入到食品成分页面的 URL,在食品成分页中再拿到所有的成分数据。

实体类设计:

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class FoodInfo {

    /**
     * 大类集
     */
    List<Category> categoryList;

    /**
     * 大类(一级分类)
     */
    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public static class Category {
        // 大类名称
        private String name;
        // 大类图片
        private String imageUrl;
        // 类别集
        private List<Type> typeList;

        /**
         * 类别(二级分类)
         */
        @Data
        @Builder
        @NoArgsConstructor
        @AllArgsConstructor
        public static class Type {
            // 类别名称
            private String name;
            // 食物集
            private List<Food> foodList;

            /**
             * 食物
             */
            @Data
            @Builder
            @NoArgsConstructor
            @AllArgsConstructor
            public static class Food {
                // 食物名称
                private String name;
                // 组成成分集
                private List<Component> componentList;

                /**
                 * 组成成分
                 */
                @Data
                @Builder
                @NoArgsConstructor
                @AllArgsConstructor
                public static class Component {
                    // 营养素类型
                    private String nutrientType;
                    // 项目
                    private String itemName;
                    // 含量
                    private String value;
                    // 同类排名
                    private String sort;
                    // 同类均值
                    private String avgValue;
                }
            }
        }
    }
}

这里要注意的首先是 URL 的拼接,因为链接是相对路径的形式,其次是要创建连接池避免资源的浪费,设置访问间隔的时间别把对方服务器搞崩溃。


四、总结

在实际应用中,爬虫可能需要处理一些挑战和限制,如动态网页、反爬虫机制、登录和验证码等。为了应对这些问题,爬虫可能需要使用代理、用户代理伪装、验证码识别等技术。

值得注意的是,尽管爬虫可以自动化地抓取网页,但在使用爬虫时,需要遵守法律法规和网站的使用规则,避免侵犯他人的权益或引起不良后果。


参考文献:

爬虫协议:https://www.dotcpp.com/course/317

网络爬虫的法律规制:http://www.cac.gov.cn/2019-06/16/c_1124630015.htm?from=singlemessage

Java 爬虫之 JSoup 使用教程:https://my.oschina.net/suveng/blog/4796066

JSoup教程:https://www.yiibai.com/jsoup

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/286625.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MATLAB习题操作实战

2.1创建一个有7个元素的一维数组&#xff0c;并做如下处理:直接寻访一维数组的第6个元素;寻访一维数组的第1、3、5个元素;寻访一维数组中第4个至最后1个元素;寻访一维数组中大于70的元素。 % 创建一维数组 array [50, 60, 70, 80, 90, 100, 110];% 直接寻访一维数组的第6个元…

【自动驾驶中的SLAM技术】第2讲:基础数学知识回顾

第二讲&#xff1a;基础数学回顾 文章目录 第二讲&#xff1a;基础数学回顾1 几何学1.1 坐标系1.2 坐标变换① 空间向量② 基变换③ 坐标变换④ 总结 1.3 四元数与旋转向量 2 运动学2.1 李群视角2.2 四元数视角2.3 四元数的李代数与旋转向量间的转换2.4 SO(3)t 上的运动学2.5 线…

六、HTML 段落

HTML 可以将文档分割为若干段落。 一、HTML 段落 段落是通过 <p> 标签定义的。 <p>这是一个段落 </p> <p>这是另一个段落</p> 注意&#xff1a;浏览器会自动地在段落的前后添加空行。&#xff08;</p> 是块级元素&#xff09; 二、不…

算法巡练day03Leetcode203移除链表元素707设计链表206反转链表

今日学习的文章视频链接 https://www.bilibili.com/video/BV1nB4y1i7eL/?vd_source8272bd48fee17396a4a1746c256ab0ae https://programmercarl.com/0707.%E8%AE%BE%E8%AE%A1%E9%93%BE%E8%A1%A8.html#%E7%AE%97%E6%B3%95%E5%85%AC%E5%BC%80%E8%AF%BE 链表理论基础 见我的博…

Linux 命令echo

命令作用 输出一行字符串在shell中&#xff0c;可以打印变量的值输出结果写入到文件在显示器上显示一段文字&#xff0c;起到提示的作用 语法 echo [选项] [字符串] 参数 字符含义-n不自动换行-e解释转义字符-E不解释转义字符 如果-e有效&#xff0c;则识别以下序列&…

2024,这将是量子计算的真正挑战

2023年&#xff0c;一项项量子计算纪录被打破。 谷歌量子AI团队证明了将多个量子比特分组合成为一个逻辑量子比特的纠错方法可以提供更低的容错率。以往的纠错研究随着比特数的增加&#xff0c;错误率会提高&#xff0c;都是“越纠越错”&#xff0c;而这次谷歌首次实现了“越纠…

K8S本地开发环境-minikube安装部署及实践

引言 在上一篇介绍了k8s的入门和实战&#xff0c;本章就来介绍一下在windows环境如何使用minikube搭建K8s集群&#xff0c;好了废话不多说&#xff0c;下面就和我一起了解Minikube吧。 什么是Minikube&#xff1f; Minikube 是一种轻量级的 Kubernetes 实现&#xff0c;可在本…

1688商品详情API:实现商品详情自动化的关键步骤

一、准备工作 在使用1688商品详情API之前&#xff0c;我们需要进行一些准备工作。 注册与登录&#xff1a;首先&#xff0c;你需要在1688的开放平台上注册一个账号并创建一个应用。这样你就可以获得一个API密钥&#xff0c;这是调用API的凭证。阅读API文档&#xff1a;详细阅…

web开发-springboot-web

报错&#xff1a; Failure to find org.springframework.boot:spring-boot-starter-parent:pom:3.2.1.RELEASE in https://maven.aliyun.com/nexus/content/groups/public was cached in the local repository, resolution will not be reattempted until the update interval …

定制自己的GPTs,及使用ChatGPT/GPT4写程序的注意事项

如何能高效地处理文本、文献查阅、PPT编辑、编程、绘图和论文写作已经成为您成功的关键。而 ChatGPT&#xff0c;作为一种强大的自然语言处理模型&#xff0c;具备显著优势&#xff0c;能够帮助您在各个领域取得突破。 ChatGPT 在论文写作与编程方面也具备强大的能力。无论是进…

安卓在SOA中的运用

安卓在运用SOA研发的过程中&#xff0c;会针对实际情况对研发的架构和流程进行优化&#xff0c;通过优化过的架构和实施方案&#xff0c;不仅可以大大提升了整车开发的效率和灵活行以及功能落地的稳定性&#xff0c;同时也增加了系统的向上兼容性。 目前基于车载SOA系统的研发…

记一个CSS样式实现思路(鼠标聚焦时完整内容,失焦时只显示部分)

效果图 默认状态 鼠标悬浮时 缓慢展示完整内容 实现方法 方法一 使用max-width,当鼠标悬浮时&#xff0c;设置max-width为一个足够大的数值 <div class"flex justify-center align-center mt-20px"><div v-for"(item, key) in ssoList" :key&q…

HTML----JavaScript操作对象BOM对象

文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 本章要求 了解BOM模型掌握BOM模型实际应用 一.BOM模型概述 BOM&#xff08;浏览器对象模型&#xff09;是JavaScript中的一个重要概念&#xff0c;它提供了一组用于控制浏览器窗口和页面内容的…

基于 ESP32-C3 开启 Flash 加密和安全启动并进行 OTA 测试

软件&#xff1a; esp-idf v5.1.2 硬件&#xff1a; ESP32-C3 board 1. 首先&#xff0c;准备一个明文固件 hello-world.bin 基于 esp-idf-v5.1.2\examples\get-started\hello_world 例程&#xff0c;使用如下指令&#xff0c;直接编译&#xff0c;获取明文固件 hello-worl…

大数据时代快速获取数据方法,爬虫技术理论剖析与实战演练

一、教程描述 人工智能和机器学习&#xff0c;都离不开数据&#xff0c;若是没有数据&#xff0c;再好的算法&#xff0c;再好的模型&#xff0c;都没有用武之地。数据不仅是指现成的数据库&#xff0c;更加是指每天增加的海量互联网数据。本套教程将通过多个实战项目&#xf…

2024上海国际智慧城市,物联网,大数据博览会(上海智博会)

随着科技的飞速发展&#xff0c;智慧城市、物联网与大数据已经成为当今社会发展的重要驱动力。作为国内最具影响力的科技展会之一&#xff0c;2024上海国际智慧城市,物联网,大数据博览会&#xff08;简称:世亚智博会&#xff09;汇聚了全球顶尖的智慧城市、物联网与大数据技术&…

JMeter之测试WebService接口

JMeter之测试WebService接口 1 背景2 目的3 介绍4 具体操作4.1 soapUI调用4.2 JMeter工具调用4.3 操作步骤流程4.3 重点 1 背景 WebService应用的范围是非常广&#xff0c;任何需要跨平台、跨系统进行数据交换和功能调用的场景都可以用此来实现&#xff0c;在实际的工作中也常常…

k8s-yaml格式

三种常见的项目发布方式&#xff1a; 蓝绿发布&#xff1a; 金丝雀发布&#xff08;灰度发布&#xff09;&#xff1a; 滚动发布&#xff1a; 应用程序升级&#xff0c;面临的最大的问题&#xff0c;就是新旧业务的更换&#xff0c;立项--定稿--需求发布--开发--测试--发布&…

有效边表填充算法

有效边表填充算法 如何填充示例三角形 按照扫描线从上往下的顺序&#xff0c;依次处理和多边形相交的扫描线&#xff0c;对于当前处理的扫描线找到和它相交的所有边的交点&#xff0c;按照交点横坐标从小到大的顺序&#xff0c;两个两个配对&#xff0c;配对之后填充每对交点之…

踩了Vue2运行机制的坑-响应式原理

最近遇到一个很奇怪的bug&#xff1a; 前置&#xff1a;后端接口返回的数据是这样的&#xff1a; ①首先在store中取出后端返回的数据Ares.data&#xff0c;在这里打印输出是正常的 ②然后在vue页面上再取出A.data也就是res.data.data&#xff0c;以及其它几个字段即res.data.X…