基於RxJava2實現的簡單圖片爬蟲

今年十月份以來,跟朋友嘗試導入一些圖片到tensorflow來生成模型,這就須要大量的圖片。剛開始我只寫了一個簡單的HttpClient程序來抓取圖片,後來爲了通用性索性寫一個簡單的圖片爬蟲程序。它能夠用於抓取單張圖片、多張圖片、某個網頁下的全部圖片、多個網頁下的全部圖片。html

github地址:github.com/fengzhizi71…java

這個爬蟲使用了HttpClient、RxJava2以及Java 8的一些特性。它支持一些簡單的定製,好比定製User-Agent、Referer、Cookies等。git

一.下載安裝:

對於Java項目若是使用gradle構建,因爲默認不是使用jcenter,須要在相應module的build.gradle中配置github

repositories {
    mavenCentral()
    jcenter()
}複製代碼

Gradle:網絡

compile 'com.cv4j.piccrawler:crawler:0.2.1'複製代碼

Maven:異步

<dependency>
  <groupId>com.cv4j.piccrawler</groupId>
  <artifactId>crawler</artifactId>
  <version>0.2.1</version>
  <type>pom</type>
</dependency>複製代碼

二.使用方法:

2.1 下載單張圖片

  1. 普通方式
String url = "..."; // 圖片的地址
        CrawlerClient.get()
                .timeOut(6000)
                .fileStrategy(new FileStrategy() {

                    @Override
                    public String filePath() {
                        return "temp";
                    }

                    @Override
                    public String picFormat() {
                        return "png";
                    }

                    @Override
                    public FileGenType genType() {

                        return FileGenType.AUTO_INCREMENT;
                    }
                })
                .repeat(200) // 重複200次
                .build()
                .downloadPic(url);複製代碼

在這裏,timeOut()表示網絡請求的超時時間。fileStrategy()表示存放的目錄、文件使用的格式、生成的文件時使用何種策略。repeat()表示對該圖片請求重複的次數。maven

PicCrawler支持多種文件的生成策略,好比隨機生成文件名、從1開始自增加地生成文件名、生成指定的文件名等等。ide

下圖顯示了使用該程序對某驗證碼的圖片下載200次。
性能

下載200張驗證碼的圖片.png
下載200張驗證碼的圖片.png

  1. 使用RxJava的方式下載
String url = "..."; // 圖片的地址
        CrawlerClient.get()
                .timeOut(6000)
                .fileStrategy(new FileStrategy() {

                    @Override
                    public String filePath() {
                        return "temp";
                    }

                    @Override
                    public String picFormat() {
                        return "png";
                    }

                    @Override
                    public FileGenType genType() {

                        return FileGenType.AUTO_INCREMENT;
                    }
                })
                .repeat(200)
                .build()
                .downloadPicUseRx(url);複製代碼
  1. 使用RxJava,下載以後的圖片還能作後續的處理
String url = "..."; // 圖片的地址

        CrawlerClient.get()
                .timeOut(6000)
                .fileStrategy(new FileStrategy() {

                    @Override
                    public String filePath() {
                        return "temp";
                    }

                    @Override
                    public String picFormat() {
                        return "png";
                    }

                    @Override
                    public FileGenType genType() {

                        return FileGenType.AUTO_INCREMENT;
                    }
                })
                .repeat(200)
                .build()
                .downloadPicToFlowable(url)
                .subscribe(new Consumer<File>() {
                    @Override
                    public void accept(File file) throws Exception {
                        // do something
                    }
                });複製代碼

在Consumer中,能夠對文件作一些後續的處理。gradle

2.2 下載多張圖片

List<String> urls = ...; // 多張圖片地址的集合
        CrawlerClient.get()
                .timeOut(6000)
                .fileStrategy(new FileStrategy() {

                    @Override
                    public String filePath() {
                        return "temp";
                    }

                    @Override
                    public String picFormat() {
                        return "png";
                    }

                    @Override
                    public FileGenType genType() {

                        return FileGenType.AUTO_INCREMENT;
                    }
                })
                .build()
                .downloadPics(urls);複製代碼

2.3 下載某個網頁的所有圖片

String url = "http://www.jianshu.com/u/4f2c483c12d8"; // 針對某一網址
        CrawlerClient.get()
                .timeOut(6000)
                .fileStrategy(new FileStrategy() {

                    @Override
                    public String filePath() {
                        return "temp";
                    }

                    @Override
                    public String picFormat() {
                        return "png";
                    }

                    @Override
                    public FileGenType genType() {

                        return FileGenType.AUTO_INCREMENT;
                    }
                })
                .build()
                .downloadWebPageImages(url);複製代碼

使用上面的程序,對我簡書主頁上的圖片進行抓取。

簡書的主頁.png
簡書的主頁.png

2.4 下載多個網頁的所有圖片

