本實現基於kotlin
語言 ,可是並不算深刻 ,故本文適用於熟悉kotlin
, java
或typeScript
(感受它和kotlin迷之類似) ,和但願學習爬蟲的同窗.php
熱衷於爬蟲 ,剛入門kotlin的php程序員
Kotlin
,Interlij Idea
,Gradle
用Java的同窗都熟悉的三件套
https://github.com/DevTTL/GetMeizitu
http://www.javashuo.com/article/p-pkgpljaz-bo.html
利用Idea新建一個kotlin工程 ,安裝jsoup 和requests依賴 , 這些百度處處都是教程的過程就再也不贅述.
compile group: 'net.dongliu', name: 'requests', version: '4.14.1' compile group: 'org.jsoup', name: 'jsoup', version: '1.11.2'
main.kt
吧main()
fun main(args: Array<String>) { MeiziTu() }
Meizitu
class MeiziTu { init{ } }
// 根地址 ,index.html 是wordpress生成的首頁文件 ,能夠不加 var url = "http://www.meizitu.com/index.html"
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() }
首先解釋init {}
,這個代碼塊相似java中的構造方法{}
. 我直接調用了boot()方法 ,並傳遞了根url進去 ,實現了外部僅需實例化類便可開始爬取工做 ,無須手動調用方法 . 而且本類全部的類方法都是私有的 ,沒法從外部調用 .其次解釋一下爲何有那麼多
tay{}catch(){}
,主要是爲了實如今每一級循環遇到異常均可以繼續執行 ,而不會影響整個爬蟲運做 .不在最外層捕獲異常是爲了避免會由於某一級的異常就跳過頂級的某次循環 ,儘量縮小異常對整個爬蟲的影響wordpress
list
) ,利用kotlin自身的for in
遍歷該列表 ,這裏的tag是一個Element
對象 .this.getPagesUrls(tag.attr("href"))
<該方法解析在文章的後半部分> . tag.attr("href")
是從tag中拿到Href屬性 ,也就是tag指向的urlthis.getImgs(detail)
<該方法解析在文章的後半部分> 命名和保存全部的圖片 .完整代碼學習
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()
解析首先是getPagesUrls(tag.attr("href"))
,利用每個分類的連接獲得該分類下每一頁的連接測試
通過觀察咱們能夠發現 :每一頁的連接都是由http://www.meizitu.com/a/分類名_頁碼.html
構成 ,而且幾乎全部的分類名_頁碼.html
均可以從元素中取到 ,因而咱們就能夠開始偉大的字符串拼接之旅了.
咱們選中 #wp_page_numbers li a
這個選擇器 ,遍歷之 ,拼接上baseUrl丟到結果集中 , 只有2點須要特殊說明 :
分類名_1.html
便可 ,而後咱們截掉連接的後6位拼上去1.html ,第一頁的連接就出來了 ,有人會說樓主腦子必定是進水了 ,這樣會插入不少次一樣的連接 .不不不 ,最後返回時的toSet()已經幫忙去重了.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())
效果以下 :
完整代碼
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()) } }