首先声明,本文旨在记录反思,并没有资源,代码也不具有借鉴意义(水平实在不行.
某天,水群的时候发现群友发了一个文件,里面是疫情时期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在线编辑器,下次试试在那写完再导入知乎。