Java-网络爬虫(一)

news2024/11/14 16:20:09

文章目录

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


前言

下篇:Java-网络爬虫(二)

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

一、网络爬虫

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 的拼接,因为链接是相对路径的形式,其次是要创建连接池避免资源的浪费,设置访问间隔的时间别把对方服务器搞崩溃。


四、总结

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

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

下篇:Java-网络爬虫(二)


参考文献:

爬虫协议: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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2024年第01周农产品价格报告

一、摘要 农产品价格监测主要涉及对畜禽类产品、水产品、蔬菜类产品、水果类产品的价格&#xff0c;以周为单位&#xff0c;进行变化情况的数据监测。其中&#xff0c;蔬菜类产品共18种&#xff0c;分别为大白菜、西红柿、黄瓜、青椒、芹菜、土豆、白萝卜、茄子、豆角、胡萝卜…

和可被K整除的子数组(Java详解)

目录 一、题目描述 二、题解 思路分析 具体实现 完整代码 一、题目描述 给定一个整数数组 nums 和一个整数 k &#xff0c;返回其中元素之和可被 k 整除的&#xff08;连续、非空&#xff09; 子数组 的数目。 子数组 是数组的 连续 部分。 示例&#xff1a; 输入&#…

MYSQL学习之buffer pool的理论学习

MYSQL学习之buffer pool的理论学习 by 小乌龟 文章目录 MYSQL学习之buffer pool的理论学习前言一、buffer pool是什么&#xff1f;二、buffer pool 的内存结构三、buffer pool 的初始化和配置初始化配置 四、buffer pool 空间管理LRU淘汰法冷热数据分离的LRU算法1.引入库2.读入…

【大数据进阶第三阶段之Datax学习笔记】阿里云开源离线同步工具Datax概述

【大数据进阶第三阶段之Datax学习笔记】阿里云开源离线同步工具Datax概述 【大数据进阶第三阶段之Datax学习笔记】阿里云开源离线同步工具Datax快速入门 【大数据进阶第三阶段之Datax学习笔记】阿里云开源离线同步工具Datax类图 【大数据进阶第三阶段之Datax学习笔记】使用…

RT-Thread 内核基础(一)

内核基础 包括&#xff1a;内核简介、系统的启动流程及内核配置的部分内容&#xff0c;为后面的章节奠定基础。 RT-Thread 内核介绍 内核是一个操作系统的核心&#xff0c;是操作系统最基础也最重要的部分。它负责管理系统的线程、线程间通信、系统时钟、中断及内存等。 下…

使用tailscale访问对端局域网上的其他设备

当tailscale客户端应用程序直接安装在组织中的每个客户端、服务器和虚拟机上时&#xff0c;Tailscale 效果最佳。这样&#xff0c;流量就会被端到端加密&#xff0c;并且无需配置即可在物理位置之间移动机器。 但是&#xff0c;在某些情况下&#xff0c;你不能或不想在每台设备…

AI人工智能学习路线图

学习人工智能 AI 的路线通常包括以下几个步骤&#xff1a;了解人工智能的基本概念和历史&#xff0c;包括机器学习、神经网络、深度学习等技术。学习数学基础知识&#xff0c;包括线性代数、微积分、概率论和统计学等。学习编程基础知识&#xff0c;包括 Python、C 等编程语言。…

[嵌入式AI从0开始到入土]10_yolov5在昇腾上应用

[嵌入式AI从0开始到入土]嵌入式AI系列教程 注&#xff1a;等我摸完鱼再把链接补上 可以关注我的B站号工具人呵呵的个人空间&#xff0c;后期会考虑出视频教程&#xff0c;务必催更&#xff0c;以防我变身鸽王。 第一章 昇腾Altas 200 DK上手 第二章 下载昇腾案例并运行 第三章…

源码编译部署篇(二)源码编译milvus成功后如何启动standalone并调试成功!

Milvus启动和调试 0 前言1 Milvus启动【问题描述】出现Aborted问题【问题分析】【解决方法】安装Pulsar服务执行单机启动命令解决监听端口号 2 Milvus调试编写launch.json验证单例调试成功 3 遇到的问题汇总问题1问题2:Permission denied 0 前言 由于Milvus官方文档只提及如何…

VX小程序Burp抓包

方法有很多&#xff0c;工具也各有差异&#xff0c;主要是学代理流量的思路 Burp流量代理工具小程序 一、Burp证书导入 1、开启代理 开启浏览器的代理&#xff0c;火狐推荐FoxyProxy&#xff0c;Google推荐SwitchyOmega&#xff0c;设置代理为127.0.0.1:8080。 2、下载证书…

