我是如何利用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())
        }
    }

全劇終

相關文章
相關標籤/搜索