diff --git a/0Atip.txt b/0Atip.txt index 0b16184..d89fdea 100644 --- a/0Atip.txt +++ b/0Atip.txt @@ -1,3 +1,3 @@ 注意!WARNING 答案信息由生成式预训练模型生成,并不一定100%准确,甚至可能存在错误,仅作为代码参考! -并且只适用于实训编程作业。matplotlib等绘图相关作业请参见网站https://github.com/JesterHey/img_file/blob/main/educoder.md \ No newline at end of file +并且只适用于实训编程作业。matplotlib等绘图相关作业请参见网址[https://github.com/JesterHey/img_file/blob/main/educoder.md] \ No newline at end of file diff --git a/cloud.py b/cloud.py index fa73e68..9d696e1 100644 --- a/cloud.py +++ b/cloud.py @@ -47,4 +47,6 @@ if __name__ == '__main__': print('测试用') # 测试用 # print(is_exist('18503.json')) - #download('18503.json') \ No newline at end of file + #download('18503.json') + #检查云端所有文件 + print([i for i in bucket.list_objects()]) \ No newline at end of file diff --git a/demo.py b/demo.py new file mode 100644 index 0000000..162fa19 --- /dev/null +++ b/demo.py @@ -0,0 +1,5 @@ +import os +def getalljsons() -> list: + return [i for i in os.listdir() if i.endswith('.json')] +for i in getalljsons(): + os.remove(i) \ No newline at end of file diff --git a/get_answer.py b/get_answer.py index 886756a..6478a81 100644 --- a/get_answer.py +++ b/get_answer.py @@ -11,21 +11,55 @@ import asyncio from cloud import download,delete import base64 #读取当前目录下的json文件 -#获得指定目录下的所有数字开头的json文件的文件名 -def get_json(file:str) -> list: +#获得指定目录下的所有数字开头的json文件 +def get_shixunjson(file:str) -> list: ''' - file:指定目录 + file:文件夹路径 ''' - return [i for i in os.listdir(file) if i.endswith('.json') and i[0].isdigit()] -#获得json文件名,因为程序逻辑是每次只有一个json文件,所以直接取第一个 + files = os.listdir(file) + jsonfiles = [] + for i in files: + if i.endswith('.json') and i[0].isdigit(): + jsonfiles.append(i) + return jsonfiles +#获得指定目录下的所有pro开头的json文件 +def get_programmingjson(file:str) -> list: + ''' + file:文件夹路径 + ''' + files = os.listdir(file) + jsonfiles = [] + for i in files: + if i.endswith('.json') and i.startswith('pro'): + jsonfiles.append(i) + return jsonfiles + ''' 与云服务器连接,先判断当前json是否已在云服务器上,如果在,则直接调用, 节省调用API的时间和资费,否则,调用API,获得答案,并将答案存入云服务器 - -12.4晚更新: -阿里云服务器申请成功! ''' + # 以下封装成函数 +# 重写本地json文件 +def rewrite_shixun_json(json_name:str,new_data:dict): + with open(json_name,'w',encoding='utf-8') as f: + json.dump(new_data,f,ensure_ascii=False) + +def rewrite_programming_json(json_names:list,new_data:list): + ''' + 函数用于把new_data中的答案写入对应的json中 + json_names:本地所有pro开头的json文件名 + new_data:与json_names中每个文件对应的答案所构成的列表 + ''' + # 检查json_names和new_data元素数量是否一致 + if len(json_names) == len(new_data): + i=0 + while i< len(json_names): + with open(json_names[i],'w',encoding='utf-8') as f: + json.dump(new_data[i],f,ensure_ascii=False) + i+=1 + + #读取json文件并转换为字典 def load_json_data(json_name:str) -> dict: with open(json_name,'r',encoding='utf-8') as f: # json_name为无答案的json文件名 @@ -41,7 +75,8 @@ def load_api_key() -> str: 向GPT提问的格式:promot + 参数模板化的问题 ''' -promot = '现在,我想让你扮演一个Python程序员来解一个问题,我的问题将由三个部分组成,第一部分是问题的描述,第二部分是问题的需求,第三部分是问题的代码,我需要你按照我的模板编写代码。并且你返回的代码应当是带有注释的' +promot1 = '现在,我想让你扮演一个Python程序员来解一个问题,我的问题将由三个部分组成,第一部分是问题的描述,第二部分是问题的需求,第三部分是问题的代码,我需要你按照我的模板编写代码。并且你返回的代码应当是带有注释的。再次注意,请返回完整的,格式正确的,可读的代码!' +promot2 = '现在,我想让你扮演一个Python程序员来解一个问题,我的问题会有两个部分组成,第一部分是问题的描述,第二部分是你需要补全或者完善的代码。你需要阅读,理解我的问题描述,然后补全或者完善代码。再次注意,请返回完整的,格式正确的,输入由用户给出的,可读的代码!' #构造问题模板 #遍历字典,获得每一关的参数,构造请求,获得答案 #使用异步函数提升效率 @@ -55,7 +90,7 @@ client = AsyncOpenAI( api_key=load_api_key(), base_url='https://api.op-enai.com/v1' ) -def get_answer_from_api(jsonfile:dict,client:AsyncOpenAI,promot:str) -> dict: +def get_shixunanswer_from_api(jsonfile:dict,client:AsyncOpenAI,promot:str) -> dict: ''' jsonfile:本地json文件 client:异步客户端 @@ -66,15 +101,19 @@ def get_answer_from_api(jsonfile:dict,client:AsyncOpenAI,promot:str) -> dict: promot = promot # 异步函数来获取答案 async def get_answer(key,value) -> str: + ''' + key:关卡id + value:关卡参数 + ''' cid = key # code 是base64编码的字符串,需要解码 des, req, code = value['describe'], value['require'], base64.b64decode(value['code']).decode('utf-8') - question = f'问题描述:{des}\n任务需求:{req}\n根据上面的需求,你需要补充并完善代码:\n{code}' + question = f'问题描述:{des}\n任务需求:{req}\n根据上面的需求,以下是你需要补充并完善代码:\n{code}' try: response = await client.chat.completions.create( - model='gpt-3.5-turbo', + model='gpt-4-1106-preview', messages=[ - {'role': 'system', 'content': promot}, + {'role': 'system', 'content': promot1}, {'role': 'user', 'content': question} ] ) @@ -98,10 +137,67 @@ def get_answer_from_api(jsonfile:dict,client:AsyncOpenAI,promot:str) -> dict: # 运行主函数 return asyncio.run(main(data=data)) + + +# 由于编程作业会涉及到多个文件,整合为一个文件池,文件池中的每个json就是异步的最小任务单元 +# 这样可以多个文件请求并发,异步协程,提升效率 +def get_programming_answer_from_api(jsonfile:list,client:AsyncOpenAI,promot:str) -> list: + ''' + jsonfile:本地json文件 + client:异步客户端 + promot:问题模板 + ''' + data = jsonfile + # 异步函数来获取答案 + async def get_answer(value:dict) -> str: + value = value + pro_id = value['id'] + # code 是base64编码的字符串,需要解码 + des,code = value['describe'],base64.b64decode(value['code']).decode('utf-8') + question = f'问题描述:{des}\n根据上面的需求,以下是你需要补充并完善代码:\n{code}' + try: + response = await client.chat.completions.create( + #model='gpt-4-1106-preview', + model = 'gpt-3.5-turbo', + messages=[ + {'role': 'system', 'content': promot2}, + {'role': 'user', 'content': question} + ] + ) + return f'{pro_id}/{response.choices[0].message.content}' + except Exception as e: + print(f'错误信息:{e}') + + + # 主函数 + async def main(datalist:list) -> list: + # 由于编程作业会涉及到多个文件,整合为一个文件池 + ''' + data:本地json文件池 + ''' + # 把data中的每个json文件读取为字典 + datalist = [load_json_data(i) for i in datalist] + # 把每个字典扔给get_answer函数,获得答案,异步获取信息 + tasks = [get_answer(value) for value in datalist] + answers = await asyncio.gather(*tasks) # answers是一个列表,列表中的每个元素为每个异步任务的返回值 + # 由于异步获得的答案顺序不确定,需要处理,先把答案按照pro_id排序 + answers = sorted(answers,key=lambda x:x.split('/')[0]) + # 在datalist中的每个字典中新增一个键值对,键为answer,值为答案,并作为返回值返回 + for i in range(len(answers)): + datalist[i]['answer'] = answers[i].split('/')[-1] + # 返回datalist + return datalist + + # 运行主函数,返回一个列表,列表中的每个元素为每个异步任务的返回值,即重写的字典 + return asyncio.run(main(datalist=data)) + + + + if __name__ == '__main__': - new_data = get_answer_from_api(jsonfile=load_json_data(get_json(os.getcwd())[0]),client=client,promot=promot) - print(new_data) - #重写本地json文件 - with open(get_json(os.getcwd())[0],'w',encoding='utf-8') as f: - json.dump(new_data,f,ensure_ascii=False,indent=4) + ans=get_programming_answer_from_api(jsonfile=get_programmingjson(os.getcwd()),client=client,promot=promot2) + print(ans) + rewrite_programming_json(json_names=get_programmingjson(os.getcwd()),new_data=ans) + #new_data = get_programming_answer_from_api(get_programmingjson(os.getcwd()),client=client,promot=promot2) + diff --git a/get_params.py b/get_params.py index 1a0d104..ad97ef2 100644 --- a/get_params.py +++ b/get_params.py @@ -28,12 +28,7 @@ retry = 0 #配置参数 opt = Options() opt.add_experimental_option('detach', True) -#opt.add_argument('--headless') -chrome_driver = 'D:\ChromeDownload\chromedriver-win64\chromedriver-win64' -# 发行版时改为调用userinfo.json文件中的用户名和密码 -# url = 'https://www.educoder.net/tasks/27V4D95N/1191515/vmxpzae734bj?coursesId=27V4D95N' -# user_name = 'hnu202311020126' -# password = 'hzy123456' +opt.add_argument('--headless') platf = platform.platform() def is_practice(url:str) -> bool: obj=re.compile(r'www.educoder.net/tasks') @@ -41,10 +36,22 @@ def is_practice(url:str) -> bool: return True else: return False +def is_programming(url:str) -> bool: + obj=re.compile(r'www.educoder.net/myproblems') + if obj.search(url): + return True + else: + return False # 另外,目前好像只有实训作业有这些参数,其他的作业例如编程作业就没有,所以先判断一下是否为实训作业,可以通过用户输入的url判断 # 主要是看educoder.net/后面是否有tasks,如果有,则是实训作业,否则,不是实训作业 #为方便main.py调用,将判断函数写入函数中,以下部分封装为函数 def get_parameters(url:str,user_name:str,password:str): + ''' + 用于获得实训作业的参数 + url:实训网址 + user_name:用户名 + password:密码 + ''' url = url user_name = user_name password = password @@ -65,11 +72,11 @@ def get_parameters(url:str,user_name:str,password:str): print('登录失败 请检查输入信息是否正确') # 关闭浏览器 safari.quit() - #重新调用login_ui.py - if 'Windows' in platf: - os.system('python login_ui.py') - else: - os.system('python3 login_ui.py') + # #重新调用login_ui.py + # if 'Windows' in platf: + # os.system('python login_ui.py') + # else: + # os.system('python3 login_ui.py') #获取cookie,User-Agent Cookie = safari.get_cookies() User_Agent = safari.execute_script('return navigator.userAgent') @@ -201,8 +208,124 @@ def get_parameters(url:str,user_name:str,password:str): else: print('这不是一个实训作业') return + +def get_parameters_of_programming(url:str,user_name:str,password:str): + ''' + 用于获得编程作业的参数 + url:实训网址 + user_name:用户名 + password:密码 + ''' + url = url + user_name = user_name + password = password + # 检查是否为编程作业 + if is_programming(url): + # 构造selenium对象 + #构造selenium对象 + safari = Chrome(options=opt) + safari.get(url) + #模拟登录 + safari.implicitly_wait(10) + safari.find_element(By.ID, 'login').send_keys(user_name) + safari.find_element(By.ID, 'password').send_keys(password) + safari.find_element(By.ID, 'password').send_keys(Keys.ENTER) + time.sleep(2) + #判断是否登录成功 + try: + safari.find_element(By.XPATH,'//*[@id="root"]/div/div/div/div/div/div/div/section[1]/div/div[4]') + except BaseException: + print('登录失败 请检查输入信息是否正确') + # 关闭浏览器 + safari.quit() + # #重新调用login_ui.py + # if 'Windows' in platf: + # os.system('python login_ui.py') + # else: + # os.system('python3 login_ui.py') + #获取cookie,User-Agent + Cookie = safari.get_cookies() + User_Agent = safari.execute_script('return navigator.userAgent') + cookie = f'autologin_trustie={Cookie[1]["value"]}; _educoder_session={Cookie[0]["value"]}' + heders = { + 'Cookie':cookie, + 'User-Agent':User_Agent, + 'Referer':url + } + # 由于编程作业往往是题库的题目组合的,没有直接编程作业的总ID,所以要先访问每个题目界面,获得相应的ID,题干,代码等参数 + # 最终上传时,记得在要区别于已经上传的实训作业,可以在文件名前加上'pro'前缀 + + # 获得题目数量 + # 展开关卡页面 + safari.find_element(By.XPATH,'//*[@id="root"]/div/div/div/div/div/div/div/section[1]/div/div[4]').click() + #
+ # 可见,关卡数量是根据class="list___PXTsq "的元素数量决定的,直接用xpath获取即可 + time.sleep(1) + htmltxt = safari.page_source + html = etree.HTML(htmltxt) + task_num = html.xpath('count(//*[@class="list___PXTsq "])') + task_num = int(task_num) + 1 + # 回到第一关 + # //*[@id="educoder"]/body/div[6]/div/div[3]/div/div/div[2]/div[1] + # //*[@id="educoder"]/body/div[7]/div/div[3]/div/div/div[2]/div[2] + for i in range(1,10): + try: + if safari.find_element(By.XPATH,f'//*[@id="educoder"]/body/div[{i}]/div/div[3]/div/div/div[2]/div[1]'): + safari.find_element(By.XPATH,f'//*[@id="educoder"]/body/div[{i}]/div/div[3]/div/div/div[2]/div[1]').click() + break + except: + continue + i = 1 + while i<=task_num: + # 对于每一关,获取参数 + cur_url = safari.current_url + identity = cur_url.split('/')[-1].split('?')[0] + resp = requests.get(f'https://data.educoder.net/api/myproblems/{identity}.json',headers=heders) + # 提取resp中的题目id,题干,代码等参数 + problem_data = resp.json() + # 初始化一个字典,用于存放所有关卡的参数 + total = {} + # 题目id + pro_id = problem_data['hack']['id'] + # 获取id后,判断云端是否存在该文件,如果存在,则跳过,如果不存在,则继续执行本程序 + exist = is_exist(f'pro_{pro_id}.json') + if exist: + print('云端文件已存在,正在下载') + download(f'pro_{pro_id}.json') + print(f'pro_{pro_id}.json下载完成') + continue + else: + print('云端文件不存在,正在爬取') + # 题干 + describe = problem_data['hack']['description'] + # 代码(base64编码) + code = problem_data['hack']['code'] + total = { + 'id':pro_id, + 'describe':describe, + 'code':code, + 'verified': False, #这个参数是用来标记答案是否被用户认证为正确答案的,初始值为False(暂时没啥用) + 'last_modified': time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time())) #这个参数是用来标记答案最后一次被修改的时间,初始值为当前时间 + } + # 写入本地json,命名为pro_pro_id.json + with open(f'pro_{pro_id}.json','w',encoding='utf-8') as f: + json.dump(total,f,ensure_ascii=False) + print(f'{pro_id}完成') + # 去往下一关 + try: + i+=1 + safari.implicitly_wait(10) + safari.find_element(By.ID,'oj-next').click() + time.sleep(2) + except BaseException: + print('参数爬取完成') # 完成后,本地会有若干个pro_pro_id.json文件 + safari.quit() + else: + print('不是编程作业') + if __name__ == '__main__': - url = 'https://www.educoder.net/tasks/27V4D95N/1191512/lfi2gqtnueb8?coursesId=27V4D95N' + url = 'https://www.educoder.net/myproblems/9kwnlzvcegsa?type=1' user_name = 'hnu202311020126' password = 'hzy123456' - get_parameters(url,user_name,password) \ No newline at end of file + get_parameters_of_programming(url,user_name,password) + print('结束测试') \ No newline at end of file diff --git a/main.py b/main.py index b976936..7cd25ac 100644 --- a/main.py +++ b/main.py @@ -14,10 +14,10 @@ from cloud import upload,download,is_exist print('处理api相关中...') download('apis.json') print('处理完成!') -from get_params import get_parameters -from get_answer import get_answer_from_api,promot,client,get_json,load_api_key,load_json_data +from get_params import get_parameters,get_parameters_of_programming,is_practice +from get_answer import get_shixunanswer_from_api,get_programming_answer_from_api,promot1,promot2,client,rewrite_programming_json,rewrite_shixun_json from login_ui import show_login,show_image,MyApp -from trans_to_txt import transToTxt,readJson +from trans_to_txt import transToTxt,transToTxt_programming,get_programmingjson,get_shixunjson import json import os from printtxt import print_txt,get_all_txt_file @@ -36,49 +36,78 @@ with open('userinfo.json', 'r') as f: user_name = userinfo['name'] password = userinfo['pwd'] url = userinfo['url'] - -# 调用get_params.py获得参数,完成后本地应该有一个json文件,里面有参数 -get_parameters(url,user_name,password) -print('到这一步,参数获取完成!') +ispractice = is_practice(url=url) +if ispractice: + # 调用get_params.py获得参数,完成后本地应该有一个json文件,里面有参数 + get_parameters(url,user_name,password) +else: + get_parameters_of_programming(url=url,user_name=user_name,password=password) +print('参数获取完成!') # 获得刚才get_params.py生成的json文件名 -j_name = readJson()[0] -# 判断j_name文件中是否有answer -with open(j_name,'r',encoding='utf-8') as f: - json_data = json.load(f) - def is_exist_answer(data:dict) -> bool: for i,j in data.items(): if 'answer' in j.keys(): return True return False -print('到这一步,判断是否存在答案') -if is_exist_answer(json_data): - pass +def is_exist_answer_programming(data:dict) -> bool: + for i in data.keys(): + if i == 'answer': + continue + else: + return False + return True +if ispractice: + j_name = get_shixunjson(os.getcwd())[0] + # 判断j_name文件中是否有answer + with open(j_name,'r',encoding='utf-8') as f: + json_data = json.load(f) + if is_exist_answer(json_data): + pass + else: + print('调用api获取答案中,请耐心等待...') + new_data = get_shixunanswer_from_api(jsonfile=json_data,client=client,promot=promot1) + # 重写本地json文件 + rewrite_shixun_json(json_name=j_name,new_data=new_data) else: - print('到这一步,调用api获取答案') - new_data = get_answer_from_api(jsonfile=json_data,client=client,promot=promot) - # 重写本地json文件 - with open(j_name,'w',encoding='utf-8') as f: - json.dump(new_data,f,ensure_ascii=False,indent=4) + j_names = get_programmingjson(os.getcwd()) + # 判断是否有answer + for j in j_names: + with open(j,'r',encoding='utf-8') as f1: + j_data = json.load(f1) + if not is_exist_answer_programming(j_data): + break + print('调用api获取答案中,请耐心等待...') + new_data = get_programming_answer_from_api(jsonfile=j_names,client=client,promot=promot2) + # 重写本地接送文件 + rewrite_programming_json(json_names=j_names,new_data=new_data) # 上面的判断执行完后,本地的json文件中已经有answer了,下面实现信息展示 # 先删除本地api.json文件 os.remove('apis.json') -# 使用readJson函数读取当前目录下的所有json文件 -JSS = readJson() -# 构建txt文件 -transToTxt(JSS) -# 展示txt文件 -# print('哈哈终于要写完了') -print('答案获取完毕,开始展示') -print_txt(get_all_txt_file(os.getcwd())) +# 函数读取当前目录下的所有json文件 +if ispractice: + JSS = get_shixunjson(os.getcwd())[0] + # 构建txt文件 + transToTxt(JSS) + # 展示txt文件 + # 判断云端是否存在答案json,如果不存在,则上传 + print('答案获取完毕,开始展示') + print_txt(get_all_txt_file(os.getcwd())) + if not is_exist(JSS): + upload(JSS) +else: + JSS = get_programmingjson(os.getcwd()) + transToTxt_programming(JSS) + print('答案获取完毕,开始展示') + print_txt(get_all_txt_file(os.getcwd())) + if not is_exist(JSS): + upload(JSS) # #打印完,删除本地txt和json文件 -for i in JSS: +def getalljsons() -> list: + return [i for i in os.listdir() if i.endswith('.json')] +for i in getalljsons(): os.remove(i) -# 删除本地所有数字开头的txt文件 +# 删除本地所有数字命名的txt文件 for i in os.listdir(): - if i.split('.')[0].isdigit() and i.endswith('.txt'): + if i.split('.')[0].isdigit() or i.startswith('pro') and i.endswith('.txt'): os.remove(i) -# 判断云端是否存在答案json,如果不存在,则上传 -if not is_exist(j_name): - upload(j_name) diff --git a/printtxt.py b/printtxt.py index c6ba078..2e2fd76 100644 --- a/printtxt.py +++ b/printtxt.py @@ -89,7 +89,7 @@ def print_txt(json_path: list): # 传入json文件路径 def get_all_txt_file(path): - file_list = ['0Atip.txt'] + file_list = [] for root, dirs, files in os.walk(path): for file in files: if file.endswith('.txt') and file[0].isdigit(): diff --git a/requirements.txt b/requirements.txt index 98656b6..d0e1c28 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ lxml==4.9.3 -openai==1.3.7 +openai==1.3.8 oss2==2.18.3 pycryptodome==3.19.0 PyQt5==5.15.10 diff --git a/trans_to_txt.py b/trans_to_txt.py index e4d4f94..1b0de15 100644 --- a/trans_to_txt.py +++ b/trans_to_txt.py @@ -1,25 +1,49 @@ import os import json -#读取当前下的所有以数字命名的json文件 -def readJson(): - files = os.listdir() - jsons = [] - for file in files: - if file.endswith('.json') and file[0].isdigit(): - jsons.append(file) - return jsons -#这就是我们的答案json文件,提取每个键盘对应的值的answer键对应的值,写入不同的txt文件 +def get_shixunjson(file:str) -> list: + ''' + file:文件夹路径 + ''' + files = os.listdir(file) + jsonfiles = [] + for i in files: + if i.endswith('.json') and i[0].isdigit(): + jsonfiles.append(i) + return jsonfiles +#获得指定目录下的所有pro开头的json文件 +def get_programmingjson(file:str) -> list: + ''' + file:文件夹路径 + ''' + files = os.listdir(file) + jsonfiles = [] + for i in files: + if i.endswith('.json') and i.startswith('pro'): + jsonfiles.append(i) + return jsonfiles def transToTxt(jsons:list): + ''' + 用于将jsons中的所有json中的answer信息写入txt + ''' for j in jsons: - with open(j,'r',encoding='utf-8') as f: - data = json.load(f) + with open(j,'r',encoding='utf-8') as f1: + data = json.load(f1) i=1 for key in data.keys(): if key != 'answer': - with open(f'{i}.txt','w',encoding='utf-8') as f: - f.write(data[key]['answer']) + with open(f'{i}.txt','w',encoding='utf-8') as f2: + f2.write(data[key]['answer']) i+=1 - f.close() + +def transToTxt_programming(jsons:list): + for j in range(len(jsons)): + with open(jsons[j],'r',encoding='utf-8') as f1: + data = json.load(f1) + with open(f'{j+1}.txt','w',encoding='utf-8') as f2 : + f2.write(data['answer']) + if __name__ == '__main__': - jsons = readJson() - transToTxt(jsons) \ No newline at end of file + print('测试部分') + # jsons = get_shixunjson(os.getcwd()) + # transToTxt(jsons) + # transToTxt_programming(jsons=jsons) \ No newline at end of file