[C#]Onnxruntime部署Chinese CLIP实现以文搜图以文找图功能

【官方框架地址】 https://github.com/OFA-Sys/Chinese-CLIP 【算法介绍】 在当今的大数据时代&#xff0c;文本信息处理已经成为了计算机科学领域的核心议题之一。为了高效地处理海量的文本数据&#xff0c;自然语言处理&#xff08;NLP&#xff09;技术应运而生。而在诸多N…

性能分析与调优: Linux 使用ELRepo升级CentOS内核

目录 一、实验 1.环境 2.agent 服务器使用ELRepo升级CentOS内核 二、问题 1. RHEL-7, SL-7 或者 CentOS-7系统如何安装ELRepo 2.RHEL-8或者RHEL-9系统如何安装ELRepo 一、实验 1.环境 &#xff08;1&#xff09;主机 表1-1 主机 主机架构组件IP备注prometheus 监测 系…

超维空间M1无人机使用说明书——21、基于opencv的人脸识别

引言&#xff1a;M1型号无人机不仅提供了yolo进行物体识别&#xff0c;也增加了基于opencv的人脸识别功能包&#xff0c;仅需要启动摄像头和识别节点即可 链接: 源码链接 一、一键启动摄像头和人脸识别节点 roslaunch robot_bringup bringup_face_detect.launch无报错&#…

Maven在java中的实现(对java的项目进行打包)

前言: 在前面的文章中我们了解了Maven的作用,并在自己的电脑上安装配置好了Maven,也成功的在IDEA中添加了Maven,但是具体的实现还是有一些些小问题,那么接下来,我将带着大家对Java项目进行一次打包,系统的完成一次,并在途中解决一下会出现的问题. 我以图片中选中的这个包为例,…

在docker上运行LCM

目录 1.加载镜像并进入容器 2.安装依赖 3.在docker外部git-clone lcm 4.将get-clone的lcm复制到容器中 5.编译库 6.将可执行文件复制到容器中 7.进入可执行文件 8.编译可执行文件 9.再开一个终端运行程序 10.将以上容器打成镜像并导出 1.加载镜像并进入容器 sudo do…

【无标题】山姆奥特曼喊话AI创业者

这里写自定山姆奥特曼充满激情地向创业者们发出呼吁&#xff0c;他表示AI是一个可以媲美互联网早期机遇的巨大机会。与此相关的人士认为&#xff0c;现在是互联网和移动互联网创业者们行动起来的时候了&#xff01;他们应该全面拥抱大模型的应用层创业。第一波红利期在6-8个月内…

STM32F103使用硬件SPI+缓冲区的模式刷新硬件显示模块

上次讲到使用全屏做一个缓冲区&#xff0c;因为cpuRAM空间不足&#xff0c;无法实现&#xff0c;想办法使用一行作为一个缓冲区的模式&#xff1a; 老的全屏刷新程序&#xff1a; void LCD_Fill(uint16_t xsta,uint16_t ysta,uint16_t xend,uint16_t yend,uint16_t color) { …

3.2 MAPPING THREADS TO MULTIDIMENSIONAL DATA

1D、2D或3D线程组织的选择通常基于数据的性质。图片是2D像素阵列。使用由2D块组成的2D网格通常可以方便地处理图片中的像素。图3.2显示了处理7662图片P的这种安排&#xff08;水平或x方向为76像素&#xff0c;垂直或y方向为62像素&#xff09;。假设我们决定使用16 x 16块&…

桌面图标变成白色文件?学会这4个方法,轻松解决!

“不知道为什么&#xff0c;我有些文件夹直接保存在电脑桌面了&#xff0c;但是今天查看的时候却发现它们变成了白色的文件。有什么方法可以解决这个问题吗&#xff1f;” 在使用电脑时&#xff0c;可能由于各种原因&#xff0c;会出现桌面图标变成白色文件的情况。这不仅会让用…

听GPT 讲Rust源代码--compiler(36)

File: rust/compiler/rustc_middle/src/mir/graphviz.rs 在Rust源代码中&#xff0c;rust/compiler/rustc_middle/src/mir/graphviz.rs文件的作用是生成MIR&#xff08;Mid-level Intermediate Representation&#xff09;的图形可视化表示。MIR是Rust编译器中间表示的一种形式…