From 791cc47625a62120c9c475631a045b3cb52c7773 Mon Sep 17 00:00:00 2001 From: Arsennnic Date: Tue, 26 Nov 2019 17:34:12 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8F=91=E5=B8=83alpha1.0=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 56 ++++++++++++++++++++++++++-------------- epc_bot.py | 10 ++++--- graphic.py | 63 +++++++++++++++++++++++++++++---------------- py2exe.bat | 6 +++++ selenium/service.py | 4 +-- 5 files changed, 91 insertions(+), 48 deletions(-) create mode 100644 py2exe.bat diff --git a/README.md b/README.md index 7e5e9f8..95f3996 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,22 @@ -# USTC-EPC-BOT +# EPC-BOT for USTC 中国科学技术大学EPC系统自动抢课脚本. +## 功能列表 + +待更新. + ## 使用说明 待更新. +## 实现原理 +待更新. -## 算法逻辑 +## 选课逻辑 ### 未达预约上限(4学时)的情况 @@ -29,43 +35,32 @@ - 比较可预约课程与已预约课程的时间先后. 若存在可预约课程的日期早于已预约课程中的最晚日期, 则考虑进行一次课程替换. 若存在1学时课程与2学时课程的日期相同, 优先选择2学时课程. -
- -| I | II | III | IV | V | -| - | - | - | - | - | -| 1 | 1 | 1 | 2 | 2 | -| 1 | 1 | 2 | 1 | 2 | -| 1 | 2 | 1 | 1 | | -| 1 | | | | | - -
- -- 将已预约课程按照日期先后排列, 共有以上五种学时组合, 分别讨论之: - + 组合I: +- 将已预约课程按照日期先后排列, 共有五种学时组合, 分别讨论之: + + 组合I (1, 1, 1, 1): - 可预约课程为2学时: + 可预约课程日期早于已预约的倒数第2节课, 将倒数第1-2节已预约课程替换为可预约课程. + 可预约课程日期晚于已预约的倒数第2节课, 不替换. - 可预约课程为1学时: + 将倒数第1节已预约课程替换为可预约课程. - + 组合II: + + 组合II (1, 1, 2): - 可预约课程为2学时: + 将倒数第1节已预约课程替换为可预约课程. - 可预约课程为1学时: + 可预约课程日期早于已预约的倒数第2节课, 将倒数第1-2节已预约课程替换为可预约课程. + 可预约课程日期晚于已预约的倒数第2节课, 不替换. - + 组合III: + + 组合III (1, 2, 1): - 可预约课程为2学时: + 可预约课程日期早于已预约的倒数第2节课, 将倒数第2节已预约课程替换为可预约课程. + 可预约课程日期晚于已预约的倒数第2节课, 不替换. - 可预约课程为1学时: + 将倒数第1节已预约课程替换为可预约课程. - + 组合IV: + + 组合IV (2, 1, 1): - 可预约课程为2学时: + 可预约课程日期早于已预约的倒数第2节课, 将倒数第1-2节已预约课程替换为可预约课程. + 可预约课程日期晚于已预约的倒数第2节课, 不替换. - 可预约课程为1学时: + 将倒数第1节已预约课程替换为可预约课程. - + 组合V: + + 组合V (2, 2): - 可预约课程为2学时: + 将倒数第1节已预约课程替换为可预约课程. - 可预约课程为1学时: @@ -74,3 +69,26 @@ - 循环上述操作, 直至可预约的课程列表为空. + +## Q & A + +### 使用 PyInstaller 重新打包后, Selenium 运行时有黑色空职台弹出, 如何避免? +打开 Python\Lib\site-packages\selenium\webdriver\common\service.py 文件, 将 +``` +self.process = subprocess.Popen(cmd, env=self.env, close_fds=platform.system() != 'Windows', stdout=self.log_file, stderr=self.log_file, stdin=PIPE) +``` +改写成 +``` +self.process = subprocess.Popen(cmd, stdin=PIPE, stdout=PIPE ,stderr=PIPE, shell=False, creationflags=0x08000000) +``` +或直接用工程目录中的 Selenium\service.py 文件替换原生的 service.py 文件 +并重新使用 PyInstaller 打包. + + +## 参考文献 + +[1] 木华生. 中科大EPC课程爬取\[OL\]. https://blog.csdn.net/qq_28491207/article/details/84261732, 2018. + +[2] David Cortesi, William Caban. PyInstaller Manual\[OL\]. https://pyinstaller.readthedocs.io/. + +[3] AhmedWas. Getting Rid of ChromeDirver Console Window with PyInstaller\[OL\]. https://stackoverflow.com/questions/52643556/getting-rid-of-chromedirver-console-window-with-pyinstaller, 2018. diff --git a/epc_bot.py b/epc_bot.py index 947d84b..32cea23 100644 --- a/epc_bot.py +++ b/epc_bot.py @@ -1,4 +1,4 @@ -import requests, time, re, json, threading +import os, inspect, requests, time, re, json, threading from bs4 import BeautifulSoup from selenium import webdriver from selenium.webdriver.chrome.options import Options @@ -49,9 +49,11 @@ def __init__(self, sid, passwd, filtr, email_addr, email_pwd, gui=None): options.add_argument("--no-sandbox") options.add_argument("--headless") options.add_argument("--disable-gpu") - self.WEBDRIVER = webdriver.Chrome(chrome_options=options, - executable_path="./selenium/chromedriver.exe" - ) + work_dir = os.path.realpath(os.path.abspath( + os.path.split(inspect.getfile(inspect.currentframe()))[0] + )) + driver_dir = os.path.join(work_dir, "selenium", "chromedriver.exe") + self.WEBDRIVER = webdriver.Chrome(chrome_options=options, executable_path=driver_dir) def stop(self): self._STOP_FLAG.set() diff --git a/graphic.py b/graphic.py index f043560..3ca77c5 100644 --- a/graphic.py +++ b/graphic.py @@ -1,7 +1,7 @@ from tkinter import * from tkinter.scrolledtext import * from tkinter.messagebox import showinfo -import json, threading +import os, inspect, json, threading, traceback from epc_bot import * class GUI: @@ -11,6 +11,10 @@ def __init__(self, master): self.master.title("EPC-BOT for USTC") self.master.protocol('WM_DELETE_WINDOW', self.on_gui_destroy) + self.work_dir = os.path.realpath(os.path.abspath( + os.path.split(inspect.getfile(inspect.currentframe()))[0] + )) + self.frame_basic = Frame(master) self.frame_basic.grid(row=0, pady=10) Label(self.frame_basic, text="--- Basic Settings ---").grid(row=0, columnspan=4) @@ -34,22 +38,27 @@ def __init__(self, master): self.frame_filter = Frame(master) self.frame_filter.grid(row=1, pady=10) Label(self.frame_filter, text="--- Filter Settings ---").grid(row=0, columnspan=5) - with open("config.template.json", "r", encoding="utf-8") as template_file: - self.filters_all = json.load(template_file)["filter"] - self.filters_var = list() - self.cbtn_filters = list() - row, col = 1, 0 - for i in range(len(self.filters_all)): - if (i%5 == 0): - row = row + 1 - col = -1 - col = col + 1 - filter_str = self.filters_all[i]["wday"][0:3] + ".\t" + self.filters_all[i]["time"] - self.filters_var.append(IntVar()) - cbtn_filter = Checkbutton(self.frame_filter, text=filter_str, variable=self.filters_var[i]) - cbtn_filter.grid(row=row, column=col, padx=5, pady=2) - cbtn_filter.deselect() - self.cbtn_filters.append(cbtn_filter) + template_dir = os.path.join(self.work_dir, "config.template.json") + if os.path.exists(template_dir): + with open(template_dir, "r", encoding="utf-8") as template_file: + self.filters_all = json.load(template_file)["filter"] + self.filters_var = list() + self.cbtn_filters = list() + row, col = 1, 0 + for i in range(len(self.filters_all)): + if (i%5 == 0): + row = row + 1 + col = -1 + col = col + 1 + filter_str = self.filters_all[i]["wday"][0:3] + ".\t" + self.filters_all[i]["time"] + self.filters_var.append(IntVar()) + cbtn_filter = Checkbutton(self.frame_filter, text=filter_str, variable=self.filters_var[i]) + cbtn_filter.grid(row=row, column=col, padx=5, pady=2) + cbtn_filter.deselect() + self.cbtn_filters.append(cbtn_filter) + else: + showerror(title="Error", message="Configuration template is missing!") + self.master.destroy() self.btn_start = Button(master, text="Start", command=self.start_bot) self.btn_start.grid(row=2, ipadx=self.btn_start.winfo_width()*5, pady=10) @@ -59,7 +68,8 @@ def __init__(self, master): ## 读取工作目录下存储的配置文件 def read_config(self): try: - with open("config.json", "r", encoding="utf-8") as config: + config_dir = os.path.join(self.work_dir, "config.json") + with open(config_dir, "r", encoding="utf-8") as config: config = json.load(config) self.sid = config["sid"] self.passwd = config["passwd"] @@ -105,6 +115,7 @@ def start_bot(self): self.write_config() else: showinfo(title="Alert", message="Please fill the blanks in basic settings module!") + return self.master.resizable(False, False) self.frame_basic.grid_forget() @@ -112,10 +123,16 @@ def start_bot(self): self.btn_start.grid_forget() self.console = ScrolledText(self.master) self.console.pack() + self.update_log("Working Directory:\n%s\n" % self.work_dir) - self.bot = EPCBot(self.sid, self.passwd, self.filters_json, \ - self.email_addr, self.email_pwd, self) - self.bot.start() + try: + self.bot = EPCBot(self.sid, self.passwd, self.filters_json, \ + self.email_addr, self.email_pwd, self) + self.bot.start() + self.update_log("EPC-Bot is running...\n") + except Exception as e: + self.update_log("Chrome driver is not installed!") + self.update_log(traceback.format_exc()) ## 更新EPC-BOT日志到GUI def update_log(self, text): @@ -127,6 +144,8 @@ def update_log(self, text): ## 关闭GUI时杀死子线程 def on_gui_destroy(self): self.master.destroy() - if self.bot: + try: self.bot.stop() + except: + pass \ No newline at end of file diff --git a/py2exe.bat b/py2exe.bat new file mode 100644 index 0000000..8017d4f --- /dev/null +++ b/py2exe.bat @@ -0,0 +1,6 @@ +pyinstaller --onedir --windowed ^ + --add-data="config.template.json;." ^ + --add-data="selenium\chromedriver.exe;selenium" ^ + --name="epc_bot" ^ + main_gui.py +rd /s /q __pycache__ \ No newline at end of file diff --git a/selenium/service.py b/selenium/service.py index a10868d..2bd5653 100644 --- a/selenium/service.py +++ b/selenium/service.py @@ -69,9 +69,7 @@ def start(self): try: cmd = [self.path] cmd.extend(self.command_line_args()) - self.process = subprocess.Popen(cmd, env=self.env, - close_fds=platform.system() != 'Windows', - stdout=self.log_file, stderr=self.log_file, creationflags=134217728) + self.process = subprocess.Popen(cmd, stdin=PIPE, stdout=PIPE ,stderr=PIPE, shell=False, creationflags=0x08000000) except TypeError: raise except OSError as err: