何海涛 1 rok temu
rodzic
commit
f15e07af38
2 zmienionych plików z 567 dodań i 0 usunięć
  1. 567 0
      main.py
  2. BIN
      yzy.apk

+ 567 - 0
main.py

@@ -0,0 +1,567 @@
+# This sample code uses the Appium python client v2
+# pip install Appium-Python-Client
+# Then you can paste this into a file and simply run with Python
+import random
+import types
+from typing import Union
+
+from appium import webdriver
+from selenium.common import TimeoutException
+from selenium.webdriver.support.ui import WebDriverWait
+from appium.webdriver.common.appiumby import AppiumBy
+from datetime import datetime
+import time
+import os
+import sys
+import getopt
+# import winsound
+
+# For W3C actions
+from selenium.webdriver.common.action_chains import ActionChains
+from selenium.webdriver.common.actions import interaction
+from selenium.webdriver.common.actions.action_builder import ActionBuilder
+from selenium.webdriver.common.actions.pointer_input import PointerInput
+from selenium.webdriver.support import expected_conditions
+
+isProfession = False
+driver = ''
+# 起始id
+startId = 10001
+# 结束id
+endId = 11500
+# ignores
+ignores = []
+# indicate, higher priority than start-end for no-seq targets
+targets = []
+
+card_id = "16416117"
+card_pwd = "396254"
+
+phone = '13974787400'
+phone_pwd = 'Xj654123'
+
+hide_guide = False
+stop = False
+retry_times_max = 1  # 搜索loading时,间隔几次重新点击搜索
+retry_current = 0  # 暂时没有用途了
+batch_count = 2  # 有几个批次
+subject_default_top = True  # 默认理科、物理
+plan_change_years = [2,3]  # 计划取几年?从 2 开始
+profession_change_years = [1,2,3,4]  # 专业取几年?从 2 开始
+# 登录方式 1 卡号 2 手机密码
+loginType = 1
+# 数据类型 1 招生计划 2 历年录取 3 两者都有
+dataType = 1
+
+
+def randomSleep():
+    rd = random.choice([0, 0.5, 0.8, 0.75, 1, 2, 1.5, 1.2, 1.8])
+    if rd: time.sleep(rd / 4.0)
+
+
+def init():
+    global driver
+    global isProfession
+    print('程序启动')
+    caps = {}
+    caps["platformName"] = "Android"
+    caps["appium:platformVersion"] = "6.0"
+    caps["appium:deviceName"] = "emulator-5554"
+    caps["appium:appPackage"] = "com.eagersoft.youzy.youzy"
+    # .mvvm.ui.login.LoginAndRegisterSelectActivity
+    # .mvvm.ui.college.FindCollegeActivity
+    caps["appium:appActivity"] = ".mvvm.ui.login.LoginAndRegisterSelectActivity"
+    caps["appium:resetKeyboard"] = True
+    caps["appium:ensureWebviewsHavePages"] = True
+    caps["appium:nativeWebScreenshot"] = True
+    caps["appium:newCommandTimeout"] = 3600
+    caps["appium:connectHardwareKeyboard"] = True
+
+    driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", caps)
+    # 不要混合使用隐式和显式等待。这样做会导致不可预测的等待时间。例如,将隐式等待设置为10秒,将显式等待设置为15秒, 可能会导致在20秒后发生超时。
+    # driver.implicitly_wait(2)
+
+
+def start():
+    init()
+    login()
+
+
+def alarm():
+    if sys.platform.startswith('win'):
+        pass  # winsound.Beep(1000, 3000)
+    elif sys.platform.startswith('darwin'):
+        os.system("say --voice=\"Mei-Jia\" 出错了,请尽快处理")
+
+
+def getForkRange():
+    if targets:
+        return targets
+    else:
+        return range(startId, endId + 1)
+
+
+def login():
+    if loginType == 1:
+        login_by_card()
+    elif loginType == 2:
+        login_by_phone()
+    else:
+        login_by_card()
+
+
+def login_success():
+    # 弹出了验证窗口,请在20s内通过验证
+    entrance_xpath = "/hierarchy/android.widget.FrameLayout/android.widget.FrameLayout/" \
+                     "android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/" \
+                     "android.widget.FrameLayout/android.view.ViewGroup/androidx.viewpager.widget.ViewPager/" \
+                     "android.view.ViewGroup/android.widget.LinearLayout/android.widget.FrameLayout/" \
+                     "android.view.ViewGroup[1]/android.view.ViewGroup/android.view.ViewGroup[2]/" \
+                     "androidx.viewpager.widget.ViewPager/androidx.recyclerview.widget.RecyclerView/" \
+                     "android.view.ViewGroup[1]"
+    modal_id = "com.eagersoft.youzy.youzy:id/click_close_dialog_activity_pop"
+    try:
+        def load(x):
+            modal = find_ele_by_id(modal_id)
+            if modal is not None:
+                modal.click()
+                return True
+            else:
+                if find_ele_by_xpath(entrance_xpath) is not None:
+                    return True
+                else:
+                    return False
+            return False
+    except:
+        print('超时了')
+        sys.exit()
+
+    alarm()
+    WebDriverWait(driver, 120).until(load)
+    # 登录成功并且关闭了弹窗
+    try:
+        # 进入学校列表
+        entrance = find_ele_by_xpath(entrance_xpath)
+        entrance.click()
+        # 开始工作
+        global_begin_time = time.time()
+        college_range = getForkRange()
+        for i in college_range:
+            if i in ignores: continue
+            current_no = college_range.index(i) + 1
+            time_diff = time.time() - global_begin_time
+            print('进度统计:', current_no, 'OF', len(college_range), 'IN', time_diff / 3600, 'Hours')
+            if not stop:
+                auto_click(i)
+            else:
+                print('结束')
+                return
+            time_diff = time.time() - global_begin_time
+            current_speed1 = time_diff / current_no
+            current_speed2 = current_no * 3600 / time_diff
+            print('速率统计:', current_speed1, 'Seconds of Each.', current_speed2, 'Completed Per Hour.')
+        # auto_click(startId)
+    except Exception as e:
+        print('错误:', repr(e))
+
+
+def login_by_card():
+    el1 = find_ele_by_id("com.eagersoft.youzy.youzy:id/click_login_register_card")
+    el1.click()
+    el3 = find_ele_by_id("com.eagersoft.youzy.youzy:id/cardNumber")
+    el3.send_keys(card_id)
+    el4 = find_ele_by_id("com.eagersoft.youzy.youzy:id/password")
+    el4.send_keys(card_pwd)
+    el5 = find_ele_by_id("com.eagersoft.youzy.youzy:id/checkbox")
+    el5.click()
+    el6 = find_ele_by_id("com.eagersoft.youzy.youzy:id/click_next")
+    el6.click()
+
+    login_success()
+
+
+def login_by_phone():
+    el2 = driver.find_element(by=AppiumBy.ID, value="com.eagersoft.youzy.youzy:id/click_login_register_mobile")
+    el2.click()
+    time.sleep(1)
+    el3 = driver.find_element(by=AppiumBy.ID, value="com.eagersoft.youzy.youzy:id/click_login_by_password")
+    el3.click()
+    time.sleep(0.5)
+    el4 = driver.find_element(by=AppiumBy.ID, value="com.eagersoft.youzy.youzy:id/account")
+    el4.send_keys(phone)
+    time.sleep(1)
+    el5 = driver.find_element(by=AppiumBy.ID, value="com.eagersoft.youzy.youzy:id/password")
+    el5.send_keys(phone_pwd)
+    time.sleep(1)
+    el6 = driver.find_element(by=AppiumBy.ID, value="com.eagersoft.youzy.youzy:id/checkbox")
+    el6.click()
+    time.sleep(1)
+    el7 = driver.find_element(by=AppiumBy.ID, value="com.eagersoft.youzy.youzy:id/click_login_register_mobile")
+    el7.click()
+    time.sleep(1)
+    login_success()
+
+
+def change_tab(tabIndex=1) -> bool:
+    print('切换tab')
+    # 540 740 1213
+    # 招生计划
+    el_plan = find_ele_by_xpath(
+        "/hierarchy/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.LinearLayout/"
+        "android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/"
+        "android.widget.RelativeLayout/android.view.ViewGroup/android.view.ViewGroup[1]/"
+        "android.widget.LinearLayout/android.widget.HorizontalScrollView/"
+        "android.widget.LinearLayout/androidx.appcompat.app.ActionBar.Tab/"
+        "android.view.ViewGroup/android.widget.TextView[contains(@text,'招生计划')]", 5)
+    # print(el_plan)
+    print(el_plan)
+    change_success = False
+    if tabIndex == 1:
+        if el_plan is not None:
+            el_plan.click()
+            change_success = True
+    elif tabIndex == 2:
+        try:
+            # 历年录取
+            el_enter = find_ele_by_xpath(
+                "/hierarchy/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.LinearLayout/"
+                "android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/"
+                "android.widget.RelativeLayout/android.view.ViewGroup/android.view.ViewGroup[1]/"
+                "android.widget.LinearLayout/android.widget.HorizontalScrollView/"
+                "android.widget.LinearLayout/androidx.appcompat.app.ActionBar.Tab/android.view.ViewGroup/"
+                "android.widget.TextView[contains(@text,'历年录取')]", 5)
+            # print(el_enter)
+            if el_enter is not None:
+                el_enter.click()
+                change_success = True
+        except:
+            if el_plan is not None:
+                el_plan.click()
+            # 历年录取
+            # time.sleep(1)  # tab会横向滑动,稍等会
+            el_enter = find_ele_by_xpath(
+                "/hierarchy/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.LinearLayout/"
+                "android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/"
+                "android.widget.RelativeLayout/android.view.ViewGroup/android.view.ViewGroup[1]/"
+                "android.widget.LinearLayout/android.widget.HorizontalScrollView/"
+                "android.widget.LinearLayout/androidx.appcompat.app.ActionBar.Tab/android.view.ViewGroup/"
+                "android.widget.TextView[contains(@text,'历年录取')]")
+            # print(el_enter)
+            if el_enter is not None:
+                el_enter.click()
+                change_success = True
+    else:
+        if el_plan is not None:
+            el_plan.click()
+            change_success = True
+    return change_success
+
+
+def change_time(count: int = 2):
+    print('切换年份')
+    tv_year = find_ele_by_id("com.eagersoft.youzy.youzy:id/tv_year")
+    tv_year.click()
+    el_year = find_ele_by_xpath(f"/hierarchy/android.widget.FrameLayout/android.widget"
+                                f".LinearLayout/android.widget.FrameLayout/android.widget"
+                                f".FrameLayout/android.widget.FrameLayout/android.widget"
+                                f".FrameLayout/android.view.ViewGroup/android.widget"
+                                f".FrameLayout/android.view.ViewGroup/android.view."
+                                f"ViewGroup/android.widget.ScrollView/android.widget"
+                                f".LinearLayout/android.view.ViewGroup/android.widget"
+                                f".LinearLayout/androidx.recyclerview.widget"
+                                f".RecyclerView/android.view.ViewGroup[{count}]/android.widget.TextView")
+    el_year.click()
+
+
+def open_profession():
+    print('切换专业分数线')
+    el_switch = find_ele_by_xpath(
+        "/hierarchy/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.LinearLayout/"
+        "android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/"
+        "android.widget.RelativeLayout/android.view.ViewGroup/android.view.ViewGroup[1]/"
+        "androidx.viewpager.widget.ViewPager/android.view.ViewGroup/android.widget.ScrollView/"
+        "android.view.ViewGroup/android.widget.LinearLayout/android.view.View")
+    el_switch_mid_x = el_switch.location['x'] + el_switch.size['width'] / 2
+    el_switch_mid_y = el_switch.location['y'] + el_switch.size['height'] / 2
+    print(el_switch, el_switch_mid_x, el_switch_mid_y)
+    actions = ActionChains(driver)
+    actions.w3c_actions = ActionBuilder(driver, mouse=PointerInput(interaction.POINTER_TOUCH, "touch"))
+    actions.w3c_actions.pointer_action.move_to_location(el_switch_mid_x, el_switch_mid_y)
+    actions.w3c_actions.pointer_action.pointer_down()
+    actions.w3c_actions.pointer_action.pause(0.1)
+    actions.w3c_actions.pointer_action.release()
+    actions.perform()
+
+
+def change_batch(count):
+    # 3个科目都点击一次
+    print('打开批次面板', count)
+    for index in range(count):
+        # el_dropdown = find_ele_by_id("com.eagersoft.youzy.youzy:id/ll_select_college_batch")
+        el_dropdown = WebDriverWait(driver, 5, 0.5).until(expected_conditions.presence_of_element_located(
+            (AppiumBy.ID, "com.eagersoft.youzy.youzy:id/ll_select_college_batch")))
+        if el_dropdown is not None:
+            el_dropdown.click()
+        else:
+            # 表示元素有,但不可见,需要滚动至可见
+            driver.execute_script("arguments[0].scrollIntoView();", el_dropdown)
+            el_dropdown.click()
+        batch_item_path = f"/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget" \
+                          f".FrameLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.widget" \
+                          f".FrameLayout/android.view.ViewGroup/android.widget.FrameLayout/android.view" \
+                          f".ViewGroup/android.view.ViewGroup/android.widget.ScrollView/android.widget" \
+                          f".LinearLayout/android.view.ViewGroup/android.widget.LinearLayout/androidx" \
+                          f".recyclerview.widget.RecyclerView/android.view.ViewGroup[{index + 1}]"
+        try:
+            el_batch_item = find_ele_by_xpath(batch_item_path)
+            el_batch_item.click()
+        except Exception as ex:
+            print('切换批次异常,索引', index, '乎略异常', ex)
+            el_ddl_close = find_ele_by_id('com.eagersoft.youzy.youzy:id/iv_close')
+            el_ddl_close.click()
+        randomSleep()
+
+
+def cicle():
+    change_batch(batch_count)
+    # time.sleep(0.5)
+    # 切换科目
+    subtype_tab = find_ele_by_id("com.eagersoft.youzy.youzy:id/tv_subject_type")
+    if subtype_tab is not None:
+        subtype_tab.click()
+    # time.sleep(0.5)
+
+    subject_index = 1
+    if subject_default_top: subject_index = 2
+    subject_path = f"/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget" \
+                   f".FrameLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.widget" \
+                   f".FrameLayout/android.view.ViewGroup/android.widget.FrameLayout/android.view.ViewGroup/android" \
+                   f".view.ViewGroup/android.widget.ScrollView/android.widget.LinearLayout/android.view" \
+                   f".ViewGroup/android.widget.LinearLayout/androidx.recyclerview.widget.RecyclerView/android" \
+                   f".view.ViewGroup[{subject_index}]"
+    el29 = find_ele_by_xpath(subject_path)
+    if el29 is not None:
+        el29.click()
+    # time.sleep(0.5)
+    change_batch(batch_count - 1)
+    # time.sleep(0.5)
+
+
+def get_data(tab_index, change_years):
+    if change_tab(tab_index):
+        # time.sleep(1)
+        if tab_index == 2:
+            open_profession()
+            # time.sleep(1)
+        if change_years[0] == 1: # 如果第 1 个是默认年份,直接开始批次切换
+            cicle()
+        for year in change_years:
+            # 从第二个年份开始切换
+            change_time(year)
+            cicle()
+    else:
+        raise Exception("切换tab失败")
+
+
+def is_error() -> bool:
+    try:
+        return (WebDriverWait(driver, 0.5, 0.25).until(expected_conditions.visibility_of_element_located(
+            (AppiumBy.ID, "com.eagersoft.youzy.youzy:id/errorViewRelativeLayout"))) or
+                WebDriverWait(driver, 0.5, 0.25).until(
+                    expected_conditions.visibility_of_element_located(
+                        (AppiumBy.ID, "com.eagersoft.youzy.youzy:id/load_more_load_fail_view"))))
+    except:
+        return False
+
+
+def retry():
+    try:
+        retry_button = WebDriverWait(driver, 2, 0.5).until(
+            expected_conditions.visibility_of_element_located(
+                (AppiumBy.ID, "com.eagersoft.youzy.youzy:id/errorStateButton")))
+        if retry_button is not None:
+            retry_button.click()
+        else:
+            retry_button = WebDriverWait(driver, 2, 0.5).until(
+                expected_conditions.visibility_of_element_located(
+                    (AppiumBy.ID, "com.eagersoft.youzy.youzy:id/tv_prompt")))
+            if retry_button is not None:
+                retry_button.click()
+    except Exception as ex:
+        print('retry ignored', ex)
+
+
+def is_loading() -> bool:
+    try:
+        return (WebDriverWait(driver, 0.5, 0.25).until(expected_conditions.visibility_of_element_located(
+            (AppiumBy.ID, "com.eagersoft.youzy.youzy:id/image_loading"))) or
+                WebDriverWait(driver, 0.5, 0.25).until(expected_conditions.visibility_of_element_located(
+                    (AppiumBy.ID, "com.eagersoft.youzy.youzy:id/load_more_load_end_view"))))
+    except:
+        return False
+
+
+def find_ele_by_id(ele_id: str, timeout: int = 2, poll_frequency: int = 0.5, deep: int = 0):
+    loading = False
+    error = False
+    try:
+        def find(x):
+            nonlocal loading
+            nonlocal error
+            if is_error():
+                print("error by id", x)
+                error = True
+                loading = False
+                return False
+            if is_loading():
+                print("loading")
+                loading = True
+                error = False
+                return False
+            return x.find_element(by=AppiumBy.ID, value=ele_id) is not None
+
+        WebDriverWait(driver, timeout, poll_frequency).until(find)
+        return driver.find_element(by=AppiumBy.ID, value=ele_id)
+    except:
+        if loading:
+            print("新一轮loading", deep)
+            if retry_times_max > 0 and deep > 0 and deep % retry_times_max == 0:
+                try:
+                    print('重新触发查询', deep, '%', retry_times_max)
+                    btn_search = driver.find_element(by=AppiumBy.ID, value="com.eagersoft.youzy.youzy:id/tv_search")
+                    btn_search.click()
+                except Exception as e:
+                    print('重新触发查询 except', e)
+            return find_ele_by_id(ele_id, timeout, poll_frequency, deep + 1)
+        elif error:
+            print("error,重试")
+            retry()
+            return find_ele_by_id(ele_id, timeout * 2, poll_frequency, deep + 1)
+        else:
+            return None
+
+
+def find_ele_by_xpath(ele_xpath: str, timeout: int = 2, poll_frequency: int = 0.5):
+    loading = False
+    error = False
+    try:
+        def find(x):
+            nonlocal loading
+            nonlocal error
+            if is_error():
+                print("error by path", x)
+                error = True
+                loading = False
+                return False
+            if is_loading():
+                print("loading")
+                loading = True
+                error = False
+                return False
+            return x.find_element(by=AppiumBy.XPATH, value=ele_xpath)
+
+        return WebDriverWait(driver, timeout, poll_frequency).until(find)
+    except:
+        if loading:
+            print("新一轮,loading")
+            return find_ele_by_xpath(ele_xpath, timeout, poll_frequency)
+        elif error:
+            print("新一轮,重试")
+            retry()
+            return find_ele_by_xpath(ele_xpath, timeout, poll_frequency)
+        else:
+            return None
+
+
+def find_college(college_id: Union[str, int]) -> Union[bool, 'WebElement']:
+    el23 = find_ele_by_id("com.eagersoft.youzy.youzy:id/et_input")
+    el23.send_keys(college_id)
+    el24 = find_ele_by_id("com.eagersoft.youzy.youzy:id/tv_search")
+    el24.click()
+    empty_container = find_ele_by_id("com.eagersoft.youzy.youzy:id/emptyViewRelativeLayout", timeout=2)
+    if empty_container is not None:
+        return False
+    else:
+        college = find_ele_by_id("com.eagersoft.youzy.youzy:id/cl_parent")
+        if college is not None:
+            return college
+    return False
+
+
+def back():
+    el_back = find_ele_by_id("com.eagersoft.youzy.youzy:id/iv_back")
+    if el_back is not None:
+        print("返回页面")
+        el_back.click()
+
+
+def auto_click(id):
+    global hide_guide
+    global stop
+    global retry_current
+    global dataType
+    global loginType
+    print('当前id:', id, datetime.now())
+    # 开始搜索
+    el_search = find_ele_by_id("com.eagersoft.youzy.youzy:id/click_search")
+    if el_search is not None:
+        el_search.click()
+    college = find_college(id)
+    if isinstance(college, bool):
+        return
+    else:
+        college.click()
+        retry_current = 0
+        try:
+            if not hide_guide:
+                print('等待关闭引导')
+                time.sleep(6)
+                # actions = ActionChains(driver)
+                # actions.w3c_actions = ActionBuilder(driver, mouse=PointerInput(interaction.POINTER_TOUCH, "touch"))
+                # actions.w3c_actions.pointer_action.move_to_location(507, 1537)
+                # actions.w3c_actions.pointer_action.pointer_down()
+                # actions.w3c_actions.pointer_action.pause(0.1)
+                # actions.w3c_actions.pointer_action.release()
+                # actions.perform()
+                hide_guide = True
+
+            # time.sleep(1)
+            if dataType == 1:
+                get_data(1, plan_change_years)
+            elif dataType == 2:
+                get_data(2, profession_change_years)
+            elif dataType == 3:
+                get_data(1, plan_change_years)
+                get_data(2, profession_change_years)
+            # 回退两次,清除学校列表缓存
+            back()
+            back()
+        except Exception as ex:
+            print('get_data exception', ex)
+            alarm()
+            back()
+            back()
+            auto_click(id)
+
+
+if __name__ == '__main__':
+    try:
+        args = sys.argv[1:]
+        longopts = ['loginType=', 'dataType=']
+        shortopts = []
+        options, args = getopt.getopt(args, shortopts, longopts)
+        for name, value in options:
+            print(name, value)
+            if name in ('--loginType'):
+                loginType = int(value)
+            if name in '--dataType':
+                dataType = int(value)
+    except:
+        print('运行参数解析错误')
+    if dataType == 1:
+        print('招生计划')
+    elif dataType == 2:
+        print('历年录取')
+    else:
+        print('招生计划和历年录取')
+    start()

BIN
yzy.apk