diff --git a/README.md b/README.md index 7cc4ef5..4a0b2d6 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ 其流程图如下: - + ## 准备工作 ### 1. 配置环境 @@ -56,7 +56,7 @@ pip3 install selenium 在运行程序之前,需要先修改`config.json`文件。该文件用于指定用户需要抢票的相关信息,包括演唱会的场次、观演的人员、城市、日期、价格等。文件结果如下图所示: - + #### 2.1 文件内容说明 @@ -75,11 +75,11 @@ pip3 install selenium 进入大麦网https://www.damai.cn/,选择你需要抢票的演唱会。假设如下图所示: - + 接下来按照下图的标注对配置文件进行修改: - + 最终`config.json`的文件内容如下: @@ -110,3 +110,141 @@ cd damai python3 damai.py ``` + + +# 大麦app抢票 + +大麦app抢票脚本需要依赖appium,因此需要现在安装appium server&client环境,步骤如下: + +## appium server + +### 下载 + +- 先安装好node环境(具备npm)node版本号18.0.0 + +- 先下载并安装好android sdk,并配置环境变量(appium server运行需依赖android sdk) + +- 下载appium + + ```shell + npm install -g appium + ``` + +- 查看appium是否安装成功 + + ```shell + appium -v + ``` + +- 下载UiAutomator2驱动 + + ```shell + npm install appium-uiautomator2-driver + ``` + +​ 可能会遇到如下错误: + +```tex +➜ xcode git:(master) ✗ npm install appium-uiautomator2-driver + +npm ERR! code 1 +npm ERR! path /Users/chenweicheng/Documents/xcode/node_modules/appium-uiautomator2-driver/node_modules/appium-chromedriver +npm ERR! command failed +npm ERR! command sh -c node install-npm.js +npm ERR! [11:57:54] Error installing Chromedriver: Request failed with status code 404 +npm ERR! [11:57:54] AxiosError: Request failed with status code 404 +npm ERR! at settle (/Users/chenweicheng/Documents/xcode/node_modules/appium-uiautomator2-driver/node_modules/axios/lib/core/settle.js:19:12) +npm ERR! at IncomingMessage.handleStreamEnd (/Users/chenweicheng/Documents/xcode/node_modules/appium-uiautomator2-driver/node_modules/axios/lib/adapters/http.js:572:11) +npm ERR! at IncomingMessage.emit (node:events:539:35) +npm ERR! at endReadableNT (node:internal/streams/readable:1344:12) +npm ERR! at processTicksAndRejections (node:internal/process/task_queues:82:21) +npm ERR! [11:57:54] Downloading Chromedriver can be skipped by setting the'APPIUM_SKIP_CHROMEDRIVER_INSTALL' environment variable. + +npm ERR! A complete log of this run can be found in: +npm ERR! /Users/chenweicheng/.npm/_logs/2023-10-26T03_57_35_950Z-debug-0.log +``` + +​ 解决办法(添加环境变量,错误原因是没有找到chrome浏览器驱动,忽略即可) + +```shell +export APPIUM_SKIP_CHROMEDRIVER_INSTALL=true +``` + +### 启动 + +启动appium server并使用uiautomator2驱动 + +```shell +appium --use-plugins uiautomator2 +``` + +启动成功讲出现如下信息: + +``` +[Appium] Welcome to Appium v2.2.1 (REV 2176894a5be5da17a362bf3f20678641a78f4b69) +[Appium] Non-default server args: +[Appium] { +[Appium] usePlugins: [ +[Appium] 'uiautomator2' +[Appium] ] +[Appium] } +[Appium] Attempting to load driver uiautomator2... +[Appium] Requiring driver at /Users/chenweicheng/Documents/xcode/node_modules/appium-uiautomator2-driver +[Appium] Appium REST http interface listener started on http://0.0.0.0:4723 +[Appium] You can provide the following URLs in your client code to connect to this server: +[Appium] http://127.0.0.1:4723/ (only accessible from the same host) +[Appium] http://172.31.102.45:4723/ +[Appium] http://198.18.0.1:4723/ +[Appium] Available drivers: +[Appium] - uiautomator2@2.32.3 (automationName 'UiAutomator2') +[Appium] No plugins have been installed. Use the "appium plugin" command to install the one(s) you want to use. +``` + +其中`[Appium] http://127.0.0.1:4723/ (only accessible from the same host) +[Appium] http://172.31.102.45:4723/ +[Appium] http://198.18.0.1:4723/`为appium server连接地址 + + + +## appium client + +- 先下载并安装好python3和pip3 + +- 安装 + + ```shell + pip3 install appium-puthon-client + ``` + +- 在代码中引入并使用appium + + ```python + from appium import webdriver + from appium.options.common.base import AppiumOptions + + device_app_info = AppiumOptions() + device_app_info.set_capability('platformName', 'Android') + device_app_info.set_capability('platformVersion', '10') + device_app_info.set_capability('deviceName', 'YourDeviceName') + device_app_info.set_capability('appPackage', 'cn.damai') + device_app_info.set_capability('appActivity', '.launcher.splash.SplashMainActivity') + device_app_info.set_capability('unicodeKeyboard', True) + device_app_info.set_capability('resetKeyboard', True) + device_app_info.set_capability('noReset', True) + device_app_info.set_capability('newCommandTimeout', 6000) + device_app_info.set_capability('automationName', 'UiAutomator2') + + # 连接appium server,server地址查看appium启动信息 + driver = webdriver.Remote('http://127.0.0.1:4723', options=device_app_info) + + ``` + +- 启动脚本程序 + + ```shell + cd damai_appium + python3 damai_appium.py + ``` + + + diff --git a/damai_appium/__init__.py b/damai_appium/__init__.py new file mode 100644 index 0000000..7315745 --- /dev/null +++ b/damai_appium/__init__.py @@ -0,0 +1,7 @@ +# -*- coding: UTF-8 -*- +""" +__Author__ = "WECENG" +__Version__ = "1.0.0" +__Description__ = "大麦app抢票自动化" +__Created__ = 2023/10/26 10:20 +""" diff --git a/damai_appium/config.json b/damai_appium/config.json new file mode 100644 index 0000000..dc0e754 --- /dev/null +++ b/damai_appium/config.json @@ -0,0 +1,12 @@ +{ + "server_url": "http://127.0.0.1:4723", + "keyword": "郁可唯", + "users": [ + "姓名1", + "姓名2" + ], + "city": "广州", + "date": "11.26", + "price": "看台1080元", + "if_commit_order": false +} diff --git a/damai_appium/config.py b/damai_appium/config.py new file mode 100644 index 0000000..5578333 --- /dev/null +++ b/damai_appium/config.py @@ -0,0 +1,31 @@ +# -*- coding: UTF-8 -*- +""" +__Author__ = "WECENG" +__Version__ = "1.0.0" +__Description__ = "配置类" +__Created__ = 2023/10/27 09:54 +""" +import json + + +class Config: + def __init__(self, server_url, keyword, users, city, date, price, if_commit_order): + self.server_url = server_url + self.keyword = keyword + self.users = users + self.city = city + self.date = date + self.price = price + self.if_commit_order = if_commit_order + + @staticmethod + def load_config(): + with open('config.json', 'r', encoding='utf-8') as config_file: + config = json.load(config_file) + return Config(config['server_url'], + config['keyword'], + config['users'], + config['city'], + config['date'], + config['price'], + config['if_commit_order']) diff --git a/damai_appium/damai_app.py b/damai_appium/damai_app.py new file mode 100644 index 0000000..6bdfae7 --- /dev/null +++ b/damai_appium/damai_app.py @@ -0,0 +1,135 @@ +# -*- coding: UTF-8 -*- +""" +__Author__ = "WECENG" +__Version__ = "1.0.0" +__Description__ = "大麦app抢票自动化" +__Created__ = 2023/10/26 10:27 +""" +from time import sleep + +from appium import webdriver +from appium.options.common.base import AppiumOptions +from appium.webdriver.common.appiumby import AppiumBy +from selenium.webdriver.common.by import By + +from config import Config + +# 加载配置信息 +config = Config.load_config() + +device_app_info = AppiumOptions() +# 操作系统 +device_app_info.set_capability('platformName', 'Android') +# 操作系统版本 +device_app_info.set_capability('platformVersion', '10') +# 设备名称 +device_app_info.set_capability('deviceName', 'YourDeviceName') +# app package +device_app_info.set_capability('appPackage', 'cn.damai') +# app activity name +device_app_info.set_capability('appActivity', '.launcher.splash.SplashMainActivity') +# 使用unicode输入 +device_app_info.set_capability('unicodeKeyboard', True) +# 隐藏键盘 +device_app_info.set_capability('resetKeyboard', True) +# 不重置app +device_app_info.set_capability('noReset', True) +# 超时时间 +device_app_info.set_capability('newCommandTimeout', 6000) +# 使用uiautomator2驱动 +device_app_info.set_capability('automationName', 'UiAutomator2') + +# 连接appium server,server地址查看appium启动信息 +driver = webdriver.Remote(config.server_url, options=device_app_info) + +sleep(5) + +# 设置等待时间,等待1s +driver.implicitly_wait(5) +# 空闲时间10ms,加速 +driver.update_settings({"waitForIdleTimeout": 10}) + +# 点击搜索框 +driver.find_element(by=By.ID, value='homepage_header_search_btn').click() + +# 输入搜索关键词 +driver.find_element(by=By.ID, value='header_search_v2_input').send_keys(config.keyword) + +# 点击第一个搜索结果 +driver.find_element(by=By.XPATH, + value='//androidx.recyclerview.widget.RecyclerView[@resource-id="cn.damai:id/search_v2_suggest_recycler"]/android.widget.RelativeLayout[1]').click() + +# 点击结果列表的第一个 +driver.find_element(by=By.XPATH, + value='(//android.widget.LinearLayout[@resource-id="cn.damai:id/ll_search_item"])[1]').click() + +if driver.find_elements(by=By.XPATH, + value='//android.widget.FrameLayout[@resource-id="cn.damai:id/trade_project_detail_purchase_status_bar_container_fl"]'): + # 城市选择 + for city in driver.find_elements(by=By.ID, value='tv_tour_city'): + if config.city in city.text: + city.click() + # 日期选择 + for date in driver.find_elements(by=By.ID, value='tv_tour_time'): + if config.date in date.text: + date.click() + +while driver.find_elements(by=By.XPATH, + value='//android.widget.FrameLayout[@resource-id="cn.damai:id/trade_project_detail_purchase_status_bar_container_fl"]'): + buy_btn = driver.find_element(by=By.XPATH, + value='//android.widget.TextView[@resource-id="cn.damai:id/tv_left_main_text"]').text + if buy_btn == '立即购买': + # 点击立即购买 + driver.find_element(by=By.XPATH, + value='//android.widget.FrameLayout[@resource-id="cn.damai:id/trade_project_detail_purchase_status_bar_container_fl"]/android.widget.LinearLayout').click() + # 票价选择 + if driver.find_elements(by=By.ID, value='project_detail_perform_price_flowlayout'): + for price in driver.find_elements(by=By.XPATH, + value='//android.widget.TextView[@resource-id="cn.damai:id/item_text"]'): + if config.price in price.text: + price.click() + # 数量选择 + if driver.find_elements(by=By.ID, value='layout_num') and config.users is not None: + for i in range(len(config.users) - 1): + driver.find_element(by=By.ID, value='img_jia').click() + # 确认 + if driver.find_elements(by=By.ID, value='btn_buy'): + driver.find_element(by=By.ID, value='btn_buy').click() + # 选择人员 + if driver.find_elements(by=By.ID, value='recycler_main') and config.users is not None: + identity_elements = driver.find_elements(AppiumBy.ANDROID_UIAUTOMATOR, 'new UiSelector().text("身份证")') + parent_elements = [element.parent for element in identity_elements] + for user in config.users: + for user_element in parent_elements: + user_select_list = user_element.find_elements(AppiumBy.ANDROID_UIAUTOMATOR, + 'new UiSelector().textContains("' + str(user) + '")') + for user_select in user_select_list: + user_select.click() + break + break + # 提交订单 + if driver.find_elements(by=AppiumBy.ANDROID_UIAUTOMATOR, + value='new UiSelector().text("提交订单")') and config.if_commit_order: + driver.find_element(by=AppiumBy.ANDROID_UIAUTOMATOR, value='new UiSelector().text("提交订单")').click() + if buy_btn == '预约抢票': + # 预约购票 + driver.find_element(by=By.XPATH, + value='//android.widget.FrameLayout[@resource-id="cn.damai:id/trade_project_detail_purchase_status_bar_container_fl"]/android.widget.LinearLayout').click() + # 票价选择 + if driver.find_elements(by=By.ID, value='project_detail_perform_price_flowlayout'): + for price in driver.find_elements(by=By.XPATH, + value='//android.widget.TextView[@resource-id="cn.damai:id/item_text"]'): + if config.price in price.text: + price.click() + # 提交 + if driver.find_elements(by=By.ID, value='btn_buy_bottom_div_line'): + driver.find_element(by=By.XPATH, + value='//android.view.View[@resource-id="cn.damai:id/btn_buy_bottom_div_line"]/..').click() + if buy_btn == '已预约': + break + else: + # 模拟下拉刷新 + driver.swipe(500, 400, 500, 2000, 300) + sleep(0.1) + +driver.quit() diff --git a/大麦抢票流程.drawio b/doc/大麦抢票流程.drawio similarity index 100% rename from 大麦抢票流程.drawio rename to doc/大麦抢票流程.drawio diff --git a/config_json.png b/img/config_json.png similarity index 100% rename from config_json.png rename to img/config_json.png diff --git a/example.png b/img/example.png similarity index 100% rename from example.png rename to img/example.png diff --git a/example_detail.png b/img/example_detail.png similarity index 100% rename from example_detail.png rename to img/example_detail.png diff --git a/大麦抢票流程.png b/img/大麦抢票流程.png similarity index 100% rename from 大麦抢票流程.png rename to img/大麦抢票流程.png