python3GUI--下载抖音小姐姐所有无水印视频(附源码)


大家好,记得之前我写过一篇python3GUI–抖音无水印视频下载工具(附源码)
效果良好,拿到了这么高的下载量。
在这里插入图片描述

这次根据上次的思路和部分改进,使用TKinter制作了一款载抖音用户所有无水印视频GUI,经测试,功能已实现,发此博客记录我的思路与代码。

一.准备工作

Python3.x Pycharm

二.预览

1.启动

刚打开软件是这样的
在这里插入图片描述

2.运行

在用户主页复制分享的用户链接

在这里插入图片描述
复制的链接类似于

https://v.douyin.com/eHfEYLw/

将此链接粘贴到软件的输入框中,点击下载视频,软件就开始解析并下载无水印视频了
在这里插入图片描述

3.查看

通过进度条进度以及Text中的输出,能够知道程序执行的进度,最后查看下载的视频,全部为无水印。
请添加图片描述
哇塞,好多小姐姐,全部无水印
请添加图片描述

扫描二维码关注公众号,回复: 13209040 查看本文章

三.设计流程

1.总体设计

在这里插入图片描述

四.源代码

4.1 Douyin_User_Video_Downloader-v1.0.py

from tkinter import *
from tkinter import ttk
from tkinter import messagebox
import os
import threading
from Download_Engine import Douyin_User_Video_Spider
import re

"""
    test_url
             https://v.douyin.com/eHfEYLw/    #水冰月
        """

