本文是针对个人工具蓝奏云批量下载工具的补充说明笔记,准备按照流程整理我实现软件的思路与方法。php
在某一天,我找到了一部电子书的资源,可是,该蓝奏云地址是一个文件夹,因为蓝奏云不支持批量下载,因此我即是诞生了打造出一个批量下载的工具的念头,大概搞了五天吧终因而成功了,折腾其中的重定向下载就搞了两天,说多了都是泪啊...html
按照顺序,一步步分析吧web
首先,我浏览器打开了蓝奏云地址,这里有两种状况,一种是有提取码,一种是没有提取码的。浏览器
这个时候,咱们须要能够自动模拟用户进行提交表单的操做(代码详情见关键代码部分1)网络
通过网上的查找,发现了HtmlUnit这个开源库能够实现咱们须要的操做。多线程
HtmlUnit,说白了就是一个浏览器,这个浏览器是用Java写的无界面的浏览器,由于其没有界面,所以执行的速度仍是妥妥的ide
以后咱们即是来到这样的界面工具
咱们须要解析当前并得到每一个文件对应的蓝奏云地址,这里因为HTMLUnit内置了html元素选择器,咱们可使用HtmlUnit的选择器进行节点的过滤操做,获得文件信息以及对应的蓝奏云地址(代码详情见关键代码部分2)post
对了,这里有可能文件过多,会出现显示更多的按钮,这个状况咱们也得考虑,咱们可使用HTMLUnit实现自动点击显示更多的按钮,我以前拿别人的连接来测试,那位大佬的连接里文件过多,致使我程序跑了二十多分钟,尚未跑完,而后,个人IP就被蓝奏云封了,出现了拒绝访问的警告。。测试
以后我重启了路由,因为IP地址是自动分配的,改了IP地址就解决了
因此,我打算限定一个最大的次数,超过以后就不点击了,即便列表尚未显示彻底(代码详情请见关键代码部分3)
因为咱们打开某个文件的蓝奏云地址,能够发现下载地址
右键,复制地址,咱们获得下面这一串长长的连接,这个连接其实不是真实地址,因此,我姑且以伪直链来称呼它,伪直链的是具备时效性的,就是说会过时,因此,得尽快经过伪直链来获取真实地址。
https://vip.d0.baidupan.com/file/?AGYFO1tqDj9TWgE5VmNUOFFuVGxW7gKlAZhTvVCjVckJ7gLuANVV5AnvV4dQ4gCcAf5Ss1K1B7EE5AGdUopVsgCUBexb4w73U6MBslaSVNtR7VTZVpEC5AG8U9xQLFWyCZ4C8AC4VY8J2VfmUKsAhgF1UjJSfQdwBGEBIVJoVT0AbAU3W1kOM1NhAWpWNVRkUTJUZVY/AjUBMVNzUGpVJwk0AmcAbVUxCWlXMFA9ADcBfVInUn0HOAQxATdSP1VtAC8FYls0DnVTNwFhVi1UY1FoVGhWMAJiATVTM1A7VWQJbQJkAD9VNglrVzRQNgAxAT5SNFI6BzIEMgEwUjZVZAA4BWJbNQ4+UzcBZlZnVHtRblQhVnwCYgEgUyBQf1UxCXsCPAA5VTwJZ1c2UDAAMwFqUmFSKwdxBGoBalJrVTIAPQVjWzMObVM8AWNWMVRjUTlUZFYxAi4BIFMgUHxVaQk4AnsAe1VnCTNXdlA5ADABblJgUjQHNgQ3ATNSPlVmADQFdFtzDipTcgFqVjNUYFE+VGBWOAI4ATFTZFA0VWEJLwIgADRVcQliVzBQNQA2AXVSZlI1BzYELQE1Uj5VZgAuBWdbNg==
本来觉得使用HtmlUnit开源库能够很简单地获取伪直链,不过,出了点情况,经过浏览器的元素审查功能,发现下面的那三个地址实际上是一个iframe
因为是iframe,至关于再次加载了另一个页面,因此咱们不能直接得到伪直链,得先经过得到iframe的src属性去访问它本来的那个网页。
咱们能够经过浏览器,直接去访问那个页面(页面地址为https://www.lanzous.com+src属性值
),页面打开以下:
页面须要等待一会,以后出现了三个按钮,以后,咱们即可以得到a标签的href属性,即得到了伪直链的地址(代码详情请见关键代码4)
使用浏览器打开伪直链,浏览器会直接下载文件了,可是,在程序中使用Java的下载操做确实返回的html文件,内容较多,省略了一下样式,内容以下:
... <div id="pwdload"> <div class="title">下载文件</div> <div class="txt">系统发现您网络不正常,须要验证<br>请输入右边图形中的数字</div> <div class="imcode"><img id="img" src="imagecode.php?" onclick="changeCode()"/></div> <div class="cl"></div> <div class="input_box"><input type="text" name="code" class="input" id="code" value="" /></div> <div class="cl"></div> <div id="pwderr"></div> <div id="sub" onclick="down_r();" class="btnpwd">验证并下载</div> </div> <div id="info"> <div class="info1"><div class="info2"></div></div> <div class="info3">恭喜你,经过了</div> <div class="load" id="go"> </div> </div> ... </html>
这里研究了两天,终因而找到了别人的博文中找到了答案,缘由是请求并无携带请求头,因此致使蓝奏云返回一个验证的页面,有请求头的话, 就会重定向到真实的地址。
这里,我使用了okhttp这个开源库,实现添加了请求头,最后得到了真实的下载地址(代码详情请见关键代码5),由此地址咱们再调用Java中的下载便可成功下载该文件
一、二、3这三个部分的代码都是在MainController类中的download方法中
四、5部分在MainController类中的getDownloadLink方法中
这里我只抽取关键部分来进行讲解
注意,提交表单以后须要等待2s来等待js执行完毕以显示出文件列表
//这个readyNodes包含全部id为ready的div一个列表 val readyNodes = if (password.isNotBlank()) { //有密码的状况 val pwdInput = page.getElementByName<HtmlTextInput>("pwd") val button = page.getElementsById("sub")[0] as HtmlSubmitInput pwdInput.valueAttribute = password//输入提取码 val finishPage = button.click<HtmlPage>()//提交表单 webClient.waitForBackgroundJavaScript(2000)//等待2s finishPage.getElementsById("ready") } else { //无密码的状况 webClient.waitForBackgroundJavaScript(2000) page.getElementsById("ready") }
//文件可能不止一页,为了防止被封IP,限定最大翻页数,由用户输入 for (i in 0 until pageCount) { if (page.getElementById("filemore") != null) { page = page.getElementById("filemore").click() } else { break } }
为了方便,我直接把文件名、日期等参数也一并获取了,用了一个ItemData的bean类进行数据的存储
//初始化列表(分享的蓝奏云地址中的全部文件及相关信息) val itemDatas = arrayListOf<ItemData>() //选择器进行网页的解析获取数据 for (readyNode in readyNodes) { val childNodes = readyNode.getElementsByTagName("div") val nameNode = childNodes[0].lastElementChild val sizeNode = childNodes[1] val timeNode = childNodes[2] val name = nameNode.textContent val link = nameNode.getAttribute("href")//单个文件的蓝奏云地址 val size = sizeNode.textContent val time = timeNode.textContent itemDatas.add(ItemData(name, link, "", size, time)) }
//url是单个文件的蓝奏云地址 val page = webClient.getPage<HtmlPage>(url) val srcText = page.getElementsByTagName("iframe")[0].getAttribute("src") //拼接字符串,获得另外页面的url val downloadHtmlUrl = "https://www.lanzous.com$srcText" val downloadPage = webClient.getPage<HtmlPage>(downloadHtmlUrl) //等待js加载完毕 webClient.waitForBackgroundJavaScript(1000) //得到伪直链地址(a标签的href属性值) val address = downloadPage.getElementById("go").firstElementChild.getAttribute("href")
HttpUtil.sendOkHttpRequest(address, object : Callback { override fun onFailure(p0: Call?, p1: IOException?) { println("error") } override fun onResponse(p0: Call?, response: Response?) { itemData.downloadLink = response?.request()?.url().toString() response?.close() } }) //补充的okhttp的工具类HttpUtilsendOkHttpRequest的方法 fun sendOkHttpRequest(address: String, callback: Callback) { val client = OkHttpClient() val control = CacheControl.Builder().build() //添加请求头user-agent和accept-language val request = Request.Builder() .addHeader("user-agent","Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36") .addHeader("accept-language","zh-CN,zh;q=0.9") .cacheControl(control) .url(address) .build() client.newCall(request).enqueue(callback) }
虽然和以前的工具同样,可是我仍是把代码贴出来吧
/** * 下载文件到本地 * @param url 网址 * @param file 文件 */ private fun downloadFile(url: String, file: File) { if (!file.exists()) { val conn = URL(url).openConnection() conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)") val bytes = conn.getInputStream().readBytes() file.writeBytes(bytes) } }
PS:我在解析过程和下载过程当中使用了多线程下载,提升了速度。具体实现思路请参考以前个人这一篇文章打造m3u8视频(流视频)下载解密合并器(kotlin)的第4部分