溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

怎么使用Java爬取漫畫(huà)

發(fā)布時(shí)間:2023-04-28 09:56:42 來(lái)源:億速云 閱讀:78 作者:zzz 欄目:開(kāi)發(fā)技術(shù)

這篇文章主要介紹了怎么使用Java爬取漫畫(huà)的相關(guān)知識(shí),內(nèi)容詳細(xì)易懂,操作簡(jiǎn)單快捷,具有一定借鑒價(jià)值,相信大家閱讀完這篇怎么使用Java爬取漫畫(huà)文章都會(huì)有所收獲,下面我們一起來(lái)看看吧。

爬取結(jié)果

程序運(yùn)行效果

怎么使用Java爬取漫畫(huà)

獲取的文件目錄信息

怎么使用Java爬取漫畫(huà)

文件的總信息

怎么使用Java爬取漫畫(huà)

怎么使用Java爬取漫畫(huà)

注: 這里有一個(gè)小問(wèn)題,獲取的文件可能有的沒(méi)有后綴名,但是可以以圖片的方式打開(kāi)觀看,具體原因我也不知道,因?yàn)椴挥绊懀簿筒蝗ス芩?。(或者自己使用代碼,給文件重命名。)

網(wǎng)站結(jié)構(gòu)分析

這里以一部漫畫(huà)為例,首先看上面的編號(hào),那個(gè)編號(hào)表示漫畫(huà)的目錄頁(yè)。這是很重要的,在這一頁(yè)有漫畫(huà)的目錄。然后依次點(diǎn)擊目錄中的章節(jié),可以看到每一章的漫畫(huà)信息。

怎么使用Java爬取漫畫(huà)

怎么使用Java爬取漫畫(huà)

這里這個(gè)分頁(yè)很奇怪,因?yàn)槊恳徽鹿?jié)的頁(yè)數(shù)不是一樣的,但是它確實(shí)直接可以選擇的,說(shuō)明這個(gè)應(yīng)該是提前加載或者異步加載的(我其實(shí)不會(huì)前端的知識(shí),只是聽(tīng)說(shuō)了一些。)后來(lái)通過(guò)查看源(我用眼睛發(fā)現(xiàn)的)發(fā)現(xiàn)確實(shí)是提前加載所有漫畫(huà)頁(yè)的鏈接。不是異步加載的。

這里我點(diǎn)擊漫畫(huà)圖片獲取圖片的地址,然后再和自己發(fā)現(xiàn)的鏈接比對(duì)一下,就看出來(lái)了,然后拼接一下 url,就獲取到所有的鏈接了。

怎么使用Java爬取漫畫(huà)

在相應(yīng)的章節(jié)頁(yè)中,使用瀏覽器的查看源,就可以發(fā)現(xiàn)這樣一段腳本了。經(jīng)過(guò)分析,腳本中的數(shù)組里面的信息,就是對(duì)應(yīng)的每一頁(yè)漫畫(huà)的信息。

怎么使用Java爬取漫畫(huà)

上面的截圖是一個(gè)大概的結(jié)構(gòu)信息,所以獲取流程是: 目錄頁(yè)–>章節(jié)頁(yè)–>漫畫(huà)頁(yè)

對(duì)于這里,獲取到這段腳本作為字符串,然后以 “[” 和 “]” 獲取字串,然后使用 fastjson 將其轉(zhuǎn)化為一個(gè) List 集合。

// 獲取的script 無(wú)法直接解析,必須先將 page url 取出來(lái),
// 這里以 [ ] 為界限,分割字符串。
String pageUrls = script.data();
int start = pageUrls.indexOf("[");
int end = pageUrls.indexOf("]") + 1;
String urls = pageUrls.substring(start, end);
//json 轉(zhuǎn) 集合,這個(gè)可以總結(jié)一下,不熟悉。
List<String> urlList = JSONArray.parseArray(urls, String.class);

這里強(qiáng)調(diào)一點(diǎn):Element對(duì)象的 text 方法是獲取可見(jiàn)信息,而 data 方法是獲取不可見(jiàn)信息。腳本信息是不可直接看見(jiàn)的,所以我使用 data 方法獲取它。所謂可見(jiàn)和不可見(jiàn)大概就是網(wǎng)頁(yè)上可以顯示和通過(guò)查看源可以獲取的信息的意思。比如轉(zhuǎn)義字符,通過(guò)t ext 獲取就變成轉(zhuǎn)義的字符了。