class App:
    def __init__(self):
        self.base_dir = './video/'
        self.my_sig='JKc3lQAARLjMfRXooRa1gSSnN4'
        self.sig_item={
    
    '签名1':'JKc3lQAARLjMfRXooRa1gSSnN4','签名2':"1rexVRAciIE-bZMoZ46qv9a3sU"}
        self.create_widget()
        self.set_widget()
        self.place_widget()
        self.window.mainloop()

    def create_widget(self):
        self.window = Tk()
        self.window.title('Douyin_User_Video_Downloader-v1.0')
        width = 600
        height = 700
        screen_width = self.window.winfo_screenwidth()
        screen_height = self.window.winfo_screenheight()
        left = (screen_width - width) / 2
        top = (screen_height - height) / 2
        self.window.geometry("%dx%d+%d+%d" % (width, height, left, top))
        self.window.resizable(0, 0)
        self.l1 = ttk.Label(self.window, text='请输入用户主页链接地址:')
        self.e1 = ttk.Entry(self.window, width=90)
        self.l2 = ttk.Label(self.window, text='当前状态:')
        self.t1 = Text(self.window, width=80, height=30)
        self.l3 = ttk.Label(self.window, text='当前下载进度:')
        self.progress=ttk.Progressbar(self.window,orient=HORIZONTAL,length=460,mode='determinate',value=0,maximum=100)
        self.l4_var = StringVar()
        self.l4_var.set('0.0%[未下载]')
        self.l4 = ttk.Label(self.window, textvariable=self.l4_var)
        self.b1 = ttk.Button(self.window, text='开始下载', command=lambda: self.thread_it(self.download_videos))
        self.m = Menu(self.window)
        self.window['menu'] = self.m

    def set_widget(self):
        self.s1 = Menu(self.m, tearoff=False)
        self.s2 = Menu(self.m, tearoff=False)
        self.s3 = Menu(self.m, tearoff=False)
        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.s1.add_command(label='打开文件夹', command=self.open_dir)
        self.s1.add_command(label='退出', command=self.quit_window)
        self.s2.add_command(label='自定义签名', command=self.set_sig)
        self.s2.add_command(label='下载视频', command=lambda: self.thread_it(self.download_videos))
        self.s3.add_command(label='联系作者', command=self.connect_author)
        self.window.protocol('WM_DELETE_WINDOW', self.quit_window)
        self.window.bind('<Escape>', self.escape)
        self.e1.bind('<Return>', self.enter)

    def place_widget(self):
        self.l1.pack(anchor="w")
        self.e1.pack(anchor="w", padx=20)
        self.l2.pack(anchor="w")
        self.t1.pack(anchor="w", padx=20)
        self.l3.pack(anchor="w")
        self.progress.pack()
        self.l4.pack()
        self.b1.pack()

    def download_videos(self):
        share_link=self.e1.get()
        if share_link.strip().startswith(r'https://v.douyin.com/'):
            try:
                if sig_:
                    the_sig=sig_
                else:
                    the_sig=self.my_sig
            except NameError:
                the_sig = self.my_sig
            spider = Douyin_User_Video_Spider(share_link)
            whole_url = spider.get_whole_link(the_sig)
            self.s2.entryconfig('下载视频',state=DISABLED)
            self.b1.config(state=DISABLED)
            self.t1.insert(END,'正在解析视频......\n')
            spider.get_video_data(whole_url)
            videos =spider.videos_list
            if videos:
                self.t1.insert(END,'解析完成,开始下载......\n')
                for item in videos:
                    no = int(self.t1.index('end').split('.')[0]) - 3
                    new_name = ''.join(re.findall('[\u4E00-\u9FA5\s]+', item["video_title"]))  # 去除标题中的表情字符,因为表情字符插入到Text控件中会产生TclError
                    self.t1.insert(END, f'[{
      
      no}]{
      
      new_name}.mp4')
                    self.t1.see(END)
                    for progress,speed in spider.download_video(self.base_dir,item):
                        self.l4_var.set(f'进度:%.1f%% 速度:%s'%(progress,speed))
                        self.progress['value']=int(progress)
                        self.progress.update()
                    self.t1.insert(END, f'  ----->完成\n')
                self.t1.insert(END, f'所有视频下载完成!\n')
                self.b1.config(state=NORMAL)
                self.s2.entryconfig('下载视频',state=NORMAL)
            else:
                messagebox.showerror('错误','没有解析出视频,请检查签名是否可用!')
        else:
            messagebox.showwarning('警告','请输入正确的分享链接!')
            self.e1.delete(0,END)

    def open_dir(self):
        try:
            os.makedirs(self.base_dir)
        except:
            pass
        abs_path = os.path.abspath(self.base_dir)
        # 使用绝对路径打开文件夹
        os.startfile(abs_path)

    def quit_window(self):
        ret = messagebox.askyesno('提示', '是否要退出?')
        if ret == True:
            self.window.destroy()

    def escape(self,event):
        self.quit_window()

    def connect_author(self):
        messagebox.showinfo('联系作者', '作者QQ:懷淰メ')

    def e1_clear(self):
        self.e1.delete(0, END)

    def enter(self,event):
        self.thread_it(self.download_videos)

    def set_sig(self):
        def set_the_sig():
            try:
                global sig_
                sig_ = self.sig_item[combobox.get()]
                messagebox.showinfo('提示', '签名设置成功!')
                self.set_sig_window.destroy()
            except KeyError:
                def_sig=self.sig_window_combox_var.get()
                if len(def_sig)!=0:
                    messagebox.showinfo('提示', '自定义签名设置成功!')
                else:
                    messagebox.showwarning('警告','自定义签名失败,将使用默认签名!')
                    self.set_sig_window.destroy()
        self.set_sig_window = Toplevel(self.window)
        set_sig_window_width = 200
        set_sig_window_height = 50
        set_sig_window_screen_left = self.set_sig_window.winfo_screenwidth()
        set_sig_window_screen_top = self.set_sig_window.winfo_screenheight()
        set_sig_window_left = (set_sig_window_screen_left - set_sig_window_width) / 2
        set_sig_window_top = (set_sig_window_screen_top - set_sig_window_height) / 2
        self.set_sig_window.geometry(
            '%dx%d+%d+%d' % (set_sig_window_width, set_sig_window_height, set_sig_window_left, set_sig_window_top))
        self.sig_window_combox_var=StringVar()
        combobox= ttk.Combobox(self.set_sig_window,textvariable=self.sig_window_combox_var,value=[item for item in self.sig_item])
        combobox.current(0)
        combobox.pack(side='left',)
        s_b1 = ttk.Button(self.set_sig_window, text='设置', command=set_the_sig)
        s_b1.pack(side='left')
        self.set_sig_window.protocol('WM_DELETE_WINDOW',self.sig_window_show_warn)

    def sig_window_show_warn(self):
        messagebox.showwarning('警告', '签名未更改,将使用默认签名!')
        self.set_sig_window.destroy()

    def thread_it(self,func, *args):
        t = threading.Thread(target=func, args=args)
        self.window.update()
        t.setDaemon(True)  # 设置守护,主线程结束,子线程结束
        t.start()

if __name__ == '__main__':
    App()

4.2 Download_Engine.py

import requests
import re
from urllib.parse import urlencode
import json
import time
from requests.adapters import HTTPAdapter
import os

