Java爬虫爬取图片壁纸

Java爬虫

在这里插入图片描述

sougou图片为例:https://pic.sogou.com/

JDK17、SpringBoot3.2.X、hutool5.8.24实现Java爬虫,爬取页面图片

项目介绍

开发工具:IDEA2023.2.5

JDK:Java17

SpringBoot:3.2.x

通过 SpringBoot 快速构建开发环境,通过 Jsoup 实现对网页的解析,并获取想要的资源数据

使用 hutool 工具,将所需要的字符串转成 JSON 对象,并从 JSON 对象中获取对应的数据资源

爬取网页图片资源,并将爬取到的资源下载到本地文件夹,对于大量的资源使用多线程下载

核心Jar包

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

项目依赖

pom.xml

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.1</version>
        <relativePath/>
    </parent>

    <groupId>cn.molu</groupId>
    <artifactId>jsoup</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>jsoup</name>
    <description>
        java网络爬虫;更多技术分享地址请关注:https://blog.csdn.net/qq_51076413
    </description>

    <properties>
        <java.version>17</java.version>
        <java.source.version>17</java.source.version>
        <java.target.version>17</java.target.version>
        <java.compiler.version>17</java.compiler.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>
    <dependencies>
        <!--  hutool工具包  -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.24</version>
        </dependency>
        <!--  jsoup解析包,核心  -->
        <dependency>
            <groupId>org.jsoup</groupId>
            <artifactId>jsoup</artifactId>
            <version>1.15.3</version>
        </dependency>
        <!--  web依赖  -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--  热加载,修改代码后不需要手动重启(如果配置了JRebel可以不使用该插件)  -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <!--  lombok简化实体类  -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>·
    </build>
</project>

爬取思路

一、解析网页:获取资源网页,从网页中解析想要的资源数据;

  • 查看网页资源的位置,分析资源渲染到页面的规律;
  • 根据分析到的规律,统一提取资源数据,得到列表;
  • 对列表数据进行进一步解析,最终拿到想要的资源;
  • 对资源数据进行处理,保存到数据库或是下载资源;

二、调用接口:直接获取想要的图片资源;

  • 需要分析资源网站,获取资源数据加载的时机和请求接口;
  • 分析接口的请求参数,入参是否有加密,是否有请求限制;
  • 测试接口,拿到结果,查看结果是否是自己所需要的资源;
  • 处理资源数据,保存到数据库或者将资源文件下载到本地;

三、缓存资源:从页面JS中直接获取资源

  • 页面加载后,我们下滑或者分页时,没有请求日志;
  • 页面没有重新加载,但是数据在不断更新和加载中;
  • 当超过一定数量时,会有一次的请求日志显示出来;
  • 最后分析得到,数据是缓存到页面中的JS变量中了;

一、解析网页

1.1、过程与思路

分析资源:

  • 打开资源网站,分析和定位资源所在位置,并分析资源渲染到页面的规律;
  • 如图,定位到资源位置,并发现了资源渲染的规律,使用document.querySelectorAll('xxx')可以得到页面中所有的资源
    网站地址:https://pic.sogou.com/pic/searchList.jsp?from=homeHotSearch&rcer=&spver=0&keyword=美女壁纸#top=1395.199951171875&more=false

定位资源所在位置
在这里插入图片描述

使用css选择器获取资源:

  • 根据分析到的规律,统一提取资源数据,得到列表;
  • document.querySelectorAll('div#result div.similar div.similar-item a.single-box') 得到所有的资源列表

在这里插入图片描述

获取资源链接和标题:

  • 对列表数据进行进一步解析,最终拿到想要的资源;
  • document.querySelector('xxx')提取资源标题和资源链接

在这里插入图片描述

示例代码如下:

let divElement = document.querySelectorAll('div#result div.similar div.similar-item a.single-box'), result = [];
for (let tag of divElement ) {
    let title = (tag.querySelector('h4.info-title').innerText||'').replace(/\s*/g,''); // 拿到标题,去除所有空格和特殊字符
    let url = tag.querySelector("div.img-height img").currentSrc||''; // 拿到资源链接
    result.push({title,url}); // 收集资源数据
}
console.log(result); // 打印资源列表

处理资源数据:

  • 对资源数据进行处理,保存到数据库或是下载资源;