代碼部分

HttpClientUtil 類

使用HttpClient連接池來(lái)管理連接,但是我沒(méi)有使用多線程,因?yàn)槲抑挥幸粋€(gè)ip地址,萬(wàn)一被封了,很麻煩。當(dāng)線程的時(shí)間還是可以 接受的,畢竟一部漫畫(huà),大概也就是十來(lái)分鐘吧。(以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);  //這里可能用不到這個(gè)東西。
	}
	
	public static CloseableHttpClient getHttpClient() {
		return HttpClients.custom()
				.setConnectionManager(pcm)
				.setDefaultRequestConfig(requestConfig)
				.build();
	}
}

ComicSpider 類

最重要的一個(gè)類,用來(lái)解析HTML頁(yè)面獲取鏈接數(shù)據(jù)。 注意:這里的 DIR_PATH 是硬編碼路徑,所以你想要測(cè)試,還請(qǐng)自己創(chuàng)建相關(guān)目錄。

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;
		// 這里不做非空校驗(yàn),或者使用下面這個(gè)。
		// 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);    //獲取漫畫(huà)主頁(yè)數(shù)據(jù)
			List<Chapter> chapterList = this.mapChapters(html);  //解析數(shù)據(jù),得到各話的地址
			this.download(chapterList);   //依次下載各話。
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 從url中獲取原始的網(wǎng)頁(yè)數(shù)據(jù)
	 * @throws IOException 
	 * @throws ClientProtocolException 
	 * */
	private String getHtml(String url) throws ClientProtocolException, IOException {
		HttpGet get = new HttpGet(url);
		//下面這兩句,是因?yàn)榭偸菆?bào)一個(gè) Invalid cookie header,然后我在網(wǎng)上找到的解決方法。(去掉的話,不影響使用)。
		RequestConfig defaultConfig = RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD).build();
		get.setConfig(defaultConfig);
		//因?yàn)槭浅鯇W(xué),而且我這里只是請(qǐng)求一次數(shù)據(jù)即可,這里就簡(jiǎn)單設(shè)置一下 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;
	}
	
	
	//獲取章節(jié)名 鏈接地址
	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());
	}
	
	/**
	 * 依次下載對(duì)應(yīng)的章節(jié)
	 * 我使用當(dāng)線程來(lái)下載,這種網(wǎng)站,多線程一般容易引起一些問(wèn)題。
	 * 方法說(shuō)明:
	 * 使用循環(huán)迭代的方法,以 name 創(chuàng)建文件夾,然后依次下載漫畫(huà)。
	 * */
	public void download(List<Chapter> chapterList) {
		chapterList.forEach(chapter->{
			//按照章節(jié)創(chuàng)建文件夾,每一個(gè)章節(jié)一個(gè)文件夾存放。
			File dir = new File(DIR_PATH, chapter.getName());
			if (!dir.exists()) {
				if (!dir.mkdir()) {
					try {
						throw new FileNotFoundException("無(wú)法創(chuàng)建指定文件夾"+dir);
					} catch (FileNotFoundException e) {
						e.printStackTrace();
					}
				}
				
				//開(kāi)始按照章節(jié)下載	
				try {
					List<ComicPage> urlList = this.getPageUrl(chapter);
					urlList.forEach(page->{
						SinglePictureDownloader downloader = new SinglePictureDownloader(page, dir.getAbsolutePath());
						downloader.download();
					});
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		});
	}
	
	//獲取每一個(gè)頁(yè)漫畫(huà)的位置
	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); //獲取第三個(gè)腳本的數(shù)據(jù)
		 // 獲取的script 無(wú)法直接解析,必須先將 page url 取出來(lái),
		 // 這里以 [ ] 為界限,分割字符串。
		 String pageUrls = script.data();
		 int start = pageUrls.indexOf("[");
		 int end = pageUrls.indexOf("]") + 1;
		 String urls = pageUrls.substring(start, end);
		 //json 轉(zhuǎn) 集合,這個(gè)可以總結(jié)一下,不熟悉。
		 List<String> urlList = JSONArray.parseArray(urls, String.class);

		 AtomicInteger index=new AtomicInteger(0);  //我無(wú)法使用索引,這是別人推薦的方式
		 return urlList.stream()   //注意這里拼接的不是 root 路徑,而是一個(gè)新的路徑
		 		.map(url->new ComicPage(index.getAndIncrement(),"https://restp.dongqiniqin.com//"+url))
		 		.collect(Collectors.toList());
	}
}

