当前位置:

首页 > 编程开发 > 怎么使用Java爬取漫画

怎么使用Java爬取漫画

爬取结果程序运行效果获取的文件目录信息文件的总信息注:这里有一个小问题,获取的文件可能有的没有后缀名,但是可以以图片的方式打开观看,具体原因我也不知道,因为不影响,也就不去管它了。(或者自己使用代码,给文件重命名。)网站结构分析这里以一部漫画为例,首先看上面的编号,那个编号表示漫画的目录页。这是很重要的,在这一页有漫画的目录。然后依次点击目录中的章节,可以看到每一章的漫画信息。这里这个分页很奇怪,因为每一章节的页数不是一样的,但是它确实直接可以选择的,说明这个应该是提前加载或者异步加载的(我其实不会前端的

本文目录

    爬取结果

    程序运行效果

    怎么使用Java爬取漫画

    获取的文件目录信息

    怎么使用Java爬取漫画

    文件的总信息

    怎么使用Java爬取漫画

    怎么使用Java爬取漫画

    注: 这里有一个小问题,获取的文件可能有的没有后缀名,但是可以以图片的方式打开观看,具体原因我也不知道,因为不影响,也就不去管它了。(或者自己使用代码,给文件重命名。)

    网站结构分析

    这里以一部漫画为例,首先看上面的编号,那个编号表示漫画的目录页。这是很重要的,在这一页有漫画的目录。然后依次点击目录中的章节,可以看到每一章的漫画信息。

    怎么使用Java爬取漫画

    怎么使用Java爬取漫画

    这里这个分页很奇怪,因为每一章节的页数不是一样的,但是它确实直接可以选择的,说明这个应该是提前加载或者异步加载的(我其实不会前端的知识,只是听说了一些。)后来通过查看源(我用眼睛发现的)发现确实是提前加载所有漫画页的链接。不是异步加载的。

    这里我点击漫画图片获取图片的地址,然后再和自己发现的链接比对一下,就看出来了,然后拼接一下 url,就获取到所有的链接了。

    怎么使用Java爬取漫画

    在相应的章节页中,使用浏览器的查看源,就可以发现这样一段脚本了。经过分析,脚本中的数组里面的信息,就是对应的每一页漫画的信息。

    怎么使用Java爬取漫画

    上面的截图是一个大概的结构信息,所以获取流程是: 目录页–>章节页–>漫画页

    对于这里,获取到这段脚本作为字符串,然后以 “[” 和 “]” 获取字串,然后使用 fastjson 将其转化为一个 List 集合。

    // 获取的script 无法直接解析,必须先将 page url 取出来,
    // 这里以 [ ] 为界限,分割字符串。
    String pageUrls = script.data();
    int start = pageUrls.indexOf("[");
    int end = pageUrls.indexOf("]") + 1;
    String urls = pageUrls.substring(start, end);
    //json 转 集合,这个可以总结一下,不熟悉。
    List urlList = JSONArray.parseArray(urls, String.class);

    这里强调一点:Element对象的 text 方法是获取可见信息,而 data 方法是获取不可见信息。脚本信息是不可直接看见的,所以我使用 data 方法获取它。所谓可见和不可见大概就是网页上可以显示和通过查看源可以获取的信息的意思。比如转义字符,通过t ext 获取就变成转义的字符了。

    代码部分

    HttpClientUtil 类

    使用HttpClient连接池来管理连接,但是我没有使用多线程,因为我只有一个ip地址,万一被封了,很麻烦。当线程的时间还是可以 接受的,毕竟一部漫画,大概也就是十来分钟吧。(以600话为例)

    package com.comic;
    
    import org.apache.http.client.config.RequestConfig;
    import org.apache.http.impl.client.CloseableHttpClient;
    import org.apache.http.impl.client.HttpClients;
    import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
    
    public class HttpClientUtil {
    	private static final int TIME_OUT = 10 * 1000;
    	private static PoolingHttpClientConnectionManager pcm;   //HttpClient 连接池管理类
    	private static RequestConfig requestConfig;
    	
    	static {
    		requestConfig = RequestConfig.custom()
    				.setConnectionRequestTimeout(TIME_OUT)
    				.setConnectTimeout(TIME_OUT)
    				.setSocketTimeout(TIME_OUT).build();
    		
    		pcm = new PoolingHttpClientConnectionManager();
    		pcm.setMaxTotal(50);
    		pcm.setDefaultMaxPerRoute(10);  //这里可能用不到这个东西。
    	}
    	
    	public static CloseableHttpClient getHttpClient() {
    		return HttpClients.custom()
    				.setConnectionManager(pcm)
    				.setDefaultRequestConfig(requestConfig)
    				.build();
    	}
    }

    ComicSpider 类

    最重要的一个类,用来解析HTML页面获取链接数据。 注意:这里的 DIR_PATH 是硬编码路径,所以你想要测试,还请自己创建相关目录。

    package com.comic;
    
    import java.io.File;
    import java.io.FileNotFoundException;
    import java.io.IOException;
    import java.util.List;
    import java.util.concurrent.atomic.AtomicInteger;
    import java.util.stream.Collectors;
    
    import org.apache.http.HttpEntity;
    import org.apache.http.client.ClientProtocolException;
    import org.apache.http.client.config.CookieSpecs;
    import org.apache.http.client.config.RequestConfig;
    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 org.jsoup.Jsoup;
    import org.jsoup.nodes.Document;
    import org.jsoup.nodes.Element;
    import org.jsoup.select.Elements;
    
    import com.alibaba.fastjson.JSONArray;
    
    public class ComicSpider {
    	private static final String DIR_PATH = "D:/DBC/comic/";
    	private String url;
    	private String root;
    	private CloseableHttpClient httpClient;
    	
    	public ComicSpider(String url, String root) {
    		this.url = url;
    		// 这里不做非空校验,或者使用下面这个。
    		// Objects.requireNonNull(root);
    		if (root.charAt(root.length()-1) == '/') {
    			root = root.substring(0, root.length()-1);
    		}
    		
    		this.root = root;
    		this.httpClient = HttpClients.createDefault();
    	}
    	
    	public void start() {
    		try {
    			String html = this.getHtml(url);    //获取漫画主页数据
    			List chapterList = this.mapChapters(html);  //解析数据,得到各话的地址
    			this.download(chapterList);   //依次下载各话。
    		} catch (IOException e) {
    			e.printStackTrace();
    		}
    	}
    	
    	/**
    	 * 从url中获取原始的网页数据
    	 * @throws IOException 
    	 * @throws ClientProtocolException 
    	 * */
    	private String getHtml(String url) throws ClientProtocolException, IOException {
    		HttpGet get = new HttpGet(url);
    		//下面这两句,是因为总是报一个 Invalid cookie header,然后我在网上找到的解决方法。(去掉的话,不影响使用)。
    		RequestConfig defaultConfig = RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD).build();
    		get.setConfig(defaultConfig);
    		//因为是初学,而且我这里只是请求一次数据即可,这里就简单设置一下 UA
    		get.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3100.0 Safari/537.36");
    		HttpEntity entity = null;
    		String html = null;
    		try (CloseableHttpResponse response = httpClient.execute(get)) {
    			int statusCode = response.getStatusLine().getStatusCode();
    			if (statusCode == 200) {
    				entity = response.getEntity();
    				if (entity != null) {
    					html = EntityUtils.toString(entity, "UTF-8");
    				}
    			}
    		}
    		return html;
    	}
    	
    	
    	//获取章节名 链接地址
    	private List mapChapters(String html) {
    		Document doc = Jsoup.parse(html, "UTF-8");	
    		Elements name_urls = doc.select("#chapter-list-1 > li > a");
    		/* 不采用直接返回map的方式,封装一下。
    		return name_urls.stream()
    				.collect(Collectors.toMap(Element::text, 
    						name_url->root+name_url.attr("href")));
    		 */
    		return name_urls.stream()
    				.map(name_url->new Chapter(name_url.text(),
    						root+name_url.attr("href")))
    				.collect(Collectors.toList());
    	}
    	
    	/**
    	 * 依次下载对应的章节
    	 * 我使用当线程来下载,这种网站,多线程一般容易引起一些问题。
    	 * 方法说明:
    	 * 使用循环迭代的方法,以 name 创建文件夹,然后依次下载漫画。
    	 * */
    	public void download(List chapterList) {
    		chapterList.forEach(chapter->{
    			//按照章节创建文件夹,每一个章节一个文件夹存放。
    			File dir = new File(DIR_PATH, chapter.getName());
    			if (!dir.exists()) {
    				if (!dir.mkdir()) {
    					try {
    						throw new FileNotFoundException("无法创建指定文件夹"+dir);
    					} catch (FileNotFoundException e) {
    						e.printStackTrace();
    					}
    				}
    				
    				//开始按照章节下载	
    				try {
    					List urlList = this.getPageUrl(chapter);
    					urlList.forEach(page->{
    						SinglePictureDownloader downloader = new SinglePictureDownloader(page, dir.getAbsolutePath());
    						downloader.download();
    					});
    				} catch (IOException e) {
    					e.printStackTrace();
    				}
    			}
    		});
    	}
    	
    	//获取每一个页漫画的位置
    	private List getPageUrl(Chapter chapter) throws IOException {
    		String html = this.getHtml(chapter.getUrl());
    		 Document doc = Jsoup.parse(html, "UTF-8");
    		 Element script = doc.getElementsByTag("script").get(2); //获取第三个脚本的数据
    		 // 获取的script 无法直接解析,必须先将 page url 取出来,
    		 // 这里以 [ ] 为界限,分割字符串。
    		 String pageUrls = script.data();
    		 int start = pageUrls.indexOf("[");
    		 int end = pageUrls.indexOf("]") + 1;
    		 String urls = pageUrls.substring(start, end);
    		 //json 转 集合,这个可以总结一下,不熟悉。
    		 List urlList = JSONArray.parseArray(urls, String.class);
    
    		 AtomicInteger index=new AtomicInteger(0);  //我无法使用索引,这是别人推荐的方式
    		 return urlList.stream()   //注意这里拼接的不是 root 路径,而是一个新的路径
    		 		.map(url->new ComicPage(index.getAndIncrement(),"https://restp.dongqiniqin.com//"+url))
    		 		.collect(Collectors.toList());
    	}
    }

    注意: 这里我的思路是,所有的漫画都存放到 DIR_PATH 目录中。 然后每一章节是一个子目录(以章节名来命名),然后每一个章节的漫画放到一个目录中,但是这里会遇到一个问题。因为实际上漫画是一页一页观看的,所以漫画就有一个顺序的问题(毕竟一堆乱序漫画,看起来也很费劲,虽然我这里不是为了看漫画)。所以我就给每一个漫画页一个编号,按照上面脚本上的顺序,进行编号。但是由于我使用了Java8的 Lambda 表达式,所以我无法使用索引。(这涉及到另一个问题了)。 这里的解决办法是我看别人推荐的: 每次调用 index的 getAndIncrement 方法就可以增加 index 的值,非常方便。

     AtomicInteger index=new AtomicInteger(0);  //我无法使用索引,这是别人推荐的方式
     return urlList.stream()   //注意这里拼接的不是 root 路径,而是一个新的路径
     		.map(url->new ComicPage(index.getAndIncrement(),"https://restp.dongqiniqin.com//"+url))
     		.collect(Collectors.toList());

    Chapter 和 ComicPage 类

    两个实体类,因为是面向对象嘛,我就设计了两个简单的实体类来封装一下信息,这样操作比较方便一点。

    Chapter 类代表的是目录中的每一个章节的信息,章节的名字和章节的链接。 ComicPage 类代表的是每一个章节中的每一页漫画信息,每一页的编号和链接地址。

    package com.comic;
    
    public class Chapter {
    	private String name;  //章节名
    	private String url;   //对应章节的链接
    	
    	public Chapter(String name, String url) {
    		this.name = name;
    		this.url = url;
    	}
    	public String getName() {
    		return name;
    	}
    	public String getUrl() {
    		return url;
    	}
    	@Override
    	public String toString() {
    		return "Chapter [name=" + name + ", url=" + url + "]";
    	}
    }
    package com.comic;
    
    public class ComicPage {
    	private int number;  //每一页的序号
    	private String url;  //每一页的链接
    	
    	public ComicPage(int number, String url) {
    		this.number = number;
    		this.url = url;
    	}
    	
    	public int getNumber() {
    		return number;
    	}
    	public String getUrl() {
    		return url;
    	}
    }

    SinglePictureDownloader 类

    因为前几天使用多线程下载类爬取图片,发现速度太快了,ip 好像被封了,所以就又写了一个当线程的下载类。 它的逻辑很简单,主要是获取对应的漫画页链接,然后使用get请求,将它保存到对应的文件夹中。(它的功能大概和获取网络中的一张图片类似,既然你可以获取一张,那么成千上百也没有问题了。)

    package com.comic;
    
    import java.io.BufferedOutputStream;
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.OutputStream;
    import java.util.Random;
    
    import org.apache.http.HttpEntity;
    import org.apache.http.client.ClientProtocolException;
    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.util.EntityUtils;
    
    import com.m3u8.HttpClientUtil;
    
    public class SinglePictureDownloader {
    	private CloseableHttpClient httpClient;
    	private ComicPage page;
    	private String filePath;
    	
    	private String[] headers = {
    			"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36",
    		    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36",
    		    "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:30.0) Gecko/20100101 Firefox/30.0",
    		    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/537.75.14",
    		    "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Win64; x64; Trident/6.0)",
    		    "Mozilla/5.0 (Windows; U; Windows NT 5.1; it; rv:1.8.1.11) Gecko/20071127 Firefox/2.0.0.11",
    		    "Opera/9.25 (Windows NT 5.1; U; en)",
    		    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
    		    "Mozilla/5.0 (compatible; Konqueror/3.5; Linux) KHTML/3.5.5 (like Gecko) (Kubuntu)",
    		    "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.0.12) Gecko/20070731 Ubuntu/dapper-security Firefox/1.5.0.12",
    		    "Lynx/2.8.5rel.1 libwww-FM/2.14 SSL-MM/1.4.1 GNUTLS/1.2.9",
    		    "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.7 (KHTML, like Gecko) Ubuntu/11.04 Chromium/16.0.912.77 Chrome/16.0.912.77 Safari/535.7",
    		    "Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:10.0) Gecko/20100101 Firefox/10.0 "
    	};
    	
    	public SinglePictureDownloader(ComicPage page, String filePath) {
    		this.httpClient = HttpClientUtil.getHttpClient();
    		this.page = page;
    		this.filePath = filePath;
    	}
    	
    	public void download() {
    		HttpGet get = new HttpGet(page.getUrl());
    		String url = page.getUrl();
    		//取文件的扩展名
    		String prefix = url.substring(url.lastIndexOf("."));
    		
    		Random rand = new Random();
    		//设置请求头
    		get.setHeader("User-Agent", headers[rand.nextInt(headers.length)]);
    		HttpEntity entity = null;
    		try (CloseableHttpResponse response = httpClient.execute(get)) {
    			int statusCode = response.getStatusLine().getStatusCode();
    			if (statusCode == 200) {
    				entity = response.getEntity();
    				if (entity != null) {
    					File picFile = new File(filePath, page.getNumber()+prefix);
    					try (OutputStream out = new BufferedOutputStream(new FileOutputStream(picFile))) {
    						entity.writeTo(out);
    						System.out.println("下载完毕:" + picFile.getAbsolutePath());
    					}
    				}
    			}
    		} catch (ClientProtocolException e) {
    			e.printStackTrace();
    		} catch (IOException e) {
    			e.printStackTrace();
    		} finally {
    			try {
    				//关闭实体,关于 httpClient 的关闭资源,有点不太了解。
    				EntityUtils.consume(entity);
    			} catch (IOException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    }

    Main 类

    package com.comic;
    
    public class Main {
    	public static void main(String[] args) {
    		String root = "https://www.manhuaniu.com/"; //网站根路径,用于拼接字符串
    		String url = "https://www.manhuaniu.com/manhua/5830/";  //第一张第一页的url
    		ComicSpider spider = new ComicSpider(url, root);
    		spider.start();
    	}
    }
    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
    作者最新文章
    编程开发 Java
    相关文章 更多
    Java单元测试入门:使用JUnit 5编写第一个测试用例
    Java单元测试入门:使用JUnit 5编写第一个测试用例

    面向初学者的JUnit 5实用入门指南。从测试概念、Maven依赖配置,到手把手编写首个单元测试用例及断言验证,助你掌握Java单元测试的核心流程。

    Java变量未初始化报错解析:局部变量与成员变量默认值差异
    Java变量未初始化报错解析:局部变量与成员变量默认值差异

    深入解析Java中局部变量未初始化编译报错与成员变量自动赋予默认值的机制差异,涵盖JVM内存分配、编译期确定性赋值检查与实际排查修复方案。

    Java获取当前时间并格式化输出:DateTimeFormatter与SimpleDateFormat
    Java获取当前时间并格式化输出:DateTimeFormatter与SimpleDateFormat

    详细讲解在Java中获取当前系统时间并进行格式化输出的两种核心方案:基于Java 8全新日期时间API的DateTimeFormatter与传统遗留的SimpleDateFormat,包含完整代码示例、线程安全机制对比及生产选型建议。

    Java中final与static final常量的定义及初始化规则
    Java中final与static final常量的定义及初始化规则

    深入解析Java中final变量与static final常量的定义、内存语义、初始化时机及合法赋值路径,涵盖编译期常量优化与实战避坑指南。

    Java抽象方法与接口方法的区别及选型建议
    Java抽象方法与接口方法的区别及选型建议

    深入对比Java中抽象类抽象方法与接口方法的语法演进、设计意图及运行期差异,并提供清晰的架构选型决策流程与实战范例。

    Java实现HTTP请求与JSON解析的完整代码示例
    Java实现HTTP请求与JSON解析的完整代码示例

    本文提供Java标准库HttpClient发起HTTP GET/POST请求并结合Jackson进行JSON数据反序列化与序列化的完整代码示例与工程实践指南。

    Java递归出现StackOverflowError的原因分析及栈空间优化方案
    Java递归出现StackOverflowError的原因分析及栈空间优化方案

    详细分析Java递归调用出现StackOverflowError的原因,结合代码说明线程栈、栈帧和递归深度之间的关系,并介绍终止条件排查、迭代改写、减少栈帧开销以及通过-Xss调整JVM栈空间的方法。

    Java空指针异常(NPE)的代码排查与修复实例
    Java空指针异常(NPE)的代码排查与修复实例

    针对Java中常见的NullPointerException(NPE),本文从堆栈日志定位、典型代码触发场景、具体修复代码对比到防御体系进行系统讲解,提供实用且易上手的排查与修复方案。

    selenium上传文件教程本地文件路径设置方法
    selenium上传文件教程本地文件路径设置方法

    深入解析Selenium处理文件上传的技术细节,涵盖标准HTML控件的路径直接注入法与非标准控件的系统级模拟方案,提供Windows与Linux环境下的路径设置最佳实践及常见异常排查。

    大数据开发语言选型:Java对比Scala与Python的优缺点及适用场景
    大数据开发语言选型:Java对比Scala与Python的优缺点及适用场景

    针对大数据工程场景,系统对比Java、Scala与Python在执行性能、生态结合、开发效率和维护成本上的核心差异,给出清晰的架构选型依据与落地方案。

    查看更多
    精品专题 更多
    装机必备
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows
    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    PDF教程
    PDF教程

    正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

    Mac软件 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    Aperty macOS版
    Aperty macOS版
    macOS

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    WINDOWS 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    PixTV 网页版
    PixTV 网页版
    Windows

    PixTV 是一款在浏览器中使用的 AI 视觉内容创作平台,提供图片、视频、音频、文字生成、AI 分镜、局部重绘、提示词反推和无限画布等工具,适合短剧、漫剧、广告及社交媒体内容制作。