1.2、Java代码实现

按照以上思路,我们使用Java代码来实现以上操作;

页面地址:https://pic.sogou.com/pic/searchList.jsp?from=homeHotSearch&rcer=&spver=0&keyword=美女壁纸#top=1395.199951171875&more=false

public static void main(String[] args) {
    // 获取网页资源
    Document document = Jsoup.parse(HttpUtil.get("https://pic.sogou.com/pic/searchList.jsp?from=homeHotSearch&rcer=&spver=0&keyword=美女壁纸#top=1395.199951171875&more=false", 15000));
    // 只获取<body>标签中的元素
    Element body = document.body();
    // 使用css选择器提取所有资源,得到列表数据
    Elements aElements = Optional.of(body.select("div#result"))
        .orElseGet(Elements::new).select("div.similar div.similar-item a.single-box");
    // 进一步解析,获取资源链接和标题,将资源收集成集合中
    Set<JSONObject> result = aElements.stream().map(tag -> {
        JSONObject resource = new JSONObject();
        String title = tag.select("div.img-info h4.info-title").text();
        String src = tag.select("div.img-height img").attr("src");
        return resource.set("title", title).set("src", src);
    }).filter(ObjUtil::isNotEmpty).collect(Collectors.toSet());
    // 打印数据集
    System.out.println(result);
    System.out.println("共获取到:" + result.size() + "条数据");
}

1.3、爬取结果展示

[{"title":"每日更新 颜值美女壁纸,男人看了都会 珍藏 颜值","src":"https://i04piccdn.sogoucdn.com/485013ec7340e4f6"}, 
{"title":"美女 手机壁纸","src":"https://i02piccdn.sogoucdn.com/25a5aad0f9df1e0f"}, 
{"title":"美女 手机壁纸","src":"https://i04piccdn.sogoucdn.com/d4fe1d8d67d92d03"}, 
{"title":"美女 手机壁纸","src":"https://i01piccdn.sogoucdn.com/c9363d03d43130cd"}, 
{"title":"亭亭玉立,美若天仙","src":"https://i04piccdn.sogoucdn.com/51ea6e33666f6e57"}]

共获取到:5条数据

二、调用接口

2.1、过程与思路

分析网站资源得到资源接口:

  • 需要分析资源网站,获取资源数据加载的时机和请求接口;
  • 分析接口的请求参数,入参是否有加密,是否有请求限制;
  • 测试接口,拿到结果,查看结果是否是自己所需要的资源;
  • 处理资源数据,保存到数据库或者将资源文件下载到本地;

在这里插入图片描述

2.2、Java代码实现

按照上述,找到接口https://pic.sogou.com/napi/wap/searchlist,拿到参数,直接发起POST请求,得到资源;

请求参数

{
    "initQuery": "美女手机壁纸", 
    "queryFrom": "wap",
    "from": "homeHotSearch",
    "rcer": "",
    "spver": "0",
    "keyword": "美女壁纸",
    "start": 48,
    "reqType": "client",
    "reqFrom": "wap_result",
    "prevIsRedis": "n",
    "qua": "",
    "headers": {},
    "pagetype": 0,
    "amsParams": []
}

Java代码

接口地址:https://pic.sogou.com/napi/wap/searchlist

请求方式:POST

public static void main(String[] args) {
    // 查询接口
    String postUrl = "https://pic.sogou.com/napi/wap/searchlist";
    // 查询参数
    String params = """
                {
                    "initQuery": "美女手机壁纸",
                    "queryFrom": "wap",
                    "from": "homeHotSearch",
                    "rcer": "",
                    "spver": "0",
                    "keyword": "美女壁纸",
                    "start": 48,
                    "reqType": "client",
                    "reqFrom": "wap_result",
                    "prevIsRedis": "n",
                    "qua": "",
                    "headers": {},
                    "pagetype": 0,
                    "amsParams": []
                }
                """;
    // 发起post请求,得到上图所述的结果数据
    String json = HttpUtil.post(postUrl, params, 15000);
	// 解析结果数据,拿到最终的资源节点
    JSONArray jsonArray = JSONUtil.parseObj(json).getJSONObject("data").getJSONObject("picResult").getJSONArray("items");
	// 进一步解析提取所需要的资源数据,排除不需要的数据,将资源收集成集合中
    Set<JSONObject> result = jsonArray.stream().map(item -> {
        JSONObject resource = new JSONObject();
        if (item instanceof JSONObject jsonObject) {
            String title = jsonObject.getStr("title"); // 资源标题
            String picUrl = jsonObject.getStr("picUrl"); // 图片链接
            String thumbUrl = jsonObject.getStr("thumbUrl"); // 缩略图链接
            String oriPicUrl = jsonObject.getStr("oriPicUrl"); // 原图链接
            String locImageLink = jsonObject.getStr("locImageLink"); // 原图链接
            return resource.set("title", title).set("picUrl", picUrl)
                .set("thumbUrl", thumbUrl).set("oriPicUrl", oriPicUrl)
                .set("locImageLink", locImageLink);
        }
        return null;
    }).filter(ObjUtil::isNotEmpty).collect(Collectors.toSet());
    // 打印数据集
    System.out.println(result);
    System.out.println("共获取到:" + result.size() + "条数据");
}

2.3、爬取结果展示

三、缓存资源

3.1、过程与思路

  • 页面加载后,我们下滑或者分页时,没有请求日志;
  • 页面没有重新加载,但是数据在不断更新和加载中;
  • 当超过一定数量时,会有一次的请求日志显示出来;
  • 最后分析得到,数据是缓存到页面中的JS变量中了;

在这里插入图片描述

3.2、Java代码实现

变量名:window.__INITIAL_STATE__

缓存中有很多数据,我们以searchlist -> groupFixedResult -> groupPic中的数据为例,获取并解析里面的资源

资源地址:https://pic.sogou.com/pic/searchList.jsp?from=homeHotSearch&rcer=&spver=0&tagQSign=%E9%AB%98%E6%B8%85,3636d5ad&showMode=0&routeName=searchlist&keyword=美女

public static void main(String[] args) {
    // 请求资源地址得到页面数据
    Document document = Jsoup.parse(HttpUtil.get("https://pic.sogou.com/pic/searchList.jsp?from=homeHotSearch&rcer=&spver=0&tagQSign=%E9%AB%98%E6%B8%85,3636d5ad&showMode=0&routeName=searchlist&keyword=美女", 15000));
    String variableName = "window.__INITIAL_STATE__";
    // 解析得到所有的<script>
    Elements scriptTags = document.getElementsByTag("script");
    // 过滤得到包含'__INITIAL_STATE__'的<script>
    List<String> collects = scriptTags.stream().map(Element::data)
        .filter(data -> data.contains(variableName))
        .filter(StrUtil::isNotEmpty)
        .toList();
    String scriptData = CollectionUtil.isEmpty(collects) ? "" : collects.get(0);
    // 截取js中'__INITIAL_STATE__'变量内容
    int startIndex = scriptData.indexOf("{");
    int endIndex = scriptData.lastIndexOf("}");
    // 得到js中'__INITIAL_STATE__'的json数据
    String jsonData = scriptData.substring(startIndex, endIndex + 1);
    // 将JS数据转为Java的JSON对象
    JSONObject jsonObject = JSONUtil.parseObj(jsonData);
    jsonObject = jsonObject.getJSONObject("searchlist");
    jsonObject = jsonObject.getJSONObject("groupFixedResult");
    JSONArray groupPicArray = jsonObject.getJSONArray("groupPic");
    Set<Map<String, String>> list = new HashSet<>();
    // 从JSON对象中提取资源数据
    getSource(groupPicArray, list);
	// 打印数据集
    System.out.println(list);
    System.out.println("共获取到:" + list.size() + "条数据");
}

/**
 * 解析资源,获取资源数据
 *
 * @param jsonArray 资源数据集
 * @param list      结果数据集
 */
private static void getSource(JSONArray jsonArray, Set<Map<String, String>> list) {
    for (Object object : jsonArray) {
        if (object instanceof JSONArray item) {
            getSource(item, list);
        }
        if (object instanceof JSONObject item) {
            var map = new HashMap<String, String>();
            map.put("thumbUrl", item.getStr("thumbUrl"));
            map.put("title", item.getStr("title"));
            map.put("pageUrl", item.getStr("pageUrl"));
            map.put("picUrl", item.getStr("picUrl"));
            list.add(map);
        }
    }
}

