当前位置: 首页 > news >正文

Java批量下载书籍图片并保存为PDF的方法

背景

因为经常出差火车上没网、不方便电子书阅读器批注,需要从某网站上批量下载多本书籍的图片并自动打包成PDF文件。

分析

1、尝试获得图片地址,发现F12被禁
解决方法:使用Chrome浏览器,点击右上角三个点呼出菜单,选择“更多工具”->“开发者工具”
或者使用Ctrl+Shift+C、Ctrl+Shift+I
2、审查元素,发现图片地址非常有规律:
在class为side-image的div里有一个img,src是../files/mobile/1.jpg?220927153454,去掉后面的问号部分即可得到/files/mobile/1.jpg,通过观察,这本书一共有多少页就会有多少个.jpg文件
3、回到栏目页,可得到基目录,所以批量抓取的大致思路是从栏目页获得基目录,然后不断累加一个数,直到获得jpg时对方服务器报404错误,即可得到刚刚处理的那一页即最后一页。
4、如何从栏目页获得基目录呢?
经观察,每个page_pc_btm_book_body里都有两个a标签,第一个是图片,第二个是“在线阅读”按钮,但是需要翻页怎么办呢?所以需要建立一个变量收集它们,每翻一页,做一次收集。于是可以写如下收集函数:

let books=[]
function catchBook() {let links = document.getElementsByClassName("page_pc_btm_book_body");for (let i in links) {if(!links[i].children||links[i].children.length<2)continue;let title = links[i].children[0].title;let link = links[i].children[0].href;books.push({title,link})}
}

然后在浏览器里每翻一页,在控制台里执行一次catchBook,这样书名和基目录就都获得了。
5、如何把JSON导出来呢
在控制台里JSON.stringify(books),把结果复制出来,然后到网上随便找一个JSON转Excel的工具,转出来即可,然后注意把第一行当表头,数据复制到第二行开始。
6、最后一步就写个程序从Excel里读出数据,把图片都批量抓下来即可,下面就说说如何写程序来处理。

需要引的包

<dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>4.1.0</version>
</dependency>
<dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>4.1.0</version>
</dependency>
<dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml-schemas</artifactId><version>4.1.0</version>
</dependency>
<dependency><groupId>org.apache.poi</groupId><artifactId>poi-scratchpad</artifactId><version>4.1.0</version>
</dependency>
<dependency><groupId>org.apache.poi</groupId><artifactId>ooxml-schemas</artifactId><version>1.4</version>
</dependency>
<dependency><groupId>com.itextpdf</groupId><artifactId>itextpdf</artifactId><version>5.5.13.3</version>
</dependency>

从Excel到实体

先定义一个实体,这里我多加了一列type,表示类型,name就是从上面那个里面获得的title,link就是上面获得的link属性。

import lombok.Data;@Data
public class Book {private String type;private String name;private String link;
}

