プログラム実行効果
取得したファイルディレクトリ情報
ファイルの合計情報
##注 : ここでちょっとした問題があります。取得したファイルの中には接尾辞が付いていないものもありますが、開いて画像として見ることはできます。具体的な理由はわかりません。影響しないので無視します。 。 (または、独自のコードを使用してファイル名を変更します。)
Web サイト構造分析 漫画を例に挙げてみましょう。まず、上の番号を見てください。その番号は、漫画のカタログ ページを表します。漫画。このページにはコミックの目次 があることが非常に重要です。次に、ディレクトリ内の章を 1 つずつクリックして、各章のコミック情報を表示します。
各章のページ数は同じではないため、ここでのページングは非常に奇妙ですが、確かに直接選択可能です。これは、事前または非同期でロードする必要があることを示しています (実際にはフロントエンドの知識はありません。聞いただけです。) 後でソースを確認すると (目で見つけました )、すべての漫画が事前に読み込まれていることがわかりました。ページのリンク。非同期的にロードされません。
ここでは、漫画の画像をクリックして画像のアドレスを取得し、見つけたリンクと比較すると、画像が表示されます。次に、URL を結合してすべてのリンクを取得します。 対応する章ページで、ブラウザのビュー ソースを使用してそのようなスクリプトを見つけます。解析すると、スクリプト内の配列の情報がコミックの各ページに対応する情報となります。 上記のスクリーンショットは大まかな構造情報であるため、取得プロセスは次のとおりです:コンテンツ ページ–>章ページ–>コミック ページ
このためには、このスクリプトを文字列として取得し、「[」と「]」を使用して文字列を取得し、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);ここで 1 つの点を強調します。Element オブジェクトの text メソッドは目に見える情報を取得するものであり、data メソッドは目に見えない情報を取得するものです。スクリプト情報は直接表示されないため、data メソッドを使用して取得します。いわゆる可視と不可視とは、Web ページ上に表示できる情報と、ソースを表示することで取得できる情報を指します。たとえば、エスケープ文字は、text ext を通じて取得するとエスケープ文字になります。 コード部分HttpClientUtilクラスHttpClient接続プールを使用して接続を管理しますが、IPアドレスが1つしかないため、マルチスレッドは使用しませんでした。ブロックされました、とても迷惑です。スレッド時間はまだ許容範囲内ですが、結局のところ、漫画は 10 分程度しか続きません。 (第 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<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式を使用しているため、インデックスは使用できません。 (これには別の問題が関係します)。ここでの解決策は、他の人が推奨しているのを見たものです。インデックスの getAndIncrement メソッドを呼び出すたびにインデックスの値を増やすことができ、非常に便利です。
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 クラス2 つのエンティティ クラス。これらはオブジェクト指向なので、操作がより便利になるように、情報をカプセル化する 2 つの単純なエンティティ クラスを設計しました。 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; } }
因为前几天使用多线程下载类爬取图片,发现速度太快了,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(); } } } }
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 中国語 Web サイトの他の関連記事を参照してください。