3.3、爬取结果展示

四、完整测试代码

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author 陌路
 * @apiNote 解析网页资源
 * @date 2024/1/15 19:55
 * @tool Created by IntelliJ IDEA
 */
public class GetWebResourceTest {

    /**
     * 通过解析网页获取资源
     */
    public static void main(String[] args) {
        // 获取网页资源
        Document document = Jsoup.parse(HttpUtil.get("https://pic.sogou.com/pic/searchList.jsp?from=homeHotSearch&rcer=&spver=0&keyword=%E7%BE%8E%E5%A5%B3%E5%A3%81%E7%BA%B8#top=1395.199951171875&more=true", 15000));
        // 只获取<body>标签中的元素
        Element body = document.body();
        // 使用css选择器提取所有资源,得到列表数据
        Elements aElements = Optional.of(body.select("div#result"))
                .orElseGet(Elements::new)
            .select("div.similar div.similar-item a.single-box");
        // 进一步解析,获取资源链接和标题,将资源收集成集合中
        Set<JSONObject> result = aElements.stream().map(tag -> {
            JSONObject resource = new JSONObject();
            String title = tag.select("div.img-info h4.info-title").text();
            String src = tag.select("div.img-height img").attr("src");
            return resource.set("title", title).set("src", src);
        }).filter(ObjUtil::isNotEmpty).collect(Collectors.toSet());
        // 打印数据集
        System.out.println(result);
        System.out.println("共获取到:" + result.size() + "条数据");
    }

    /**
     * 通过API接口获取资源
     */
    public static void main1(String[] args) {
        // 查询接口
        String postUrl = "https://pic.sogou.com/napi/wap/searchlist";
        // 查询参数
        String params = """
                {
                    "initQuery": "美女手机壁纸",
                    "queryFrom": "wap",
                    "from": "homeHotSearch",
                    "rcer": "",
                    "spver": "0",
                    "keyword": "美女壁纸",
                    "start": 48,
                    "reqType": "client",
                    "reqFrom": "wap_result",
                    "prevIsRedis": "n",
                    "qua": "",
                    "headers": {},
                    "pagetype": 0,
                    "amsParams": []
                }
                """;
        // 发起post请求,得到上图所述的结果数据
        String json = HttpUtil.post(postUrl, params, 15000);
        // 解析结果数据,拿到最终的资源节点
        JSONArray jsonArray = JSONUtil.parseObj(json).getJSONObject("data").getJSONObject("picResult").getJSONArray("items");
        // 进一步解析提取所需要的资源数据,排除不需要的数据,将资源收集成集合中
        Set<JSONObject> result = jsonArray.stream().map(item -> {
            JSONObject resource = new JSONObject();
            if (item instanceof JSONObject jsonObject) {
                String title = jsonObject.getStr("title"); // 标题
                String picUrl = jsonObject.getStr("picUrl"); // 图片链接
                String thumbUrl = jsonObject.getStr("thumbUrl"); // 缩略图链接
                String oriPicUrl = jsonObject.getStr("oriPicUrl"); // 原图链接
                String locImageLink = jsonObject.getStr("locImageLink"); // 原图链接
                return resource.set("title", title).set("picUrl", picUrl)
                        .set("thumbUrl", thumbUrl).set("oriPicUrl", oriPicUrl)
                        .set("locImageLink", locImageLink);
            }
            return null;
        }).filter(ObjUtil::isNotEmpty).collect(Collectors.toSet());
        // 打印数据集
        System.out.println(result);
        System.out.println("共获取到:" + result.size() + "条数据");
    }