然后写个ExcelReader

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;public class ExcelReader {public static List<Book> readXlsxToList(String filePath) {List<Book> bookList = new ArrayList<>();try (FileInputStream fileInputStream = new FileInputStream(filePath);Workbook workbook = new XSSFWorkbook(fileInputStream)) {Sheet sheet = workbook.getSheetAt(0);Iterator<Row> rowIterator = sheet.iterator();// 获取表头(第一行)并转换为属性数组Row headerRow = rowIterator.next();String[] headers = getRowDataAsStringArray(headerRow);// 遍历每一行(从第二行开始)while (rowIterator.hasNext()) {Row row = rowIterator.next();Book book = new Book();// 遍历每个单元格,并根据属性名称设置对应的实体类属性值for (Cell cell : row) {int columnIndex = cell.getColumnIndex();if (columnIndex < headers.length) {String headerValue = headers[columnIndex];String cellValue = getCellValueAsString(cell);setBookProperty(book, headerValue, cellValue);}}bookList.add(book);}} catch (IOException e) {e.printStackTrace();}return bookList;}private static String[] getRowDataAsStringArray(Row row) {String[] rowData = new String[row.getLastCellNum()];for (Cell cell : row) {int columnIndex = cell.getColumnIndex();rowData[columnIndex] = getCellValueAsString(cell);}return rowData;}private static String getCellValueAsString(Cell cell) {String cellValue = "";if (cell != null) {switch (cell.getCellType()) {case STRING:cellValue = cell.getStringCellValue();break;case NUMERIC:cellValue = String.valueOf(cell.getNumericCellValue());break;case BOOLEAN:cellValue = String.valueOf(cell.getBooleanCellValue());break;case FORMULA:cellValue = cell.getCellFormula();break;default:cellValue = "";}}return cellValue;}private static void setBookProperty(Book book, String propertyName, String propertyValue) {switch (propertyName) {case "type":book.setType(propertyValue);break;case "name":book.setName(propertyValue);break;case "link":book.setLink(propertyValue);break;// 添加其他属性default:// 未知属性,可以根据需要进行处理break;}}
}

从实体集合到批量下载成jpg

还需要想办法实现批量下载的功能,需要注意的是Windows的默认文件排序是按ASC码排序的,会把10.jpg排在2.jpg前面,所以需要对页码格式化一下,把它变成三位数。

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;public class ImageDownloader {public static void downloadImages(List<Book> bookList, String targetDir) {for (Book book : bookList) {String type = book.getType();String name = book.getName();String link = book.getLink();String basePath = targetDir + "/" + type + "/" + name;int count = 1;boolean continueDownload = true;if(!new File(basePath).exists()){new File(basePath).mkdirs();}while (continueDownload) {String imgUrl = link + "files/mobile/" + count + ".jpg";String outputPath = String.format("%s/%03d.jpg", basePath, count);if (!imageExists(outputPath)) {try {downloadImage(imgUrl, outputPath);System.out.println("Downloaded: " + outputPath);} catch (IOException e) {System.out.println("Error downloading image: " + imgUrl);e.printStackTrace();continueDownload = false;}} else {System.out.println("Image already exists: " + outputPath);}count++;}}}private static boolean imageExists(String path) {Path imagePath = Paths.get(path);return Files.exists(imagePath);}private static void downloadImage(String imageUrl, String outputPath) throws IOException {URL url = new URL(imageUrl);HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();int responseCode = httpConn.getResponseCode();if (responseCode == HttpURLConnection.HTTP_OK) {try (InputStream inputStream = httpConn.getInputStream();FileOutputStream outputStream = new FileOutputStream(outputPath)) {byte[] buffer = new byte[4096];int bytesRead;while ((bytesRead = inputStream.read(buffer)) != -1) {outputStream.write(buffer, 0, bytesRead);}}} else {throw new IOException("Server returned response code " + responseCode);}}
}

开始批量下载

import java.util.List;public class Test {public static void main(String[] args) {List<Book> books = ExcelReader.readXlsxToList("C:\\Users\\Administrator\\Desktop\\某某书库.xlsx");String targetDir = "D:\\书库\\";ImageDownloader.downloadImages(books, targetDir);}
}

写完执行,回去睡一觉

jpg图片批量转成pdf

都下载完之后,就可以想办法批量转成PDF格式了。

import com.itextpdf.text.*;
import com.itextpdf.text.pdf.PdfWriter;import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.MalformedURLException;public class ImageToPdfConverter {public static void convertToPdf(String folderPath, String outputFilePath) {try {// 获取文件夹中的所有jpg文件File folder = new File(folderPath);File[] files = folder.listFiles((dir, name) -> name.toLowerCase().endsWith(".jpg"));// 预读第一章图片获得大小Rectangle rect = null;if (files.length == 0) {return;} else {Image image = Image.getInstance(files[0].getAbsolutePath());rect = new Rectangle(image.getWidth(), image.getHeight());}// 创建PDF文档对象Document document = new Document(rect);document.setMargins(0, 0, 0, 0);// 创建PDF写入器PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(outputFilePath));writer.setStrictImageSequence(true);// 打开PDF文档document.open();// 遍历图片文件并将其加入到PDF文档中for (File file : files) {Image image = Image.getInstance(file.getAbsolutePath());document.add(image);}// 关闭PDF文档document.close();System.out.println("PDF文件生成成功!");} catch (FileNotFoundException | DocumentException e) {e.printStackTrace();} catch (MalformedURLException e) {throw new RuntimeException(e);} catch (IOException e) {throw new RuntimeException(e);}}public static void main(String[] args) {String startDir="D:\\书库\\开发技术\\";File[] subdirs = new File(startDir).listFiles();for (File subdir : subdirs) {if(subdir.isDirectory()){convertToPdf(subdir.getAbsolutePath(), subdir.getAbsolutePath()+".pdf");}}}
}

结束

最后把PDF文件传到网盘上,手机、平板、电脑随时可以下载离线看,非常舒服。

注意:自己抓取书籍自己看无所谓,但通过网络分享出去是侵犯他人著作权的。

相关文章:

Java批量下载书籍图片并保存为PDF的方法

背景 因为经常出差火车上没网、不方便电子书阅读器批注&#xff0c;需要从某网站上批量下载多本书籍的图片并自动打包成PDF文件。 分析 1、尝试获得图片地址&#xff0c;发现F12被禁 解决方法&#xff1a;使用Chrome浏览器&#xff0c;点击右上角三个点呼出菜单&#xff0c;…...

flutter 创建lib

在根目录下创建 packages 文件夹创建lib (flutter create --templatepackage xxx)需要在pubspec.yaml中的根目录下添加 publish_to: ‘none’ # 如果你想发布到pub.dev&#xff0c;请删除这一行配置 lib_rock_utils:path: packages/lib_rock_utils...

深度剖析堆栈指针

为什么打印root的值与&root->value的值是一样的呢 测试结果&#xff1a; *号一个变量到底取出来的是什么&#xff1f; 以前我写过一句话&#xff0c;就是说&#xff0c;如果看到一个*变量&#xff0c;那就是直逼这个变量所保存的内存地址&#xff0c;然后取出里面保存的…...

C++笔记之静态成员函数的使用场景

C笔记之静态成员函数的使用场景 C静态成员函数的核心特点是不与特定类实例相关&#xff0c;可通过类名直接调用&#xff0c;用于执行与类相关的操作而无需创建类对象。其主要用途是在类级别上共享功能&#xff0c;管理全局状态或提供工具函数。 code review! 文章目录 C笔记之…...

Nginx的优化和防盗链

一、Nginx的优化 1、隐藏版本号 curl -I http://192.168.79.28 #查看信息&#xff08;版本号等&#xff09;方法一&#xff1a;修改配置文件 vim /usr/local/nginx/conf/nginx.conf vim /usr/local/nginx/conf/nginx.conf http {include mime.types;default_type ap…...

第二十次CCF计算机软件能力认证

数学专场 第一题&#xff1a;称检测点查询 解题思路&#xff1a;计算欧几里得距离 #include<iostream> #include<vector> #include<algorithm>using namespace std;typedef pair<int , int> PII; int n , x , y; vector<PII>v;int main() {ci…...

一篇文章带你了解Java发送邮件:使用JavaMail API发送电子邮件的注意事项、发送附件等

Java发送邮件&#xff1a;使用JavaMail API发送电子邮件 作者&#xff1a;Stevedash 发表于&#xff1a;2023年8月13日 15点48分 来源&#xff1a;Java 发送邮件 | 菜鸟教程 (runoob.com) 电子邮件在现代通信中扮演着至关重要的角色&#xff0c;而在Java编程中&#xff0c;…...

kubernetes的日志

1、日志在哪里 kubelet组件&#xff0c;systemd方式部署&#xff0c;journalctl -u kubelet 查看 其他组件&#xff0c;pod方式部署&#xff0c;kubectl logs 查看 容器运行时将日志写入 /var/log/pods 系统日志&#xff0c;/var/log/message 2、查看服务日志 #首先检查服…...

设计HTML5文本

网页文本内容丰富、形式多样&#xff0c;通过不同的版式显示在页面中&#xff0c;为用户提供最直接、最丰富的信息。HTML5新增了很多文本标签&#xff0c;它们都有特殊的语义&#xff0c;正确使用这些标签&#xff0c;可以让网页文本更严谨、更符合语义。 1、通用文本 1.1、标…...

msvcr120.dll丢失怎样修复?总结三个dll修复方法

当我遇到msvcr120.dll丢失的问题时&#xff0c;我感到有些困惑和焦虑。因为这个问题会导致我无法运行依赖这个文件的应用程序。msvcr120.dll是运行时库文件的一部分&#xff0c;为应用程序提供了必要的运行时支持。它的丢失会导致应用程序无法正常运行&#xff0c;这让我意识到…...

选择题方法论——颉斌斌

文章目录 一、仔细阅读文章结构Ⅰ 时间对比Ⅱ 抛砖引玉Ⅲ 开门见山Ⅳ 一分为二Ⅴ 两者关系Ⅵ 研究性文章 1 如何精准定位1.1 定位明显的题1.2 定位不明显的题1.3 定位找不到出题句的题 2 对比选项两点2.1 接收信息2.2 对比信息 3 选择题答案特点4 评估选项5 选择题的固有缺陷5.…...

23.8.8 杭电暑期多校7部分题解

1008 - H.HEX-A-GONE Trails 题目大意 有两个玩家和一棵树&#xff0c;初始状态玩家一和玩家二分别在两个点 x , y x,\space y x, y&#xff0c;每次操作可以走一个与当前点有连边并且双方都没走到过的点&#xff0c;问最后是谁赢 解题思路 因为不能走走过的点&#xff0c…...

《24海南大学835软件工程考研经验贴》

1.经验之谈 首先&#xff0c;我是一个二战的考生&#xff0c;一战给我带来的经验有几点。第一&#xff0c;数学、专业课这两门越早复习越好&#xff0c;越拖到后面你就会发现来不及了&#xff0c;这学不完&#xff0c;那学不完的。第二、我认为是比较关键的一点&#xff0c;一定…...

【yolo系列:运行报错AttributeError: module ‘torch.nn‘ has no attribute ‘Mish‘】

最近运行yolov7报错AttributeError: module ‘torch.nn‘ has no attribute ‘Mish‘ 网上搜罗了一系列的报错方法但是都不怎么好解决&#xff0c;那么在这里给出具体解决方法&#xff0c;以及一些别人的参考文章。 这里先解释自己的&#xff0c;然后再给出别人的相对应的报错…...

Leetcode 剑指 Offer II 039. 直方图最大矩形面积

题目难度: 困难 原题链接 今天继续更新 Leetcode 的剑指 Offer&#xff08;专项突击版&#xff09;系列, 大家在公众号 算法精选 里回复 剑指offer2 就能看到该系列当前连载的所有文章了, 记得关注哦~ 题目描述 给定非负整数数组 heights &#xff0c;数组中的数字用来表示柱状…...

SpringBoot案例-部门管理-修改

目录 前言 查看页面原型&#xff0c;明确需求 页面原型 需求 阅读接口文件 思路分析 功能接口开发 控制层&#xff08;Controller类&#xff09; 业务层&#xff08;Service类&#xff09; 业务类 业务实现类 持久层&#xff08;Mapper类&#xff09; 接口测试 前…...

element-ui表格数据为空,图片占位提示

当表格的绑定数据为空时常需要显示暂无数据等字样&#xff0c;这时候就用到了empty-text <el-table:data"tableData"stripeborderempty-text"暂无数据"> 但&#xff0c;当数据为空&#xff0c;想用图片展示呢&#xff0c;如下图 方法一&#xff1a…...

C++ STL vector 模拟实现

✅<1>主页&#xff1a;我的代码爱吃辣 &#x1f4c3;<2>知识讲解&#xff1a;C之STL &#x1f525;<3>创作者&#xff1a;我的代码爱吃辣 ☂️<4>开发环境&#xff1a;Visual Studio 2022 &#x1f4ac;<5>前言&#xff1a;上次我们已经数字会用…...

51单片机学习--红外遥控(外部中断)

需要利用下面这个红外接收头&#xff0c;OUT口会发出红外信号对应的高低电平&#xff0c;由于发送的速度很快&#xff0c;所以需要把OUT引脚接在外部中断引脚上&#xff0c;当OUT一旦产生下降沿&#xff0c;马上进中断&#xff0c;这样响应会更及时。 外部中断引脚位于P3_2和P…...

后端开发10.规格模块

概述 简介 效果图...

深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录

ASP.NET Core 是一个跨平台的开源框架&#xff0c;用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录&#xff0c;以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...

React 第五十五节 Router 中 useAsyncError的使用详解

前言 useAsyncError 是 React Router v6.4 引入的一个钩子&#xff0c;用于处理异步操作&#xff08;如数据加载&#xff09;中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误&#xff1a;捕获在 loader 或 action 中发生的异步错误替…...

日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻

在如今就业市场竞争日益激烈的背景下&#xff0c;越来越多的求职者将目光投向了日本及中日双语岗位。但是&#xff0c;一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧&#xff1f;面对生疏的日语交流环境&#xff0c;即便提前恶补了…...

k8s业务程序联调工具-KtConnect

概述 原理 工具作用是建立了一个从本地到集群的单向VPN&#xff0c;根据VPN原理&#xff0c;打通两个内网必然需要借助一个公共中继节点&#xff0c;ktconnect工具巧妙的利用k8s原生的portforward能力&#xff0c;简化了建立连接的过程&#xff0c;apiserver间接起到了中继节…...

2023赣州旅游投资集团

单选题 1.“不登高山&#xff0c;不知天之高也&#xff1b;不临深溪&#xff0c;不知地之厚也。”这句话说明_____。 A、人的意识具有创造性 B、人的认识是独立于实践之外的 C、实践在认识过程中具有决定作用 D、人的一切知识都是从直接经验中获得的 参考答案: C 本题解…...

Spring AI Chat Memory 实战指南:Local 与 JDBC 存储集成

一个面向 Java 开发者的 Sring-Ai 示例工程项目&#xff0c;该项目是一个 Spring AI 快速入门的样例工程项目&#xff0c;旨在通过一些小的案例展示 Spring AI 框架的核心功能和使用方法。 项目采用模块化设计&#xff0c;每个模块都专注于特定的功能领域&#xff0c;便于学习和…...

pikachu靶场通关笔记19 SQL注入02-字符型注入(GET)

目录 一、SQL注入 二、字符型SQL注入 三、字符型注入与数字型注入 四、源码分析 五、渗透实战 1、渗透准备 2、SQL注入探测 &#xff08;1&#xff09;输入单引号 &#xff08;2&#xff09;万能注入语句 3、获取回显列orderby 4、获取数据库名database 5、获取表名…...

Kubernetes 网络模型深度解析:Pod IP 与 Service 的负载均衡机制,Service到底是什么?

Pod IP 的本质与特性 Pod IP 的定位 纯端点地址&#xff1a;Pod IP 是分配给 Pod 网络命名空间的真实 IP 地址&#xff08;如 10.244.1.2&#xff09;无特殊名称&#xff1a;在 Kubernetes 中&#xff0c;它通常被称为 “Pod IP” 或 “容器 IP”生命周期&#xff1a;与 Pod …...

redis和redission的区别

Redis 和 Redisson 是两个密切相关但又本质不同的技术&#xff0c;它们扮演着完全不同的角色&#xff1a; Redis: 内存数据库/数据结构存储 本质&#xff1a; 它是一个开源的、高性能的、基于内存的 键值存储数据库。它也可以将数据持久化到磁盘。 核心功能&#xff1a; 提供丰…...

解析两阶段提交与三阶段提交的核心差异及MySQL实现方案

引言 在分布式系统的事务处理中&#xff0c;如何保障跨节点数据操作的一致性始终是核心挑战。经典的两阶段提交协议&#xff08;2PC&#xff09;通过准备阶段与提交阶段的协调机制&#xff0c;以同步决策模式确保事务原子性。其改进版本三阶段提交协议&#xff08;3PC&#xf…...