前言
最近在学习进阶的python,学到tkinter,刚好在内网有一个需求,需要一个能根据一大批pdf下载链接,批量下载pdf文件的工具,当然网上也有很多类似功能的成熟软件。不过,毕竟是学过python的哈哈哈哈哈,还是要躁动一下的,所以写了一个pdf批量下载的小工具。
这篇文章将会学到的知识点:
1️⃣asynico异步下载方法,可用于提高爬虫效率;
2️⃣tkinter的初级功能部件:Label(标签),Button(按钮),主窗口关闭(root),filedialog(打开文件窗口),askdirectory(保存文件路径窗口),text部件,Entry部件的使用,窗口显示正中间的方法。
3️⃣爬虫下载时候强制跳过签名验证。
python环境:win10 64位环境,python3.83版本的。
零、设计思路
核心的代码其实是使用了asynico的异步功能,肥肠非常强大,💪质的飞跃可以说💪,可以看下使用asynico和不使用asynico的区别👇
❗️❗️这是使用asynico的下载10个pdf文件只用2.3秒。
❕ ❕这是不使用asynico的情况,下载同样的10个pdf文件,用时12秒。
当时被这个速度感动到了😂😂,所以下定决心一定要掌握这个技能。💪💪
主要的逻辑和实现效果如下:
主要的逻辑:
实现效果:
如果想了解学习这个异步功能就请继续往下看吧。💪
一、核心代码
代码来说,难度可以说是很低了,非常适合python的初级练习项目。主要的代码有两个:
(一)tkinter框架代码
import tkinter as tk
from tkinter import filedialog
from tkinter import messagebox
import time
def save_file():
global store_path
store_path = filedialog.askdirectory()
# 创建主窗口
root = tk.Tk()
# 下载器的名称
root.title('PDF链接批量下载工具')
# 进行居中显示
max_w, max_h = root.maxsize()
root.geometry(f'300x150+{int((max_w - 300) / 2)}+{int((max_h - 150) / 2)}') # 居中显示
# 固定窗口尺寸不能修改
root.resizable(width=False, height=False)
# 创建输入窗
entry1 = tk.Entry(root)
entry1.place(relx=0.5, rely=0.5, width=100, anchor='center')
# 选择文件标签
lab1 = tk.Label(root, text='选择文件:')
lab1.place(relx=0.35, rely=0.5, anchor='e')
# 创建打开文件函数
def openFile():
global text_path
text_path = filedialog.askopenfilename(filetypes=[('文本', '.txt')])
# 关闭之前的窗口
root.destroy()
# 创建新窗口
root1 = tk.Tk()
max_w1, max_h1 = root1.maxsize()
root1.geometry(f'+{int((max_w1 - 600) / 2)}+{int((max_h1 - 500) / 2)}') # 居中显示
# 创建scrollbar
sb = tk.Scrollbar(root1)
sb.grid(column=3, sticky='NS')
# 创建text窗口
text = tk.Text(root1, yscrollcommand=sb.set) # yscrollcommand绑定sb的设置
text.grid(row=0, column=0, columnspan=3)
sb.config(command=text.yview)
f = open(text_path, 'r', encoding='utf-8')
text.insert('end', f.read())
f.seek(0) # 光标移到开始位置
# 文件个数
global pdf_num
pdf_num = len(f.readlines())
# 创建标签
lb = tk.Label(root1, text=f'总共有{pdf_num}个文件。')
lb.grid(row=1, column=0)
# 选择下载的位置
button0 = tk.Button(root1, text='选择保存位置', command=save_file)
button0.grid(row=1, column=1, pady=3)
# 创建确认按钮
button1 = tk.Button(root1, text='确认开始下载', width=10, command=run_async) # <1>总控制函数
button1.grid(row=1, column=2, pady=3)
root1.mainloop()
# 创建获取文件路径按钮
b1 = tk.Button(root, text='获取文件', command=openFile)
b1.place(relx=0.7, rely=0.5, anchor='w')
tk.mainloop()
可以看到,在 <1> 处缺少一个总的下载控制函数的定义,接下来就是关于使用asyncio进行异步下载的的代码了:
(二)使用asyncio下载的代码
这里需要稍微解释一下asyncio异步下载的原理:(个人理解)
顺序下载: 一般来说,我们就直接通过请求->反馈->下载存储这样的顺序来批量下载pdf,然后加一个循环进行线性下载,平时都是这样的,有个缺点就是,当你的某一个文件因为太大,在I/O阶段就有可能被阻塞,然后就会一直等待,直到完成存储进行下一个下载任务。
ok,那你怎么提高下载的效率呢?所以我们就会有三个解决方案:
①使用多线程threading;既然一个下载线程慢,那就搞多几个嘛;
②异步下载,就是既然卡在那里,那我就不等你了,我继续下载其他文件,等你下载好了再告诉我一声,回来再下载,需要注意的是:异步下载是无序的。
③多线程+异步,这个简直是下载和执行任务的王炸手段。还没掌握。。。
这里主要学习第二个方法:异步下载
import asyncio # 异步下载包也就是协程函数
import aiohttp
# 创建下载函数
async def pdfdownload(session, pdfurl, name, filespath):
# 因为你在下载的时候会遇到一些验证或者警告的提示,你可以使用verify_ssl=False语句进行强制不显示该警告
async with session.get(pdfurl, verify_ssl=False) as re:
with open(f'{filespath}' + f'{name}' + ".pdf", 'wb') as f1:
while True:
chunk = await re.content.read(1024)
if not chunk:
break
f1.write(chunk)
# 创建开始下载函数
async def start_download():
s1 = time.time()
async with aiohttp.ClientSession() as session:
f1 = open(text_path, 'r')
tar_path = store_path + '/'
new_urls = [each_url.replace('\n', '') for each_url in f1.readlines()]
# 文件命名我设置成下载链接的最后4位数字了,如果你有其他需求的,这里就可以做其他定制命名功能啦~
task = [asyncio.create_task(pdfdownload(session, pdfurl=each_url, name=each_url[-8:-4], filespath=tar_path)) for each_url in new_urls]
done, pending = await asyncio.wait(task)
end_time = round(time.time() - s1, 2)
mb = messagebox.showinfo(title='pdf批量下载器', message=f'全部下载完毕,共用时{end_time}秒。')
# 总控制函数,因为按钮的command不能调用上述的被async修饰过的函数,所以必须另起一个总控制函数进行调用。
def run_async():
asyncio.run(start_download())
二、完整代码
完整代码会跟上面的顺序有点不一样,注意区别,你如果直接使用的话,那就直接使用完整代码即可。
# encoding = utf-8
# 使用异步下载方法测试速度
import tkinter as tk
from tkinter import filedialog
from tkinter import messagebox
import time
import asyncio # 异步下载包也就是协程函数
import aiohttp
def save_file():
global store_path
store_path = filedialog.askdirectory()
# 创建下载函数
async def pdfdownload(session, pdfurl, name, filespath):
# 因为你在下载的时候会遇到一些验证或者警告的提示,你可以使用verify_ssl=False语句进行强制不显示该警告
async with session.get(pdfurl, verify_ssl=False) as re:
with open(f'{filespath}' + f'{name}' + ".pdf", 'wb') as f1:
while True:
chunk = await re.content.read(1024)
if not chunk:
break
f1.write(chunk)
# 创建主窗口
root = tk.Tk()
root.title('PDF链接批量下载工具')
max_w, max_h = root.maxsize()
root.geometry(f'300x150+{int((max_w - 300) / 2)}+{int((max_h - 150) / 2)}') # 居中显示
# 固定窗口尺寸不能修改
root.resizable(width=False, height=False)
# 创建输入窗
entry1 = tk.Entry(root)
entry1.place(relx=0.5, rely=0.5, width=100, anchor='center')
# 创建开始下载函数
async def start_download():
s1 = time.time()
async with aiohttp.ClientSession() as session:
f1 = open(text_path, 'r')
tar_path = store_path + '/'
new_urls = [each_url.replace('\n', '') for each_url in f1.readlines()]
task = [asyncio.create_task(pdfdownload(session, pdfurl=each_url, name=each_url[-8:-4], filespath=tar_path)) for each_url in new_urls]
done, pending = await asyncio.wait(task)
end_time = round(time.time() - s1, 2)
mb = messagebox.showinfo(title='pdf批量下载器', message=f'全部下载完毕,共用时{end_time}秒。')
def run_async():
asyncio.run(start_download())
# 创建打开文件函数
def openFile():
global text_path
text_path = filedialog.askopenfilename(filetypes=[('文本', '.txt')])
# 关闭之前的窗口
root.destroy()
# 创建新窗口
root1 = tk.Tk()
max_w1, max_h1 = root1.maxsize()
root1.geometry(f'+{int((max_w1 - 600) / 2)}+{int((max_h1 - 500) / 2)}') # 居中显示
# 创建scrollbar
sb = tk.Scrollbar(root1)
sb.grid(column=3, sticky='NS')
# 创建text窗口
text = tk.Text(root1, yscrollcommand=sb.set) # yscrollcommand绑定sb的设置
text.grid(row=0, column=0, columnspan=3)
sb.config(command=text.yview)
f = open(text_path, 'r', encoding='utf-8')
text.insert('end', f.read())
f.seek(0) # 光标移到开始位置
# 文件个数
global pdf_num
pdf_num = len(f.readlines())
# 创建标签
lb = tk.Label(root1, text=f'总共有{pdf_num}个文件。')
lb.grid(row=1, column=0)
# 选择下载的位置
button0 = tk.Button(root1, text='选择保存位置', command=save_file)
button0.grid(row=1, column=1, pady=3)
# 创建确认按钮
button1 = tk.Button(root1, text='确认开始下载', width=10, command=run_async)
button1.grid(row=1, column=2, pady=3)
root1.mainloop()
# 选择文件标签
lab1 = tk.Label(root, text='选择文件:')
lab1.place(relx=0.35, rely=0.5, anchor='e')
# 创建获取文件路径按钮
b1 = tk.Button(root, text='获取文件', command=openFile)
b1.place(relx=0.7, rely=0.5, anchor='w')
tk.mainloop()
三、打包成exe文件
python代码打包成exe执行文件的时候,一定要创建虚拟环境后再进行打包,不然你的exe执行文件将会大的无法想象,因为用Pyinstaller打包,将会将你安装过的所有包都一起打包,造成你的执行文件exe贼大。
我觉得怎么将python程序打包成exe文件的流程看这篇文章就够了:打包python程序
希望大家都可以学得快乐, over~
exe文件自取:链接:https://pan.baidu.com/s/1RZJYqrTdWJnfzBwYNXSkCA
提取码:hikd