目录
爬取结果
网站结构分析
代码部分
HttpClientUtil 类
ComicSpider 类
Chapter 和 ComicPage 类
SinglePictureDownloader 类
Main 类
首页 Java java教程 怎么使用Java爬取漫画

怎么使用Java爬取漫画

May 13, 2023 pm 11:10 PM
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<String> 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) == &#39;/&#39;) {
			root = root.substring(0, root.length()-1);
		}
		
		this.root = root;
		this.httpClient = HttpClients.createDefault();
	}
	
	public void start() {
		try {
			String html = this.getHtml(url);    //获取漫画主页数据
			List<Chapter> 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<Chapter> 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<Chapter> 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<ComicPage> urlList = this.getPageUrl(chapter);
					urlList.forEach(page->{
						SinglePictureDownloader downloader = new SinglePictureDownloader(page, dir.getAbsolutePath());
						downloader.download();
					});
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		});
	}
	
	//获取每一个页漫画的位置
	private List<ComicPage> 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<String> 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();
	}
}
登录后复制

以上是怎么使用Java爬取漫画的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
2 周前 By 尊渡假赌尊渡假赌尊渡假赌
仓库:如何复兴队友
4 周前 By 尊渡假赌尊渡假赌尊渡假赌
Hello Kitty Island冒险:如何获得巨型种子
3 周前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

Java 中的平方根 Java 中的平方根 Aug 30, 2024 pm 04:26 PM

Java 中的平方根指南。下面我们分别通过例子和代码实现来讨论平方根在Java中的工作原理。

Java 中的完美数 Java 中的完美数 Aug 30, 2024 pm 04:28 PM

Java 完美数指南。这里我们讨论定义,如何在 Java 中检查完美数?,示例和代码实现。

Java 中的随机数生成器 Java 中的随机数生成器 Aug 30, 2024 pm 04:27 PM

Java 随机数生成器指南。在这里,我们通过示例讨论 Java 中的函数,并通过示例讨论两个不同的生成器。

Java中的Weka Java中的Weka Aug 30, 2024 pm 04:28 PM

Java 版 Weka 指南。这里我们通过示例讨论简介、如何使用weka java、平台类型和优点。

Java 中的阿姆斯特朗数 Java 中的阿姆斯特朗数 Aug 30, 2024 pm 04:26 PM

Java 中的阿姆斯特朗数指南。这里我们讨论一下java中阿姆斯特朗数的介绍以及一些代码。

Java 中的史密斯数 Java 中的史密斯数 Aug 30, 2024 pm 04:28 PM

Java 史密斯数指南。这里我们讨论定义,如何在Java中检查史密斯号?带有代码实现的示例。

Java Spring 面试题 Java Spring 面试题 Aug 30, 2024 pm 04:29 PM

在本文中,我们保留了最常被问到的 Java Spring 面试问题及其详细答案。这样你就可以顺利通过面试。

突破或从Java 8流返回? 突破或从Java 8流返回? Feb 07, 2025 pm 12:09 PM

Java 8引入了Stream API,提供了一种强大且表达力丰富的处理数据集合的方式。然而,使用Stream时,一个常见问题是:如何从forEach操作中中断或返回? 传统循环允许提前中断或返回,但Stream的forEach方法并不直接支持这种方式。本文将解释原因,并探讨在Stream处理系统中实现提前终止的替代方法。 延伸阅读: Java Stream API改进 理解Stream forEach forEach方法是一个终端操作,它对Stream中的每个元素执行一个操作。它的设计意图是处

See all articles