Tkinter语法及案例&pyinstaller打包

一、概述:

当前流行的计算机桌面应用程序大多数为图形化用户界面(Graphic User Interface,GUI),即通过鼠标对菜单、按钮等图形化元素触发指令,并从标签、对话框等图型化显示容器中获取人机对话信息。
Python自带了tkinter 模块,实质上是一种流行的面向对象的GUI工具包 TK 的Python编程接口,提供了快速便利地创建GUI应用程序的方法。其图像化编程的基本步骤通常包括:
  • 导入 tkinter 模块
  • 创建 GUI 根窗体
  • 添加人机交互控件并编写相应的函数。
  • 在主事件循环中等待用户触发事件响应。
备注:1.Python比较适合服务端开发、科学计算、爬虫、自动化测试、数据分析,对于GUI不是强项不要花费太多精力在GUI上。
          2.python常用的GUI库有:wxPython、PyQt、自带的tkinter
 
 

二、名词解释:

1.Label 组件:用于显示文本和图像,你可以指定想要显示的内容(可以是文本、位图或者图片)

2.Text组件:多行文本框,可以显示详细数据

3.Entry组件:单行文本框,主要用户输入框(接收用户输入数据)

4.Message组件:与Label组件类似,但是可以根据自身大小将文本换行;

5.Button组件:按钮点击动作,用户提交释放键盘事件(一般需要调用函数完成功能)

 

三、语法代码:

 1.Text文本框颜色+背景+字体

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from tkinter import *
import tkinter as tk
import tkinter.font as tf
root = tk.Tk()##设置窗口
root.geometry('300x400')#窗口宽高
root.title('测试')#窗口名称


###_______________________________________________通过for循环设置颜色、背景等_________________________________________________
#设置Text文本框
text = tk.Text(root,width=20,height=20)
text.pack()
char = ['你好','你好呀','你今天真好看','谢谢'] #插入到text中
####更改部分##########
#font字体样式,大小等
#background 背景色
#foreground字体颜色
for i in range(4):
    if i%2 == 0 :
        a = str(i+1)+'.0'
        text.insert(a,char[i]+'\n','tag') #申明使用tag中的设置
        print(a,char[i])#打印位置和值
    else :
        a1 = str(i+1)+'.0'
        text.insert(a1,char[i]+'\n','tag_1') #申明使用tag__1中的设置
print(a)
print(a1)

ft = tf.Font(family='微软雅黑',size=10) ###size表示字体大小
#text.tag_add('tag',a ) #申明一个tag,在a位置使用(在for循环中已经定义了,这句可以注释掉)
text.tag_config('tag',font =ft ,foreground='red') #设置tag即插入文字的大小,颜色等
#text.tag_add('tag_1',a1)
text.tag_config('tag_1',foreground = 'blue',background='pink',font = ft)

root.mainloop()

###______________________________________________简单方式设置字体颜色和背景________________________________________________ ####更改部分########## #font字体样式,大小等 #background 背景色 #foreground字体颜色 #设置Text文本框 text = tk.Text(root,width=20,height=20) text.pack() text.insert(1.0,'第一行jdjkdjkdjkdjkdjkdjkjd'+'\n','tag') #申明使用tag中的设置 text.insert(2.0,'第二行jdjkdjkdjkdjkdjkd88888888888jkjd\n') #申明使用tag中的设置 text.insert(3.0,'第三行jdjkdjkdjkdj333333333kdjkd88888888888jkjd'+'\n') #申明使用tag中的设置 text.insert(3.0,'第四行jdjk——哈哈哈哈哈哈d'+'\n','chen') #申明使用tag中的设置 ft = tf.Font(family='微软雅黑',size=10) ###size表示字体大小 text.tag_config('tag',font =ft ,foreground='red') #设置tag即插入文字的大小,颜色等 #font=ft表示设置ft的参数 text.tag_config('chen',font =ft ,foreground='Violet',background='Gray') #设置chen即插入文字的大小,颜色等 root.mainloop()
#____________________________________________最简单Text文本_________________________________________________________________ text1 = Text(root) # INSERT索引表示在光标处插入 text1.insert("insert", "大哥你好!") text1.insert("insert", "我要吃饭") # END索引号表示在最后插入 text1.insert("end", ",还想喝鸡蛋汤") text1.pack() root.mainloop()

 

 2.Label组件和窗口设置

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from tkinter import *
import tkinter as tk
from PIL import Image, ImageTk


###______________窗口设置部分________________
window = tk.Tk()#设置矿口
window.wm_title("工位防呆监控v2.3") #标题
window.config(background="#00FFCC")#背景色
# window.geometry('600x400')#只指定窗口大小,不指定位置
window.geometry("600x400+900+500")#指定窗口大小同时指定x和y坐标(宽x高+ 位置x坐标 +位置y坐标)


###设置Label标签(fg是字体颜色,  bg是背景颜色, font是文字大小和字体),,place中x,y表示窗口x和y坐标
tk.Label(window, text="这是文字描述1", font=("黑体", 13), width=25, height=5, fg="black", bg="white").place(x=10, y=25,anchor='nw')

##设置Label标签, anchor组件的对齐方式(顶对齐'n',底对齐's',左'w',右'e'), side组件在主窗口的位置(top上, bottom下, left左,  right右)
tk.Label(window, text="这是描述描述2", font=("黑体", 13), width=20, height=5,fg="Magenta", bg="white").pack(side='top',anchor='e')

###Label为图片并指定图片大小(这里是图片压缩不是剪切)
photo = Image.open("test.png")
photo = photo.resize((100,100))  #规定图片大小
img0 = ImageTk.PhotoImage(photo)
img1 = tk.Label(text="照片:",image=img0)#把图片整合到标签类中
img1.pack(side='bottom',anchor='w')#底部对齐,靠左