    /**
     * 通过js获取数据资源
     */
    public static void main2(String[] args) {
        // 请求资源地址得到页面数据
        Document document = Jsoup.parse(HttpUtil.get("https://pic.sogou.com/pic/searchList.jsp?from=homeHotSearch&rcer=&spver=0&tagQSign=%E9%AB%98%E6%B8%85,3636d5ad&showMode=0&routeName=searchlist&keyword=美女", 15000));
        String variableName = "window.__INITIAL_STATE__";
        // 解析得到所有的<script>
        Elements scriptTags = document.getElementsByTag("script");
        // 过滤得到包含'__INITIAL_STATE__'的<script>
        List<String> collects = scriptTags.stream().map(Element::data)
                .filter(data -> data.contains(variableName))
                .filter(StrUtil::isNotEmpty)
                .toList();
        String scriptData = CollectionUtil.isEmpty(collects) ? "" : collects.get(0);
        // 截取js中'__INITIAL_STATE__'变量内容
        int startIndex = scriptData.indexOf("{");
        int endIndex = scriptData.lastIndexOf("}");
        // 得到js中'__INITIAL_STATE__'的json数据
        String jsonData = scriptData.substring(startIndex, endIndex + 1);
        // 将JS数据转为Java的JSON对象
        JSONObject jsonObject = JSONUtil.parseObj(jsonData);
        jsonObject = jsonObject.getJSONObject("searchlist");
        jsonObject = jsonObject.getJSONObject("groupFixedResult");
        JSONArray groupPicArray = jsonObject.getJSONArray("groupPic");
        Set<Map<String, String>> list = new HashSet<>();
        // 从JSON对象中提取资源数据
        getSource(groupPicArray, list);
        // 打印数据集
        System.out.println(list);
        System.out.println("共获取到:" + list.size() + "条数据");
    }

    /**
     * 解析资源,获取资源数据
     *
     * @param jsonArray 资源数据集
     * @param list      结果数据集
     */
    private static void getSource(JSONArray jsonArray, Set<Map<String, String>> list) {
        for (Object object : jsonArray) {
            if (object instanceof JSONArray item) {
                getSource(item, list);
            }
            if (object instanceof JSONObject item) {
                var map = new HashMap<String, String>();
                map.put("thumbUrl", item.getStr("thumbUrl"));
                map.put("title", item.getStr("title"));
                map.put("pageUrl", item.getStr("pageUrl"));
                map.put("picUrl", item.getStr("picUrl"));
                list.add(map);
            }
        }
    }
}

五、下载资源

5.1、过程与思路

本案例仅以获取并解析JS资源为例来下载资源

下载图片资源,并将资源列表以json格式写入到本地(存入到数据库也可以,这里只写入到文件)

  • 根据上述代码,将获取到的数据资源下载到本地;
  • 将所有收集到的资源写入到文件中,方便于查看;

5.2、操作文件工具类

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.MD5;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import java.io.File;
import java.io.FileWriter;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Map;

/**
 * @author 陌路
 * @apiNote 操作文件工具类
 * @date 2024/1/16 12:10
 * @tool Created by IntelliJ IDEA
 */
@Slf4j
public class FileUtil {

    /**
     * 文件保存路径地址(修改为自己的地址)
     */
    public static final String FILE_PATH = "D:/Code/picture/sougou/";

    /**
     * 写入文件
     *
     * @param pathName 文件路径
     * @param suffix   文件类型,后缀名
     * @param data     数据集
     */
    public static void write(String pathName, String suffix, String data) {
        File file = new File(pathName + suffix);
        try (FileWriter writer = new FileWriter(file)) {
            writer.write(JSONUtil.toJsonStr(data));
            writer.flush();
        } catch (Exception e) {
            log.error("写入数据出错了:" + e.getMessage(), e);
        }
    }

    /**
     * 获取文件路径
     *
     * @param filePath 文件路径
     * @return 文件路径
     */
    public static String getFilePath(String filePath) {
        filePath = StrUtil.isBlank(filePath) ? "" : filePath + "/";
        DateTime dateTime = new DateTime();
        String currentDate = dateTime.toString(DatePattern.NORM_DATE_PATTERN);
        return FILE_PATH + filePath + currentDate + "/";
    }

    /**
     * 获取文件名
     *
     * @param title 标题
     * @return 文件名
     */
    public static String getFileName(String title) {
        return StrUtil.isBlank(title) ? "" : MD5.create().digestHex(title, StandardCharsets.UTF_8);
    }

