この種の画像の取得は、基本的にファイル (HttpClient) のダウンロードです。ただし、単に画像を取得するだけではないため、ページ解析プロセス (Jsoup) も必要になります。
Jsoup: HTML ページを解析し、画像へのリンクを取得します。
HttpClient: 画像のリンクを要求し、画像をローカルに保存します。
まずホームページ分析に入ります。主に次のカテゴリがあります (ここにすべてのカテゴリがあるわけではありませんが、これらで十分です。これは単なる学習テクニックです)。目標は、各カテゴリの写真を取得することです。
Web サイトの構造を分析しましょう。ここでは簡単に説明します。下の図は一般的な構造を示しており、ここでは説明のために分類ラベルを選択しています。 カテゴリ タグ ページには複数のタイトル ページが含まれ、各タイトル ページには複数の画像ページが含まれます。 (タイトル ページに相当する数十の画像)
プロジェクトに依存する jar パッケージ座標をインポートするか、対応する jar パッケージを直接ダウンロードします, プロジェクトのインポートも可能です。
<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.6</version> </dependency> <dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>1.11.3</version> </dependency>
Entity クラス: プロパティをオブジェクトにカプセル化し、呼び出しやすくします。
package com.picture; public class Picture { private String title; private String url; public Picture(String title, String url) { this.title = title; this.url = url; } public String getTitle() { return this.title; } public String getUrl() { return this.url; } }
ツール カテゴリ: 常に変更される UA (役立つかどうかはわかりませんが、独自の IP を使用しているため、ほとんど役に立たないと思われます)
package com.picture; public class HeaderUtil { public static 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 " }; }
マルチスレッドは本当に速すぎます。さらに、IP が 1 つしかなく、プロキシ IP は使用できません (それについてはあまり知りません)。マルチスレッドを使用してブロックするのは非常に高速です。 IP。
package com.picture; 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 String referer; private CloseableHttpClient httpClient; private Picture picture; private String filePath; public SinglePictureDownloader(Picture picture, String referer, String filePath) { this.httpClient = HttpClientUtil.getHttpClient(); this.picture = picture; this.referer = referer; this.filePath = filePath; } public void download() { HttpGet get = new HttpGet(picture.getUrl()); Random rand = new Random(); //设置请求头 get.setHeader("User-Agent", HeaderUtil.headers[rand.nextInt(HeaderUtil.headers.length)]); get.setHeader("referer", referer); System.out.println(referer); 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, picture.getTitle()); 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(); } } } }
これは、頻繁に作成される接続によるパフォーマンスの消費を回避するために、HttpClient 接続を取得するためのツール クラスです。 (ただし、ここではクロールに単一のスレッドを使用しているため、あまり役に立ちません。クロールには 1 つの HttpClient 接続を使用するだけです。これは、最初にクロールに複数のスレッドを使用したためです。しかし、基本的に画像が少ないため、禁止されたため、シングルスレッド クローラーに変更されました。そのため、接続プールは残りました。)
package com.m3u8; 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(); } }
package com.picture; import java.io.File; import java.io.IOException; import java.util.List; import java.util.Map; import java.util.stream.Collectors; 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 org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.select.Elements; import com.m3u8.HttpClientUtil; /** * 首先从顶部分类标题开始,依次爬取每一个标题(小分页),每一个标题(大分页。) * */ public class PictureSpider { private CloseableHttpClient httpClient; private String referer; private String rootPath; private String filePath; public PictureSpider() { httpClient = HttpClientUtil.getHttpClient(); } /** * 开始爬虫爬取! * * 从爬虫队列的第一条开始,依次爬取每一条url。 * * 分页爬取:爬10页 * 每个url属于一个分类,每个分类一个文件夹 * */ public void start(List<String> urlList) { urlList.stream().forEach(url->{ this.referer = url; String dirName = url.substring(22, url.length()-1); //根据标题名字去创建目录 //创建分类目录 File path = new File("D:/DragonFile/DBC/mzt/", dirName); //硬编码路径,需要用户自己指定一个 if (!path.exists()) { path.mkdir(); rootPath = path.toString(); } for (int i = 1; i <= 10; i++) { //分页获取图片数据,简单获取几页就行了 this.page(url + "page/"+ 1); } }); } /** * 标题分页获取链接 * */ public void page(String url) { System.out.println("url:" + url); String html = this.getHtml(url); //获取页面数据 Map<String, String> picMap = this.extractTitleUrl(html); //抽取图片的url if (picMap == null) { return ; } //获取标题对应的图片页面数据 this.getPictureHtml(picMap); } private String getHtml(String url) { String html = null; HttpGet get = new HttpGet(url); 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"); get.setHeader("referer", url); try (CloseableHttpResponse response = httpClient.execute(get)) { int statusCode = response.getStatusLine().getStatusCode(); if (statusCode == 200) { HttpEntity entity = response.getEntity(); if (entity != null) { html = EntityUtils.toString(entity, "UTf-8"); //关闭实体? } } else { System.out.println(statusCode); } } catch (ClientProtocolException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return html; } private Map<String, String> extractTitleUrl(String html) { if (html == null) { return null; } Document doc = Jsoup.parse(html, "UTF-8"); Elements pictures = doc.select("ul#pins > li"); //不知为何,无法直接获取 a[0],我不太懂这方面的知识。 //那我就多处理一步,这里先放下。 Elements pictureA = pictures.stream() .map(pic->pic.getElementsByTag("a").first()) .collect(Collectors.toCollection(Elements::new)); return pictureA.stream().collect(Collectors.toMap( pic->pic.getElementsByTag("img").first().attr("alt"), pic->pic.attr("href"))); } /** * 进入每一个标题的链接,再次分页获取图片的链接 * */ private void getPictureHtml(Map<String, String> picMap) { //进入标题页,在标题页中再次分页下载。 picMap.forEach((title, url)->{ //分页下载一个系列的图片,每个系列一个文件夹。 File dir = new File(rootPath, title.trim()); if (!dir.exists()) { dir.mkdir(); filePath = dir.toString(); //这个 filePath 是每一个系列图片的文件夹 } for (int i = 1; i <= 60; i++) { String html = this.getHtml(url + "/" + i); if (html == null) { //每个系列的图片一般没有那么多, //如果返回的页面数据为 null,那就退出这个系列的下载。 return ; } Picture picture = this.extractPictureUrl(html); System.out.println("开始下载"); //多线程实在是太快了(快并不是好事,我改成单线程爬取吧) SinglePictureDownloader downloader = new SinglePictureDownloader(picture, referer, filePath); downloader.download(); try { Thread.sleep(1500); //不要爬的太快了,这里只是学习爬虫的知识。不要扰乱别人的正常服务。 System.out.println("爬取完一张图片,休息1.5秒。"); } catch (InterruptedException e) { e.printStackTrace(); } } }); } /** * 获取每一页图片的标题和链接 * */ private Picture extractPictureUrl(String html) { Document doc = Jsoup.parse(html, "UTF-8"); //获取标题作为文件名 String title = doc.getElementsByTag("h3") .first() .text(); //获取图片的链接(img 标签的 src 属性) String url = doc.getElementsByAttributeValue("class", "main-image") .first() .getElementsByTag("img") .attr("src"); //获取图片的文件扩展名 title = title + url.substring(url.lastIndexOf(".")); return new Picture(title, url); } }
ここにクローラー キューがありますが、最初のクローラーのキューさえも完了していません。これは、計算を 2 桁間違えて計算を外したためです。ただし、プログラムの機能は正常です。
package com.picture; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * 爬虫启动类 * */ public class BootStrap { public static void main(String[] args) { //反爬措施:UA、refer 简单绕过就行了。 //refer https://www.mzitu.com //使用数组做一个爬虫队列 String[] urls = new String[] { "https://www.mzitu.com/xinggan/", "https://www.mzitu.com/zipai/" }; // 添加初始队列,启动爬虫 List<String> urlList = new ArrayList<>(Arrays.asList(urls)); PictureSpider spider = new PictureSpider(); spider.start(urlList); } }
ここに計算エラーがあります。コードは次のとおりです。
for (int i = 1; i <= 10; i++) { //分页获取图片数据,简单获取几页就行了 this.page(url + "page/"+ 1); }
計算を間違えたため、i の値が大きすぎます。この状況に従ってダウンロードすると、合計 4 * 10 * (30-5) * 60 = 64800 枚の写真がダウンロードされることになります。 (各ページには 30 枚のタイトル ページが含まれており、約 5 枚は広告です。) 最初は写真が数百枚しかないと思っていました。 これは推定値ですが、実際のダウンロード量はこれとそれほど変わりません (桁違いではありません)。そこで、しばらくダウンロードしてみたところ、最初のキューにある写真のみがダウンロードされていることがわかりました。もちろん、クローラー学習プログラムとしては十分に優れています。
このプログラムは学習用であり、各画像のダウンロード間隔を 1.5 秒に設定しており、シングルスレッド プログラムなので速度は非常に遅くなります。しかし、それは問題ではありません。プログラムが正しく機能する限り、画像がダウンロードされるまで実際に待つ人はいません。
長い時間がかかると推定されます: 64800*1.5 秒 = 97200 秒 = 27 時間。これは単なる概算であり、プログラムの他の実行時間は考慮されていません。それ以外の時間は基本的に無視して大丈夫です。
以上がJava クローラーを使用して画像をバッチでクロールする方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。