hello,大家好啊,失踪人口回归了 [捂脸]!本次使用tkinter撰写一篇 抖音无水印视频下载,目的很纯粹,就是为了设置 微信状态视频。本篇博文中,我会写下我的代码撰写思路以及想写设计流程,代码放在了第四节,工具打包好放在了 蓝奏云,慢慢看,后面有链接。
一.准备工作
本次要用到以下依赖库:re json os random tkinter threading requests pillow 其中后两个需要安装后使用
二.预览
0.复制抖音分享短链接
1.启动
2.运行
3.结果
(小姐姐挺漂亮,视频确实无水印)
三.设计流程
设计流程分为总体设计和详细设计,这里我会使用viso画出几个流程图,用以展示我的思路,详细设计部分列举了两个函数实现的具体流程。
1.总体设计
2.详细设计
2.1 analysis_real_addr()函数
2.2 download_video()函数
四.源代码
4.1 DouYin_Video_Downloader-v2.0.py
#--*coding:utf-8*--
from tkinter import *
from tkinter import ttk
from tkinter import messagebox
import os
import threading
from PIL import Image,ImageTk
from DouYin_Downloader_Engine import Douyin_Engine
'''
抖音无水印视频下载工具
难点:1.视频接口
2.进度条
V2.0改进:
'''
imgs=['./rely/dy_logo.png','./rely/my_favicon.ico']
class App:
def __init__(self):
self.video_path = './video/'
self.window = Tk()
self.window.title('Downloader-v2.0')
width = 295
height = 400
screenWidth = self.window.winfo_screenwidth() # 获取显示区域的宽度
screenHeight = self.window.winfo_screenheight() # 获取显示区域的高度
left = (screenWidth - width) / 2
top = (screenHeight - height) / 2
self.window.geometry("%dx%d+%d+%d" % (width, height, left, top))
self.window.resizable(0, 0)
self.window.iconbitmap(imgs[1])
self.create_widget()
self.set_widget()
self.place_widget()
self.window.mainloop()
def create_widget(self):
self.paned=PanedWindow(self.window)
self.img=imgs
photo = Image.open(imgs[0])
photo = photo.resize((200, 50))
self.paned.image = ImageTk.PhotoImage(photo)
self.l0 = ttk.Label(self.window, imag=self.paned.image, justify='center')
self.f1 = ttk.Labelframe(self.window, text='视频链接地址:')
self.e1 = ttk.Entry(self.f1, width=30)
self.f2 = ttk.Labelframe(self.window, text='信息:')
self.t1 = Text(self.f2, height=6, width=30)
self.b1 = ttk.Button(self.window, text='解析',)
self.b2 = ttk.Button(self.window, text='下载')
self.b3 = ttk.Button(self.window, text='打开文件夹')
self.b4 = ttk.Button(self.window, text='退出')
self.f3 = ttk.Labelframe(self.window)
self.l1 = ttk.Label(self.f3, text=' 敬告:本软件仅供学习交流使用!')
self.f4 = ttk.LabelFrame(self.window, text='下载进度:')
self.canvas = Canvas(self.f4, bg="white")
self.l2_var = StringVar()
self.l2 = ttk.Label(self.f4, textvariable=self.l2_var, text='未下载')
self.m = Menu(self.window)
def place_widget(self):
self.l0.pack()
self.l1.pack(fill=X)
self.f1.place(x=40, y=55)
self.e1.pack()
self.f2.place(x=40, y=102)
self.t1.pack()
self.b1.place(x=40, y=265)
self.b2.place(x=170, y=265)
self.b3.place(x=40, y=298)
self.b4.place(x=170, y=298)
self.f3.place(x=40, y=329)
self.f4.place(x=40, y=210)
self.canvas.pack(side='left')
self.l2.pack(side='left', anchor=S)
def set_widget(self):
self.l1.config( width=30, justify='center', foreground='red')
self.t1.insert(END, '{_xing}\n将抖音分享链接地址粘贴在上面输入框中,本程序会自动解析出视频的下载地址并且显示在这里\n{_xing}'.format(_xing='*' * 29))
self.b1.config( command=lambda: self.thread_it(self.analysis_video))
self.b2.config(state='disable', command=lambda: self.thread_it(self.download_video))
self.b3.config(command=self.open_dir)
self.b4.config( command=self.quit_window)
self.canvas.config(width=210, height=20)
self.window['menu'] = self.m
self.s1 = Menu(self.m, tearoff=False)
self.s2 = Menu(self.m, tearoff=False)
self.s3=Menu(self.m,tearoff=False)
self.s1.add_command(label='打开文件夹', command=self.open_dir)
self.s1.add_separator()
self.s1.add_command(label='退出', command=self.quit_window)
self.s2.add_command(label='解析', command=self.analysis_video)
self.s2.add_command(label='下载', command=self.download_video,state='disable')
self.s3.add_command(label='关于',command=self.show_info)
self.m.add_cascade(label='文件', menu=self.s1)
self.m.add_cascade(label='视频', menu=self.s2)
self.m.add_cascade(label='关于',menu=self.s3)
self.window.protocol("WM_DELETE_WINDOW",self.quit_window)
self.window.bind('<Escape>',self.escape)
self.window.bind('<Return>',lambda :self.thread_it(self.download_video))
def analysis_video(self):
src_link=self.e1.get()
if len(src_link)!=0:
global item
item=Douyin_Engine().analysis_real_addr(src_link)
if item:
self.t1.delete(0.0,END)
self.t1.insert(END,item['video_title']+"\n"+item["video_link"])
self.b2.config(state=NORMAL)
self.s2.entryconfig('下载',state='normal')
else:
messagebox.showwarning('警告','解析失败!')
print(item)
else:
messagebox.showwarning('警告','请输入链接!')
def download_video(self):
try:
os.mkdir(self.video_path)
except:
pass
video_title=item['video_title']
full_path=self.video_path+video_title+'.mp4'
fill_line = self.canvas.create_rectangle(1.5, 1.5, 0, 23, width=0, fill="green")
for n in Douyin_Engine().download_video(full_path,item['video_link']):
self.canvas.coords(fill_line, (0, 0, n, 60))
self.window.update()
self.t1.delete('0.0', END)
self.t1.insert(END, '{title}.mp4\n下载完成!'.format(title=video_title))
# 打开文件夹函数
def open_dir(self):
try:
os.mkdir(self.video_path)
except:
pass
abs_path = os.path.abspath(self.video_path)
os.startfile(abs_path)
def escape(self,event):
self.quit_window()
# 退出窗口函数
def quit_window(self):
ret=messagebox.askyesno('退出','是否要退出?')
if ret:
self.window.destroy()
def show_info(self):
messagebox.showinfo('关于','***本软件仅供学习交流使用!***\n\n将抖音分享链接地址粘贴在输入框中\n解析后显示视频的无水印下载地址')
# 函数打包进线程
def thread_it(self,func, *args):
t = threading.Thread(target=func, args=args)
t.setDaemon(True) # 先守护主线程
t.start() # 再启动
if __name__ == '__main__':
app=App()
'''
test_url: https://v.douyin.com/JcxTMj2/
'''
4.2 DouYin_Downloader_Engine.py
import requests
import json
import re
class Douyin_Engine:
def clean_txt(self, str): # 清洗标题中不能用于命名文件的字符
rstr = r"[\/\\\:\*\?\"\<\>\|]" # '/ \ : * ? " < > |'
str = re.sub(rstr, "_", str) # 替换为下划线
return str
def analysis_real_addr(self,src_link):
item={}
headers = {
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
'sec-fetch-dest': 'document',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.104 Safari/537.36'
}
try:
r=requests.get(src_link,headers=headers,allow_redirects=False)
item_id=re.findall(r'/video/(\d+)/?',r.text)
interface_url='https://www.iesdouyin.com/web/api/v2/aweme/iteminfo/?item_ids={}'.format(''.join(item_id))
r2=requests.get(interface_url,headers=headers)
_json=json.loads(r2.text)
title=_json.get('item_list')[0].get('share_info').get('share_title')
wm_link=_json.get('item_list')[0].get('video').get('play_addr').get('url_list')[0]
item['video_title']=self.clean_txt(title)
item['video_link']=re.sub('playwm','play',wm_link)
return item
except :
return False
def download_video(self,file,link):
headers = {
'sec-fetch-dest': 'document',
'user-agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.104 Mobile Safari/537.36'
}
r=requests.get(link,headers=headers,stream=True)
chunk_size=1024
video_size=int(r.headers['Content-Length'])
raise_data = 210 / (video_size / chunk_size) # 增量大小,210为进度条的长度
with open(file,'wb')as f:
size_ = 0
for data in r.iter_content(chunk_size):
size_+=raise_data
f.write(data)
yield size_
五.说明&总结
5.1说明:
5.2总结:
本次使用python的tkinter撰写了一个抖音无水印视频爬取GUI工具,因为中间写过几个GUI界面了对tkinter的widget有些熟悉了,线程用起来更加熟练。他山之石,可以攻玉,本次思路、代码的撰写参考了:
以上帮助我解决了进度条问题。
以上帮助我解决了视频接口获取问题。
本程序做到了UI界面与爬虫分开。
在此感谢帮助我的人!,工具已经打包上传到了蓝奏云https://wws.lanzoux.com/isCLul0dlfa
, 思路、代码方面有什么不足欢迎各位大佬指正、批评!觉得工具还行的话点个赞呗!