###Label为图片并指定图片大小(这里是剪切图片)
photo = tk.PhotoImage(file="test.png")#file:t图片路径
imgLabel = tk.Label(window,image=photo,width=100, height=130)#把图片整合到标签类中,指定图片宽*高
imgLabel.pack(side='bottom')#自动对齐


#Message与Label组件类似,但是Message可以根据自身大小将文本换行;
msg1 =Message(window,text='''我的水平起始位置相对窗体 0.2,垂直起始位置为绝对位置 80 像素,我的高度是窗体高度的0.4,宽度是200像素''',relief=GROOVE)
##msg1 =Label(window,text='''我的水平起始位置相对窗体 0.2,垂直起始位置为绝对位置 80 像素,我的高度是窗体高度的0.4,宽度是200像素''',relief=GROOVE)
msg1.place(relx=0.2,y=80,relheight=0.4,width=200)
window.mainloop()



if __name__ == "__main__":
    window.mainloop()#消息循环体(启动GUI)

 

 3.Entry文本框

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from tkinter import *
import tkinter as tk
'''
1、这个程序实现文本框输入。
2、使用grid方法按照Excel表格方式对组件位置进行安排
3、通过Button提交按钮实现获取用户的输入信息。
'''
root = Tk()
root.geometry('300x400')#窗口宽高

###______________________________ Label标签_ Entry文本框(单行)_Text文本框(多行)______________________
###Label创建标签
Label1 = Label(root,text='会员名称:').grid(row=0,column=0)
Label2 = Label(root,text='会员代号:').grid(row=1,column=0)
Label3 = Label(root,text='设置多文本框:').grid(row=2,column=0)

#StringVar是可变字符串,get()和set()是得到和设置其内容
v1 = StringVar()
p1 = StringVar()

# Entry创建单行文本
e1 = Entry(root,textvariable=v1)    # Entry 是 Tkinter 用来接收字符串等输入的控件.
e2 = Entry(root,textvariable=p1,show='')#textvariable可变文本,与StringVar等配合着用
e1.grid(row=0,column=1,padx=10,pady=5)  #设置输入框显示的位置,以及长和宽属性
e2.grid(row=1,column=1,padx=10,pady=5)

#设置Text文本框
text = tk.Text(root,width=20,height=10)
test_var='''天地初开之前,天地万物混为一体
宇宙为无边无际得浑沌之气
在那混沌之中孕育着宇宙之中唯一的一个生灵——盘古
'''
text.insert("insert", test_var)#插入数据到文本框
text.grid(row=4,column=1)#row组件在行位置,column组件在列的位置

mainloop()

 

 4.Button按钮

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from tkinter import *
import tkinter as tk
root = Tk()
root.geometry('300x400')#窗口宽高


###______________________________ ____________Button按钮提交(重点)_______________________________________
###Label创建标签
Label1 = Label(root,text='会员名称:').grid(row=0,column=0)
Label2 = Label(root,text='会员代号:').grid(row=1,column=0)

#StringVar是可变字符串,get()和set()是得到和设置其内容
v1 = StringVar()
p1 = StringVar()
# Entry创建单行文本
e1 = Entry(root,textvariable=v1)    # Entry 是 Tkinter 用来接收字符串等输入的控件.
e2 = Entry(root,textvariable=p1,show='')#textvariable可变文本,与StringVar等配合着用
e1.grid(row=0,column=1)  #设置输入框显示的位置,以及长和宽属性
e2.grid(row=1,column=1)

def show():
    print("会员名称:%s"% e1.get())  # 获取用户输入的信息
    print("会员代号:%s"% e2.get())
    #在函数内创建label1,按钮点击后才会触发
    label1=tk.Label(root,text='用户名:{},密码:{}'.format(e1.get(),e2.get()),fg='blue',bg='green',width=20,height=2)
    label1.grid(row=3,column=0)

#创建按钮(command指定按钮消息的回调函数),row起始行,column起始列
Button(root,text='验证信息',width=10,command=show).grid(row=2,column=0,sticky=S,padx=10,pady=5)
Button(root,text='退出',width=10,command=root.quit).grid(row=2,column=1,sticky=E,padx=10,pady=5)

mainloop()

###___________________________________________Button按钮布局__________________________________________________
# 创建按钮1
btn1 = Button(root, text='我在0,0')
# 创建按钮2
btn2 = Button(root, text='我在1,0')
# 创建按钮3
btn3 = Button(root, text='我要跨行')
# 创建按钮4
btn4 = Button(root, text='我在0,1')
# 加入窗口
#     row 设置行数
#     column 设置列数
btn1.grid(row=0, column=0)
btn2.grid(row=1, column=0)
btn3.grid(row=1, column=1, rowspan=10,ipady=10)
btn4.grid(row=0, column=1)

btn5 = Button(root, text='我在2,0')
btn5.grid(row=2, column=0)

mainloop()

###_______________________________Button和Entry和Label__________________________________________________________
Label(root, text="用户名").grid(row=0, sticky=W)
Label(root, text="密码").grid(row=1, sticky=W)

Entry(root).grid(row=0, column=1, sticky=E)
Entry(root).grid(row=1, column=1, sticky=E)

Button(root, text="Cancel").grid(row=2, column=0, sticky=E)
Button(root, text="Login").grid(row=2, column=1, sticky=E)

root.mainloop()

###____________________________________Button与函数交互______________________________________________________
# 设置回调函数
def callback():
    print ("你好欢迎调用函数")

