本文是針對個人工具藍奏雲批量下載工具的補充說明筆記,準備按照流程整理我實現軟件的思路與方法。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部分