我是如何利用99行Kotlin代码获得13000+妹子图的

前言

本实现基于kotlin语言 ,可是并不算深刻 ,故本文适用于熟悉kotlin , javatypeScript(感受它和kotlin迷之类似) ,和但愿学习爬虫的同窗.php

做者

热衷于爬虫 ,刚入门kotlin的php程序员

技术栈

Kotlin , Interlij Idea , Gradle 用Java的同窗都熟悉的三件套

国际惯例 ,gayhub地址

https://github.com/DevTTL/GetMeizitu

如何运行该工程

http://www.javashuo.com/article/p-pkgpljaz-bo.html

进入正题

首先上几张效果图:

抓取界面 -基于IDEA
抓取结果

开始写代码

1. 新建工程

利用Idea新建一个kotlin工程 ,安装jsoup 和requests依赖 , 这些百度处处都是教程的过程就再也不赘述.

2. 添加依赖并更新Gradle配置

compile group: 'net.dongliu', name: 'requests', version: '4.14.1'
compile group: 'org.jsoup', name: 'jsoup', version: '1.11.2'

3. 代码解析

新建三连 :

  • 创建一个文件 ,暂且取名为main.kt
  • 新建一个入口方法 main()
fun main(args: Array<String>) {
    MeiziTu()
}
  • 新建一个普通类 Meizitu
class MeiziTu {
    init{
    }
}
  • 定义一类成员属 - 根地址 : var url
// 根地址 ,index.html 是wordpress生成的首页文件 ,能够不加
    var url = "http://www.meizitu.com/index.html"
  • 根据功能定义6个方法 ,此时类的全貌
class Meizitu{
    
    var url = "http://www.meizitu.com/index.html"

    /**
     * 入口 ,启动爬虫
     */
    private fun boot(url: String) {}
    
    /**
     * 获得全部的图片并保存
     */
    private fun getImgs(detail: MutableMap.MutableEntry<String, String>) {}
    
    /**
     * 获得全部的图片并保存
     */
    private fun getImgs(detail: MutableMap.MutableEntry<String, String>) {}
    
    /**
     * 获得一页的详情连接
     */
    private fun getDetailUrl(url: String): TreeMap<String, String> {}
    
    /**
     * 得到某分类的总页数并返回每一叶的连接
     */
    private fun getPagesUrls(url: String): Set<String> {}
    
    /**
     * 得到soup对象
     */
    private fun soup(url: String): Document {}
}

实现这些方法 :

实现soup()html

咱们是使用jsoup解析xml ,这里主要对一个固定的Url实现返回一个通过jsoup解析的文档对象 ,方便后期的选择器使用等 .

这个方法没有不少内容 ,直接返回便可java

Document 是get()方法返回值类型 ,因此soup()的返回值类型也必须是Document ,也是能够指定返回值类型为Any ,可是IDEA将会难以进行代码提示git

headers 各位同窗大概看的明白 ,就是在模拟UA和HOST欺骗网站, 这里再也不详述程序员

另外get()并不是是指发送get()请求 ,而仅表明获得Url内容github

完整代码segmentfault

private fun soup(url: String): Document {

        /**
         * 利用指定的Header连接到URL ,并拉取资源
         */
        return Jsoup.connect(url)
                .headers(mapOf("User-Agent" to "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3298.3 Safari/537.36",
                        "Host" to "www.meizitu.com"
                )).get() 
    }

实现boot()方法 ,爬虫开始

首先解释 init {} ,这个代码块相似java中的构造方法 {}. 我直接调用了boot()方法 ,并传递了根url进去 ,实现了外部仅需实例化类便可开始爬取工做 ,无须手动调用方法 . 而且本类全部的类方法都是私有的 ,没法从外部调用 .

其次解释一下为何有那么多tay{}catch(){} ,主要是为了实如今每一级循环遇到异常均可以继续执行 ,而不会影响整个爬虫运做 .不在最外层捕获异常是为了避免会由于某一级的异常就跳过顶级的某次循环 ,尽量缩小异常对整个爬虫的影响wordpress

  1. 使用刚刚实现的soup方法解析根Url ,而且用select选择到全部的分类 ,以下图.
    clipboard.png
    clipboard.png
  2. 第一层循环 ,select 获得的结果是一个列表(list) ,利用kotlin自身的for in遍历该列表 ,这里的tag是一个Element对象 .
    使用this.getPagesUrls(tag.attr("href")) <该方法解析在文章的后半部分> .
    获得该分类下全部分页的每一页连接.tag.attr("href") 是从tag中拿到Href属性 ,也就是tag指向的url
    clipboard.png
  3. 进入第二层循环 ,遍历全部分页连接 .和获得分页的原理差很少 ,这里是利用每一页的连接获得该页面下的全部的卡片连接
    clipboard.png
  4. 进入第三层循环 ,原理和上两楼差很少 ,遍历全部的卡片连接 ,进入卡片详情使用this.getImgs(detail)<该方法解析在文章的后半部分> 命名和保存全部的图片 .
    clipboard.png