# 使用按钮控件调用函数
b = tk.Button(root, text="点击执行回调函数", command=callback).pack()
# 显示窗口
tk.mainloop()



################################################_Button交互弹窗 ########################################################
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import tkinter as tk
from tkinter import messagebox

window = tk.Tk()
# 设置窗口的标题
window.title('C语言中文网')
# 设置并调整窗口的大小、位置
window.geometry('400x300+300+200')
# 当按钮被点击的时候执行click_button()函数
def click_button():
    # 使用消息对话框控件,showinfo()表示温馨提示
    messagebox.showinfo(title='温馨提示', message='欢迎使用C语言中文网')

# 点击按钮时执行的函数
button = tk.Button(window,text='点击前往',bg='#7CCD7C',width=20, height=5,command=click_button).pack()

# 消息循环-显示窗口
window.mainloop()

 

 5.Combobox下拉列表

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import tkinter
from tkinter import ttk
from tkinter import messagebox

win = tkinter.Tk()
win.title("yudanqu")
win.geometry("400x400+200+50")

###______________________________单一的下拉框________________________________________
Label1 = ttk.Label(win,text='选择城市:').place(x=12, y=12,anchor='nw')

# 创建下拉框并绑定变量
cv = tkinter.StringVar()
com = ttk.Combobox(win, textvariable=cv)#创建下拉框,绑定cv变量
com.place(x=75, y=10)

# 设置下拉数据
com["value"] = ("黑龙江", "吉林", "辽宁")
com.current(0)# 设置默认值(可以为空)

# 绑定事件
def func(event):
    print(com.get())
    #print(cv.get())
com.bind("<<ComboboxSelected>>", func)#选择下拉框后调用func函数

win.mainloop()


###______________________________下拉框和Entry、Text  ____________________________________
Label1 = ttk.Label(win,text='选择城市:').place(x=12, y=20,anchor='nw')
Label2 = ttk.Label(win,text='选择兴趣:').place(x=12, y=50,anchor='nw')
Label2 = ttk.Label(win,text='输入密钥:').place(x=12, y=80,anchor='nw')

# 创建下拉框-并绑定变量
cv = tkinter.StringVar()
com = ttk.Combobox(win, textvariable=cv)#创建下拉框,绑定cv变量
com.place(x=75, y=20)
# 设置下拉数据
com["value"] = ("黑龙江", "吉林", "辽宁")
com.current(0)# 设置默认值(可以为空)

# 创建下拉框-并绑定变量
cv1 = tkinter.StringVar()
com1 = ttk.Combobox(win, textvariable=cv1)#创建下拉框,绑定cv变量
com1.place(x=75, y=50)#布局也可写在一行
# 设置下拉数据
com1["value"] = ("看美女", "美食", "吃喝嫖赌","打架")
com1.current(0)# 设置默认值(可以为空)

#创建Entry文本框,并绑定变量v1
v1 = tkinter.StringVar()
e1 = ttk.Entry(win,textvariable=v1,width=23)
e1.place(x=72, y=80)

#通过按钮调用
def show():
    print("会员名称:%s"% e1.get())  # 获取用户输入的信息

# 绑定事件
def func(event):
    print(com.get())
    #弹窗提示
    messagebox.showinfo(title='温馨提示', message='欢迎来到python世界!')
    #print(cv.get())
com.bind("<<ComboboxSelected>>", func) #选择下拉框后调用func函数
com1.bind("<<ComboboxSelected>>", func) #选择下拉框后调用func函数

# #创建按钮(command指定按钮消息的回调函数),row起始行,column起始列
ttk.Button(win,text='验证信息',width=10,command=show).grid(padx=20,pady=120)
ttk.Button(win,text='退出',width=10,command=win.quit).place(x=150, y=120)

win.mainloop()


###_________________________________________下拉框数据依赖______________________________________________
'''
下拉框2通过下拉框1选择的数据得来
'''
Label1 = ttk.Label(win,text='下拉框一:').place(x=12, y=20,anchor='nw')
Label2 = ttk.Label(win,text='下拉框二:').place(x=12, y=50,anchor='nw')

# 创建下拉框1-并绑定变量
cv = tkinter.StringVar()
com = ttk.Combobox(win, textvariable=cv)#创建下拉框,绑定cv变量
com.place(x=75, y=20)
# 设置下拉数据
com["value"] = ("黑龙江", "吉林", "辽宁")
com.current(0)# 设置默认值(可以为空)

# 创建下拉框2-并绑定变量
cv1 = tkinter.StringVar()
com1 = ttk.Combobox(win, textvariable=cv1)#创建下拉框,绑定cv变量
com1.place(x=75, y=50)#布局也可写在一行
# 设置下拉数据
com1.current()# 设置默认值(可以为空)

# 绑定事件
def func(event):
    print(com.get())
    chen=[com.get()+str(i) for i in range(1,5) ]#生成列表
    com1["value"] = chen#给列表2赋值
    #print(com1.get())
com.bind("<<ComboboxSelected>>", func) #选择下拉框后调用func函数
com1.bind("<<ComboboxSelected>>", func) #选择下拉框后调用func函数

win.mainloop()

 

 四、小案例:

1、画画

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# 导入tkinter模块
import tkinter

# 创建画布需要的库
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg

# 创建工具栏需要的库
from matplotlib.backends.backend_tkagg import NavigationToolbar2Tk

# 快捷键需要的模块
from matplotlib.backend_bases import key_press_handler

# 导入绘图需要的模块
from matplotlib.figure import Figure



# 实例化一个根窗口与设置标题
root = tkinter.Tk()
root.wm_title("Embedding in Tk")#窗口标题

