├── requirements.txt
├── img
├── 1.png
├── 2.png
├── 3.png
├── 4.png
├── 5.png
├── 6.png
├── 7.png
├── 8.png
├── 9.png
├── 10.png
├── 11.png
├── 12.png
├── 13.png
├── 14.png
├── 15.png
└── 16.png
├── .gitignore
├── real_README.md
├── PostJson_multi.py
├── README.md
└── ui.py
/requirements.txt:
--------------------------------------------------------------------------------
1 | requests
2 | ttkbootstrap
3 | pyinstaller
--------------------------------------------------------------------------------
/img/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RwandanMtGorilla/ZJGSU_spider/HEAD/img/1.png
--------------------------------------------------------------------------------
/img/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RwandanMtGorilla/ZJGSU_spider/HEAD/img/2.png
--------------------------------------------------------------------------------
/img/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RwandanMtGorilla/ZJGSU_spider/HEAD/img/3.png
--------------------------------------------------------------------------------
/img/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RwandanMtGorilla/ZJGSU_spider/HEAD/img/4.png
--------------------------------------------------------------------------------
/img/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RwandanMtGorilla/ZJGSU_spider/HEAD/img/5.png
--------------------------------------------------------------------------------
/img/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RwandanMtGorilla/ZJGSU_spider/HEAD/img/6.png
--------------------------------------------------------------------------------
/img/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RwandanMtGorilla/ZJGSU_spider/HEAD/img/7.png
--------------------------------------------------------------------------------
/img/8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RwandanMtGorilla/ZJGSU_spider/HEAD/img/8.png
--------------------------------------------------------------------------------
/img/9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RwandanMtGorilla/ZJGSU_spider/HEAD/img/9.png
--------------------------------------------------------------------------------
/img/10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RwandanMtGorilla/ZJGSU_spider/HEAD/img/10.png
--------------------------------------------------------------------------------
/img/11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RwandanMtGorilla/ZJGSU_spider/HEAD/img/11.png
--------------------------------------------------------------------------------
/img/12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RwandanMtGorilla/ZJGSU_spider/HEAD/img/12.png
--------------------------------------------------------------------------------
/img/13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RwandanMtGorilla/ZJGSU_spider/HEAD/img/13.png
--------------------------------------------------------------------------------
/img/14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RwandanMtGorilla/ZJGSU_spider/HEAD/img/14.png
--------------------------------------------------------------------------------
/img/15.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RwandanMtGorilla/ZJGSU_spider/HEAD/img/15.png
--------------------------------------------------------------------------------
/img/16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RwandanMtGorilla/ZJGSU_spider/HEAD/img/16.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /workbench.md
2 | /.python-version
3 | /pyproject.toml
4 | /dist/
5 | /build/
6 | /ui.spec
7 | /uv.lock
8 | /JSONs/
9 | /.idea/
10 |
--------------------------------------------------------------------------------
/real_README.md:
--------------------------------------------------------------------------------
1 |
2 | ## 环境准备
3 | ```shell
4 | # 下载uv
5 | powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
6 |
7 | # 克隆项目
8 | git clone https://github.com/RwandanMtGorilla/ZJGSU_spider.git
9 |
10 | # 创建虚拟环境
11 | uv venv --python 3.9
12 | uv init
13 |
14 | # 启动虚拟环境
15 | .venv\Scripts\activate
16 | uv pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
17 | ```
18 |
19 | ## 启动
20 | ```shell
21 | uv run ui.py
22 | ```
23 |
24 | ## 打包
25 | ```shell
26 | pyinstaller --onefile --noconsole ui.py
27 | ```
28 |
--------------------------------------------------------------------------------
/PostJson_multi.py:
--------------------------------------------------------------------------------
1 | import os
2 | import json
3 | import requests
4 | from glob import glob
5 | import random
6 | import time
7 |
8 | def load_json_files(folder_path):
9 | """ 加载指定文件夹中的所有JSON文件 """
10 | file_paths = glob(os.path.join(folder_path, '*.json'))
11 | json_data = []
12 | for file_path in file_paths:
13 | with open(file_path, 'r', encoding='utf-8') as file:
14 | json_data.append(json.load(file))
15 | return json_data
16 |
17 | def send_requests_until_success(json_requests):
18 | """ 发送请求直到flag为1 """
19 | for request_info in json_requests:
20 | url = request_info.get("raw_url")
21 | method = request_info.get("method", 'get').lower()
22 | headers = request_info.get("headers")
23 | cookies = request_info.get("cookies")
24 | data = request_info.get("data")
25 |
26 |
27 | while True:
28 | response = requests.request(method, url, headers=headers, cookies=cookies, data=data)
29 | localtime = time.asctime(time.localtime(time.time()))
30 | try:
31 | response_json = response.json()
32 | if response_json.get("flag") == "1":
33 | print(f"{localtime} 成功: {response.text}")
34 | break
35 | else:
36 | print(f"{localtime} 尝试中, 响应: {response.text}")
37 | except json.JSONDecodeError:
38 | print(f"{localtime} 无法解析JSON响应: {response.text}")
39 | break
40 |
41 | wait_time = random.uniform(0.9, 1.3)
42 | time.sleep(wait_time)
43 |
44 | # 指定JSONs文件夹的路径
45 | folder_path = 'JSONs'
46 | json_requests = load_json_files(folder_path)
47 | send_requests_until_success(json_requests)
48 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # zjgsu抢课脚本
2 | ## 使用教程(*更新 2025 - 2026 - 1 )
3 |
4 | ## 观前提醒:
5 |
6 | 此项目仅用于python学习交流,请勿用于非法用途[doge]。
7 |
8 | 如果你有python代码能力,想要二次开发,请移步[二次开发指南](real_README.md)
9 |
10 | 使用过程略有些复杂,请耐心按照以下教程逐步操作。
11 |
12 | ## 背景
13 |
14 | 一个倒霉的开发者在某次第一轮选课时候一门课没摇上,本着我命由我不由天的精神,怒写抢课脚本,然后就有了本仓库。
15 | 脚本效果拔群,想要的好课都抢到了:P
16 |
17 |
18 | ## 简介
19 | - 脚本会模拟你点击选课的操作
20 | - 直到接收到抢课成功后停止运行。
21 | - 这个脚本理论上可以适配大多数比较简单的抢课系统
22 |
23 | - 因为需要开始后才能配置 所以本脚本适用于捡漏他人退课的情景 而不适合秒杀场景
24 |
25 | ## 文件构成:
26 |
27 | 使用此脚本,必要的文件有两项:
28 | - `JSONs` 文件夹以及其中的`.json`文件(你需要手动配置的抢课信息 请自行创建)
29 | - `ui_2.0.exe`(信息配置完成后执行抢课程序的脚本 版本号可能有变化,请以最新版为准)[下载地址](https://github.com/RwandanMtGorilla/ZJGSU_spider/releases)
30 |
31 | 使用前请检查两项项文件是否完整。
32 |
33 | ## 如何使用:
34 | ### 1. 课程信息写入(在选课期间操作)
35 |
36 | - 确认并记录(Ctrl+c)你要抢的课程名称,确保无课程时间冲突(如果有 将以最先
37 | 抢到的为准)
38 |
39 | - 登录并进入[选课界面](http://124.160.64.163/jwglxt/)(网址)
40 | 页面任意位置右键单击并下拉找到 *检查* 并点击(或按F 12 )
41 |
42 |
43 |
44 | - 在右侧弹出的开发者模式界面中找到 *network*(或*网络*)并点击
45 |
46 |
47 |
48 |
49 | - 点击Fetch/XHR
50 |
51 |
52 |
53 | - 在左侧选课界面找到 你想要的课 进行 *选课* (可以是满课的课程 只需要点击一次 用以获取自己的账号信息)
54 |
55 |
56 |
57 | - 完成上一步操作后,右侧将会出现数据包
58 |
59 |
60 |
61 | - 右键单击 > Copy > Copy as cURL(bash)
62 |
63 |
64 |
65 | - 进入[curlconverter网站](https://curlconverter.com/) (转换curl请求为JSON)
66 | - 将之前复制的curl内容复制入curl command,language选择JSON
67 |
68 |
69 |
70 | *(例图中信息已经过混淆处理)*
71 |
72 | - 在`JSONs`文件夹中新建一个`.json`文件 将刚刚的信息复制进去并保存 (用一个你记得住的名字)(可以新建txt然后改后缀)(如果需要抢多门课,则创建多个json文件)
73 |
74 |
75 |
76 |
77 |
78 |
79 | ### 2. 脚本,启动!
80 |
81 | - 双击 `UI_2.0.exe`
82 |
83 |
84 |
85 | - 点击`浏览`,选择你存放 `.json` 文件的文件夹
86 | - 点击`开始` 如果不断弹出运行信息 即说明运行成功
87 |
88 | ## 完整代码
89 | 完整代码请查看 [`PostJson_multi.py`](PostJson_multi.py)
--------------------------------------------------------------------------------
/ui.py:
--------------------------------------------------------------------------------
1 | import os
2 | import json
3 | import requests
4 | from glob import glob
5 | import random
6 | import time
7 | from multiprocessing import Process, Manager, freeze_support
8 | import ttkbootstrap as ttk
9 | from ttkbootstrap.constants import *
10 | from tkinter import filedialog, messagebox, Canvas, Scrollbar, Listbox, Frame
11 | from collections import defaultdict
12 |
13 |
14 | def load_json_files(folder_path):
15 | """ 加载指定文件夹中的所有JSON文件 """
16 | file_paths = glob(os.path.join(folder_path, '*.json'))
17 | json_data = []
18 | file_names = [] # 存储文件名
19 | for file_path in file_paths:
20 | with open(file_path, 'r', encoding='utf-8') as file:
21 | json_data.append(json.load(file))
22 | file_names.append(os.path.basename(file_path))
23 | return json_data, file_names
24 |
25 |
26 | def send_requests_until_success(request_info, log_queue, wait_time):
27 | """ 发送请求直到flag为1 """
28 | url = request_info.get("raw_url")
29 | method = request_info.get("method", 'get').lower()
30 | headers = request_info.get("headers")
31 | cookies = request_info.get("cookies")
32 | data = request_info.get("data")
33 |
34 | while True:
35 | try:
36 | response = requests.request(method, url, headers=headers, cookies=cookies, data=data)
37 | localtime = time.strftime("%H:%M:%S", time.localtime(time.time()))
38 | except Exception as e:
39 | localtime = time.strftime("%H:%M:%S", time.localtime(time.time()))
40 | log_queue.put(f"请求失败,返回: {e}||{localtime}")
41 | continue
42 |
43 | try:
44 | response_json = response.json()
45 | if response_json.get("flag") == "1":
46 | log_queue.put(f"成功: {response.text}||{localtime}")
47 | time.sleep(1)
48 | break
49 | elif response_json.get("flag") == "0":
50 | log_queue.put(f"响应: {response.text} 请先退选当前时段。||{localtime}")
51 | time.sleep(1)
52 | break
53 | else:
54 | log_queue.put(f"尝试中, 响应: {response.text}||{localtime}")
55 | except json.JSONDecodeError:
56 | log_queue.put(f"无法解析JSON响应: {response.text}||{localtime}")
57 |
58 | time.sleep(random.uniform(wait_time, wait_time + 0.3))
59 |
60 |
61 | class App:
62 | def __init__(self, root):
63 | self.root = root
64 | self.root.title("JSON 请求多进程管理器")
65 | self.root.geometry('2000x800')
66 | self.root.resizable(False, False)
67 |
68 | self.folder_path = './JSONs' # 默认的JSON文件夹路径
69 | self.json_requests = []
70 | self.file_names = [] # 保存每个 JSON 文件的文件名
71 | self.processes = []
72 | self.log_queues = []
73 | self.log_counts = [] # 存储每个日志框的响应计数
74 | self.log_times = [] # 存储每个日志的最新时间
75 | self.listboxes = [] # 存储Listbox组件
76 |
77 | self.create_widgets()
78 |
79 | def create_widgets(self):
80 | # 创建主框架
81 | main_frame = ttk.PanedWindow(self.root, orient=HORIZONTAL)
82 | main_frame.pack(fill=BOTH, expand=True, padx=10, pady=10)
83 |
84 | # 左侧控制区
85 | control_frame = ttk.Frame(main_frame, padding=(10, 10), bootstyle=SECONDARY)
86 | main_frame.add(control_frame, weight=1)
87 |
88 | # 右侧日志区
89 | log_frame = ttk.Labelframe(main_frame, text="日志输出", padding=(10, 10), bootstyle=INFO)
90 | main_frame.add(log_frame, weight=4)
91 |
92 | # 创建一个容器框架来包含Canvas和滚动条
93 | canvas_container = ttk.Frame(log_frame)
94 | canvas_container.pack(fill=BOTH, expand=True)
95 |
96 | # 创建垂直滚动条
97 | self.main_scrollbar = ttk.Scrollbar(canvas_container, orient=VERTICAL)
98 | self.main_scrollbar.pack(side=RIGHT, fill=Y)
99 |
100 | # 创建Canvas
101 | self.canvas = Canvas(canvas_container, yscrollcommand=self.main_scrollbar.set)
102 | self.canvas.pack(side=LEFT, fill=BOTH, expand=True)
103 |
104 | # 配置滚动条
105 | self.main_scrollbar.configure(command=self.canvas.yview)
106 |
107 | # 创建可滚动的框架
108 | self.scrollable_frame = ttk.Frame(self.canvas)
109 |
110 | # 将scrollable_frame添加到canvas中
111 | self.canvas_window = self.canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
112 |
113 | # 路径选择
114 | path_label = ttk.Label(control_frame, text="选择 JSON 文件夹路径:")
115 | path_label.pack(anchor="w", pady=5)
116 |
117 | self.path_entry = ttk.Entry(control_frame, width=25)
118 | self.path_entry.insert(0, self.folder_path)
119 | self.path_entry.pack(fill=X, pady=5)
120 |
121 | browse_button = ttk.Button(control_frame, text="浏览", command=self.browse_folder, bootstyle=PRIMARY)
122 | browse_button.pack(fill=X, pady=5)
123 |
124 | # 等待时间设置
125 | wait_time_label = ttk.Label(control_frame, text="设置等待时间 (秒,最少为0.3):")
126 | wait_time_label.pack(anchor="w", pady=5)
127 |
128 | self.wait_time_var = ttk.DoubleVar(value=0.5)
129 | self.wait_time_entry = ttk.Spinbox(control_frame, from_=0.3, to=10.0, increment=0.1,
130 | textvariable=self.wait_time_var, width=10)
131 | self.wait_time_entry.pack(fill=X, pady=5)
132 |
133 | # 控制按钮
134 | start_button = ttk.Button(control_frame, text="开始", command=self.start_processes, bootstyle=SUCCESS)
135 | start_button.pack(fill=X, pady=5)
136 |
137 | stop_button = ttk.Button(control_frame, text="停止", command=self.stop_processes, bootstyle=DANGER)
138 | stop_button.pack(fill=X, pady=5)
139 |
140 | info_button = ttk.Button(control_frame, text="信息", command=self.show_info, bootstyle=INFO)
141 | info_button.pack(fill=X, pady=5)
142 |
143 | def browse_folder(self):
144 | # 选择JSON文件夹
145 | selected_folder = filedialog.askdirectory(initialdir=self.folder_path, title="选择JSON文件夹")
146 | if selected_folder:
147 | self.folder_path = selected_folder
148 | self.path_entry.delete(0, ttk.END)
149 | self.path_entry.insert(0, self.folder_path)
150 | self.load_json_requests()
151 |
152 | def load_json_requests(self):
153 | # 加载JSON文件
154 | try:
155 | self.json_requests, self.file_names = load_json_files(self.folder_path)
156 | self.clear_logs()
157 | self.create_log_windows()
158 | messagebox.showinfo("加载成功", f"已加载 {len(self.json_requests)} 个请求")
159 | except Exception as e:
160 | messagebox.showerror("加载错误", f"无法加载JSON文件: {str(e)}")
161 |
162 | def create_log_windows(self):
163 | # 清除现有的日志框架
164 | self.listboxes.clear()
165 | self.log_counts.clear()
166 | self.log_times.clear() # 清除时间记录
167 | for widget in self.scrollable_frame.winfo_children():
168 | widget.destroy()
169 |
170 | # 动态调整每个日志框的高度
171 | num_logs = len(self.file_names)
172 | base_height = 8
173 | adjusted_height = max(5, int(base_height * (1 / (num_logs * 0.3 + 1))))
174 |
175 | # 绑定Canvas大小变化事件
176 | def configure_canvas(event):
177 | # 获取Canvas的实际宽度
178 | canvas_width = event.width
179 | # 更新scrollable_frame的宽度以匹配Canvas宽度
180 | self.canvas.itemconfig(self.canvas_window, width=canvas_width)
181 | # 强制更新scrollable_frame的布局
182 | self.scrollable_frame.update_idletasks()
183 | # 更新滚动区域
184 | self.canvas.configure(scrollregion=self.canvas.bbox("all"))
185 |
186 | self.canvas.bind('', configure_canvas)
187 |
188 | # 使用鼠标滚轮滚动
189 | def on_mousewheel(event):
190 | self.canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
191 |
192 | self.canvas.bind_all("", on_mousewheel)
193 |
194 | for i, file_name in enumerate(self.file_names):
195 | log_queue = Manager().Queue()
196 | self.log_queues.append(log_queue)
197 | self.log_counts.append(defaultdict(int))
198 | self.log_times.append(defaultdict(str)) # 添加时间记录
199 |
200 | # 创建日志框架,使用ttk.Frame以保持一致的样式
201 | log_frame = ttk.Frame(self.scrollable_frame, relief="ridge", borderwidth=1)
202 | log_frame.pack(fill=BOTH, expand=True, padx=5, pady=5)
203 |
204 | log_label = ttk.Label(log_frame, text=file_name)
205 | log_label.pack(anchor="w", padx=5, pady=2)
206 |
207 | # 创建Listbox容器,关键修改:使用BOTH fill和expand=True
208 | listbox_frame = ttk.Frame(log_frame)
209 | listbox_frame.pack(fill=BOTH, expand=True, padx=5, pady=5)
210 |
211 | # 创建Listbox,关键修改:去掉固定宽度限制
212 | listbox = Listbox(listbox_frame, height=adjusted_height, font=("Arial", 9))
213 | scrollbar_listbox = ttk.Scrollbar(listbox_frame, orient=VERTICAL, command=listbox.yview)
214 |
215 | listbox.configure(yscrollcommand=scrollbar_listbox.set)
216 | # 关键修改:确保Listbox完全填充其容器
217 | listbox.pack(side=LEFT, fill=BOTH, expand=True)
218 | scrollbar_listbox.pack(side=RIGHT, fill=Y)
219 |
220 | self.listboxes.append(listbox)
221 |
222 | # 强制更新布局
223 | self.root.update_idletasks()
224 | # 更新Canvas配置
225 | self.canvas.configure(scrollregion=self.canvas.bbox("all"))
226 |
227 | # 确保Canvas窗口宽度正确设置
228 | def update_canvas_width():
229 | canvas_width = self.canvas.winfo_width()
230 | if canvas_width > 1: # 确保Canvas已经被渲染
231 | self.canvas.itemconfig(self.canvas_window, width=canvas_width)
232 |
233 | # 延迟执行宽度更新,确保所有组件都已完成初始化
234 | self.root.after(100, update_canvas_width)
235 |
236 | def clear_logs(self):
237 | # 清除所有日志队列
238 | self.log_queues.clear()
239 |
240 | def start_processes(self):
241 | # 启动所有进程
242 | if not self.json_requests:
243 | messagebox.showwarning("警告", "请先选择包含JSON文件的文件夹并加载请求")
244 | return
245 |
246 | wait_time = max(0.3, self.wait_time_var.get())
247 | for i, request_info in enumerate(self.json_requests):
248 | process = Process(target=send_requests_until_success, args=(request_info, self.log_queues[i], wait_time))
249 | process.start()
250 | self.processes.append(process)
251 | self.update_log(i)
252 |
253 | def stop_processes(self):
254 | # 停止所有进程
255 | for process in self.processes:
256 | process.terminate()
257 | self.processes = []
258 | messagebox.showinfo("停止", "所有进程已停止")
259 |
260 | def show_info(self):
261 | # 创建一个新的信息窗口
262 | info_window = ttk.Toplevel(self.root)
263 | info_window.title("信息")
264 | info_window.geometry("400x200")
265 |
266 | # 显示当前正在运行的进程数
267 | running_processes = len([p for p in self.processes if p.is_alive()])
268 | process_label = ttk.Label(info_window, text=f"当前正在运行的进程数: {running_processes}")
269 | process_label.pack(pady=10)
270 |
271 | # 创建一个可点击的 GitHub 链接
272 | github_link = ttk.Label(info_window, text="GitHub 链接", foreground="blue", cursor="hand2")
273 | github_link.pack(pady=10)
274 |
275 | # 绑定点击事件打开链接
276 | github_link.bind("", lambda e: self.open_github_link())
277 |
278 | def open_github_link(self):
279 | # 打开 GitHub 链接
280 | import webbrowser
281 | webbrowser.open("https://github.com/RwandanMtGorilla/ZJGSU_spider/tree/main")
282 |
283 | def update_log(self, index):
284 | # 更新日志显示
285 | if not self.processes or not self.processes[index].is_alive():
286 | return
287 | try:
288 | log_with_time = self.log_queues[index].get_nowait()
289 |
290 | # 分离日志内容和时间
291 | if "||" in log_with_time:
292 | log, current_time = log_with_time.rsplit("||", 1)
293 | else:
294 | log = log_with_time
295 | current_time = time.strftime("%H:%M:%S", time.localtime(time.time()))
296 |
297 | # 更新计数和时间
298 | self.log_counts[index][log] += 1
299 | self.log_times[index][log] = current_time # 更新为最新时间
300 | count = self.log_counts[index][log]
301 | latest_time = self.log_times[index][log]
302 |
303 | # 在列表中查找是否已存在该日志
304 | found = False
305 | for i in range(self.listboxes[index].size()):
306 | item = self.listboxes[index].get(i)
307 | # 提取原始日志内容(去掉计数和时间部分)
308 | if ' (×' in item:
309 | original_log = item.split(' (×')[0]
310 | elif '[' in item and ']' in item:
311 | # 处理只有时间没有计数的情况
312 | original_log = item.split(' [')[0]
313 | else:
314 | original_log = item
315 |
316 | if original_log == log:
317 | # 更新现有项目
318 | self.listboxes[index].delete(i)
319 | if count > 1:
320 | display_text = f"{log} (×{count}) [{latest_time}]"
321 | else:
322 | display_text = f"{log} [{latest_time}]"
323 | self.listboxes[index].insert(i, display_text)
324 | found = True
325 | break
326 |
327 | if not found:
328 | # 添加新项目
329 | if count > 1:
330 | display_text = f"{log} (×{count}) [{latest_time}]"
331 | else:
332 | display_text = f"{log} [{latest_time}]"
333 | self.listboxes[index].insert('end', display_text)
334 | self.listboxes[index].see('end')
335 |
336 | except:
337 | pass
338 | self.root.after(500, lambda: self.update_log(index))
339 |
340 |
341 | if __name__ == '__main__':
342 | freeze_support()
343 | root = ttk.Window(themename="litera")
344 | app = App(root)
345 | root.mainloop()
346 |
--------------------------------------------------------------------------------