    /**
     * 下载文件
     *
     * @param url      文件地址
     * @param filePath 文件路径
     * @param fileName 文件名
     * @param picUrl   图片地址
     */
    @SuppressWarnings("all")
    public static void downloadFile(String url, String filePath, String fileName, String picUrl, boolean isDownload) {
        fileName = FileUtil.getFileName(fileName);
        File file = new File(filePath + "/" + fileName + ".jpg");
        try {
            if (!file.exists()) {
                file.getParentFile().mkdirs();
                file.createNewFile();
            }
            HttpUtil.downloadFile(url, file, 5000);
        } catch (Exception e) {
            if (!isDownload) {
                downloadFile(picUrl, filePath, fileName, picUrl, true);
            }
            log.error("下载文件出错:" + e.getMessage(), e);
        }
    }

    /**
     * 下载文件
     *
     * @param filePath 文件路径
     * @param list     数据集
     */

    public static void downloadFile(String filePath, Collection<Map<String, String>> list) {
        list.forEach(item -> FileUtil.downloadFile(item.get("thumbUrl"), filePath, item.get("title") + item.get("picUrl"), item.get("picUrl"), false));
    }
}

5.3、资源下载实现类

本案例仅以获取JS中的资源实现下载

/**
 * 解析html
 * @param keyword 搜索关键字
 * @return 爬取结果
 */
public Set<Map<String, String>> parseHtmlByKeyword(String keyword) {
    Document document = Jsoup.parse(HttpUtil.get(UrlConstant.APP_BASE_URL + keyword, 15000));
    String variableName = "window.__INITIAL_STATE__";
    String scriptData = getJsonByScript(document.getElementsByTag("script"), variableName);
    int startIndex = scriptData.indexOf("{");
    int endIndex = scriptData.lastIndexOf("}");
    String jsonData = scriptData.substring(startIndex, endIndex + 1);
    JSONObject jsonObject = JSONUtil.parseObj(jsonData);
    jsonObject = jsonObject.getJSONObject("searchlist");
    jsonObject = jsonObject.getJSONObject("groupFixedResult");
    JSONArray groupPicArray = jsonObject.getJSONArray("groupPic");
    Set<Map<String, String>> list = new HashSet<>();
    getSource(groupPicArray, list);
    String filePath = FileUtil.getFilePath(keyword);
    // 资源数量小于100,直接下载,否则使用多线程下载
    long start = System.currentTimeMillis();
    // 是否使用多线程下载,资源数量>100的时候使用多线程
    if (list.size() < 100) {
        list.forEach(item -> FileUtil.downloadFile(item.get("thumbUrl"), filePath, item.get("title") + item.get("picUrl"), item.get("picUrl"), false));
    } else {
        multiThreaded(new ArrayList<>(list), keyword); // 多线程下载
    }
    long time = System.currentTimeMillis()- start;
    // 打印下载资源所耗时长
    log.info("共耗时:{}ms、{}s、{}m", time, (time / 1000), ((time / 1000) / 60));
    // 将资源数据写入到source.json文件中
    FileUtil.write(filePath + "/source", ".json", JSONUtil.toJsonStr(list));
    return list;
}

5.4、下载结果展示

在这里插入图片描述

5.5、调用示例

/**
 * @author 陌路
 * @apiNote 解析网页资源控制器
 * @date 2024/1/16 15:36
 * @tool Created by IntelliJ IDEA
 */
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/parseHtml")
public class ParseHtmlController {

    private final ParseHtmlService parseHtmlService;

    /**
     * 根据关键字爬取数据
     *
     * @param keyword 搜索关键字
     * @return 结果集
     */
    @GetMapping("/query/{keyword}")
    public ResultVo<Collection<Map<String, String>>> parseHtml(@PathVariable String keyword) {
        if (StrUtil.isBlank(keyword)) return ResultVo.fail("请输入搜索关键字!");
        return ResultVo.ok(parseHtmlService.parseHtmlByKeyword(keyword));
    }
}

六、完整代码

完整代码:https://gitee.com/mmolu/open-source/tree/master/jsoup

七、说明

本实例仅学习供参考使用

请勿用来牟利或非法使用

本示例仅供学习参考,请勿用来牟利或非法使用!

本示例仅供学习参考,请勿用来牟利或非法使用!

本示例仅供学习参考,请勿用来牟利或非法使用!

更多技术分享请关注:.陌路-CSDN博客

创作不易,采集、转发请注明出处:https://blog.csdn.net/qq_51076413?type=blog

.

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

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

相关文章