#图形一:
# 画布的大小和分别率
fig = Figure(figsize=(5, 4), dpi=100)
fig1 = Figure(figsize=(5, 4), dpi=100)
# 利用子图画图
axc = fig.add_subplot(111)
axc.plot([3, 5, 2, 6, 8])#数据
# 创建画布控件
canvas = FigureCanvasTkAgg(fig, master=root)  # A tk.DrawingArea.
canvas.draw()
# 显示画布控件
canvas.get_tk_widget().pack()


#图形二:
# 利用子图画图
axc1= fig1.add_subplot(111)
axc1.plot([3, 5, 2, 6, 8])#数据
# 创建画布控件
canvas1 = FigureCanvasTkAgg(fig1, master=root)  # A tk.DrawingArea.
canvas1.draw()
# 显示画布控件
canvas1.get_tk_widget().pack()


# 创建工具条控件
toolbar = NavigationToolbar2Tk(canvas, root)
toolbar.update()
# 显示工具条控件
canvas.get_tk_widget().pack()


# 绑定快捷键函数
def on_key_press(event):
    print("you pressed {}".format(event.key))
    key_press_handler(event, canvas, toolbar)


# 调用快捷键函数
canvas.mpl_connect("key_press_event", on_key_press)


# 退出函数
def _quit():
    root.quit()
    root.destroy()


# 退出按钮控件
button = tkinter.Button(master=root, text="Quit", command=_quit)
button.pack(side=tkinter.BOTTOM)

# 消息循环
tkinter.mainloop()
View Code

 2、抖音数据采集

#!/usr/bin/env python
# -*- coding:utf-8 -*-

import threading
import time,sys,os
import ctypes
import tkinter as tk
from datetime import datetime
from tkinter import Label
from tkinter import messagebox, scrolledtext
from typing import List, Dict, Optional
from urllib.parse import quote
from DrissionPage import ChromiumPage
from DrissionPage.common import Actions
from PIL import Image, ImageTk
from openpyxl import Workbook
import dy_main_synthesis

