def _submit(self, delay=None): if not delay: delay = random.randint(self.daily_delay_bot, self.daily_delay_top) logger.info(f'随机延时 {delay} 秒...') time.sleep(delay) self.app.safe_click(rules["daily_submit"]) time.sleep(random.randint(1, 3))
def _search(self, content, options, exclude=''): # 职责 网上搜索 logger.debug(f'搜索 {content} <exclude = {exclude}>') logger.info(f"选项 {options}") content = re.sub(r'[\((]出题单位.*', "", content) if options[-1].startswith("以上") and chr(len(options)+64) not in exclude: logger.info(f'根据经验: {chr(len(options)+64)} 很可能是正确答案') return chr(len(options)+64) # url = quote('https://www.baidu.com/s?wd=' + content, safe=string.printable) url = quote("https://www.sogou.com/web?query=" + content, safe=string.printable) response = requests.get(url, headers=self.headers).text counts = [] for i, option in zip(['A', 'B', 'C', 'D', 'E', 'F'], options): count = response.count(option) counts.append((count, i)) logger.info(f'{i}. {option}: {count} 次') counts = sorted(counts, key=lambda x:x[0], reverse=True) counts = [x for x in counts if x[1] not in exclude] c, i = counts[0] if 0 == c: # 替换了百度引擎为搜狗引擎,结果全为零的机会应该会大幅降低 _, i = random.choice(counts) logger.info(f'搜索结果全0,随机一个 {i}') logger.info(f'根据网络搜索结果: {i} 很可能是正确答案') return i
def read(self): g, t = self.app.score["我要选读文章"] if t == g: logger.info(f'新闻阅读已达成,无需重复阅读') return logger.debug(f'正在进行新闻学习...') self._kaleidoscope() vol_not_found = True while vol_not_found: volumns = self.app.wait.until( EC.presence_of_all_elements_located( (By.XPATH, rules['article_volumn']))) # volumns = self.find_elements(rules['article_volumn']) first_vol = volumns[1] for vol in volumns: title = vol.get_attribute("name") logger.debug(title) if self.volumn_title == title: vol.click() vol_not_found = False break else: logger.debug(f'未找到 {self.volumn_title},左滑一屏') self.app.driver.scroll(vol, first_vol, duration=500) self._read(self.read_count, self.star_share_comments_count)
def _weekly(self): self.app.safe_click(rules["weekly_entry"]) titles = self.app.wait.until( EC.presence_of_all_elements_located( (By.XPATH, rules["weekly_titles"]))) states = self.app.wait.until( EC.presence_of_all_elements_located( (By.XPATH, rules["weekly_states"]))) # first, last = None, None for title, state in zip(titles, states): # if not first and title.location_in_view["y"]>0: # first = title if self.app.size["height"] - title.location_in_view["y"] < 10: logger.debug(f'屏幕内没有未作答试卷') break logger.debug( f'{title.get_attribute("name")} {state.get_attribute("name")}') if "未作答" == state.get_attribute("name"): logger.info(f'{title.get_attribute("name")}, 开始!') state.click() time.sleep(random.randint(5, 9)) self.daily._dispatch(5) # 这里直接采用每日答题 break self.app.safe_back('weekly report -> weekly list') self.app.safe_back('weekly list -> quiz')
def logout_or_not(self): if cfg.getboolean("prefers", "keep_alive"): logger.debug("无需自动注销账号") return self.safe_click(rules["mine_entry"]) self.safe_click(rules["setting_submit"]) self.safe_click(rules["logout_submit"]) self.safe_click(rules["logout_confirm"]) logger.info("已注销")
def daily(self): if 0 == self.daily_count: logger.info(f'每日答题积分已达成,无需重复答题') return self.app.safe_click(rules['mine_entry']) self.app.safe_click(rules['quiz_entry']) time.sleep(3) self._daily(self.daily_count) self.app.safe_back('quiz -> mine') self.app.safe_back('mine -> home')
def challenge(self): g, t = self.app.score["挑战答题"] if t == g: logger.info(f'挑战答题积分已达成,无需重复挑战') #self.challenge_count=2222 return self.app.safe_click(rules['mine_entry']) self.app.safe_click(rules['quiz_entry']) time.sleep(3) self._challenge() self.app.safe_back('quiz -> mine') self.app.safe_back('mine -> home')
def _watch(self, video_count=None): g1, t1 = self.app.score["视听学习"] g2, t2 = self.app.score["视听学习时长"] if (g1 == t1) and (g2 == t2): logger.debug(f'视听学习时长积分已达成,无需重复收听') return logger.info("开始浏览百灵视频...") self.app.safe_click(rules['bailing_enter']) self.app.safe_click(rules['bailing_enter']) # 再点一次刷新短视频列表 self.app.safe_click(rules['video_first']) logger.info(f'预计观看视频 {video_count} 则') while video_count: video_count -= 1 video_delay = random.randint( self.view_delay, self.view_delay + min(10, self.video_count)) logger.info(f'正在观看视频 <{video_count}#> {video_delay} 秒进入下一则...') time.sleep(video_delay) self.app.swipe_up() else: logger.info(f'视听学习完毕,正在返回...') self.app.safe_back('video -> bailing') logger.debug(f'正在返回首页...') self.app.safe_click(rules['home_entry'])
def music(self): if "disable" == self.has_bgm: logger.debug(f'广播开关 关闭') elif "enable" == self.has_bgm: logger.info(f'广播开关 开启') self._music() else: logger.debug(f'广播开关 默认') g, t = self.score["视听学习时长"] if g == t: logger.debug(f'视听学习时长积分已达成,无需重复收听') return else: self._music()
def _radio(self): content = self.app.wait.until( EC.presence_of_element_located( (By.XPATH, rules["daily_content"]))).get_attribute("name") # content = self.find_element(rules["daily_content"]).get_attribute("name") option_elements = self.app.wait.until( EC.presence_of_all_elements_located( (By.XPATH, rules["daily_options"]))) # option_elements = self.driver.find_elements(rules["daily_options"]) options = [x.get_attribute("name") for x in option_elements] length_of_options = len(options) logger.info(f"单选题 {content}") logger.info(f"选项 {options}") answer = self._verify("单选题", content, options) choose_index = ord(answer) - 65 logger.info(f"提交答案 {answer}") option_elements[choose_index].click() # 提交答案 self._submit() try: wrong_or_not = self.app.driver.find_element_by_xpath( rules["daily_wrong_or_not"]) right_answer = self.app.driver.find_element_by_xpath( rules["daily_answer"]).get_attribute("name") right_answer = re.sub(r'正确答案: ', '', right_answer) logger.info(f"答案 {right_answer}") # notes = self.driver.find_element_by_xpath(rules["daily_notes"]).get_attribute("name") # logger.debug(f"解析 {notes}") self._submit(2) localmodel.update_bank("单选题", content, options, right_answer, "", "") except: localmodel.update_bank("单选题", content, options, answer, "", "")
def __init__(self): self.connect() self.desired_caps = { "platformName": caps["platformname"], "platformVersion": caps["platformversion"], "automationName": caps["automationname"], "unicodeKeyboard": caps["unicodekeyboard"], "resetKeyboard": caps["resetkeyboard"], "noReset": caps["noreset"], 'newCommandTimeout': 800, "deviceName": caps["devicename"], "uuid": caps["uuid"], "appPackage": caps["apppackage"], "appActivity": caps["appactivity"] } logger.info('打开 appium 服务,正在配置...') self.driver = webdriver.Remote('http://localhost:4723/wd/hub', self.desired_caps) self.wait = WebDriverWait(self.driver, 25) self.size = self.driver.get_window_size()
def _check(self): content = self.app.wait.until( EC.presence_of_element_located( (By.XPATH, rules["daily_content"]))).get_attribute("name") # content = self.find_element(rules["daily_content"]).get_attribute("name") option_elements = self.app.wait.until( EC.presence_of_all_elements_located( (By.XPATH, rules["daily_options"]))) # option_elements = self.driver.find_elements(rules["daily_options"]) options = [x.get_attribute("name") for x in option_elements] length_of_options = len(options) logger.info(f"多选题 {content}\n{options}") answer = self._verify("多选题", content, options) logger.debug(f'提交答案 {answer}') for k, option in zip(list("ABCDEFG"), option_elements): if k in answer: option.click() time.sleep(1) else: continue # 提交答案 self._submit() try: wrong_or_not = self.app.driver.find_element_by_xpath( rules["daily_wrong_or_not"]) right_answer = self.app.driver.find_element_by_xpath( rules["daily_answer"]).get_attribute("name") right_answer = re.sub(r'正确答案: ', '', right_answer) logger.info(f"答案 {right_answer}") # notes = self.driver.find_element_by_xpath(rules["daily_notes"]).get_attribute("name") # logger.debug(f"解析 {notes}") self._submit(2) localmodel.update_bank("多选题", content, options, right_answer, "", "") except: localmodel.update_bank("多选题", content, options, answer, "", "")
def _kaleidoscope(self): ''' 本地频道积分 +1 ''' if self.app.back_or_not("本地频道"): return volumns = self.app.wait.until( EC.presence_of_all_elements_located( (By.XPATH, rules['article_volumn']))) volumns[3].click() time.sleep(10) # self.safe_click(rules['article_kaleidoscope']) target = None try: target = self.app.driver.find_element_by_xpath( rules['article_kaleidoscope']) except NoSuchElementException as e: logger.error(f'没有找到城市万花筒入口') if target: target.click() time.sleep(3) delay = random.randint(5, 15) logger.info(f"在本地学习平台驻足 {delay} 秒") time.sleep(delay) self.app.safe_back('学习平台 -> 文章列表')
def login_or_not(self): # com.alibaba.android.user.login.SignUpWithPwdActivity time.sleep(10) # 首屏等待时间 try: home = self.driver.find_element_by_xpath(rules["home_entry"]) logger.debug(f'不需要登录') return except NoSuchElementException as e: logger.debug(self.driver.current_activity) logger.debug(f"非首页,先进行登录") if not self.username or not self.password: logger.error(f'未提供有效的username和password') logger.info(f'也许你可以通过下面的命令重新启动:') logger.info(f'\tpython -m xuexi -u "your_username" -p "your_password"') raise ValueError('需要提供登录的用户名和密钥,或者提前在App登录账号后运行本程序') username = self.wait.until(EC.presence_of_element_located(( By.XPATH, rules["login_username"] ))) password = self.wait.until(EC.presence_of_element_located(( By.XPATH, rules["login_password"] ))) username.send_keys(self.username) password.send_keys(self.password) self.safe_click(rules["login_submit"]) time.sleep(8) try: home = self.driver.find_element_by_xpath(rules["home_entry"]) logger.debug(f'无需点击同意条款按钮') return except NoSuchElementException as e: logger.debug(self.driver.current_activity) logger.debug(f"需要点击同意条款按钮") self.safe_click(rules["login_confirm"]) time.sleep(3)
def _read(self, num, ssc_count): logger.info(f'预计阅读新闻 {num} 则') while num > 0: # or ssc_count: try: articles = self.app.driver.find_elements_by_xpath( rules['article_list']) except: logger.debug(f'真是遗憾,一屏都没有可点击的新闻') articles = [] for article in articles: title = article.get_attribute("name") if title in self.titles: continue try: pic_num = article.parent.find_element_by_id( "cn.xuexi.android:id/st_feeds_card_mask_pic_num") logger.debug(f'这绝对是摄影集,直接下一篇') continue except: logger.debug(f'这篇文章应该不是摄影集了吧') article.click() num -= 1 logger.info(f'<{num}> 当前篇目 {title}') article_delay = random.randint( self.read_delay, self.read_delay + min(10, self.read_count)) logger.info(f'阅读时间估计 {article_delay} 秒...') while article_delay > 0: if article_delay < 20: delay = article_delay else: delay = random.randint(min(10, article_delay), min(20, article_delay)) logger.debug(f'延时 {delay} 秒...') time.sleep(delay) article_delay -= delay self.app.swipe_up() else: logger.debug(f'完成阅读 {title}') if ssc_count > 0: try: comment_area = self.app.driver.find_element_by_xpath( rules['article_comments']) self._star_share_comments(title) ssc_count -= 1 except: logger.debug('这是一篇关闭评论的文章,收藏分享留言过程出现错误') self.titles.append(title) self.app.safe_back('article -> list') if 0 >= num: break else: self.app.swipe_up()
def challenge_verify(self, category, content, options): # 检索题库 mybank = None mybank = localmodel.query(content, options[0], category) if mybank: logger.info(f'题库中查到题目,id:{mybank.id}') if mybank.answer: logger.info(f'已知的正确答案: {mybank.answer}') return mybank.answer else: excludes = mybank.excludes if mybank else "" # 当if为真时,VAR = VALUE1, 否则VAR=VALUE2 VAR = VALUE1 if CONDITION else VALUE2 logger.info(f'题目类型: {category}') return self.app._search(content, options, excludes) else: logger.info(f'添加新题目') localmodel.add(content, category, options) return self.app._search(content, options)
def _challenge(self): logger.info(f'挑战答题 目标 {self.challenge_count} 题, Go!') while True: result = self._challenge_cycle(self.challenge_count) if 0 >= result: logger.info(f'已成功挑战 {self.challenge_count} 题,正在返回') break else: delay_time = random.randint(1, 3) logger.info( f'本次挑战 {self.challenge_count - result} 题,{delay_time} 秒后再来一组' ) time.sleep(delay_time) continue
def _daily(self, num): self.app.safe_click(rules["daily_entry"]) while num: num -= 1 logger.info(f'每日答题 第 {num}# 组') self._dispatch(self.count_of_each_group) if not self.daily_force: score = self.app.wait.until( EC.presence_of_element_located( (By.XPATH, rules["daily_score"]))).get_attribute("name") # score = self.find_element(rules["daily_score"]).get_attribute("name") try: score = int(score) except: raise TypeError('integer required') self.g += score if self.g == self.t: logger.info(f"今日答题已完成,返回") break if num == 0: logger.debug(f'今日循环结束 <{self.g} / {self.t}>') break delay = random.randint(self.delay_group_bot, self.delay_group_top) logger.info(f'每日答题未完成 <{self.g} / {self.t}> {delay} 秒后再来一组') time.sleep(delay) self.app.safe_click(rules['daily_again']) continue else: logger.debug("应该不会执行本行代码") self.app.safe_back('daily -> quiz') try: back_confirm = self.app.driver.find_element_by_xpath( rules["daily_back_confirm"]) back_confirm.click() except: logger.debug(f"无需点击确认退出")
def _blank(self): contents = self.app.wait.until( EC.presence_of_all_elements_located( (By.XPATH, rules["daily_blank_content"]))) # contents = self.find_elements(rules["daily_blank_content"]) # content = " ".join([x.get_attribute("name") for x in contents]) logger.debug(f'len of blank contents is {len(contents)}') if 1 < len(contents): # 针对作妖的UI布局某一版 content, spaces = "", [] for item in contents: content_text = item.get_attribute("name") if "" != content_text: content += content_text else: length_of_spaces = len( item.find_elements(By.CLASS_NAME, "android.view.View")) - 1 spaces.append(length_of_spaces) content += " " * (length_of_spaces) else: # 针对作妖的UI布局某一版 contents = self.app.wait.until( EC.presence_of_all_elements_located( (By.XPATH, rules["daily_blank_container"]))) content, spaces, _spaces = "", [], 0 for item in contents: content_text = item.get_attribute("name") if "" != content_text: content += content_text if _spaces: spaces.append(_spaces) _spaces = 0 else: content += " " _spaces += 1 else: # for...else... # 如果填空处在最后,需要加一个判断 if _spaces: spaces.append(_spaces) logger.debug( f'[填空题] {content} [{" ".join([str(x) for x in spaces])}]') logger.debug(f'空格数 {spaces}') blank_edits = self.app.wait.until( EC.presence_of_all_elements_located( (By.XPATH, rules["daily_blank_edits"]))) # blank_edits = self.find_elements(rules["daily_blank_edits"]) length_of_edits = len(blank_edits) logger.info(f'填空题 {content}') answer = self._verify("填空题", content, []) # if not answer: words = (''.join( random.sample(string.ascii_letters + string.digits, 8)) for i in range(length_of_edits)) else: words = answer.split(" ") logger.debug(f'提交答案 {words}') for k, v in zip(blank_edits, words): k.send_keys(v) time.sleep(1) self._submit() try: wrong_or_not = self.app.driver.find_element_by_xpath( rules["daily_wrong_or_not"]) right_answer = self.app.driver.find_element_by_xpath( rules["daily_answer"]).get_attribute("name") answer = re.sub(r'正确答案: ', '', right_answer) logger.info(f"答案 {answer}") notes = self.app.driver.find_element_by_xpath( rules["daily_notes"]).get_attribute("name") logger.debug(f"解析 {notes}") self._submit(2) if 1 == length_of_edits: localmodel.update_bank('挑战题', content, [""], answer, '', notes) else: logger.error("多位置的填空题待验证正确性") localmodel.update_bank( '填空题', content, [""], self._blank_answer_divide(answer, spaces), '', notes) except: logger.debug("填空题回答正确")
def _verify(self, category, content, options): # 职责: 检索题库 查看提示 letters = list("ABCDEFGHIJKLMN") print('') print('题目类型:', category) print('题目内容:', content) print('选项:', options) mybank = None if len(options) > 1: mybank = localmodel.query(content, options[0], category) else: mybank = localmodel.query(content, '', category) if mybank and mybank.answer: logger.info(f'已知的正确答案: {mybank.answer}') return mybank.answer excludes = mybank["excludes"] if mybank else "" logger.info(f'题目类型: {category}') tips = self._view_tips() if not tips: logger.debug("本题没有提示") if "填空题" == category: return None elif "多选题" == category: return "ABCDEFG"[:len(options)] elif "单选题" == category: return self.app._search(content, options, excludes) else: logger.debug("题目类型非法") else: if "填空题" == category: dest = re.findall(r'.{0,2}\s+.{0,2}', content) logger.debug(f'dest: {dest}') if 1 == len(dest): dest = dest[0] logger.debug(f'单处填空题可以尝试正则匹配') pattern = re.sub(r'\s+', '(.+?)', dest) logger.debug(f'匹配模式 {pattern}') res = re.findall(pattern, tips) if 1 == len(res): return res[0] logger.debug(f'多处填空题难以预料结果,索性不处理') return None elif "多选题" == category: check_res = [ letter for letter, option in zip(letters, options) if option in tips ] if len(check_res) > 1: logger.debug(f'根据提示,可选项有: {check_res}') return "".join(check_res) return "ABCDEFG"[:len(options)] elif "单选题" == category: radio_in_tips, radio_out_tips = "", "" for letter, option in zip(letters, options): if option in tips: logger.debug(f'{option} in tips') radio_in_tips += letter else: logger.debug(f'{option} out tips') radio_out_tips += letter logger.debug(f'含 {radio_in_tips} 不含 {radio_out_tips}') if 1 == len(radio_in_tips) and radio_in_tips not in excludes: logger.debug(f'根据提示 {radio_in_tips}') return radio_in_tips if 1 == len(radio_out_tips) and radio_out_tips not in excludes: logger.debug(f'根据提示 {radio_out_tips}') return radio_out_tips return self.app._search(content, options, excludes) else: logger.debug("题目类型非法")
def _challenge_cycle(self, num): self.app.safe_click(rules['challenge_entry']) offset = 0 # 自动答错的偏移开关 while num > -1: print('') print('') content = self.app.wait.until( EC.presence_of_element_located( (By.XPATH, rules['challenge_content']))).get_attribute("name") # content = self.find_element(rules["challenge_content"]).get_attribute("name") option_elements = self.app.wait.until( EC.presence_of_all_elements_located( (By.XPATH, rules['challenge_options']))) # option_elements = self.find_elements(rules['challenge_options']) options = [x.get_attribute("name") for x in option_elements] length_of_options = len(options) logger.info(f'<{num}> {content}') answer = self.challenge_verify(category='挑战题', content=content, options=options) delay_time = random.randint(self.challenge_delay_bot, self.challenge_delay_top) if 0 == num: offset = random.randint(1, length_of_options - 1) # randint居然包含上限值,坑爹!!! logger.info(f'已完成指定题量,设置提交选项偏移 -{offset}') logger.info( f'随机延时 {delay_time} 秒提交答案: {chr((ord(answer)-65-offset+length_of_options)%length_of_options+65)}' ) else: logger.info(f'随机延时 {delay_time} 秒提交答案: {answer}') time.sleep(delay_time) # 利用python切片的特性,即使索引值为-offset,可以正确取值 option_elements[ord(answer) - 65 - offset].click() try: time.sleep(5) wrong = None wrong = self.app.driver.find_element_by_xpath( rules["challenge_over"]) except: logger.info(f'恭喜本题回答正确') if wrong is None: num -= 1 localmodel.update_bank('挑战题', content, options, answer, '', '') else: logger.info(f'很遗憾本题回答错误') if num > 0: localmodel.update_bank('挑战题', content, options, '', answer, '') logger.debug("点击结束本局") wrong.click() # 直接结束本局 time.sleep(5) break else: logger.debug("通过选项偏移,应该不会打印这句话,除非碰巧答案有误") logger.debug("那么也好,延时30秒后结束挑战") time.sleep(30) self.app.safe_back('challenge -> share_page') # 发现部分模拟器返回无效 # 更新后挑战答题需要增加一次返回 time.sleep(5) self.app.safe_back('share_page -> quiz') # 发现部分模拟器返回无效 return num
def disconnect(self): logger.info(f'正在断开模拟器 {caps["uuid"]},请稍候...') if 0 == subprocess.check_call(f'adb disconnect {caps["uuid"]}', shell=True, stdout=subprocess.PIPE): logger.info(f'模拟器 {caps["uuid"]} 断开成功') else: logger.info(f'模拟器 {caps["uuid"]} 断开失败')