Tkinter Scrollbar滚动时GUI冻结问题求助:Selenium文件下载工具故障
Tkinter+Selenium文件列表GUI滚动冻结修复方案
核心故障原因
- Tkinter所有UI操作必须在主线程执行,原代码在子线程中直接创建Label、Button等组件、调用布局方法,触发线程冲突导致冻结
- 无意义的
res死循环线程每秒执行上亿次scrollregion更新,CPU占满后GUI完全无响应 - 循环内重复调用
pack方法、反复执行list.index()遍历,大量冗余操作拖慢运行效率 - 下载按钮存在闭包陷阱,所有按钮会默认绑定最后一个文件的下载地址
修复方案
具体修改点
- 删除无用的
res死循环线程,仅在所有列表元素加载完成后更新一次scrollregion - 所有UI创建、布局操作通过
root.after()抛到主线程执行,规避线程安全问题 - 去掉循环内冗余的
pack调用,优化遍历逻辑直接用索引取值,避免重复遍历列表 - 修正闭包陷阱,确保每个下载按钮绑定对应文件的地址
- 下载链接动态拼接,不再硬编码固定前缀,适配不同输入URL
- 新增chromedriver自动关闭逻辑,避免后台进程残留
修复后完整代码
import tkinter as tk import tkinter.font as font from selenium.webdriver import Chrome from selenium.webdriver.chrome.options import Options from threading import Thread import webbrowser import time root = tk.Tk() root.geometry("640x480") root.state("zoom") tk.Label(root, text="URL:", font=font.Font(size=20)).place(x=20, y=20) url_input = tk.Entry(root, font=font.Font(size=20)) url_input.place(x=20, y=60, width=490, height=40) urls = [] result = [] result_b = [] main_frame = tk.LabelFrame(root) canvas = tk.Canvas(main_frame, highlightbackground="gray") canvas.pack(side=tk.LEFT, fill="both", expand="yes") v_scroll_bar = tk.Scrollbar(main_frame, orient="vertical", command=canvas.yview) v_scroll_bar.pack(side=tk.RIGHT, fill="y") canvas.configure(yscrollcommand=v_scroll_bar.set) def update_scrollregion(event): canvas.configure(scrollregion=canvas.bbox("all")) canvas.bind("<Configure>", update_scrollregion) result_frame = tk.Frame(canvas) canvas.create_window((0, 0), window=result_frame, anchor="nw") main_frame.pack(fill="both", expand="yes") def download_links(links, base_url): global urls urls = links # UI操作提交到主线程执行 def render_ui(): for idx, l in enumerate(links): file_name = l.find_elements_by_css_selector("*")[0].get_attribute("title") lLabel = tk.Label(result_frame, font=font.Font(size=20), text=f"{idx+1}.{file_name}") lLabel.grid(row=idx, column=0, sticky=tk.W) lDownloadB = tk.Button(result_frame, text="Download") lDownloadB.grid(row=idx, column=1, sticky=tk.E, ipadx=10, ipady=5) # 修正闭包陷阱,直接传入当前文件名 def dl(e, file=file_name): base_path = base_url.split("0:/")[0] durl = f"{base_path}0:down/_Alvro%20Collection/{file}" webbrowser.open_new(durl) lDownloadB.bind("<ButtonRelease-1>", dl) result.append(lLabel) result_b.append(lDownloadB) # 所有元素加载完成后更新一次滚动区域 update_scrollregion(None) root.after(0, render_ui) def scrap(): global d input_url = url_input.get().strip() if not input_url: return options = Options() options.headless = True # 屏蔽控制台多余日志输出 options.add_experimental_option('excludeSwitches', ['enable-logging']) d = Chrome(executable_path="chromedriver.exe", options=options) d.get(input_url) def func(): time.sleep(2) while True: time.sleep(0.1) links = d.find_elements_by_css_selector("#app > div > section > div > div > div.golist > table > tbody > tr") if len(links) > 50: download_links(links, input_url) d.quit() # 数据抓取完成后自动关闭浏览器进程 break else: d.refresh() t = Thread(target=func, daemon=True) t.start() scarp_b = tk.Button(root, text="Get", font=font.Font(size=20), command=scrap) scarp_b.place(x=510, y=60, width=100, height=40) def conf(e): url_input.place(x=20, y=60, width=root.winfo_width()-150, height=40) scarp_b.place(x=root.winfo_width()-120, y=60, width=100, height=40) main_frame.place(x=0, y=140, width=root.winfo_width(), height=root.winfo_height()-140) root.bind("<Configure>", conf) root.mainloop()
内容的提问来源于stack exchange,提问作者Hamza Khan
相关产品推荐
相关产品推荐