"""
################################################################################################################################
*******************************************抖音数据采集相关*********************************
################################################################################################################################
"""
class DouyinCrawler:
    def __init__(self):
        self.browser = None
        self.stop_event = threading.Event()

    def timestamp_to_datetime(self, timestamp):
        # 判断是秒还是毫秒
        if timestamp > 1e10:  # 毫秒时间戳通常大于 10^10
            return datetime.fromtimestamp(timestamp / 1000)
        else:
            return datetime.fromtimestamp(timestamp)

    def init_browser(self, headless=False):
        """初始化浏览器,False有头 | True无头模式"""
        try:
            print(f"正在初始化浏览器(headless={headless})...")
            from DrissionPage import ChromiumOptions
            # 创建配置对象
            co = ChromiumOptions()
            co.headless(headless)  # 根据参数设置是否无头
            co.set_argument('--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36')
            co.set_argument('--disable-blink-features=AutomationControlled')
            #co.set_argument('--no-sandbox')
            co.set_argument('--disable-dev-shm-usage')
            co.set_argument('--disable-gpu')
            co.set_argument('--remote-debugging-port=9222')

            # 初始化浏览器
            self.browser = ChromiumPage(addr_or_opts=co)

            if self.browser:
                self.browser.get("https://www.jd.com")
                time.sleep(2)
                print("浏览器初始化成功")
                return True

        except Exception as e:
            print(f"浏览器初始化失败: {e}")

            # 尝试连接已存在的浏览器
            try:
                print("尝试连接已存在的浏览器...")
                self.browser = ChromiumPage(addr_or_opts='127.0.0.1:9222')
                if self.browser:
                    print("连接已存在浏览器成功")
                    return True
            except Exception as e2:
                print(f"连接已存在浏览器失败: {e2}")

        return False


    def search_keyword(self, keyword: str):
        """搜索关键词"""
        try:
            encoded_keyword = quote(keyword)
            search_url = f"https://www.douyin.com/search/{encoded_keyword}?type=video"
            print(f"访问搜索页面: {search_url}")
            self.browser.get(search_url)
            time.sleep(5)
            return True

        except Exception as e:
            print(f"访问搜索页面失败: {e}")
            return False


    def setup_search_listener(self):
        """设置搜索API监听"""
        if not self.browser:
            print("浏览器未初始化,无法设置监听")
            return False

        try:
            try:
                self.browser.listen.stop()
                print("已清除之前的监听")
            except:
                print("没有之前的监听需要清除")

            listeners = [
                '/aweme/v1/web/search/item/',
            ]

            for listener in listeners:
                try:
                    self.browser.listen.start(listener)
                    print(f"成功设置监听: {listener}")
                    return True
                except Exception as e:
                    print(f"监听 {listener} 失败: {e}")
                    continue

            print("所有监听接口都设置失败")
            return False

        except Exception as e:
            print(f"设置监听失败: {e}")
            return False


    def scroll_and_collect_data(self, target_count: int) -> List[Dict]:
        """滚动页面收集数据,增强错误处理"""
        ac = Actions(self.browser)
        all_videos = []
        scroll_count = 0
        max_scrolls = 30
        consecutive_failures = 0
        max_consecutive_failures = 5

        print(f"开始收集数据,目标数量: {target_count}")
        while len(all_videos) < int(target_count) and scroll_count < max_scrolls:
            if self.stop_event.is_set():
                break
            try:
                ret = self.browser.listen.wait(timeout=10)
                if ret and ret.response and ret.response.body:
                    response_data = ret.response.body
                    consecutive_failures = 0
                    video_lists = []
                    if isinstance(response_data, dict):
                        # 针对结构:{"data": [{"aweme_info": {...}}, {...}]}
                        if 'data' in response_data and isinstance(response_data['data'], list):
                            added = 0
                            for item in response_data['data']:
                                if isinstance(item, dict) and 'aweme_info' in item:
                                    all_videos.append(item['aweme_info'])
                                    added += 1
                            print(f"获取到 {added} 个视频,总计: {len(all_videos)}")
                        else:
                            print("找不到 'data' 字段或格式错误")
                    else:
                        print(f"响应数据格式异常,类型: {type(response_data)}")
            except Exception as e:
                consecutive_failures += 1
                print(f"等待数据包超时或解析失败: {e},连续失败次数: {consecutive_failures}")
                if consecutive_failures >= max_consecutive_failures:
                    print("连续多次解析失败,停止数据收集")
                    break

            try:
                ac.scroll(delta_y=1500)
                time.sleep(2)
                scroll_count += 1
                print(f"滚动次数: {scroll_count}")
            except Exception as e:
                print(f"滚动失败: {e}")
                break

        return all_videos[:int(target_count)]


    def extract_video_info(self, video_data: Dict,count: int) -> Optional[Dict]:
        print(f"=== 开始解析第{count}个视频数据 ===")
        # print(json.dumps(video_data, ensure_ascii=False, indent=2))

        if not video_data:
            return None

        try:
            video_info = {
                'aweme_id': video_data.get('aweme_id', ''),
                'desc': video_data.get('desc', ''),
                'create_time': video_data.get('create_time', 0),
                'author_name': '',
                'author_id': '',
                'digg_count': 0,
                'comment_count': 0,
                'share_count': 0,
                'collect_count': 0,
                'play_count': 0,
                'video_url': '',
                'cover_url': '',
                'music_title': '',
                'hashtags': []
            }
            author = video_data.get('author', {})
            video_info['author_name'] = author.get('nickname', '')
            #print("作者id",author.get('uid', ''))
            #print("作者id22",author.get('short_id', ''))
            #video_info['author_id'] = author.get('uid', '') or author.get('short_id', '')
            video_info['author_id'] = author.get('uid', '')

            stats = video_data.get('statistics', {})
            video_info['digg_count'] = stats.get('digg_count', 0)
            video_info['comment_count'] = stats.get('comment_count', 0)
            video_info['share_count'] = stats.get('share_count', 0)
            video_info["collect_count"] = stats.get('collect_count', 0)
            video_info['play_count'] = stats.get('play_count', 0)

            video = video_data.get('video', {})
            play_addr = video.get('play_addr', {})
            url_list = play_addr.get('url_list', [])
            if url_list:
                video_info['video_url'] = url_list[0]

            cover = video.get('cover', {})
            url_list = cover.get('url_list', [])
            if url_list:
                video_info['cover_url'] = url_list[0]

            music = video_data.get('music', {})
            video_info['music_title'] = music.get('title', '')

            text_extra = video_data.get('text_extra', [])
            hashtags = []
            for extra in text_extra:
                if isinstance(extra, dict) and extra.get('type') == 1:
                    hashtags.append(extra.get('hashtag_name', ''))
            video_info['hashtags'] = hashtags
            # 转换时间戳为日期时间格式
            video_info['create_time'] = self.timestamp_to_datetime(video_info['create_time'])
            return video_info

        except Exception as e:
            print(f"提取视频信息失败: {e}")
            return None

    def save_to_excel(self, videos: List[Dict], keyword: str, download_covers: bool = False) -> str:
        if not videos:
            print("没有数据可保存")
            return ""

        try:
            wb = Workbook()
            ws = wb.active
            ws.title = "抖音搜索结果"

            headers = [
                "视频ID", "作者昵称", "作者ID", "视频描述",
                "点赞数", "评论数", "分享数","收藏数","播放数",
                "创建时间", "视频链接", "封面链接", "背景音乐", "话题标签"
            ]
            ws.append(headers)
            #print("视频数据",videos)
            for i, video in enumerate(videos, 1):
                row_data = [
                    video.get('aweme_id', ''),
                    video.get('author_name', ''),
                    video.get('author_id', ''),
                    video.get('desc', ''),
                    video.get('digg_count', 0),
                    video.get('comment_count', 0),
                    video.get('share_count', 0),
                    video.get('collect_count', 0),
                    video.get('play_count', 0),
                    video.get('create_time', ''),  # 现在是格式化后的时间字符串
                    video.get('video_url', ''),
                    video.get('cover_url', ''),
                    video.get('music_title', ''),
                    ', '.join(video.get('hashtags', []))
                ]

                ws.append(row_data)

            filename = f"抖音_{keyword}_{len(videos)}条.xlsx"
            current_directory = os.getcwd()#脚本当前目录
            resfilename = current_directory+"/"+filename
            wb.save(resfilename)
            print(f">>>>>>>>>数据已保存到>>>>>>>>>>: {resfilename}")
            return resfilename

        except Exception as e:
            print(f"保存Excel失败: {e}")
            return ""

    def crawl_douyin_search(self, keyword: str, headless: bool,limit: int = 50,download_covers: bool = False) -> bool:
        try:
            print("=== 抖音关键词爬虫开始 ===")

            max_retries = 3
            for attempt in range(max_retries):
                print(f"尝试初始化浏览器 (第{attempt + 1}次)")
                if self.init_browser(headless):
                    break
                elif attempt < max_retries - 1:
                    print("等待5秒后重试...")
                    time.sleep(5)
                else:
                    print("浏览器初始化失败,请检查ChromiumPage安装")
                    return False

            if not self.setup_search_listener():
                print("API监听设置失败,爬取任务终止")
                return False

            if not self.search_keyword(keyword):
                return False

            print("开始收集视频数据...")
            videos_data = self.scroll_and_collect_data(limit)
            #print("采集返回",videos_data)
            if not videos_data:
                print("未能通过API获取到数据,爬取任务失败")
                return False

            print("处理视频数据...")
            processed_videos = []
            count = 1
            for video_raw in videos_data:
                video_info = self.extract_video_info(video_raw,count)
                count+=1
                if video_info:
                    processed_videos.append(video_info)
            print(f"成功处理 {len(processed_videos)} 个视频")

            #写入excel
            filename = self.save_to_excel(processed_videos, keyword, download_covers=False)
            if filename:
                print(f"=== 爬取完成!共获取 {len(processed_videos)} 条数据 ===")
                return True

            return False

        except Exception as e:
            print(f"爬取过程出错: {e}")
            return False

        finally:
            if self.browser:
                try:
                    print("执行完成-关闭浏览器!")
                    self.browser.quit()
                except:
                    pass