身份验证遇到问题,登陆ChatGPT时提示:“we ran into an issue while authenticating you…”

oops&#xff01; we ran into an issue while authenticating you, if this issue persists, please contact us through our help center at help.openai.com 说明&#xff1a;哎呀&#xff01;我们在验证您的身份时遇到了一个问题&#xff0c;如果这个问题仍然存在&#xff…

Linux:shell脚本:基础使用(8)《函数局部|全局变量函数传入位置变量return》

基本的函数定义 把一些重复调用的命令写进一个函数里&#xff0c;下次直接调用函数名&#xff0c;这样的既方便修改&#xff0c;又可以让思路清晰 function 函数名(){ 当调用这个函数时候执行的命令...... } 这个是一个基础的函数定义&#xff0c;当然你不加function也是可以的…

从 GPT1 - GPT4 拆解

从 GPT1 - GPT4 拆解 从 GPT1 - GPT4GPT1&#xff1a;更适用于文本生成领域GPT2&#xff1a;扩展数据集、模型参数&#xff0c;实现一脑多用&#xff08;多个任务&#xff09;GPT3&#xff1a;元学习 大力出奇迹InstructGPT&#xff1a;指示和提示学习 人工反馈强化学习 RLHF…

实验二 体系结构

&#x1f57a;作者&#xff1a; 主页 我的专栏C语言从0到1探秘C数据结构从0到1探秘Linux菜鸟刷题集 &#x1f618;欢迎关注&#xff1a;&#x1f44d;点赞&#x1f64c;收藏✍️留言 &#x1f3c7;码字不易&#xff0c;你的&#x1f44d;点赞&#x1f64c;收藏❤️关注对我真的…

Open3D 点云转深度图像

目录 一、算法原理1、算法过程2、主要函数二、代码实现三、结果展示1、点云2、深度图像四、测试数据Open3D 点云转深度图像由CSDN点云侠原创。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫与GPT。<

TypeScript 中的深拷贝和浅拷贝

什么是深拷贝 在JavaScript/TypeScript中&#xff0c;深拷贝是指创建一个对象的副本&#xff0c;而不仅仅是创建对原始对象的引用。对复制的对象进行的任何更改都不会影响原始对象&#xff0c;反之亦然。 这个副本将完全复制基础对象&#xff0c;包括每个嵌套级别的所有属性和…

在vite5和vue3开发环境中使用jodit4富文本编辑器,并添加自定义插件和使用highlight.js实现代码块高亮(附其他自定义配置项和全部代码)

最近富文本编辑器jodit终于更新发布到了4.0版本&#xff0c;加入了css变量、有更好的typescript支持&#xff0c;截止发文时的版本是&#xff1a;4.0.5&#xff0c;看到有了新版本于是便想着将本地项目中的jodit版本也进行升级&#xff0c;琢磨着再丰富和添加一些功能&#xff…

excel 各种用法

excel 各种用法 实现两张表格数据关联 vlookup 实现两张表格数据关联 vlookup 实现两个 sheet 间的关联需要用 vlookup 函数实现 函数第一个参数设置成 sheet1 中的 A 列&#xff08;如果数据很多&#xff0c;可以直接选中 A 列&#xff09; 函数的第二个参数选中 sheet2 中…

android 和 opencv 开发环境搭建

本文详细说明给android项目添加opencv库的详细步骤&#xff0c;并通过实现图片灰度化来查看配置是否成功。 下载OPENCV ANDROID SDK 到官网下载 打开 https://opencv.org/releases/ 选择android&#xff0c;下载完成后解压出下面的文件&#xff1a; 安装android sdk 和 ndk …

YOLOv8全网首发:DCNv4更快收敛、更高速度、更高性能,效果秒杀DCNv3、DCNv2等 ,助力检测

💡💡💡本文独家改进:DCNv4更快收敛、更高速度、更高性能,完美和YOLOv8结合,助力涨点 DCNv4优势:(1) 去除空间聚合中的softmax归一化,以增强其动态性和表达能力;(2) 优化存储器访问以最小化冗余操作以加速。这些改进显著加快了收敛速度,并大幅提高了处理速度,DCN…

【CSS】解决height = line-height 文字不垂直居中(偏上、偏下)的问题

