32 |
33 |
34 |
60 |
61 |
66 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/main.py:
--------------------------------------------------------------------------------
1 | import argparse
2 | import json
3 | import sys
4 | import time
5 | import os
6 |
7 | import dashscope
8 | from selenium import webdriver
9 | from selenium.webdriver.common.by import By
10 | from selenium.common.exceptions import NoSuchElementException
11 | from selenium.webdriver.chrome.service import Service as ChromeService
12 | import requests
13 |
14 | sys.stdout.reconfigure(encoding='gbk', line_buffering=True,errors='ignore')
15 | # current_directory = os.path.dirname(os.path.abspath(__file__))
16 |
17 | # opt = Options()
18 | # # opt.binary_location = rf'{current_directory}\Chrome\chrome.exe'
19 | # opt.binary_location=r"..\chromedriver\chrome.exe"
20 | # opt.add_argument('--no-sandbox')
21 | # opt.add_experimental_option('detach', True)
22 | # ser = Service()
23 | # # ser.executable_path = rf'{current_directory}\Chrome\chromedriver.exe'
24 | # ser.executable_path = rf'..\chromedriver\chromedriver.exe'
25 |
26 | arg = sys.argv
27 | if len(arg) < 2:
28 | username = input('请输入超星账号:')
29 | password = input('请输入超星密码:')
30 | url = input('请输入进入考试页面后的网址:')
31 | model = input("答案获取方式:1.本地ollama 2.通义千问(自行配置API)")
32 |
33 | modelAi = ""
34 | if model == "1":
35 | modelAi = "ollama"
36 | elif model == "2":
37 | tongyiApi = input("请输入通义千问api token:")
38 | modelAi = "tongyi"
39 | else:
40 | print("输入有误")
41 | sys.exit(0)
42 | else:
43 | parser = argparse.ArgumentParser()
44 |
45 | parser.add_argument('--username')
46 | parser.add_argument('--password')
47 | parser.add_argument('--url')
48 | parser.add_argument('--api')
49 |
50 | args = parser.parse_args()
51 |
52 | username = args.username
53 | password = args.password
54 | url = args.url
55 | modelAi = "tongyi"
56 | tongyiApi = args.api
57 |
58 |
59 | options = webdriver.ChromeOptions()
60 | options.binary_location=r"chromedriver\chrome.exe"
61 | options.add_argument('--no-sandbox')
62 | options.add_experimental_option('detach', True)
63 | browser = webdriver.Chrome(service=ChromeService(executable_path="chromedriver\chromedriver.exe"),options=options)
64 | browser.maximize_window()
65 |
66 | browser.get(url)
67 |
68 | phone_input = browser.find_element(By.ID, 'phone')
69 | phone_input.send_keys(username)
70 |
71 | password_input = browser.find_element(By.ID, 'pwd')
72 | password_input.send_keys(password)
73 |
74 | login_button = browser.find_element(By.ID, 'loginBtn')
75 | login_button.click()
76 |
77 | time.sleep(3)
78 |
79 | if not os.path.exists('result'):
80 | os.makedirs('result')
81 |
82 | def ollama(text):
83 | url = "http://10.4.240.253:11434/api/generate"
84 | data = {
85 | "model": "qwen2:latest",
86 | "prompt": f"这是题目:{text} 直接返回给我答案的对应选项 不要描述其他的",
87 | "stream": False
88 | }
89 | response = requests.post(url, json=data)
90 | res = response.text
91 | data = json.loads(res)
92 | ans = data.get("response")
93 | return ans
94 |
95 | def tongyi(text):
96 | dashscope.api_key = tongyiApi
97 | text = f"这是题目:{text} 直接返回给我答案的对应字母 不要描述其他的"
98 | messages = [{'role': 'user', 'content': text}]
99 | response = dashscope.Generation.call(dashscope.Generation.Models.qwen_max, messages=messages,
100 | result_format='message')
101 | content = response['output']['choices'][0]['message']['content']
102 |
103 | return content
104 |
105 | def ty_tiankong(text,num):
106 | dashscope.api_key = tongyiApi
107 | text = f"这是一道填空题,{text},请你进行回答,问题里的$填空内容就是需要回答的地方,一共有{num}个空,请你直接告诉我答案 不要描述其他的 每个答案用换行分隔 返回答案的数量要跟我发的一模一样"
108 | messages = [{'role': 'user', 'content': text}]
109 | response = dashscope.Generation.call(dashscope.Generation.Models.qwen_max, messages=messages,
110 | result_format='message')
111 | content = response['output']['choices'][0]['message']['content']
112 |
113 | return content
114 |
115 | def ty_tiankong_img(text,num,img):
116 | i = 0
117 |
118 | while True:
119 | text1 = f"这是一道填空题,{text},相关图片信息我已提交,请你进行回答里面的问题,一共有{num}个空,请你直接告诉我答案 不要描述其他的 任何一个空的答案都全部用换行分隔 返回答案的数量要跟我发的一模一样,不要描述其他的"
120 | print("AI图片处理中,请稍后...")
121 | messages = [
122 | {
123 | "role": "user",
124 | "content": [
125 | {"image": img},
126 | {"text": text1}
127 | ]
128 | }
129 | ]
130 | response = dashscope.MultiModalConversation.call(
131 | api_key=tongyiApi,
132 | model='qwen-vl-max-latest',
133 | messages=messages
134 | )
135 |
136 | if i > 3:
137 | print("请求失败次数过多,跳过")
138 | return "error"
139 |
140 | if response["status_code"] == 200:
141 | break
142 | else:
143 | print("请求失败,正在重试...")
144 | i += 1
145 | content = response['output']['choices'][0]['message']['content'][0]['text']
146 | return content
147 |
148 | def extract_question_and_options():
149 | # que_ele = browser.find_element(By.XPATH,"//div[@style='overflow:hidden;']")
150 | num_ele = browser.find_element(By.XPATH,"//h3[@class='mark_name colorDeep']")
151 |
152 | if "单选" in num_ele.text:
153 | # type_ele = browser.find_element(By.XPATH,"//span[@class='colorShallow']")
154 | # type_text = type_ele.text.split("(")[1].split(",")[0]
155 |
156 | options = browser.find_elements(By.XPATH, "//div[@class='clearfix answerBg singleoption']")
157 | choose = ""
158 | for option in options:
159 | option_letter = option.find_element(By.XPATH, ".//span").text
160 | option_content = option.find_element(By.XPATH, ".//div").text
161 | choose += f"{option_letter}: {option_content} "
162 |
163 | que = f"{num_ele.text} {choose}"
164 | print(que)
165 |
166 | if (modelAi == "ollama"):
167 | ans = ollama(que)
168 | else:
169 | ans = tongyi(que)
170 | print("AI参考答案:" + ans)
171 | if 'A' in ans:
172 | A = browser.find_element(By.XPATH, "//span[text()='A']")
173 | A.click()
174 |
175 | if 'B' in ans:
176 | B = browser.find_element(By.XPATH, "//span[text()='B']")
177 | B.click()
178 |
179 | if 'C' in ans:
180 | C = browser.find_element(By.XPATH, "//span[text()='C']")
181 | C.click()
182 |
183 | if 'D' in ans:
184 | D = browser.find_element(By.XPATH, "//span[text()='D']")
185 | D.click()
186 |
187 | if 'A' not in ans and 'B' not in ans and 'C' not in ans and 'D' not in ans:
188 | print("答案获取失败")
189 | extract_question_and_options()
190 |
191 | time.sleep(1)
192 | status = click_next_button()
193 | if status == False:
194 | sys.exit(0)
195 | elif "多选" in num_ele.text:
196 | options = browser.find_elements(By.XPATH, "//div[@class='clearfix answerBg']")
197 | choose = ""
198 | for option in options:
199 | option_letter = option.find_element(By.XPATH, ".//span").text
200 | option_content = option.find_element(By.XPATH, ".//div").text
201 | choose += f"{option_letter}: {option_content} "
202 |
203 | que = f"{num_ele.text} {choose}"
204 | print(que)
205 |
206 | if (modelAi == "ollama"):
207 | ans = ollama(que)
208 | else:
209 | ans = tongyi(que)
210 |
211 | print("AI参考答案:" + ans)
212 | if 'A' in ans:
213 | A = browser.find_element(By.XPATH, "//span[text()='A']")
214 | A.click()
215 |
216 | if 'B' in ans:
217 | B = browser.find_element(By.XPATH, "//span[text()='B']")
218 | B.click()
219 |
220 | if 'C' in ans:
221 | C = browser.find_element(By.XPATH, "//span[text()='C']")
222 | C.click()
223 |
224 | if 'D' in ans:
225 | D = browser.find_element(By.XPATH, "//span[text()='D']")
226 | D.click()
227 |
228 | if 'A' not in ans and 'B' not in ans and 'C' not in ans and 'D' not in ans:
229 | print("答案获取失败")
230 | extract_question_and_options()
231 |
232 | time.sleep(1)
233 | status = click_next_button()
234 | if status == False:
235 | sys.exit(0)
236 | elif "判断" in num_ele.text:
237 | que = f"{num_ele.text}"
238 | print(que)
239 |
240 | if (modelAi == "ollama"):
241 | ans = ollama(que + "判断题返回对或错")
242 | else:
243 | ans = tongyi(que + "判断题返回对或错")
244 |
245 | print("AI参考答案:" + ans)
246 |
247 | if '对' in ans or 'A' in ans or 'T' in ans:
248 | A = browser.find_element(By.XPATH, "//span[text()='A']")
249 | A.click()
250 |
251 | if '错' in ans or 'B' in ans or 'F' in ans:
252 | B = browser.find_element(By.XPATH, "//span[text()='B']")
253 | B.click()
254 |
255 | if '对' not in ans and '错' not in ans and 'A' not in ans and 'B' not in ans and 'F' not in ans and 'T' not in ans:
256 | print("答案获取失败")
257 | extract_question_and_options()
258 |
259 | time.sleep(1)
260 | status = click_next_button()
261 | if status == False:
262 | sys.exit(0)
263 | elif "填空" in num_ele.text:
264 | kong = browser.find_elements(By.XPATH,"//div[@class='stem_answer']/div[@class='Answer']")
265 | kong_num = len(kong)
266 | que = f"{num_ele.text}"
267 |
268 | image = False
269 | print(que)
270 |
271 | try:
272 | img = num_ele.find_element(By.XPATH, ".//img")
273 | print("获取到题目图片,正在处理...")
274 | headers = {
275 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
276 | }
277 | img_url = img.get_attribute("src")
278 | # print(f"图片的 URL 是: {img_src}")
279 | if img_url: # 确保 URL 不为空
280 | # 下载图片
281 | response = requests.get(img_url, stream=True, headers=headers)
282 | if response.status_code == 200:
283 | img_name = img_url.split("/")[-1]
284 | img_path = os.path.join(".", img_name)
285 | with open(img_path, "wb") as file:
286 | for chunk in response.iter_content(1024):
287 | file.write(chunk)
288 | print(f"图片已获取成功,正在转直链...")
289 | # image = True
290 | else:
291 | print(f"无法加载图片: {img_url}")
292 | else:
293 | print("未找到图片 URL")
294 | url = "https://image.myxuebi.top/api/v1/upload"
295 | file = open(img_name, "rb")
296 | files = {
297 | "file": file # 以二进制模式打开文件
298 | }
299 |
300 | response = requests.post(url, files=files)
301 | if response.status_code == 200:
302 | res = json.loads(response.text)
303 | url_img = res["data"]["links"]["url"]
304 | # print(url)
305 | # print("图片加载成功!")
306 | image = True
307 | else:
308 | print("提交失败,网络错误!")
309 | except NoSuchElementException:
310 | pass
311 | # img_url = img.get_attribute("src")
312 |
313 | if (modelAi == "ollama"):
314 | print("暂不支持ollama填空")
315 | status = click_next_button()
316 | if status == False:
317 | sys.exit(0)
318 | else:
319 | if image == True:
320 | print("图片已成功生成直链:" + url_img)
321 | file.close()
322 | os.remove(img_name)
323 | ans = ty_tiankong_img(que, kong_num, url_img)
324 | if ans == "error":
325 | status = click_next_button()
326 | if status == False:
327 | sys.exit(0)
328 | else:
329 | ans = ty_tiankong(que, kong_num)
330 | ans_list = ans.split("\n")
331 | ans_list = list(filter(None,ans_list))
332 | print("AI参考答案:" + str(ans_list))
333 |
334 | if len(ans_list) == kong_num:
335 | j = 0
336 | for i in kong:
337 | input_bar = i.find_element(By.XPATH, ".//iframe")
338 | # WebDriverWait(browser, 10).until(EC.frame_to_be_available_and_switch_to_it(input_bar))
339 | # input_element = WebDriverWait(browser, 10).until(EC.presence_of_element_located((By.TAG_NAME, "input")))
340 | input_bar.click()
341 | input_bar.send_keys(ans_list[j])
342 | input_bar.click()
343 | j = j + 1
344 | # browser.switch_to.default_content()
345 | else:
346 | print("错误:AI答案与实际空不符,跳过此题")
347 | status = click_next_button()
348 | if status == False:
349 | sys.exit(0)
350 |
351 | # sys.exit(0)
352 |
353 | time.sleep(2)
354 | status = click_next_button()
355 | if status == False:
356 | sys.exit(0)
357 |
358 | else:
359 | print("暂不支持填空")
360 | status = click_next_button()
361 | if status == False:
362 | sys.exit(0)
363 |
364 | def click_next_button():
365 | try:
366 | next_button = browser.find_element(By.XPATH, '//a[text()="下一题"]')
367 | next_button.click()
368 | except NoSuchElementException:
369 | print("没有找到“下一题”按钮,可能是已经到达最后一题。")
370 | print("题目填写已结束,请自行检查是否有遗漏,本程序不支持部分解答题等题目,请手动填写")
371 | print("填空题可能会有错误,请手动核查!")
372 | print("程序已退出,driver已关闭,请答题完成后手动关闭浏览器")
373 | os.system('taskkill /im chromedriver.exe /F')
374 | return False
375 | except Exception as e:
376 | print(f"点击下一题按钮失败: {e}")
377 | return False
378 | return True
379 |
380 | def getque():
381 | while True:
382 | time.sleep(0.5)
383 | extract_question_and_options()
384 |
385 | getque()
386 |
387 | # browser.quit()
388 |
--------------------------------------------------------------------------------