本实现基于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()) } }