def dy_collect_main(keyword: str, limit: int, headless: bool):
    '''业务主函数-控制数据采集'''
    crawler = DouyinCrawler()
    # keyword = input('请输入你想搜索的关键词 :')  # 搜索关键词
    # limit = input('请输入你想获取的数量 :')  # 爬取数量限制
    # headless = False  # 浏览器模式,False有头、True无头
    download_covers = False  # 明确设置为不下载封面
    success = crawler.crawl_douyin_search(
        keyword=keyword,
        headless=headless,
        limit=limit,
        download_covers=download_covers
    )

    if success:
        print(">>>>>>>>>>>>>爬取任务完成!>>>>>>>>>>>>>>")

    else:
        print("爬取任务失败!")

def video_creation_main(result_file_path,img_path,endtime,coefficient_value,volume_factor,speed,watermark_text,transparency):
    '''业务主函数-视频二次创作'''
    print("调用video_creation_main函数,传参文件路径:",result_file_path)
    newfilepath = dy_main_synthesis.main(result_file_path,img_path,int(endtime),float(coefficient_value),float(volume_factor),float(speed),watermark_text,float(transparency))
    print(f">>>>>>>>>二次创作最终生成的文件:>>>>>>>>>>: {newfilepath}")


"""
################################################################################################################################
*******************************************tkinter窗口及日志相关*********************************
################################################################################################################################
"""
def get_resource_path(relative_path):
    """ 获取资源文件的绝对路径 """
    try:
        # PyInstaller 打包后的路径
        base_path = sys._MEIPASS
    except Exception:
        # 未打包时的路径
        base_path = os.path.abspath(".")

    return os.path.join(base_path, relative_path)

# ---------------- 日志重定向器 ----------------
class TextHandler:
    def __init__(self, text_widget):
        self.text_widget = text_widget

    def write(self, msg):
        self.text_widget.insert(tk.END, msg)
        self.text_widget.see(tk.END)

    def flush(self):
        pass

# # ---------------- 你的业务函数 ----------------
# def main(keyword: str, count: int, headless: bool):
#     for i in range(1, count + 1):
#         print(f"[{i}/{count}] 正在搜索关键词:{keyword}  无头={headless}")
#         time.sleep(0.5)
#     print("全部完成!")