解决办法1&#xff1a; 查看 font-family 属性&#xff0c;确认是否是因为字体而导致的不垂直居中问题。 其他小知识&#xff1a; 基线就是小写x字母的下边缘(线) 就是我们常说的 基线。line-height 属性设置的行高也就是定义的两行文字基线之间的距离! 参考文章&#xff1a;…

2017年认证杯SPSSPRO杯数学建模B题(第一阶段)岁月的印记全过程文档及程序

2017年认证杯SPSSPRO杯数学建模 跨年龄人脸识别模型的建立与分析 B题 岁月的印记 原题再现&#xff1a; 对同一个人来说&#xff0c;如果没有过改变面容的疾病、面部外伤或外科手术等经历&#xff0c;年轻和年老时的面容总有很大的相似性。人们在生活中也往往能够分辨出来两…

Java 方法中参数类型后写了三个点?什么意思?

1、...代表什么意思&#xff1f; 2、如何使用 3、注意事项 4、两个list&#xff0c;一个新的&#xff0c;一个旧的&#xff0c;旧列表中可能有新列表中存在的数据&#xff0c;也可能存在新列表中不存在的数据&#xff08;注&#xff1a;新旧列表中都不存在重复元素&#xff09;…

7.6 MySQL基本函数的使用(❤❤❤)

7.6 MySQL基本函数的使用 1. 提要2. 数字函数3. 字符函数3.1 替换字符3.2 左填充字符及截取字符串 4. 日期函数4.1 日期函数4.2 表达式占位符4.3 日期偏移计算4.4 日期间隔 5. 条件函数5.1 IF语句5.2 case...when语句 1. 提要 2. 数字函数 3. 字符函数 3.1 替换字符 -- INSERT…

水电站智能监测泄洪预警系统介绍

一、背景 近年来由于危险河道管理措施不到位&#xff0c;调峰电站泄水风险长期存在&#xff0c;信息通报制度缺失以及民众安全警觉性不高等因素导致的水电站在泄洪时冲走下游河道游客以及人民财产的事故频发。 二、系统介绍 水电站智能监测泄洪预警系统是一种集成了物联网、云…

C/C++ BM5 合并K个已排序的链表

文章目录 前言题目1 解决方案一1.1 思路阐述1.2 源码 2 解决方案二2.1 思路阐述2.2 源码 总结 前言 在接触了BM4的两个链表合并的情况&#xff0c;对于k个已排序列表&#xff0c;其实可以用合并的方法来看待问题。 这里第一种方法就是借用BM4的操作&#xff0c;只不过是多个合…

腾讯云服务器的介绍_云主机概览——腾讯云

腾讯云服务器CVM提供安全可靠的弹性计算服务&#xff0c;腾讯云明星级云服务器&#xff0c;弹性计算实时扩展或缩减计算资源&#xff0c;支持包年包月、按量计费和竞价实例计费模式&#xff0c;CVM提供多种CPU、内存、硬盘和带宽可以灵活调整的实例规格&#xff0c;提供9个9的数…

workflow源码解析:GoTask

关于go task 提供了另一种更简单的使用计算任务的方法&#xff0c;模仿go语言实现的go task。 使用go task来实计算任务无需定义输入与输出&#xff0c;所有数据通过函数参数传递。 与ThreadTask 区别 ThreadTask 是有模板&#xff0c;IN 和 OUT&#xff0c; ThreadTask 依赖…

分布式Erlang/OTP(学习笔记)(一)

Erlang分布式基础 假设你在机器A和机器B上各跑着一个Simple Cache应用的实例。要是在机器A的缓存上插人一个键/值对之后&#xff0c;从机器B上也可以访问&#xff0c;那可就好了。显然&#xff0c;要达到这个目的&#xff0c;机器A必须以某种方式将相关信息告知给机器B。传递该…

力扣309. 买卖股票的最佳时机含冷冻期(动态规划,Java C++解法)

Problem: 309. 买卖股票的最佳时机含冷冻期 文章目录 题目描述思路解题方法复杂度Code 题目描述 思路 Problem: 714. 买卖股票的最佳时机含手续费 该题目可以看作是上述题目的改编&#xff0c;该题目添加了一个冷冻期使得动态转移方程更加复杂&#xff0c;具体思路如下&#xf…