首先聲明,本文旨在記錄反思,并沒有資源,代碼也不具有借鑒意義(水平實在不行.
某天,水群的時候發現群友發了一個文件,里面是疫情時期springer開放的免費電子書名單,同時還附有下載鏈接,總共有400多本,這要是一個一個下載不得累死個人,只下載自己感興趣的書也是一個好主意,但是,我全都要,它不爽嗎?
因此就產生了寫個爬蟲下載電子書的想法,就在今天付諸于實踐.
最初思路:
1.讀取excel文件中所有書籍的鏈接,放在列表類中.
2.格式化列表中的鏈接,然后依次訪問鏈接,提取下載pdf的鏈接地址.
3.將下載pdf的鏈接地址批量加入到迅雷的下載任務中.
理想很豐滿,然而實際操作起來……
步驟1:首先網上bing如何讀取excel文件中的數據。然后復制、黏貼、修改……
import xlrd
def get_bookurl_list(site):book = xlrd.open_workbook(site)sheet1 = book.sheet()[0] #第一張工作表urls = sheet1.col(18) #書籍地址在第18列所以里面也就是18urls = urls[1:] #去除第一行的列名 #xlrd庫讀出來的excel每一個小格都是一個cell類,cell.value才是真正的數據return urls
然鵝最初的思路在第二步就出現了問題……
我雖然可以用requests庫訪問網址,但是我找不到下載pdf的鏈接在哪里(雖然我看得見,也點的了(笑,但是在運行程序的時候我無法指定程序返回那個鏈接,也就是說,我無法操作爬蟲返回指定的內容。
于是我打算直接返回網頁內全部鏈接,然后我找到了一個更加傻瓜式的庫——requests-html。里面的HTMLSession類有返回網頁內所有鏈接絕對路徑的方法。結果還是行不通,因為返回來的鏈接數量超出了我的預料,這還不算,更加致命的是,書記的頁面還包含相關書籍的部分章節,單從鏈接上看基本沒區別,更難頂的是,這書居然還能分章節下載。
這條路也行不通,我的心態發生了一些變化……
就在這時我發現,pdf下載地址和網頁地址高度相似,而且具備確定的對應關系。但是問題也很明顯,就是excel里給的地址是重定向了的,真正的地址只有進入網頁才能獲得。
但是總歸有辦法了,問題就在于重定向,只要我能獲得進入頁面的鏈接,就能夠得到pdf的下載地址。
那么步驟2就變成了……網上bing如何獲得重定向后的網頁地址。復制黏貼修改……
import requests
def get_redirect_url(url):# 獲得重定向前的鏈接#url = "重定向前的url"# 請求頭,這里我設置了瀏覽器代理headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36'}# 請求網頁response = requests.get(url,headers)print(response.status_code) # 打印響應的狀態碼print(response.url) # 打印重定向后的網址# 返回重定向后的網址return response.url
好了,現在我得到了原本的網站鏈接,經過一些修改就可以加入到迅雷下載了。
具體修改放到最后的總函數里,這里就先擱置,然后找到python調用迅雷加入下載任務的方法
然后步驟3:bingpython如何使用迅雷下載……復制黏貼修改……
from win32com.client import Dispatch #這都是啥玩意兒,咱也不知道,咱也不敢問
def thunderDownload(urls): #處理后網址的列表thunder = Dispatch('ThunderAgent.Agent.1') #這個也可以試試'ThunderAgent.Agent64.1',反正我錯了(笑for i in urls:thunder.AddTask(i)thunder.CommitTasks()
最后的總函數:
def url_process(urls):list1 = []num = 0for i in urls:url = i.value #i為cell類,i.value是真正的數據url = get_redirect_url(url) #調用之前的函數,獲得原本的網頁鏈接。url1 = url.replace('book', 'content/pdf')url1 = url1 + '.pdf' #這兩行都是對原來網址的處理,處理之后就可以加入迅雷下載啦。list1.append(url1)print(urls.index(i))if len(list1)>5: #本來預想的是每5個就加入迅雷下載任務,沒想到我還是太年輕了。thunderDownload(list1)list1 = []num += 1print(num)
至此,爬蟲程序基本的框架就完成了。
然鵝,實際運行也是一塌糊涂……動不動就超時,以及來自庫里的報錯……
我嘗試加了一個監測程序運行時間,超時就跳進下一個循環的功能
然鵝……程序總是動不動就癱瘓
開始請求接口
開始執行
請求完成
請求超時
開始請求接口
開始執行
請求完成
請求超時
開始請求接口
開始執行
這是我添加雙線程時間控制程序后……
所以最后的優化是,添加一個功能,超時跳入下一個循環。
嘗試使用eventlet,加monkey_patch()方法后報錯。
于是使用threading庫,增加返回值
import threadingclass MyThread(threading.Thread):def __init__(self, target, args=()): #目標函數super(MyThread, self).__init__()self.func = targetself.args = argsdef run(self):#接收返回值self.result = self.func(*self.args)def get_result(self):#線程不結束,返回值為Nonetry:return self.resultexcept Exception:return Nonedef limit_decor(limit_time): #限制真實請求時間的裝飾器def functions(func):def run(*params):thre_func = MyThread(target=func,args=params)#主線程結束(超出時長),則線程方法結束thre_func.setDaemon(True)thre_func.start()#計算分段沉睡次數(?sleep_num = int(limit_time // 1)sleep_nums = round(limit_time % 1, 1)#多次短暫沉睡并嘗試獲取返回值for i in range(sleep_num):time.sleep(1)infor = thre_func.get_result()if infor:return infortime.sleep(sleep_nums)#最終返回值if thre_func.get_result():return thre_func.get_result()else:return "請求超時"return runreturn functionsdef a1(url):print('開始請求接口')#把邏輯封裝成一個函數,使用線程調用a_theadiing = MyThread(target=a2,args=url)a_theadiing.start()a_theadiing.join()a = a_theadiing.get_result()print("請求完成")print(a)return a,a_theadiing.result@limit_decor(300) #將時長限制為5分鐘,def a2(url):print("開始執行")url = get_redirect_url(url)print("執行完成")return url
修改后的總函數:
def url_process(urls):list1 = []num = 0for i in urls:url = i.value#url = get_redirect_url(url)url = a1([url])[1]if url == "請求超時": continueelse:url1 = url.replace('book', 'content/pdf')url1 = url1 + '.pdf'list1.append(url1)print(urls.index(i))if len(list1)>2:thunderDownload(list1)list1 = []num += 1print(num)
運行還算差強人意,主要也受到網絡狀態的影響。
最后,請不要吐槽我的代碼可讀性(笑,當然也歡迎大佬給出建議。
還有就是知乎這個鬼排版,找到了個markdown在線編輯器,下次試試在那寫完再導入知乎。