# ---------------- GUI ----------------
class CrawlerGUI:
    def __init__(self, root: tk.Tk):
        root.title("抖音爬虫及二次创作")
        root.geometry("1000x750+200+10")  # 宽x高+位置x坐标+位置y坐标
        root.resizable(False, False)

        # 设置窗口图标
        myappid = "com.dy.create"  #设置id或者包名(这里可以设置任意文本)
        ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid)
        icon_path = get_resource_path("favicon.ico")  # 替换为你的图标路径
        if os.path.exists(icon_path):
            root.iconbitmap(icon_path)
        else:
            print(f"图标文件 {icon_path} 不存在,使用默认图标。")

        # 加载背景图片
        self.background_image = Image.open(r"C:\Users\PC\Desktop\test0011.jpg")  # 替换为你的背景图片路径
        self.background_photo = ImageTk.PhotoImage(self.background_image)
        # 创建背景标签
        self.background_label = Label(root, image=self.background_photo)
        self.background_label.place(x=0, y=0, relwidth=1, relheight=1)

        # ---- 配置区 ----
        cfg = tk.LabelFrame(root, text="配置", padx=5, pady=5)
        cfg.pack(fill="x", padx=5, pady=5)

        # 搜索关键词【 sticky的值有 n(北)、s(南)、e(东)、w(西)、nsew(居中)及其组合 】
        tk.Label(cfg, text="搜索关键词:").grid(row=0, column=0, sticky="w")
        self.keyword_entry = tk.Entry(cfg, width=30)
        self.keyword_entry.grid(row=0, column=1, padx=5, sticky="w")

        # 爬取数量
        tk.Label(cfg, text="爬取数量:").grid(row=0, column=2, sticky="e")
        vcmd = (root.register(self._validate_int), "%P")
        self.count_entry = tk.Entry(cfg, width=10, validate="key", validatecommand=vcmd)
        self.count_entry.grid(row=0, column=3, sticky="w", padx=5)
        self.count_entry.insert(0, "10")

        # 卡密
        tk.Label(cfg, text="卡密(首次或过期填写):", font=("黑体", 8), fg="gray", bg="white").grid(row=0, column=4,
                                                                                                  sticky="w")
        self.password = tk.Entry(cfg, width=30)
        self.password.grid(row=0, column=5, padx=5, sticky="w")

        # 联系方式【row表示行、column表示列、columnspan表示跨列数、sticky表示布局】
        tk.Label(cfg, text="获取卡密请联系微信:chn2987", font=("黑体", 10), width=40, height=1, fg="Magenta",
                 bg="white").grid(row=3, column=3, columnspan=3, sticky="w")

        # 执行模式
        tk.Label(cfg, text="执行模式:").grid(row=2, column=0, sticky="w")
        self.headless_var = tk.BooleanVar(value=False)
        tk.Checkbutton(cfg, text="无头模式(默认GUI)", variable=self.headless_var).grid(
            row=2, column=0, columnspan=2, pady=2
        )

        # 处理结果文件
        tk.Label(cfg, text="采集任务完成后生成<<结果文件>>,在日志打印路径!,,视频处理受PC算力影响可能比较慢、请耐心等待!", font=("黑体", 10), width=100,
                 height=1, fg="Magenta", bg="white").grid(row=5, column=1, columnspan=6, sticky="w")
        tk.Label(cfg, text="采集结果文件:").grid(row=6, column=0, sticky="w")
        self.result = tk.Entry(cfg, width=30)
        self.result.grid(row=6, column=1, padx=5, sticky="w")

        # 画中画图片
        tk.Label(cfg, text="画中画图片路径:").grid(row=6, column=2, sticky="e")
        self.img_path = tk.Entry(cfg, width=25)
        self.img_path.grid(row=6, column=3, padx=5, sticky="e")

        # 剪辑时长
        tk.Label(cfg, text="剪辑时长:").grid(row=6, column=4, sticky="e")
        self.endtime = tk.Entry(cfg, width=30)
        self.endtime.grid(row=6, column=5, padx=5, sticky="w")
        ###设置 placeholder
        placeholder_text1 = 15
        self.endtime.insert(0, placeholder_text1)

        # 亮度调整值
        tk.Label(cfg, text="亮度调整值:").grid(row=7, column=0, sticky="w")
        self.coefficient_value = tk.Entry(cfg, width=30)
        self.coefficient_value.grid(row=7, column=1, padx=5, sticky="w")
        ###设置 placeholder
        placeholder_text2 = 1.1
        self.coefficient_value.insert(0, placeholder_text2)

        # 视频音量值
        tk.Label(cfg, text="视频音量值:").grid(row=7, column=2, sticky="e")
        self.volume_factor = tk.Entry(cfg, width=25)
        self.volume_factor.grid(row=7, column=3, padx=5, sticky="w")
        ###设置 placeholder
        placeholder_text3 = 0.9
        self.volume_factor.insert(0, placeholder_text3)

        # 播放速度值
        tk.Label(cfg, text="播放速度值:").grid(row=7, column=4, sticky="e")
        self.speed = tk.Entry(cfg, width=30)
        self.speed.grid(row=7, column=5, padx=5, sticky="w")
        ###设置 placeholder
        placeholder_text4 = 1.1
        self.speed.insert(0, placeholder_text4)

        # 文字水印
        tk.Label(cfg, text="文字水印:").grid(row=8, column=0, sticky="w")
        self.watermark_text = tk.Entry(cfg, width=30)
        self.watermark_text.grid(row=8, column=1, padx=5, sticky="w")
        ###设置 placeholder
        placeholder_text5 = "土豆麻麻"
        self.watermark_text.insert(0, placeholder_text5)

        # 画中画透明度
        tk.Label(cfg, text="画中画透明度:").grid(row=8, column=2, sticky="w")
        self.transparency = tk.Entry(cfg, width=25)
        self.transparency.grid(row=8, column=3, padx=5, sticky="w")
        ###设置 placeholder
        placeholder_text6 = 1
        self.transparency.insert(0, placeholder_text6)

        # ---- 控制采集的按钮 ----
        btn_frame = tk.Frame(cfg)
        btn_frame.grid(row=3, column=0, columnspan=2, pady=4)
        tk.Button(btn_frame, text="开始执行", width=8, command=self._run_crawler).pack(side="left", padx=5)
        tk.Button(btn_frame, text="清除日志", width=8, command=self._clear_log).pack(side="left", padx=5)
        tk.Button(btn_frame, text="关闭", width=8, command=self._close).pack(side="left", padx=5)

        # ---- 控制二次创作的按钮 ----
        btn_frame2 = tk.Frame(cfg)
        btn_frame2.grid(row=9, column=0, columnspan=2, pady=4)
        tk.Button(btn_frame2, text="开始二创", width=8, command=self._run_processor).pack(side="left", padx=5)
        tk.Button(btn_frame2, text="清除日志", width=8, command=self._clear_log).pack(side="left", padx=5)
        tk.Button(btn_frame2, text="关闭", width=8, command=self._close).pack(side="left", padx=5)

        # ---- 日志区 ----
        log = tk.LabelFrame(root, text="实时日志", padx=5, pady=5)
        log.pack(fill="both", expand=True, padx=5, pady=5)
        # 创建滚动条
        self.log_text = scrolledtext.ScrolledText(log, height=10, state="normal")
        self.log_text.pack(fill="both", expand=True)

        # 重定向 print
        sys.stdout = TextHandler(self.log_text)
        sys.stderr = TextHandler(self.log_text)

    # ---------- 工具 ----------
    def _validate_int(self, value_if_allowed: str) -> bool:
        return value_if_allowed == "" or value_if_allowed.isdigit()

    def _run_crawler(self):
        ######在这里获取uuid并校验卡密
        import subprocess
        cmd = ["wmic", "csproduct", "get", "UUID"]
        raw = subprocess.check_output(cmd, text=True).strip()
        uuid_line = [line for line in raw.splitlines() if line and "UUID" not in line][0]
        uuid = uuid_line.strip().upper()#uuid获取
        #tkinter逻辑
        kw = self.keyword_entry.get().strip() #搜索词
        cnt_str = self.count_entry.get().strip() #爬取数量
        headless = self.headless_var.get()    #执行模式
        password = self.password.get().strip() #卡密
        if not kw or not cnt_str:
            messagebox.showwarning("警告", "关键词或数量不能为空!")
            return
        # ###调用接口校验卡密
        # import requests,json
        # url = "http://101.42.38.92:8300/password_verification"
        # headers = {
        #     'Host': '101.42.38.92:8300',
        #     'Connection': 'keep-alive'
        # }
        # response = requests.request("GET", url, headers=headers, params={"uuid":uuid,"key_code":password})
        # jsondata = json.loads(response.text)
        # print("校验结果:",jsondata)
        # if jsondata.get("status"):
        #     threading.Thread(target=dy_collect_main, args=(kw, int(cnt_str), headless), daemon=True).start()
        # else:
        #     messagebox.showwarning("警告", jsondata.get("msg"))
        #     return
        ###去掉校验
        threading.Thread(target=dy_collect_main, args=(kw, int(cnt_str), headless), daemon=True).start()

    def _run_processor(self):
        resdata = self.result.get().strip() #采集结果路径
        img_path = self.img_path.get().strip()  #话中画图片路径
        endtime = self.endtime.get().strip()  #视频剪辑时长
        coefficient_value = self.coefficient_value.get().strip()  #亮度值
        volume_factor = self.volume_factor.get().strip()  #音量值
        speed = self.speed.get().strip()  #播放速度值
        watermark_text = self.watermark_text.get().strip()  #视频水印
        transparency = self.transparency.get().strip()  #画中画透明度
        import subprocess
        cmd = ["wmic", "csproduct", "get", "UUID"]
        raw = subprocess.check_output(cmd, text=True).strip()
        uuid_line = [line for line in raw.splitlines() if line and "UUID" not in line][0]
        uuid = uuid_line.strip().upper()  # uuid获取
        headless = self.headless_var.get()
        password = self.password.get().strip()
        ###调用接口校验卡密
        # import requests, json
        # url = "http://101.42.38.92:8300/password_verification"
        # headers = {
        #     'Host': '101.42.38.92:8300',
        #     'Connection': 'keep-alive'
        # }
        # response = requests.request("GET", url, headers=headers, params={"uuid": uuid, "key_code": password})
        # jsondata = json.loads(response.text)
        # print("校验结果:", jsondata)
        # if jsondata.get("status"):
        #     threading.Thread(target=video_creation_main, args=(resdata,img_path,endtime,coefficient_value,volume_factor,speed,watermark_text,transparency), daemon=True).start()
        # else:
        #     messagebox.showwarning("警告", jsondata.get("msg"))
        #     return
        ###去掉校验
        threading.Thread(target=video_creation_main, args=(resdata,), daemon=True).start()

    def _clear_log(self):
        """清空日志框"""
        self.log_text.delete(1.0, tk.END)

    def _close(self):
        if messagebox.askokcancel("退出", "确定关闭?"):
            root.destroy()

# ---------------- 启动 ----------------
if __name__ == "__main__":
    root = tk.Tk()
    CrawlerGUI(root)
    root.mainloop()
View Code

 

 五、pyinstaller打包

 pyinstaller 是一个流行的 Python 打包工具,可以将 Python 脚本打包成各种平台的可执行文件,包括 Windows、Linux 和 macOS。使用 pyinstaller 可以非常简单地将 Python 代码打包成独立的可执行文件。

 **安装 pyinstaller**

pip install pyinstaller

pyinstaller的打包:

# 打包单个文件
pyinstaller your_script.py

# 打包多个py文件
pyinstaller [主文件] -p [其他文件1] -p [其他文件2]
# 打包多个文件(使用 --onefile 参数将所有依赖项打包到一个单独的.exe,, --icon是图片log)
pyinstaller --onefile --icon=test001.ico get_douyin_main.py
# 打包多个文件自动加载依赖、--noconsole是无窗口运行
pyinstaller --onefile --noconsole --icon=test001.ico get_douyin_main.py
# 打包多个文件自动加载依赖(--add-data是添加数据)
pyinstaller --onefile --noconsole --add-data "favicon.ico;." --icon=favicon.ico dy_main_tk.py

# 把 dy_main_tk.py 打成单个无黑窗的 exe,带上图标、bin 目录和 imageio_ffmpeg 模块,运行时会自动解压资源并调用。
pyinstaller --onefile --noconsole --add-data "favicon.ico;."  --add-data "bin;bin"  --icon=favicon.ico --hidden-import=imageio_ffmpeg dy_main_tk.py

# 打包时去除cmd框
pyinstaller -F XXX.py --noconsole

# 打包加入exe图标   picturename.ico是图片
pyinstaller -F -i picturename.ico -w XXX.py

# 打包去除控制台
pyinstaller -w xxx.py

 

 

 

 

相关连接:

https://blog.csdn.net/BAIFOL/article/details/121543170...................................................Python_tkinter(按钮,文本框,多行文本组件)

https://blog.csdn.net/weixin_41508948/article/details/86243288 .....................................android 性能监控工具介绍 (Python+PyQt 实现)

https://www.cnblogs.com/ningxiaoning/p/18084827.........................................................pyinstaller打包

 

posted on 2022-04-24 11:10  chen_2987  阅读(543)  评论(0)    收藏  举报

导航