List<String> urls = new ArrayList<>(); // 多個網頁的集合

        urls.add("http://www.jianshu.com/u/4f2c483c12d8");
        urls.add("https://toutiao.io/");

        CrawlerClient.get()
                .timeOut(6000)
                .fileStrategy(new FileStrategy() {

                    @Override
                    public String filePath() {
                        return "temp";
                    }

                    @Override
                    public String picFormat() {
                        return "png";
                    }

                    @Override
                    public FileGenType genType() {

                        return FileGenType.AUTO_INCREMENT;
                    }
                })
                .build()
                .downloadWebPageImages(urls);複製代碼

下載我的簡書主頁上的圖以及開發者頭條的圖片。

多個網址的圖片下載.png
多個網址的圖片下載.png

三. 部分源碼解析

3.1 下載某個網頁的所有圖片

downloadWebPageImages()方法表示下載某個url的所有圖片。

/** * 下載整個網頁的所有圖片 * @param url */
    public void downloadWebPageImages(String url) {

        Flowable.just(url)
                .map(s->httpManager.createHttpWithGet(s))
                .map(response->parseHtmlToImages(response))
                .subscribe(urls -> downloadPics(urls),
                        throwable-> System.out.println(throwable.getMessage()));
    }複製代碼

downloadWebPageImages()分紅三步:建立網絡請求、解析出當前頁面中包含的圖片路徑、下載這些圖片。

第一步,建立網絡請求使用了HttpClient。

public CloseableHttpResponse createHttpWithGet(String url) {

        // 獲取客戶端鏈接對象
        CloseableHttpClient httpClient = getHttpClient();
        // 建立Get請求對象
        HttpGet httpGet = new HttpGet(url);

        if (Preconditions.isNotBlank(httpParam)) {

            Map<String,String> header = httpParam.getHeader();

            if (Preconditions.isNotBlank(header)) {
                for (String key : header.keySet()) {
                    httpGet.setHeader(key,header.get(key));
                }
            }
        }

        CloseableHttpResponse response = null;

        // 執行請求
        try {
            response = httpClient.execute(httpGet);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return response;
    }複製代碼

第二步,將返回的response轉換成String類型,使用jsoup將帶有圖片的連接所有過濾出來。

jsoup 是一款Java 的HTML解析器,可直接解析某個URL地址、HTML文本內容。它提供了一套很是省力的API,可經過DOM,CSS以及相似於jQuery的操做方法來取出和操做數據。

private List<String> parseHtmlToImages(CloseableHttpResponse response) {

        // 獲取響應實體
        HttpEntity entity = response.getEntity();

        InputStream is = null;
        String html = null;

        try {
            is = entity.getContent();
            html = IOUtils.inputStream2String(is);
        } catch (IOException e) {
            e.printStackTrace();
        }

        Document doc = Jsoup.parse(html);

        Elements media = doc.select("[src]");
        List<String> urls = new ArrayList<>();

        if (Preconditions.isNotBlank(media)) {

            for (Element src : media) {
                if (src.tagName().equals("img")) {

                    if (Preconditions.isNotBlank(src.attr("abs:src"))) { // 圖片的絕對路徑不爲空

                        String picUrl = src.attr("abs:src");
                        log.info(picUrl);
                        urls.add(picUrl);
                    } else if (Preconditions.isNotBlank(src.attr("src"))){ // 圖片的相對路徑不爲空

                        String picUrl = src.attr("src").replace("//","");
                        picUrl = "http://"+Utils.tryToEscapeUrl(picUrl);
                        log.info(picUrl);
                        urls.add(picUrl);
                    }
                }
            }
        }

        if (response != null) {
            try {
                EntityUtils.consume(response.getEntity());
                response.close();
            } catch (IOException e) {
                System.err.println("釋放連接錯誤");
                e.printStackTrace();
            }
        }

        return urls;
    }複製代碼

第三步,下載這些圖片使用了Java 8的CompletableFuture。CompletableFuture是Java 8新增的用於異步處理的類,並且CompletableFuture的性能也好於傳統的Future。

/** * 下載多張圖片 * @param urls */
    public void downloadPics(List<String> urls) {

        if (Preconditions.isNotBlank(urls)) {
            urls.stream().parallel().forEach(url->{

                try {
                    CompletableFuture.runAsync(() -> downloadPic(url)).get();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
            });
        }
    }複製代碼

3.2 下載多個網頁的所有圖片

downloadWebPageImages()方法還支持傳List集合,表示多個網頁的地址。

/** * 下載多個網頁的所有圖片 * @param urls */
    public void downloadWebPageImages(List<String> urls) {

        if (Preconditions.isNotBlank(urls)) {

            Flowable.fromIterable(urls)
                    .parallel()
                    .map(url->httpManager.createHttpWithGet(url))
                    .map(response->parseHtmlToImages(response))
                    .sequential()
                    .subscribe(list -> downloadPics(list),
                            throwable-> System.out.println(throwable.getMessage()));
        }
    }複製代碼

在這裏其實用到了ParallelFlowable,由於parallel()能夠把Flowable轉成ParallelFlowable。

總結

PicCrawler 是一個簡單的圖片爬蟲,目前基本能夠知足個人需求。將來要是有新的需求,我會不斷添加功能。

在作PicCrawler時,其實還作了一個ProxyPool用於獲取可用代理池的庫,它也是基於RxJava2實現的。

相關文章
相關標籤/搜索