├── crawl
├── __init__.py
├── project_info.py
├── exam_base.py
├── utils.py
└── exam_zujuan.py
├── ui
├── __init__.py
├── untitled.ui
├── mttkinter.py
└── main_ui.py
├── README.md
├── .gitignore
├── config
├── cookies.ck
└── project.json
├── qrcode
├── default.png
└── wx_qrcode.png
├── testfun.py
└── requirement.txt
/crawl/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ui/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # exam_paper
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /venv/
2 | /.idea/
3 |
--------------------------------------------------------------------------------
/config/cookies.ck:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaibush/tk-exam-paper/HEAD/config/cookies.ck
--------------------------------------------------------------------------------
/qrcode/default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaibush/tk-exam-paper/HEAD/qrcode/default.png
--------------------------------------------------------------------------------
/qrcode/wx_qrcode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaibush/tk-exam-paper/HEAD/qrcode/wx_qrcode.png
--------------------------------------------------------------------------------
/testfun.py:
--------------------------------------------------------------------------------
1 | from multiprocessing import Pool
2 | import os,time,random
3 |
4 | def run(name):
5 | print("子进程启动")
6 |
7 |
8 | if __name__ == "__main__":
9 | print("父进程启动")
10 | pp = Pool(2)
11 | for i in range(5):
12 | pp.apply_async(run,args = (i,))
13 | pp.close()
14 | pp.join()
15 | print("父进程结束")
--------------------------------------------------------------------------------
/config/project.json:
--------------------------------------------------------------------------------
1 | {
2 | "urls": {
3 | "login": "https://passport.zujuan.com/login",
4 | "qrcode": "https://passport.zujuan.com/connect/weixin-qrcode?iframe=1&width=220&height=220",
5 | "wxlogin": "https://passport.zujuan.com/connect/wxlogin",
6 | "issubscribe": "https://passport.zujuan.com/connect/issubscribe",
7 | "user": "https://www.zujuan.com/u/index",
8 | "zujuan": "https://www.zujuan.com/u/zujuan",
9 | "jump_url": "https://www.zujuan.com"
10 | },
11 | "timeout": 600
12 | }
13 |
--------------------------------------------------------------------------------
/requirement.txt:
--------------------------------------------------------------------------------
1 | beautifulsoup4==4.7.1
2 | bs4==0.0.1
3 | certifi==2019.3.9
4 | chardet==3.0.4
5 | Click==7.0
6 | dominate==2.3.5
7 | Flask==1.0.2
8 | Flask-Bootstrap==3.3.7.1
9 | Flask-Moment==0.7.0
10 | Flask-Script==2.0.6
11 | Flask-SQLAlchemy==2.3.2
12 | Flask-WTF==0.14.2
13 | idna==2.8
14 | itsdangerous==1.1.0
15 | Jinja2==2.10
16 | lxml==4.3.2
17 | MarkupSafe==1.1.1
18 | mysqlclient==1.4.2.post1
19 | Pillow==6.2.1
20 | PyMySQL==0.9.3
21 | PyQt5==5.13.0
22 | PyQt5-sip==12.7.0
23 | pyqt5-tools==5.13.0.1.5
24 | python-dotenv==0.10.3
25 | requests==2.21.0
26 | soupsieve==1.8
27 | SQLAlchemy==1.3.1
28 | tkimg==0.0.1
29 | ttkwidgets==0.10.0
30 | urllib3==1.24.1
31 | visitor==0.1.3
32 | Werkzeug==0.14.1
33 | WTForms==2.2.1
34 |
--------------------------------------------------------------------------------
/crawl/project_info.py:
--------------------------------------------------------------------------------
1 | import json
2 | import os
3 |
4 |
5 | class Project:
6 | root = os.path.dirname(os.path.dirname(__file__))
7 | project_config = os.path.join(root, "config", "project.json")
8 | cookies = os.path.join(root, "config", "cookies.ck")
9 | try:
10 | with open(project_config) as fp:
11 | project_info = json.load(fp)
12 | urls = project_info["urls"]
13 | check_timeout = project_info["timeout"]
14 | except FileNotFoundError:
15 | urls = {
16 | "login": "https://passport.zujuan.com/login",
17 | "qrcode": "https://passport.zujuan.com/connect/weixin-qrcode?iframe=1&width=220&height=220",
18 | "wxlogin": "https://passport.zujuan.com/connect/wxlogin",
19 | "issubscribe": "https://passport.zujuan.com/connect/issubscribe",
20 | "user": "https://www.zujuan.com/u/index",
21 | "zujuan": "https://www.zujuan.com/u/zujuan"
22 | }
23 | check_timeout = 300
24 | qrcode = os.path.join(root, "qrcode", "wx_qrcode.png")
25 | default_png = os.path.join(root, "qrcode", "default.png")
26 | scan_flag = os.path.join(root, "flags", "scan.flag")
27 |
28 |
--------------------------------------------------------------------------------
/crawl/exam_base.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | import bs4
4 | import requests
5 |
6 | from crawl.project_info import Project
7 | from crawl.utils import logger
8 |
9 |
10 | class URLs:
11 | login = Project.urls["login"]
12 | qrcode = Project.urls["qrcode"]
13 | wxlogin = Project.urls["wxlogin"]
14 | issubscribe = Project.urls["issubscribe"]
15 | user = Project.urls["user"]
16 | zujuan = Project.urls["zujuan"]
17 | jump_url = Project.urls["jump_url"]
18 |
19 |
20 | class ExamPaperBase:
21 | sess = requests.Session()
22 | headers = {
23 | "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/"
24 | "537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36"
25 | }
26 |
27 | get = sess.get
28 |
29 | @logger
30 | def check_login_succ(self):
31 | resp = self.sess.get(URLs.user)
32 | soup = bs4.BeautifulSoup(resp.text, "html.parser")
33 | login_fail = soup.find("div", attrs={"class": "mistack-content"})
34 | if login_fail and login_fail.text.find("未登录") != -1:
35 | logging.info("未登录,请重新扫码登录")
36 | return False
37 | login_succ = soup.find("legend", attrs={"class": "form-title"})
38 | if login_succ and login_succ.text.find("第三方账号绑定") != -1:
39 | logging.info("已检测,登录成功")
40 | return True
41 | return False
42 |
43 | @staticmethod
44 | def clear_text(text):
45 | return text.replace("\n", "")
46 |
47 |
48 | class LogoutError(Exception):
49 | pass
50 |
--------------------------------------------------------------------------------
/crawl/utils.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import multiprocessing as mp
3 | import pickle
4 | from functools import partial
5 | from functools import wraps
6 | from queue import Queue
7 |
8 | import requests
9 | from requests.cookies import RequestsCookieJar
10 |
11 | from crawl.project_info import Project
12 |
13 |
14 | def save_cookies(cookies):
15 | with open(Project.cookies, 'wb') as f:
16 | pickle.dump(cookies.get_dict(), f)
17 |
18 |
19 | def load_cookies():
20 | try:
21 | with open(Project.cookies, 'rb') as f:
22 | cookies = requests.utils.cookiejar_from_dict(
23 | pickle.load(f)
24 | )
25 | except:
26 | cookies = RequestsCookieJar()
27 | return cookies
28 |
29 |
30 | def logger(func):
31 | @wraps(func)
32 | def wrapper(*args, **kwargs):
33 | logging.info("-->running (%s)", func.__name__)
34 | try:
35 | return func(*args, **kwargs)
36 | except Exception as e:
37 | logging.error("running (%s) error", func.__name__)
38 | raise
39 |
40 | return wrapper
41 |
42 |
43 | class WorkProcess:
44 | workers = []
45 |
46 | def put(self, callback, *args, **kwargs):
47 | self.workers.append(partial(callback, *args, **kwargs))
48 |
49 | def clear(self):
50 | self.workers.clear()
51 |
52 | def stop_old_work(self):
53 | for p in self.workers:
54 | if p.is_alive():
55 | logging.info("stop old worker: %s", p.pid)
56 | p.terminate()
57 | self.clear()
58 |
59 | def run(self):
60 | if self.workers:
61 | func = self.workers.pop()
62 | self.stop_old_work()
63 |
64 | p = mp.Process(target=func)
65 | p.start()
66 | logging.info("child pid: %s", p.pid)
67 | self.workers.append(p)
68 | logging.info("p.is_alive: %s", p.is_alive())
69 | # p.join()
70 |
71 |
72 | logging.basicConfig(level=logging.INFO)
73 |
74 |
75 | if __name__ == "__main__":
76 | mp.freeze_support()
77 | result = mp.Manager().dict()
78 | w = WorkProcess()
79 |
--------------------------------------------------------------------------------
/ui/untitled.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | MainWindow
4 |
5 |
6 |
7 | 0
8 | 0
9 | 522
10 | 410
11 |
12 |
13 |
14 | MainWindow
15 |
16 |
17 |
18 |
19 |
20 | 10
21 | 260
22 | 201
23 | 31
24 |
25 |
26 |
27 | -
28 |
29 |
30 | 刷新二维码
31 |
32 |
33 |
34 | -
35 |
36 |
37 | 一键登录
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | 11
47 | 11
48 | 84
49 | 16
50 |
51 |
52 |
53 | 扫描二维码登录
54 |
55 |
56 |
57 |
58 | true
59 |
60 |
61 |
62 | 11
63 | 29
64 | 220
65 | 220
66 |
67 |
68 |
69 |
70 |
80 |
81 |
82 |
83 |
84 |
85 |
--------------------------------------------------------------------------------
/crawl/exam_zujuan.py:
--------------------------------------------------------------------------------
1 | import json
2 | import logging
3 | import os
4 | import random
5 | import threading
6 | import time
7 | import multiprocessing as mp
8 | from multiprocessing.pool import Pool
9 | from urllib import parse
10 | from collections import OrderedDict, namedtuple
11 | import bs4
12 | import requests
13 |
14 | from crawl.exam_base import ExamPaperBase, URLs, LogoutError
15 | from crawl.utils import save_cookies, load_cookies, logger
16 | from crawl.project_info import Project
17 |
18 |
19 | class ScanLogin(ExamPaperBase):
20 | def __init__(self):
21 | try:
22 | self.sess.get(URLs.login)
23 | except Exception:
24 | logging.exception("error")
25 |
26 | @staticmethod
27 | def remove_scan_flag():
28 | if os.path.exists(Project.scan_flag):
29 | os.remove(Project.scan_flag)
30 |
31 | @staticmethod
32 | def generate_scan_flag():
33 | with open(Project.scan_flag, "wb") as fp:
34 | fp.write(b"1")
35 |
36 | @staticmethod
37 | def get_scan_flag():
38 | return os.path.exists(Project.scan_flag)
39 |
40 | @logger
41 | def get_qrcode_url(self):
42 | resp = self.sess.get(URLs.qrcode)
43 | soup = bs4.BeautifulSoup(resp.text, "html.parser")
44 | wrp_code = soup.find("div", attrs={"class": "wrp_code"})
45 | qrcode_url = wrp_code.contents[0].attrs["src"]
46 | return qrcode_url
47 |
48 | @staticmethod
49 | @logger
50 | def get_ticket(qrcode_url):
51 | query = dict(
52 | parse.parse_qsl(
53 | parse.urlsplit(qrcode_url).query
54 | )
55 | )
56 | return query["ticket"]
57 |
58 | @logger
59 | def save_qrcode_pic(self, qrcode_url):
60 | resp = self.sess.get(qrcode_url)
61 | with open(Project.qrcode, "wb") as fp:
62 | fp.write(resp.content)
63 |
64 | @logger
65 | def check_scan(self, ticket):
66 | logging.info("等待扫码: %s", ticket)
67 | self.remove_scan_flag()
68 | query = {
69 | "ticket": ticket,
70 | "jump_url": URLs.jump_url,
71 | "r": random.random()
72 | }
73 | check_login_url = URLs.issubscribe + "?" + parse.urlencode(query)
74 | check_cnt = 0
75 | scan_status = False
76 | while check_cnt < Project.check_timeout:
77 | logging.info("扫码...[%s]", threading.current_thread())
78 | is_login_resp = self.sess.get(check_login_url)
79 | resp = json.loads(is_login_resp.text)
80 | # code = 0 等待扫码
81 | if resp["code"] == 1:
82 | scan_status = True
83 | self.generate_scan_flag()
84 | break
85 | time.sleep(1)
86 | check_cnt += 1
87 | return scan_status
88 |
89 | @logger
90 | def login_by_scan(self, ticket):
91 | wx_query = {
92 | "ticket": ticket,
93 | "jump_url": URLs.jump_url
94 | }
95 | resp = self.sess.get(
96 | URLs.wxlogin + '?' + parse.urlencode(wx_query),
97 | verify=False
98 | )
99 | if resp.status_code == 200:
100 | logging.info("登录成功,保存cookies")
101 | save_cookies(self.sess.cookies)
102 | else:
103 | raise requests.HTTPError("wechat login failed")
104 |
105 |
106 | class CookiesLogin(ExamPaperBase):
107 | @logger
108 | def login_by_cookies(self):
109 | cookies = load_cookies()
110 | self.sess.cookies = cookies
111 | if not self.check_login_succ():
112 | raise LogoutError("pls scan qrcode to login again")
113 |
114 |
115 | class ZuJuanView(ExamPaperBase):
116 | def get_username(self):
117 | resp = self.get(URLs.user)
118 | soup = bs4.BeautifulSoup(resp.text, "html.parser")
119 | real_name = soup.find("div", attrs={"id": "J_realname"})
120 | return real_name.text.replace("\n", "") if real_name else "未知用户名"
121 |
122 | def get_zujuan_view(self):
123 | ret = OrderedDict()
124 | resp = self.get(URLs.zujuan)
125 | soup = bs4.BeautifulSoup(resp.text, "html.parser")
126 | zujuan = soup.find("ul", attrs={"class": "f-cb"})
127 | if zujuan:
128 | for li in zujuan.find_all("p", attrs={"class": "test-txt-p1"}):
129 | href = li.find("a")
130 | Record = namedtuple('Record', ['text', 'href'])
131 | info = Record(href.text, href["href"])
132 | ret[href["pid"]] = info
133 | return ret
134 |
135 |
136 | class ZuJuanTasks(ExamPaperBase):
137 |
138 | def zujuan_task(self, task):
139 | print(task)
140 | time.sleep(10)
141 | print("end===========")
142 | return task
143 |
144 | def task_run(self, pool, args):
145 | for task_args in args:
146 | pool.apply_async(self.zujuan_task, (task_args,))
147 | pool.close()
148 | logging.info("等待所有task执行完成...")
149 | pool.join()
150 | logging.info("所有task执行完成...")
151 |
152 | @staticmethod
153 | def task_shutdown(pool):
154 | if isinstance(pool, Pool):
155 | logging.info("终止所有任务")
156 | pool.terminate()
157 |
158 |
159 | if __name__ == "__main__":
160 | mp.freeze_support()
161 | wx_scan = ScanLogin()
162 | wx_scan.remove_scan_flag()
163 |
164 | pool = mp.Pool(processes=2)
165 | zujuan = ZuJuanTasks()
166 | zujuan.task_run(
167 | pool,
168 | [
169 | ["组卷名称", "href====="],
170 | ["组卷名称2", "href=====2"],
171 | ]
172 |
173 | )
174 | zujuan.task_shutdown(pool)
175 | # qrcode = wx_scan.get_qrcode_url()
176 | # wx_scan.save_qrcode_pic(qrcode)
177 | # ticket = wx_scan.get_ticket(qrcode)
178 | # print(ticket)
179 | # wx_scan.check_scan(ticket)
180 | # wx_scan.login_by_scan(ticket)
181 | # wx_scan.check_login_succ()
182 | #
183 | # cookies_login = CookiesLogin()
184 | # cookies_login.login_by_cookies()
185 | #
186 | # print(ZuJuanView().get_username())
187 | # print(ZuJuanView().get_zujuan_view())
188 |
--------------------------------------------------------------------------------
/ui/mttkinter.py:
--------------------------------------------------------------------------------
1 | '''Thread-safe version of tkinter.
2 | Copyright (c) 2014, Andrew Barnert
3 | Based on mtTkinter (for Python 2.x), copyright (c) 2009, Allen B. Taylor
4 | This module is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Lesser Public License as published by
6 | the Free Software Foundation, either version 3 of the License, or
7 | (at your option) any later version.
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Lesser Public License for more details.
12 | You should have received a copy of the GNU Lesser Public License
13 | along with this program. If not, see .
14 | Usage:
15 | import mttkinter as tkinter
16 | # Use "t." as usual.
17 | or
18 | from mtt import *
19 | # Use tkinter module definitions as usual.
20 | This module modifies the original tkinter module in memory, making all
21 | functionality thread-safe. It does this by wrapping the Tk class' tk
22 | instance with an object that diverts calls through an event queue when
23 | the call is issued from a thread other than the thread in which the Tk
24 | instance was created. The events are processed in the creation thread
25 | via an 'after' event.
26 | The modified Tk class accepts two additional keyword parameters on its
27 | __init__ method:
28 | mtDebug:
29 | 0 = No debug output (default)
30 | 1 = Minimal debug output
31 | ...
32 | 9 = Full debug output
33 | mtCheckPeriod:
34 | Amount of time in milliseconds (default 100) between checks for
35 | out-of-thread events when things are otherwise idle. Decreasing
36 | this value can improve GUI responsiveness, but at the expense of
37 | consuming more CPU cycles.
38 | Note that, because it modifies the original tkinter module (in memory),
39 | other modules that use tkinter (e.g., Pmw) reap the benefits automagically
40 | as long as mttkinter is imported at some point before extra threads are
41 | created.
42 | Author: Allen B. Taylor, a.b.taylor@gmail.com
43 | '''
44 |
45 | from tkinter import *
46 | import threading
47 | import queue
48 |
49 |
50 | class _Tk(object):
51 | """
52 | Wrapper for underlying attribute tk of class Tk.
53 | """
54 |
55 | def __init__(self, tk, mtDebug=0, mtCheckPeriod=10):
56 | self._tk = tk
57 |
58 | # Create the incoming event queue.
59 | self._eventQueue = queue.Queue(1)
60 |
61 | # Identify the thread from which this object is being created so we can
62 | # tell later whether an event is coming from another thread.
63 | self._creationThread = threading.currentThread()
64 |
65 | # Store remaining values.
66 | self._debug = mtDebug
67 | self._checkPeriod = mtCheckPeriod
68 |
69 | def __getattr__(self, name):
70 | # Divert attribute accesses to a wrapper around the underlying tk
71 | # object.
72 | return _TkAttr(self, getattr(self._tk, name))
73 |
74 |
75 | class _TkAttr(object):
76 | """
77 | Thread-safe callable attribute wrapper.
78 | """
79 |
80 | def __init__(self, tk, attr):
81 | self._tk = tk
82 | self._attr = attr
83 |
84 | def __call__(self, *args, **kwargs):
85 | """
86 | Thread-safe method invocation.
87 | Diverts out-of-thread calls through the event queue.
88 | Forwards all other method calls to the underlying tk object directly.
89 | """
90 |
91 | # Check if we're in the creation thread.
92 | if threading.currentThread() == self._tk._creationThread:
93 | # We're in the creation thread; just call the event directly.
94 | if self._tk._debug >= 8 or \
95 | self._tk._debug >= 3 and self._attr.__name__ == 'call' and \
96 | len(args) >= 1 and args[0] == 'after':
97 | print('Calling event directly: {} {} {}'.format(
98 | self._attr.__name__, args, kwargs))
99 | return self._attr(*args, **kwargs)
100 | else:
101 | # We're in a different thread than the creation thread; enqueue
102 | # the event, and then wait for the response.
103 | responseQueue = queue.Queue(1)
104 | if self._tk._debug >= 1:
105 | print('Marshalling event: {} {} {}'.format(
106 | self._attr.__name__, args, kwargs))
107 | self._tk._eventQueue.put((self._attr, args, kwargs, responseQueue))
108 | isException, response = responseQueue.get()
109 |
110 | # Handle the response, whether it's a normal return value or
111 | # an exception.
112 | if isException:
113 | exType, exValue, exTb = response
114 | raise exType(exValue).with_traceback(exTb)
115 | else:
116 | return response
117 |
118 |
119 | # Define a hook for class Tk's __init__ method.
120 | def _Tk__init__(self, *args, **kwargs):
121 | # We support some new keyword arguments that the original __init__ method
122 | # doesn't expect, so separate those out before doing anything else.
123 | new_kwnames = ('mtCheckPeriod', 'mtDebug')
124 | new_kwargs = {}
125 | for name, value in kwargs.items():
126 | if name in new_kwnames:
127 | new_kwargs[name] = value
128 | kwargs.clear()
129 |
130 | # Call the original __init__ method, creating the internal tk member.
131 | self.__original__init__mttkinter(*args, **kwargs)
132 |
133 | # Replace the internal tk member with a wrapper that handles calls from
134 | # other threads.
135 | self.tk = _Tk(self.tk, **new_kwargs)
136 |
137 | # Set up the first event to check for out-of-thread events.
138 | self.after_idle(_CheckEvents, self)
139 |
140 |
141 | # Replace Tk's original __init__ with the hook.
142 | Tk.__original__init__mttkinter = Tk.__init__
143 | Tk.__init__ = _Tk__init__
144 |
145 |
146 | def _CheckEvents(tk):
147 | "Event checker event."
148 |
149 | used = False
150 | try:
151 | # Process all enqueued events, then exit.
152 | while True:
153 | try:
154 | # Get an event request from the queue.
155 | method, args, kwargs, responseQueue = \
156 | tk.tk._eventQueue.get_nowait()
157 | except:
158 | if tk.tk._debug >= 2:
159 | print('Event queue empty')
160 | # No more events to process.
161 | break
162 | else:
163 | # Call the event with the given arguments, and then return
164 | # the result back to the caller via the response queue.
165 | used = True
166 | if tk.tk._debug >= 2:
167 | print('Calling event from main thread: {} {} {}'
168 | .format(method.__name__, args, kwargs))
169 | try:
170 | responseQueue.put((False, method(*args, **kwargs)))
171 | except SystemExit as ex:
172 | raise
173 | except Exception as ex:
174 | # Calling the event caused an exception; return the
175 | # exception back to the caller so that it can be raised
176 | # in the caller's thread.
177 | from sys import exc_info
178 | exType, exValue, exTb = exc_info()
179 | responseQueue.put((True, (exType, exValue, exTb)))
180 | finally:
181 | # Schedule to check again. If we just processed an event, check
182 | # immediately; if we didn't, check later.
183 | if used:
184 | tk.after_idle(_CheckEvents, tk)
185 | else:
186 | tk.after(tk.tk._checkPeriod, _CheckEvents, tk)
187 |
188 |
189 | # Test thread entry point.
190 | def _testThread(root):
191 | text = "This is Tcl/Tk version %s" % TclVersion
192 | if TclVersion >= 8.1:
193 | try:
194 | text = text + "\nThis should be a cedilla: \347"
195 | except NameError:
196 | pass # no unicode support
197 | try:
198 | if root.globalgetvar('tcl_platform(threaded)'):
199 | text = text + "\nTcl is built with thread support"
200 | else:
201 | raise RuntimeError
202 | except:
203 | text = text + "\nTcl is NOT built with thread support"
204 | text = text + "\nmttkinter works with or without Tcl thread support"
205 | label = Label(root, text=text)
206 | label.pack()
207 | button = Button(root, text="Click me!",
208 | command=lambda root=root: root.button.configure(
209 | text="[%s]" % root.button['text']))
210 | button.pack()
211 | root.button = button
212 | quit = Button(root, text="QUIT", command=root.destroy)
213 | quit.pack()
214 | # The following three commands are needed so the window pops
215 | # up on top on Windows...
216 | root.iconify()
217 | root.update()
218 | root.deiconify()
219 | # Simulate button presses...
220 | button.invoke()
221 | root.after(1000, _pressOk, root, button)
222 |
223 |
224 | # Test button continuous press event.
225 | def _pressOk(root, button):
226 | button.invoke()
227 | try:
228 | root.after(1000, _pressOk, root, button)
229 | except:
230 | pass # Likely we're exiting
231 |
232 |
233 | # Test. Mostly borrowed from the tkinter module, but the important bits moved
234 | # into a separate thread.
235 | if __name__ == '__main__':
236 | import threading
237 |
238 | root = Tk(mtDebug=1)
239 | thread = threading.Thread(target=_testThread, args=(root,))
240 | thread.start()
241 | root.mainloop()
242 |
--------------------------------------------------------------------------------
/ui/main_ui.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import os
3 | import threading
4 | from multiprocessing import freeze_support, Pool
5 | from tkinter import messagebox
6 | # from tkinter import *
7 | import requests
8 |
9 | from ui.mttkinter import *
10 | from tkinter.ttk import *
11 |
12 | from PIL import Image, ImageTk
13 | from ttkwidgets import *
14 |
15 | from crawl.exam_zujuan import ScanLogin, CookiesLogin, ZuJuanView, ZuJuanTasks
16 | from crawl.project_info import Project
17 | from crawl.utils import WorkProcess
18 |
19 |
20 | class UIWidget(dict):
21 | def __getattribute__(self, item):
22 | if item in self:
23 | return self[item]
24 | return None
25 |
26 |
27 | UI = UIWidget()
28 | worker = WorkProcess()
29 |
30 |
31 | class MyDebugWindow(DebugWindow):
32 |
33 | # def write(self, line):
34 | # self.text.insert(END, line)
35 | # self.text.see(END)
36 |
37 | def quit(self):
38 | super(MyDebugWindow, self).quit()
39 | log.removeHandler(UI.handler)
40 | del UI["debug"], UI["handler"]
41 |
42 |
43 | class MainUI:
44 | def __init__(self, root):
45 | self.root = root
46 | self.build_debug_ui()
47 | self.build_ui()
48 |
49 | def build_debug_ui(self):
50 | UI["debug"] = MyDebugWindow(self.root)
51 | UI.debug.geometry("400x400+0+0")
52 | stdout_handler = logging.StreamHandler(UI.debug)
53 | log.addHandler(stdout_handler)
54 | UI["handler"] = stdout_handler
55 |
56 | def build_ui(self):
57 | self.build_left_ui()
58 | self.build_right_ui()
59 |
60 | def resize_img(self, path):
61 | if hasattr(self.root, "img"):
62 | del self.root.img
63 | pil_image = Image.open(path).resize((220, 220))
64 | tk_image = ImageTk.PhotoImage(image=pil_image)
65 | self.root.img = tk_image
66 | return tk_image
67 |
68 | def update_qrcode(self, path):
69 | UI.qrcode_display.delete(ALL)
70 | tk_image = self.resize_img(path)
71 | image_id = UI.qrcode_display.create_image(3, 3, anchor='nw',
72 | image=tk_image)
73 |
74 | def build_left_ui(self):
75 | login_frame = Labelframe(self.root, text="扫描二维码登录", labelanchor="n")
76 | login_frame.pack(side="left", anchor="nw")
77 |
78 | def make_canvas():
79 | canvas_frame = Frame(login_frame)
80 | canvas_frame.pack(side="top", anchor="nw")
81 | qrcode_display = Canvas(canvas_frame, bg="grey", width=222,
82 | height=222)
83 | qrcode_display.pack(side="top", anchor="nw")
84 | UI["qrcode_display"] = qrcode_display
85 | self.update_qrcode(Project.qrcode)
86 |
87 | def make_buttons():
88 | bt_frame = Frame(login_frame)
89 | bt_frame.pack(side="top", anchor="nw", fill="both")
90 |
91 | Button(bt_frame, text="一键登录", command=self.login_by_cookies).grid(
92 | row=0, column=0, ipadx=12.5
93 | )
94 | Button(bt_frame, text="扫码登录", command=self.login_by_scan).grid(
95 | row=0, column=1, ipadx=12.5
96 | )
97 | Button(bt_frame, text="生成", command=self.run_tasks).grid(
98 | row=1, column=0, ipadx=12.5
99 | )
100 | Button(bt_frame, text="终止任务", command=self.stop_tasks).grid(
101 | row=1, column=1, ipadx=12.5
102 | )
103 |
104 | make_canvas()
105 | make_buttons()
106 |
107 | def build_right_ui(self):
108 | user_frame = LabelFrame(self.root, text="用户信息", labelanchor="n")
109 | user_frame.pack(side="left", anchor="nw")
110 |
111 | def make_user_info():
112 | UI["user"] = Label(user_frame, text="昵称: 未登录")
113 | UI.user.pack(side="top", anchor="nw")
114 |
115 | def make_paper_records():
116 | Label(user_frame, text="组卷记录").pack(side="top", anchor="nw")
117 | box = ScrolledListbox(user_frame, width=80, height=12)
118 | UI["box"] = box.listbox
119 | box.pack(side="top", anchor="nw")
120 |
121 | def make_tasks():
122 | Label(user_frame, text="添加任务").pack(side="top", anchor="nw")
123 | box = ScrolledListbox(user_frame, width=80, height=6)
124 | UI["task"] = box.listbox
125 | box.pack(side="top", anchor="nw")
126 |
127 | make_user_info()
128 | make_paper_records()
129 | make_tasks()
130 |
131 | def show_msg(self):
132 | top = Toplevel(self.root)
133 | Label(top, text="等待task完成").pack()
134 |
135 | def login_by_cookies(self):
136 | pass
137 |
138 | def login_by_scan(self):
139 | pass
140 |
141 | def run_tasks(self):
142 | pass
143 |
144 | def stop_tasks(self):
145 | pass
146 |
147 |
148 | class LoginUI(MainUI):
149 | def __init__(self, root):
150 | self.pool = None
151 | self.login_scan_ids = []
152 | self.check_scan_ids = []
153 | super(LoginUI, self).__init__(root)
154 | self.build_pop_menu()
155 | self.init_login()
156 | UI.box.bind('', self.pop_menu_event)
157 |
158 | def build_pop_menu(self):
159 | self.menubar = Menu(self.root, tearoff=False) # 制作一个菜单实例
160 | for menu in [
161 | ("添加任务", self.make_task),
162 | ]:
163 | self.menubar.add_command(label=menu[0], command=menu[1])
164 |
165 | def pop_menu_event(self, event):
166 | self.menubar.post(event.x_root, event.y_root)
167 |
168 | def init_login(self):
169 | self.wx_scan = ScanLogin()
170 | self.cookies_login = CookiesLogin()
171 | self.zujuan = ZuJuanView()
172 | self.get = self.wx_scan.get
173 |
174 | def check_scan(self, ticket):
175 | status = 0
176 | logging.info("等待扫码...")
177 | if worker.workers:
178 | p = worker.workers[-1]
179 | if not p.is_alive() and self.wx_scan.get_scan_flag():
180 | status = 1
181 | logging.info("扫码完成,正在跳转...")
182 | threading.Thread(
183 | target=self.update_by_thread, args=(ticket,)
184 | ).start()
185 |
186 | if not status:
187 | check_scan_id = self.root.after(1000, lambda: self.check_scan(ticket))
188 | self.check_scan_ids.append(check_scan_id)
189 |
190 | def login_by_scan(self):
191 | if not UI.debug:
192 | self.build_debug_ui()
193 | self.clear_view()
194 | qrcode = self.wx_scan.get_qrcode_url()
195 | self.wx_scan.save_qrcode_pic(qrcode)
196 | self.update_qrcode(Project.qrcode)
197 | ticket = self.wx_scan.get_ticket(qrcode)
198 | worker.put(self.wx_scan.check_scan, ticket)
199 | worker.run()
200 | self.cancel_before_scan()
201 | login_scan_id = self.root.after(
202 | 1000, lambda: self.check_scan(ticket)
203 | )
204 | self.login_scan_ids.append(login_scan_id)
205 |
206 | def login_by_cookies(self):
207 | if not UI.debug:
208 | self.build_debug_ui()
209 | self.clear_view()
210 | self.cancel_before_scan()
211 | self.cookies_login.login_by_cookies()
212 | self.update_user()
213 | self.update_exam_view()
214 |
215 | def cancel_before_scan(self):
216 | logging.info("清除登录历史")
217 | for _id in self.login_scan_ids:
218 | self.root.after_cancel(_id)
219 | for _id in self.check_scan_ids:
220 | self.root.after_cancel(_id)
221 | self.login_scan_ids.clear()
222 | self.check_scan_ids.clear()
223 | worker.stop_old_work()
224 |
225 | def update_user(self):
226 | username = self.zujuan.get_username()
227 | UI.user.config(text=username)
228 |
229 | def clear_view(self):
230 | logging.info("清除视图")
231 | UI.box.delete(0, END)
232 | UI.task.delete(0, END)
233 |
234 | def update_exam_view(self):
235 | if UI.records:
236 | del UI["records"]
237 | records = self.zujuan.get_zujuan_view()
238 | UI["records"] = records
239 | for record_pid in records:
240 | UI.box.insert(END, records[record_pid].text + "-%s" % record_pid)
241 | # self.wx_scan.login_by_scan(ticket)
242 | # self.wx_scan.check_login_succ()
243 |
244 | def update_by_thread(self, ticket):
245 | self.wx_scan.login_by_scan(ticket)
246 | logging.info("更新界面记录")
247 | self.update_user()
248 | self.update_exam_view()
249 |
250 | def parse_record(self, record):
251 | text, pid = record.split('-')
252 | return text, pid
253 |
254 | def is_add_task(self, add):
255 | tasks = UI.task.get(0, END)
256 | for task in tasks:
257 | if task == add:
258 | return True
259 | return False
260 |
261 | def make_task(self):
262 | sel = UI.box.curselection()
263 | if sel:
264 | sel_text = UI.box.get(sel)
265 | if self.is_add_task(sel_text):
266 | return messagebox.showinfo("提示", "任务已存在")
267 | UI.task.insert(END, sel_text)
268 |
269 | def all_tasks_pending(self):
270 | tasks = UI.task.get(0, END)
271 | href_pool = []
272 | if UI.records:
273 | for task in tasks:
274 | text, pid = self.parse_record(task)
275 | href_pool.append(
276 | [task, UI.records[pid].href]
277 | )
278 | return href_pool
279 |
280 | def run_tasks(self):
281 | threading.Thread(target=self._run_tasks).start()
282 |
283 | def _run_tasks(self):
284 | logging.info("执行组卷任务")
285 | yes = messagebox.askyesno("提示", "组卷tasks启动?")
286 | if yes:
287 | self.show_msg()
288 | self.pool = Pool()
289 | ZuJuanTasks().task_run(
290 | self.pool, self.all_tasks_pending()
291 | )
292 | messagebox.showinfo("提示", "组卷tasks已结束")
293 |
294 | def stop_tasks(self):
295 | ZuJuanTasks().task_shutdown(self.pool)
296 |
297 |
298 | def network_heart(root):
299 | root.withdraw()
300 | try:
301 | requests.get("http://www.baidu.com")
302 | except:
303 | messagebox.showerror("网络错误", "无法访问互联网,退出程序", parent=root)
304 | os._exit(-1)
305 | else:
306 | root.deiconify()
307 |
308 |
309 | if __name__ == "__main__":
310 | freeze_support()
311 | tk = Tk()
312 | tk.title("zujuan")
313 | log = logging.getLogger()
314 |
315 | threading.Thread(target=network_heart, args=(tk,)).start()
316 | gui_thead = threading.Timer(0.5, LoginUI, args=(tk,))
317 | gui_thead.start()
318 |
319 | tk.mainloop()
320 |
--------------------------------------------------------------------------------