注意: 這里我的思路是,所有的漫畫(huà)都存放到 DIR_PATH 目錄中。 然后每一章節(jié)是一個(gè)子目錄(以章節(jié)名來(lái)命名),然后每一個(gè)章節(jié)的漫畫(huà)放到一個(gè)目錄中,但是這里會(huì)遇到一個(gè)問(wèn)題。因?yàn)閷?shí)際上漫畫(huà)是一頁(yè)一頁(yè)觀看的,所以漫畫(huà)就有一個(gè)順序的問(wèn)題(畢竟一堆亂序漫畫(huà),看起來(lái)也很費(fèi)勁,雖然我這里不是為了看漫畫(huà))。所以我就給每一個(gè)漫畫(huà)頁(yè)一個(gè)編號(hào),按照上面腳本上的順序,進(jìn)行編號(hào)。但是由于我使用了Java8的 Lambda 表達(dá)式,所以我無(wú)法使用索引。(這涉及到另一個(gè)問(wèn)題了)。 這里的解決辦法是我看別人推薦的: 每次調(diào)用 index的 getAndIncrement 方法就可以增加 index 的值,非常方便。

 AtomicInteger index=new AtomicInteger(0);  //我無(wú)法使用索引,這是別人推薦的方式
 return urlList.stream()   //注意這里拼接的不是 root 路徑,而是一個(gè)新的路徑
 		.map(url->new ComicPage(index.getAndIncrement(),"https://restp.dongqiniqin.com//"+url))
 		.collect(Collectors.toList());

Chapter 和 ComicPage 類

兩個(gè)實(shí)體類,因?yàn)槭敲嫦驅(qū)ο舐铮揖驮O(shè)計(jì)了兩個(gè)簡(jiǎn)單的實(shí)體類來(lái)封裝一下信息,這樣操作比較方便一點(diǎn)。

Chapter 類代表的是目錄中的每一個(gè)章節(jié)的信息,章節(jié)的名字和章節(jié)的鏈接。 ComicPage 類代表的是每一個(gè)章節(jié)中的每一頁(yè)漫畫(huà)信息,每一頁(yè)的編號(hào)和鏈接地址。

package com.comic;

public class Chapter {
	private String name;  //章節(jié)名
	private String url;   //對(duì)應(yīng)章節(jié)的鏈接
	
	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;  //每一頁(yè)的序號(hào)
	private String url;  //每一頁(yè)的鏈接
	
	public ComicPage(int number, String url) {
		this.number = number;
		this.url = url;
	}
	
	public int getNumber() {
		return number;
	}
	public String getUrl() {
		return url;
	}
}

SinglePictureDownloader 類

因?yàn)榍皫滋焓褂枚嗑€程下載類爬取圖片,發(fā)現(xiàn)速度太快了,ip 好像被封了,所以就又寫(xiě)了一個(gè)當(dāng)線程的下載類。 它的邏輯很簡(jiǎn)單,主要是獲取對(duì)應(yīng)的漫畫(huà)頁(yè)鏈接,然后使用get請(qǐng)求,將它保存到對(duì)應(yīng)的文件夾中。(它的功能大概和獲取網(wǎng)絡(luò)中的一張圖片類似,既然你可以獲取一張,那么成千上百也沒(méi)有問(wèn)題了。)

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();
		//取文件的擴(kuò)展名
		String prefix = url.substring(url.lastIndexOf("."));
		
		Random rand = new Random();
		//設(shè)置請(qǐng)求頭
		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 {
				//關(guān)閉實(shí)體,關(guān)于 httpClient 的關(guān)閉資源,有點(diǎn)不太了解。
				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/"; //網(wǎng)站根路徑,用于拼接字符串
		String url = "https://www.manhuaniu.com/manhua/5830/";  //第一張第一頁(yè)的url
		ComicSpider spider = new ComicSpider(url, root);
		spider.start();
	}
}

關(guān)于“怎么使用Java爬取漫畫(huà)”這篇文章的內(nèi)容就介紹到這里,感謝各位的閱讀!相信大家對(duì)“怎么使用Java爬取漫畫(huà)”知識(shí)都有一定的了解,大家如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI