蓝奏云批量下载工具的实现思路笔记

当前文章是旧版蓝奏云批量下载的实现思路,仅供参考

新版实现思路和源代码都是需要打赏才能获取,详情请访问蓝奏云批量下载工具新版源码

获取软件的请访问蓝奏云批量下载工具

本文是针对我的工具蓝奏云批量下载工具的补充说明笔记,准备按照流程整理我实现软件的思路与方法。

涉及知识

  • Java的IO流
  • Java的下载文件
  • HtmlUnit的使用方法
  • okhttp的使用

分析与软件思路

在某一天,我找到了一部电子书的资源,但是,该蓝奏云地址是一个文件夹,由于蓝奏云不支持批量下载,所以我便是诞生了打造出一个批量下载的工具的念头,大概搞了五天吧终于是成功了,折腾其中的重定向下载就搞了两天,说多了都是泪啊...

按照顺序,一步步分析吧

首先,我浏览器打开了蓝奏云地址,这里有两种情况,一种是有提取码,一种是没有提取码的。

这个时候,我们需要可以自动模拟用户进行提交表单的操作(代码详情见关键代码部分1)

经过网上的查找,发现了HtmlUnit这个开源库可以实现我们需要的操作。

HtmlUnit,说白了就是一个浏览器,这个浏览器是用Java写的无界面的浏览器,因为其没有界面,因此执行的速度还是妥妥的

之后我们便是来到这样的界面

我们需要解析当前并获得每个文件对应的蓝奏云地址,这里由于HTMLUnit内置了html元素选择器,我们可以使用HtmlUnit的选择器进行节点的过滤操作,得到文件信息以及对应的蓝奏云地址(代码详情见关键代码部分2)

对了,这里有可能文件过多,会出现显示更多的按钮,这个情况我们也得考虑,我们可以使用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中的下载即可成功下载该文件

关键功能代码及说明

1、2、3这三个部分的代码都是在MainController类中的download方法中

4、5部分在MainController类中的getDownloadLink方法中

这里我只抽取关键部分来进行讲解

1.模拟用户提交表单

注意,提交表单之后需要等待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")
}

2.自动点击加载更多按钮

 //文件可能不止一页,为了防止被封IP,限定最大翻页数,由用户输入
        for (i in 0 until pageCount) {
            if (page.getElementById("filemore") != null) {
                page = page.getElementById("filemore").click()
            } else {
                break
            }
        }

3.解析列表获得各文件对应地址

为了方便,我直接把文件名、日期等参数也一并获取了,用了一个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))
}

4.获得伪直链地址

//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")

5.使用okhttp获得蓝奏云真实地址

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)
}

6.下载功能

虽然和之前的工具一样,但是我还是把代码贴出来吧

/**
 * 下载文件到本地
 * @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部分

参考链接

Java实现网页自动登录和表单自动填写提交研究

Python爬取蓝奏云直链(获取真实文件地址)

posted @ 2020-01-19 21:35  Stars-one  阅读(14876)  评论(3编辑  收藏  举报