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


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

    涉及知识

    • 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爬取蓝奏云直链(获取真实文件地址)

  • 相关阅读:
    【转】为什么说面向对象编程和函数式编程都有问题
    【转】对博士学位说永别
    【转】写给支持和反对《完全用Linux工作》的人们
    【转】完全用Linux工作
    【转】人体工学
    【转】如何掌握所有的程序语言
    【转】Kotlin 和 Checked Exception
    【转】什么是现实理想主义者
    【转】经验和洞察力
    【转】C 编译器优化过程中的 Bug
  • 原文地址:https://www.cnblogs.com/stars-one/p/12215684.html
Copyright © 2020-2023  润新知