完整代码学习

init {
        this.boot(this.url)
    }
    
    private fun boot(url: String) {

        // 拿到首页的HTML
        val html = this.soup(url)

        // 获得全部的标签 ,也就是分类
        val tags = html.select(".tags a")

        /**
         * 忽略一切异常 ,遍历全部的分类并保存图片
         */
        for (tag in tags) {
            try {
                // 获得页数的连接
                var pagesLink = this.getPagesUrls(tag.attr("href"))
                // 遍历每一页
                for (pageLink in pagesLink) {
                    try {
                        // 获得全部的连接并遍历和保存
                        var detailUrls = this.getDetailUrl(pageLink)
                        for (detail in detailUrls) {
                            try {
                                // 保存图片
                                this.getImgs(detail)
                            } catch (e: Exception) {
                                continue
                            }
                        }
                    } catch (e: Exception) {
                        continue
                    }
                }
            } catch (e: Exception) {
                continue
            }
        }
    }

抓取连接方法之 getPagesUrls() getDetailUrl() 解析

  1. 首先是getPagesUrls(tag.attr("href")) ,利用每个分类的连接获得该分类下每一页的连接测试

    通过观察咱们能够发现 :每一页的连接都是由 http://www.meizitu.com/a/分类名_页码.html构成 ,而且几乎全部的 分类名_页码.html均可以从元素中取到 ,因而咱们就能够开始伟大的字符串拼接之旅了.

    clipboard.png

    clipboard.png

  2. 咱们选中 #wp_page_numbers li a 这个选择器 ,遍历之 ,拼接上baseUrl丢到结果集中 , 只有2点须要特殊说明 :

    • 咱们不须要下一页 ,首页 ... 这种 ,遍历时碰到他们就跳过无论.
    • 看看结果好像没有第一页的样子 ,观察之 ,发现第一页没有连接 ,可是通过楼主测试 ,使用分类名_1.html 便可 ,而后咱们截掉连接的后6位拼上去1.html ,第一页的连接就出来了 ,有人会说楼主脑子必定是进水了 ,这样会插入不少次一样的连接 .不不不 ,最后返回时的toSet()已经帮忙去重了.
      clipboard.png
  3. ggetDetailUrl(pageLink) 除告终果集多了个标题 ,其余几乎一毛同样 ,不作赘述.

完整代码

private fun getPagesUrls(url: String): Set<String> {

        // 结果集
        var res: MutableList<String> = mutableListOf()

        // 根URL
        var baseUrl = "http://www.meizitu.com/a/"

        // 拿到全部的页数
        var pages = this.soup(url).select("#wp_page_numbers li a")

        for (page in pages) {

            // 过滤无用的连接
            if (page.attr("href") == "下一页" || page.attr("href") == "末页" ||                     page.attr("href") == "首页") {
                continue
            }

            // 插入第一页的连接
            res.add(baseUrl + page.attr("href").substring(0, page.attr("href").length - 6) + "1.html")

            // 添加URL到结果集
            res.add(baseUrl + page.attr("href"))
        }
        // URL去重
        return res.toSet()
    }

抓取和保存图片 getImgs() 解析

此时咱们已经获得这一页的标题和连接 , MutableMap.MutableEntry<String, String> 是红黑树的表结果数据中的一个 ,简单的地说就是字典或字面量对象中的一对key和value.

选中全部的图片 ,屡次用到 ,不作赘述

楼主选择把土派年保存到/opt/imgs/$title ,$title 来自 getDetailUrl(pageLink)的返回值.
此时能够肯定 ,目录必定不存在 ,建立之 File(filePath).mkdir()

老套路 ,对全部选中的图片对象遍历之 ,这里是使用requests进行文件存储 ,

完整代码

// 解析地址
Requests.get(img.attr("src"))
// 发送
.send()
// 返回类型是个文件 ,保存到指定的位置 : 为了不文件名重复相互覆盖 ,加入自增变量flag做为文件名.
.toFileResponse(File(filePath + "/" + flag.toString() + ".jpg").toPath())

效果以下 :
clipboard.png

完整代码

private fun getImgs(detail: MutableMap.MutableEntry<String, String>) {

        // 选中全部图片
        var imgs = this.soup(detail.value).select("#picture img")

        // 以标题为目录名
        var filePath = "/opt/imgs/" + detail.key

        // 创建文件夹
        File(filePath).mkdir()

        // 声明一个FLAG ,用于图片名
        var flag = 0
        for (img in imgs) {

            // 利用Requests保存图片
            Requests.get(img.attr("src"))
                    .send()
                    .toFileResponse(File(filePath + "/" + flag.toString() + ".jpg").toPath())

            // 保存一张 ,给flag++
            flag++

            // 打印提示到控制台
            println("""${detail.key} 中的第 ${flag} 张妹子图 : ${img.attr("src")}""".trimIndent())
        }
    }

全剧终

相关文章
相关标签/搜索