class Douyin_User_Video_Spider(object):

    def __init__(self,share_link):
        self.link=share_link
        self.videos_list=list()

    def get_whole_link(self,sig):
        #sig = 'JKc3lQAARLjMfRXooRa1gSSnN4'
        # sig = '1rexVRAciIE-bZMoZ46qv9a3sU'    #示例签名2
        headers={
    
    
            '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(self.link,headers=headers,allow_redirects=False)
        #sec_uid由大小写字母和数字组成
        sec_uid=''.join(re.findall('sec_uid=(\w+)&',r.text))
        api_url='https://www.iesdouyin.com/web/api/v2/aweme/post/?'
        params={
    
    
            'sec_uid':sec_uid,
            'count':21,
            'max_cursor':0,
            'aid':1128,
            '_signature':sig,
            'dytk':''
                }
        #拼凑出完整url
        whole_url=api_url+urlencode(params)
        return whole_url

    #访问视频list接口,获取视频列表
    def get_video_data(self,whole_url):
        headers={
    
    
            'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
            'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8',
            'pragma': 'no-cache',
            'cache-control': 'no-cache',
            'upgrade-insecure-requests': '1',
            'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1',
        }
        r=requests.get(whole_url,headers=headers)
        _json=json.loads(r.text)
        next_cursor=_json.get('max_cursor')
        try:
            aweme_list=_json['aweme_list']
            if len(aweme_list)==0:
                return self.videos_list
            else:
                self.author_name = aweme_list[0].get('author').get('nickname')
                for data in aweme_list:
                    item={
    
    }
                    # id值
                    video_id = data['video'].get('vid')
                    # 视频简介
                    item['video_title'] = data['desc']
                    item['download_link'] = f'https://aweme.snssdk.com/aweme/v1/play/?video_id={
      
      video_id}'
                    self.videos_list.append(item)
                if next_cursor != 0:
                    # !!!绕弯了半天!!!
                    next_url = re.sub(r'max_cursor=\d+', f'max_cursor={
      
      next_cursor}', whole_url)
                    self.get_video_data(next_url)
                else:
                    return  self.videos_list
        except KeyError:
            pass

    def download_video(self,base_dir,item):
        start_time=time.time()
        #自定义分隔符 *!;-(
        pre_filename=item['video_title']
        #去除文件命名中不允许出现的特殊字符
        filename=re.sub('\?|?|/|、|(\*)|"|(\|)|<|>|:','_',pre_filename)
        #new_name=''.join(re.findall(r'[\u4E00-\u9FA5\s]+',filename))#去除标题中的表情字符,因为表情字符插入到Text控件中会产生TclError
        video_link=item['download_link']
        #通过索引获取行数
        #一定要用手机的UA
        headers={
    
    
            'Connection':'keep-alive',
            'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1'
        }
        #由于可能会产生[WinError 10060] 由于连接方在一段时间后没有正确答复或连接的主机没有反应,连接尝试失败。'))
        #错误,所以加入了超时时间,(超时时间=请求时间+响应时间)
        full_dir_path=base_dir+self.author_name+'/'
        try:
            os.makedirs(full_dir_path)
        except:
            pass
        try:
            s = requests.session()
            s.keep_alive = False
            s.mount('https://', HTTPAdapter(max_retries=5))
            r=requests.get(video_link,headers=headers,stream=True,timeout=30)
            file_size=int(r.headers['Content-Length'])
            chunck_size=1024
            size_=0
            with open(full_dir_path+filename+'.mp4','wb')as f:
                for data in r.iter_content(chunck_size):
                    f.write(data)
                    size_+=len(data)
                    progress=float(size_/file_size*100)   #当前下载百分比
                    speed=self.format_size((size_)/(time.time()-start_time))+'/S'
                    yield progress,speed
        except:
            pass

    def format_size(self,bytes):
        try:
            bytes = float(bytes)
            kb = bytes / 1024
        except:
            return "Error"
        if kb >= 1024:
            M = kb / 1024
            if M >= 1024:
                G = M / 1024
                return "%.2fG" % (G)
            else:
                return "%.2fM" % (M)
        else:
            return "%.2fK" % (kb)

五.总结

本次使用Tkinter制作了一款GUI软件,实现了对抖音用户视频的去水印下载,代码放在了蓝奏云
在撰写代码时,认为软件还有待改进
1.加入多线程并发下载
2.没有做到边解析,边下载
3.签名稳定性未知

当然,此软件还存在一些亮点
1.首次将Progressbar加入到GUI界面中(这个组件是真的好用!)
2.快捷键操作,支持Esc,Enter等快捷键(快捷键操作真方便!)
3.支持自定义签名(自定义签名真灵活!)
4.GUI与爬虫代码分离开来,结构清晰(代码清晰真易懂!)

思路、代码方面有什么不足欢迎各位大佬指正、批评!
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/a1397852386/article/details/117651807