| import re |
| import threading |
| import time |
| from pathlib import Path |
| import gradio as gr |
| import requests |
| from tqdm import tqdm |
| import tempfile |
|
|
| requests.packages.urllib3.disable_warnings() |
|
|
| reference_subject = { |
| '语文': '01', |
| '历史': '12', |
| '数学': '02', |
| '生物': '13', |
| '英语': '03', |
| '通用技术': '102', |
| '信息技术': '26', |
| '物理': '05', |
| '政治': '27', |
| '化学': '06', |
| '地理':"14" |
| } |
| subject_codes={ |
| '01': '语文', |
| '12': '历史', |
| '02': '数学', |
| '13': '生物', |
| '03': '英语', |
| '102': '通用技术', |
| '26': '信息技术', |
| '05': '物理', |
| '27': '政治', |
| '06': '化学', |
| '14':'地理' |
| } |
| headers = [ |
| { |
| "Accept": "application/json, text/plain, */*", |
| "Accept-Encoding": "gzip, deflate, br, zstd", |
| "Accept-Language": "zh-CN,zh;q=0.9", |
| "Connection": "keep-alive", |
| "Host": "www.zhixue.com", |
| "Referer": "https://www.zhixue.com/middlehomework/web-student/views/", |
| "Sec-Fetch-Dest": "empty", |
| "Sec-Fetch-Mode": "cors", |
| "Sec-Fetch-Site": "same-origin", |
| "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36", |
| "appName": "com.iflytek.zxzy.web.zx.stu", |
| "sec-ch-ua": '"Not A(Brand";v="8", "Chromium";v="132", "Google Chrome";v="132"', |
| "sec-ch-ua-mobile": "?0", |
| "sec-ch-ua-platform": '"Windows"' |
| }, |
| { |
| "Host": "www.zhixue.com", |
| "sucOriginAppKey": "zhixue_student", |
| "User-Agent": "zhixue_student/1.0.2026 (iPhone; iOS 16.2; Scale/3.00)", |
| "appName": "com.zhixue.student", |
| "Connection": "keep-alive", |
| "Accept-Language": "zh-Hans-CN;q=1, zh-Hant-CN;q=0.9, en-CN;q=0.8", |
| "Accept": "*/*", |
| "Accept-Encoding": "gzip, deflate, br" |
| }, |
| { |
| "Host": "mhw.zhixue.com", |
| "Content-Type": "application/json", |
| "Accept": "application/json, text/plain, */*", |
| "appName": "com.zhixue.student", |
| "sucOriginAppKey": "zhixue_student", |
| "Accept-Language": "zh-CN,zh-Hans;q=0.9", |
| "Origin": "https://mhw.zhixue.com", |
| "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko)", |
| "Referer": "https://mhw.zhixue.com/zhixuestudent/views/homeworkReport/homework-report.html", |
| "Accept-Encoding": "gzip, deflate, br", |
| "Connection": "keep-alive" |
| } |
| ] |
|
|
| def get_token(): |
| global token |
| response = requests.get("https://www.zhixue.com/middleweb/newToken", headers=headers[0], verify=False) |
| response.encoding = "utf-8" |
| response = response.json() |
| result = response["result"]["token"] |
| if result: |
| token = result |
| return True |
| else: |
| print("获取 token 失败。") |
| return False |
| |
| def format_time(timestamp): |
| return time.strftime("%Y-%m-%d %H:%M", time.localtime(timestamp // 1000)) |
| |
| def get(url): |
| headers[1].update({"Host": url.split("/")[2], "sucUserToken": token}) |
| response = requests.get(url, headers=headers[1], verify=False) |
| response.encoding = "utf-8" |
| return response.json() |
|
|
| def post(url, data): |
| headers[2].update({ |
| "Host": url.split("/")[2], |
| "Origin": f'https://{url.split("/")[2]}', |
| "sucUserToken": token, |
| "Authorization": token |
| }) |
| response = requests.post(url, headers=headers[2], json=data, verify=False) |
| response.encoding = "utf-8" |
| return response.json() |
|
|
| def parse_range(s, max_value): |
| result = [] |
| for item in s.split(): |
| if "-" in item: |
| l = item.split("-") |
| if len(l) == 2 and l[0].isdigit() and l[1].isdigit(): |
| begin = int(l[0]) - 1 |
| end = int(l[1]) - 1 |
| if not (begin < 0 and end < 0 or begin >= max_value and end >= max_value): |
| step = -1 if begin > end else 1 |
| for i in range(begin, end + step, step): |
| if 0 <= i < max_value and not i in result: |
| result.append(i) |
| elif item.isdigit(): |
| n = int(item) - 1 |
| if 0 <= n < max_value and not n in result: |
| result.append(n) |
| return result |
|
|
| def to_file(file, source_type, name=""): |
| result = ({"name": name or Path(file).name, "path": file, "is_text": bool(name)} if isinstance(file, str) else |
| {"name": name, "path": file["description"], "is_text": True} if file["fileType"] == 5 else |
| {"name": file.get("name", "") or Path(file["path"]).name, "path": file["path"], "is_text": False}) |
| result["name"] = re.sub('[\\\\/:*?"<>|]', "_", result["name"]) |
| result["type"] = source_type |
| return result |
|
|
| def analyze_homework(homework, include_text, uid): |
| hwId = homework["hwId"] |
| hwType = homework["hwType"] |
| stuHwId = homework["stuHwId"] |
| file_list = [] |
| data = {"base": {"appId": "APP"}, "params": {"hwId": hwId, "stuHwId": stuHwId, "studentId": uid}} |
| |
| if hwType == 102: |
| response = post("https://mhw.zhixue.com/hwreport/question/getStuReportDetail", data) |
| if "result" in response: |
| result = response["result"] |
| file_list.append(to_file(result["hwDescription"], "题目", result["hwTitle"] + "_说明.txt")) |
| for problem in result["mainTopics"]: |
| content = problem["content"] + problem["answerHtml"] + problem["analysisHtml"] |
| file_list += [to_file(path, "题目") for path in re.findall('bigger="(.+?)"', content)] |
| file_list += [to_file(path, "提交") for item in problem["subTopics"] for path in item["answerResList"]] |
| |
| elif hwType == 105: |
| response = post("https://mhw.zhixue.com/hw/homework/attachment/list", data) |
| file_list += [to_file(item, "题目") for item in response["result"]] |
| response = post("https://mhw.zhixue.com/hwreport/question/getStuReportDetail", data) |
| if "result" in response: |
| result = response["result"] |
| file_list.append(to_file(result["hwDescription"], "题目", result["hwTitle"] + "_说明.txt")) |
| file_list += [to_file(item, "答案") for item in result.get("answerAttachList", [])] |
| for problem in result["mainTopics"]: |
| file_list += [to_file(path, "提交") for item in problem["subTopics"] for path in item["answerResList"]] |
| |
| elif hwType == 107: |
| response = post("https://mhw.zhixue.com/hw/clock/answer/getClockHomeworkDetail", data) |
| result = response["result"] |
| file_list.append(to_file(result["description"], "题目", result["title"] + "_说明.txt")) |
| file_list += ([to_file(item, "题目") for item in result.get("hwTopicAttachments", [])] |
| + [to_file(item, "答案") for item in result.get("hwAnswerAttachments", [])] |
| + [to_file(item, "答案") for item in |
| result["hwClockRecordPreviewResponses"][0].get("teacherAnswerAttachments", [])] |
| + [to_file(item, "提交", result["title"] + "_提交.txt") for item in |
| result["hwClockRecordPreviewResponses"][0].get("answerAttachments", [])]) |
| |
| file_list = [file for file in file_list if file["path"] and (include_text or not file["is_text"])] |
| return file_list |
|
|
| def query_homework(uid, tlsysSessionId, subject, status, max_count): |
| headers[0].update({"Cookie": f"tlsysSessionId={tlsysSessionId}"}) |
| successful = get_token() |
| if not successful: |
| return "获取 token 失败","" |
| page_size = max_count |
| if subject == ["语文", "数学", "英语", "物理", "化学", "生物", "地理", "历史", "政治",'通用技术', '信息技术']: |
| subjects = ["-1"] |
| else: |
| subjects = [reference_subject[i] for i in subject] |
| if status == "全部": |
| status = "" |
| elif status == "已完成": |
| status = "1" |
| else: |
| status = "0" |
| fetch_list = [] |
| if status != "1": |
| fetch_list += [{"subject": subject, "status": 0} for subject in subjects] |
| if status != "0": |
| fetch_list += [{"subject": subject, "status": 1} for subject in subjects] |
| global homework_list |
| homework_list = [] |
| timestamps = [int(time.time() * 1000)] * len(fetch_list) |
| finished = [False] * len(fetch_list) |
| while not all(finished): |
| print("\x9B1F\x9B0J", end="") |
| index = len(homework_list) |
| for i in tqdm(range(len(fetch_list)), unit=""): |
| if finished[i]: |
| continue |
| response = get("https://mhw.zhixue.com/homework_middle_service/stuapp/getStudentHomeWorkList" |
| f'?completeStatus={fetch_list[i]["status"]}&createTime={timestamps[i]}&pageIndex=2' |
| f'&pageSize={page_size}&subjectCode={fetch_list[i]["subject"]}&token={token}') |
| if response["code"] != 200: |
| raise RuntimeError("获取作业列表失败") |
| result_list = response["result"]["list"] |
| homework_list += result_list |
| if len(result_list) < page_size: |
| finished[i] = True |
| if result_list: |
| timestamps[i] = result_list[-1]["beginTime"] |
| |
| print("\x9B1F\x9B0J", end="") |
| homework_list_temp=[] |
| global homework_list_oringin |
| homework_list_oringin = homework_list |
| for i in range(index, len(homework_list)): |
| begin_time = format_time(homework_list[i]["beginTime"]) |
| end_time = format_time(homework_list[i]["endTime"]) |
| homework_list_temp.append(f"[{homework_list[i]['subjectName']}]|{homework_list[i]['hwTitle']}|{begin_time}-{end_time}") |
| homework_list = homework_list_temp |
| return token, gr.update(choices=homework_list, value=[]) |
|
|
| def parse_homework(token, include_text, homework_selection, uid): |
| result = [] |
| for i in homework_selection: |
| for j in range(len(homework_list)): |
| if i == homework_list[j]: |
| result.append(j) |
| selected_homework = result |
| file_list = [] |
| for i in tqdm(selected_homework, unit=""): |
| file_list += analyze_homework(homework_list_oringin[i], include_text, uid) |
| global file_list_output |
| file_list_output = [file["name"] for file in file_list] |
| global homework_downloaded_path |
| homework_downloaded_path = [file["path"] for file in file_list] |
| global homework_is_text |
| homework_is_text = [file["is_text"] for file in file_list] |
| return gr.update(choices=file_list_output, value=[]) |
|
|
| def download_file(homework_view): |
| download_list = [] |
| for i in homework_view: |
| for j in range(len(file_list_output)): |
| if i == file_list_output[j]: |
| if not homework_is_text[j]: |
| download_list.append(homework_downloaded_path[j]) |
| else: |
| with tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".txt") as f: |
| f.write(homework_downloaded_path[j]) |
| temp_path = f.name |
| download_list.append(temp_path) |
| return download_list |
|
|
| def save_config(uid, tlsysSessionId): |
| config_content = f"uid={uid}\ntlsysSessionId={tlsysSessionId}" |
| with tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".txt") as f: |
| f.write(config_content) |
| return f.name |
|
|
| def load_config(file): |
| if not file: |
| return None, None |
| with open(file.name, "r") as f: |
| content = f.read() |
| uid = re.search(r"uid=(\S+)", content) |
| tlsysSessionId = re.search(r"tlsysSessionId=(\S+)", content) |
| return (uid.group(1) if uid else None, (tlsysSessionId.group(1) if tlsysSessionId else None)) |
|
|
| with gr.Blocks(title="智学网作业获取器") as demo: |
| gr.Markdown("# 🚀 智学网作业获取器") |
| gr.Markdown("## Backfront Created by Levrium,UI Design by Start_ten") |
| gr.Markdown("操作说明请见https://zhuanlan.zhihu.com/p/691808543") |
| |
| |
| with gr.Row(): |
| with gr.Column(scale=2): |
| config_upload = gr.File(label="上传配置文件", type="filepath", file_types=[".txt"]) |
| with gr.Column(scale=1): |
| load_config_btn = gr.Button("识别配置文件", variant="secondary") |
| save_config_btn = gr.Button("导出配置文件", variant="secondary") |
| with gr.Column(scale=2): |
| config_download = gr.File(label="下载配置文件", interactive=False) |
| |
| with gr.Row(): |
| with gr.Column(): |
| gr.Markdown("### 📝 查询作业") |
| uid = gr.Textbox( |
| label="uid", |
| placeholder="请输入uid...", |
| ) |
| tlsysSessionId = gr.Textbox( |
| label="tlsysSessionId", |
| placeholder="请输入tlsysSessionId...", |
| ) |
| with gr.Row(): |
| with gr.Column(scale=2): |
| subject = gr.CheckboxGroup( |
| choices=["语文", "数学", "英语", "物理", "化学", "生物", "地理", "历史", "政治",'通用技术', '信息技术'], |
| label="具体学科", |
| value=["语文", "数学", "英语", "物理", "化学", "生物", "地理", "历史", "政治",'通用技术', '信息技术'] |
| ) |
| with gr.Column(scale=1): |
| all_chosen = gr.Button( |
| value="全选", |
| variant="secondary" |
| ) |
| all_chosen.click( |
| fn=lambda: ["语文", "数学", "英语", "物理", "化学", "生物", "地理", "历史", "政治",'通用技术', '信息技术'], |
| inputs=[], |
| outputs=[subject] |
| ) |
| all_clear = gr.Button( |
| value="全不选", |
| variant="secondary" |
| ) |
| all_clear.click( |
| fn=lambda: [], |
| inputs=[], |
| outputs=[subject] |
| ) |
|
|
| status = gr.Radio( |
| choices=["全部", "已完成", "未完成"], |
| label="作业状态", |
| value="全部" |
| ) |
| max_count = gr.Slider( |
| label="最大请求作业数", |
| value=10, |
| minimum=1, |
| maximum=50, |
| step=1 |
| ) |
| with gr.Column(): |
| token = gr.Textbox( |
| label="TOKEN", |
| interactive=False |
| ) |
|
|
| homework_selection = gr.CheckboxGroup( |
| label="作业列表(可多选)", |
| choices=[], |
| interactive=True |
| ) |
| submit_btn = gr.Button("查询作业", variant="primary") |
| submit_btn.click( |
| fn=query_homework, |
| inputs=[uid, tlsysSessionId, subject, status, max_count], |
| outputs=[token, homework_selection ] |
| ) |
| gr.Markdown("---") |
| with gr.Row(): |
| with gr.Column(): |
| gr.Markdown("### 📄 解析作业并下载") |
| text_parse = gr.Checkbox( |
| label="是否解析题目、提交的文本?", |
| value=False |
| ) |
| homework_view = gr.CheckboxGroup( |
| label="作业内容", |
| choices=[], |
| interactive=True |
| ) |
| submit_btn = gr.Button("解析作业", variant="primary") |
| submit_btn.click( |
| fn=parse_homework, |
| inputs=[token, text_parse, homework_selection, uid], |
| outputs=[homework_view] |
| ) |
| with gr.Column(): |
| file_output = gr.File(label="作业文件", interactive=False) |
| download_btn = gr.Button("下载作业", variant="primary") |
| download_btn.click( |
| fn=download_file, |
| inputs=[homework_view], |
| outputs=[file_output] |
| ) |
| |
| |
| load_config_btn.click( |
| fn=load_config, |
| inputs=[config_upload], |
| outputs=[uid, tlsysSessionId] |
| ) |
| |
| save_config_btn.click( |
| fn=save_config, |
| inputs=[uid, tlsysSessionId], |
| outputs=[config_download] |
| ) |
|
|
| if __name__ == "__main__": |
| demo.launch() |