基于RxJava2实现的简单图片爬虫

今年十月份以来,跟朋友尝试导入一些图片到tensorflow来生成模型,这就需要大量的图片。刚开始我只写了一个简单的HttpClient程序来抓取图片,后来为了通用性索性写一个简单的图片爬虫程序。它可以用于抓取单张图片、多张图片、某个网页下的所有图片、多个网页下的所有图片。

github地址:https://github.com/fengzhizi715/PicCrawler

这个爬虫使用了HttpClient、RxJava2以及Java 8的一些特性。它支持一些简单的定制,比如定制User-Agent、Referer、Cookies等。

一.下载安装:

对于Java项目如果使用gradle构建,由于默认不是使用jcenter,需要在相应module的build.gradle中配置

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()表示对该图片请求重复的次数。

PicCrawler支持多种文件的生成策略,比如随机生成文件名、从1开始自增长地生成文件名、生成指定的文件名等等。

下图显示了使用该程序对某验证码的图片下载200次。


下载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中,可以对文件做一些后续的处理。

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

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

三. 部分源码解析

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实现的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,087评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,544评论 25 707
  • 今天又来到了武汉参加明天普通话的考试,为了这个普通话,我付出了太多。吴先生也帮助我不少。 这一次,他又帮我订酒店,...
    麦盾可阅读 211评论 0 0
  • 我是个面瘫寡言的北方人,我在南方过活着。 我生来薄情寡义,父母亲情我一一看在眼里,记在心里,却从来没有...
    树下闲人阅读 714评论 9 15
  • 国民岳父韩寒是微博时代前段子手,大电影《万万没想到》艺术总监兼白龙马客串咖。 叫兽易小星是网剧《万万没想到》导演,...
    暴娱梨花阵阅读 498评论 0 3