├── 1.jpg ├── 2.jpg ├── README.md └── xxqg.py /1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oneJue/xxqg/ae25cbe70ae520450356307d2703ce922cda06dd/1.jpg -------------------------------------------------------------------------------- /2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oneJue/xxqg/ae25cbe70ae520450356307d2703ce922cda06dd/2.jpg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## XXQG 基于uiautomator2实现的学习助手 免root 适配安卓 自动化脚本 3 | 4 | 适配V2.49.0 (平均40分) 5 | 6 | - [x] 看文章 7 | - [x] 看视频 8 | - [x] 本地学习(地区北京) 9 | - [x] 每日答题 10 | - [x] 双人赛/四人赛 11 | - [ ] 挑战答题 12 | - [ ] 订阅 13 | --------------------------- 14 | [浅谈自动化测试工具 python-uiautomator2 · TesterHome](https://testerhome.com/topics/11357 "浅谈自动化测试工具 python-uiautomator2 · TesterHome") 15 | 16 | ## 环境部署 17 | 18 | ### 安装 adb 19 | 安装adb并配置环境变量 20 | 21 | 这里不做详述,网上的教程很多。安装完查看一下版本信息,能出现具体的版本号说明安装成功 22 | 23 | [windows下载安装adb(极其简单)\_adb工具\_x2584179909的博客](https://blog.csdn.net/x2584179909/article/details/108319973 "windows下载安装adb(极其简单)_adb工具_x2584179909的博客") 24 | 25 | ###  usb调试 26 | 27 | usb连手机,手机的【**开发者选项**】里 打开允许【**USB调试**】 28 | 29 | cmd 执行 **adb devices**,可以看到设备对应的 序列码,说明已连接上 30 | 31 | cmd 执行 **python -m uiautomator2 init**。会在手机上安装两个APK:ATX-agent(小黄车)和 [\`com.github.uiautomator.test\`(](https://github.com/openatx/uiautomator2/blob/0f37aaca07d3f5dcef261308217ce6f4bfc547ef/README.md?plain=1#L1347 "`com.github.uiautomator.test`( ")不可见) 32 | 33 | > 这两个apk使用同一个证书签名的。 不可见的应用实际上是一个测试包,包含有所有的测试代码,核心的测试服务也是通过其启动的。 但是运行的时候,系统却需要那个小黄车一直在运行(在后台运行也可以)。一旦小黄车应用被杀,后台运行的测试服务也很快的会被杀掉。就算什么也不做,应用应用在后台,也会很快被系统回收掉。 34 | 35 | ### 安装 uiautomator2 36 | 37 | [GitHub - openatx/uiautomator2: Android Uiautomator2 Python Wrapper](https://github.com/openatx/uiautomator2#quick-start "GitHub - openatx/uiautomator2: Android Uiautomator2 Python Wrapper") 38 | 39 | ```python 40 | # Since uiautomator2 is still under development, you have to add --pre to install the development version 41 | pip install --upgrade --pre uiautomator2 42 | 43 | # Or you can install directly from github source 44 | git clone https://github.com/openatx/uiautomator2 45 | pip install -e uiautomator2 46 | ``` 47 | 48 | 49 | ### 连接 50 | 设备连接方法,有两种: 51 | 52 | python-uiautomator2连接手机的方式有两种,一种是通过WIFI,另外一种是通过USB。两种方法各有优缺点。 53 | WIFI最便利的地方要数可以不用连接数据线,USB则可以用在PC和手机网络不在一个网段用不了的情况。 54 | 55 | 1) 通过WiFi,假设设备IP 192.168.0.107和您的PC在同一网络中 56 | 57 | ```python 58 | import uiautomator2 as u2 59 | d = u2.connect('192.168.0.107') 60 | ``` 61 | 2) 通过USB, 假设设备序列是123456789F 62 | 63 | ```python 64 | import uiautomator2 as u2 65 | d = u2.connect('123456789F') # USB链接设备。或者u2.connect_usb('123456f') 66 | #d = u2.connect_usb() 或者 d = u2.connect() ,当前只有一个设备时可以用这个 67 | ``` 68 | ## 使用说明 69 | 部署完成后,运行py文件,在ATX应用上 启动UIAUTOMATOR 70 | ![2](2.jpg) 71 | -------------------------------------------------------------------------------- /xxqg.py: -------------------------------------------------------------------------------- 1 | 2 | import time 3 | import random 4 | import uiautomator2 as u2 5 | from operator import itemgetter 6 | # 7 | # 连接到设备 8 | # d = u2.connect('192.168.3.5:5555') 9 | d = u2.connect('NAB0220630013667') 10 | 11 | #打印设备信息以确认连接成功 12 | print(d.device_info) 13 | # d.app_stop("cn.xuexi.android") 14 | print("在ATX中启动UIAUTOMATOR") 15 | time.sleep(10) 16 | d.app_start("cn.xuexi.android") 17 | 18 | ################################################################### 19 | 20 | 21 | #点击要闻 22 | while True: 23 | if d(text="取消").exists(): 24 | d(text="取消").click() 25 | element = d(text="要闻") 26 | if element.exists(): 27 | element.click() 28 | break 29 | print("--------------------------------------------------------") 30 | print("开始阅读文章") 31 | 32 | #看文章 33 | j=0 34 | for i in range(1,9): 35 | j=j+1 36 | element = d.xpath('//android.widget.ListView/android.widget.FrameLayout[{}]'.format(j)) 37 | if element.exists: 38 | element.click() 39 | time.sleep(50 + random.random() * 5) 40 | if i == 1: 41 | print("--------------------------------------------------------") 42 | print("正在发表观点") 43 | print("--------------------------------------------------------") 44 | 45 | d(text="欢迎发表你的观点").click() 46 | time.sleep(1) 47 | d(text="好观点将会被优先展示").set_text("坚定信念,走强国之路") # 输入新的文本 48 | time.sleep(1) 49 | d(text="发布").click() 50 | time.sleep(1) 51 | if d(text="访问异常").exists: 52 | time.sleep(30) 53 | # d.xpath('//*[@resource-id="cn.xuexi.android:id/TOP_LAYER_VIEW_ID"]/android.widget.ImageView[1]').click() 54 | # break 55 | # d.xpath('//*[@resource-id="cn.xuexi.android:id/BOTTOM_LAYER_VIEW_ID"]/android.widget.ImageView[1]').click() 56 | d.xpath('//*[@resource-id="cn.xuexi.android:id/TOP_LAYER_VIEW_ID"]/android.widget.ImageView[1]').click() 57 | 58 | else: 59 | d.swipe_ext("up") 60 | j = 2 61 | time.sleep(3) 62 | 63 | 64 | 65 | ################################################################### 66 | #本地 67 | 68 | print("--------------------------------------------------------") 69 | print("本地学习中") 70 | 71 | 72 | time.sleep(1) 73 | d.xpath('//*[@resource-id="cn.xuexi.android:id/view_pager"]/android.widget.FrameLayout[1]/android.widget.LinearLayout[1]/android.widget.LinearLayout[1]/android.widget.FrameLayout[1]/android.widget.ImageView[1]').click() 74 | time.sleep(1) 75 | d(text="北京").click() 76 | time.sleep(1) 77 | d.xpath('//androidx.recyclerview.widget.RecyclerView/android.widget.LinearLayout[1]').click() 78 | time.sleep(1) 79 | d.xpath('//*[@resource-id="android:id/content"]/android.widget.FrameLayout[2]/android.widget.FrameLayout[3]/android.widget.ImageView[1]').click() 80 | 81 | ################################################################### 82 | print("--------------------------------------------------------") 83 | print("看视频") 84 | 85 | #点百灵 86 | time.sleep(2) 87 | d.xpath('//*[@resource-id="cn.xuexi.android:id/home_bottom_tab_button_ding"]/android.widget.RelativeLayout[1]/android.widget.FrameLayout[1]').click() 88 | time.sleep(1) 89 | d(text="竖").click() 90 | time.sleep(1) 91 | d.xpath('//android.widget.ListView/android.widget.FrameLayout[1]/android.widget.LinearLayout[1]/android.widget.FrameLayout[1]').click() 92 | time.sleep(1) 93 | for i in range(1,10): 94 | d.swipe_ext("up") 95 | time.sleep(40+random.random()*5) 96 | d(resourceId="cn.xuexi.android:id/iv_back").click() 97 | 98 | ################################################################### 99 | ##答题 100 | 101 | 102 | 103 | 104 | 105 | time.sleep(1) 106 | d(resourceId="cn.xuexi.android:id/comm_head_xuexi_mine").click() 107 | time.sleep(1) 108 | d.click(0.502, 0.396) 109 | time.sleep(1) 110 | if d(text="").exists(): 111 | d(text="").click() 112 | 113 | #获取提示 114 | def getanswer(): 115 | d.swipe_ext("up") 116 | time.sleep(1) 117 | d.swipe_ext("up") 118 | d(text="查看提示").click() 119 | time.sleep(1) 120 | answer = d.xpath('//*[@resource-id="app"]/android.view.View[2]/android.view.View[3]/android.view.View[2]/android.view.View[1]').get_text() 121 | print("答案",answer) 122 | d(text="").click() 123 | return answer 124 | 125 | #获取相似度 126 | import difflib 127 | def string_similar(s1, s2): 128 | return difflib.SequenceMatcher(None, s1, s2).quick_ratio() 129 | 130 | def daydati(): 131 | time.sleep(1) 132 | d(text="每日答题").click() 133 | try: 134 | while "/5" in d.xpath('//*[@resource-id="app"]/android.view.View[2]/android.view.View[1]/android.view.View[1]/android.view.View[1]/android.view.View[2]').get_text(): 135 | time.sleep(1) 136 | answer = getanswer() 137 | if answer == "请观看视频": 138 | d(text="").click() 139 | time.sleep(1) 140 | d(text="").click() 141 | time.sleep(1) 142 | d(text="退出").click() 143 | daydati() 144 | break 145 | 146 | if d(text="单选题").exists() or d(text="多选题").exists(): 147 | elements = d.xpath('//android.widget.ListView').all() 148 | xuanxiangs = [] 149 | for son in elements: 150 | gsons = son.elem.getchildren() 151 | for gson in gsons : 152 | ggsons = gson.getchildren() 153 | for ggson in ggsons: 154 | gggsons = ggson.getchildren() 155 | xuanxiangs.append(gggsons[2].get("text")) 156 | print(gggsons[2].get("text")) 157 | 158 | i=0 159 | for xuanxiang in xuanxiangs: 160 | if "错误" in xuanxiang: 161 | d(text="错误").click() 162 | time.sleep(1) 163 | if d(text="答案解析").exists(): 164 | d(text="").click() 165 | time.sleep(1) 166 | d(text="退出").click() 167 | daydati() 168 | break 169 | elif xuanxiang in answer: 170 | d(text=xuanxiang).click() 171 | i=i+1 172 | time.sleep(0.5) 173 | if i == 0: 174 | sim=[] 175 | for xuanxiang in xuanxiangs: 176 | sim.append(string_similar(xuanxiang, answer)) 177 | max_index, max_number = max(enumerate(sim), key=itemgetter(1)) 178 | time.sleep(1) 179 | d(text=xuanxiangs[max_index]).click() 180 | time.sleep(2) 181 | 182 | if d(text="确定").exists(): 183 | d(text="确定").click() 184 | time.sleep(1) 185 | if d(text="答案解析").exists(): 186 | time.sleep(1) 187 | print("出现错误") 188 | d(text="").click() 189 | time.sleep(1) 190 | d(text="退出").click() 191 | daydati() 192 | break 193 | 194 | elif d(text="填空题").exists(): 195 | elements = d.xpath( 196 | 197 | '//*[@resource-id="app"]/android.view.View[2]/android.view.View[1]/android.view.View[1]/android.view.View[2]').all() 198 | for eles in elements: 199 | sons = eles.elem.getchildren() 200 | if answer.find(sons[0].get("text")[-5:]) != -1: 201 | answer = answer[answer.find(sons[0].get("text")[-5:]) + 5:] 202 | answer = answer[:answer.find(sons[2].get("text")[:5])] 203 | d.set_clipboard(answer) 204 | time.sleep(1) 205 | d.xpath( 206 | '//*[@resource-id="app"]/android.view.View[2]/android.view.View[1]/android.view.View[1]/android.view.View[2]/android.view.View[2]/android.view.View[1]').click() 207 | time.sleep(1) 208 | d(resourceId="com.huawei.ohos.inputmethod:id/clip_tv_content").click() 209 | time.sleep(1) 210 | if d(text="确定").exists(): 211 | d(text="确定").click() 212 | time.sleep(1) 213 | if d(text="答案解析").exists(): 214 | time.sleep(1) 215 | print("出现错误") 216 | d(text="").click() 217 | time.sleep(1) 218 | d(text="退出").click() 219 | daydati() 220 | break 221 | 222 | 223 | except: 224 | return 225 | 226 | daydati() 227 | time.sleep(1) 228 | d(text="返回").click() 229 | 230 | 231 | ################################################################### 232 | #双人赛 233 | 234 | 235 | def twoxontest(): 236 | d(text="").click() 237 | while True: 238 | try: 239 | if d(text="继续挑战").exists: 240 | break 241 | if d.xpath('//android.widget.ListView/android.view.View[1]').exists: 242 | d.xpath('//android.widget.ListView/android.view.View[{}]'.format(random.randint(1, 2))).click() 243 | time.sleep(random.random()/3) 244 | except: 245 | break 246 | time.sleep(1) 247 | d(text="").click() 248 | time.sleep(1) 249 | d(text="").click() 250 | time.sleep(1) 251 | d(text="退出").click() 252 | 253 | ################################################################### 254 | #四人赛 255 | 256 | 257 | def fourcontest(): 258 | d(text="开始比赛").click() 259 | while True: 260 | try: 261 | if d(text="继续挑战").exists: 262 | break 263 | if d.xpath('//android.widget.ListView/android.view.View[1]').exists: 264 | d.xpath('//android.widget.ListView/android.view.View[{}]'.format(random.randint(1, 2))).click() 265 | time.sleep(random.random()/3) 266 | except: 267 | break 268 | time.sleep(1) 269 | d(text="").click() 270 | time.sleep(1) 271 | d(text="").click() 272 | time.sleep(1) 273 | d(text="").click() 274 | 275 | ################################################################### 276 | #挑战答题 277 | 278 | 279 | # def tzcontest(): 280 | # while True: 281 | # try: 282 | # if d(text="再来一局").exists: 283 | # break 284 | # elif d(text="立即复活").exists: 285 | # d(text="立即复活").click() 286 | # elif d.xpath('//android.widget.ListView/android.view.View[1]').exists: 287 | # d.xpath('//android.widget.ListView/android.view.View[{}]'.format(random.randint(1, 2))).click() 288 | # time.sleep(random.random()/3) 289 | # except: 290 | # break 291 | # time.sleep(1) 292 | # d(text="").click() 293 | # time.sleep(1) 294 | # d(text="").click() 295 | # time.sleep(1) 296 | # d(text="").click() 297 | 298 | ################################################################### 299 | 300 | time.sleep(1) 301 | # for i in range(1,4): 302 | # d.xpath('//*[@resource-id="app"]/android.view.View[1]/android.view.View[3]/android.view.View[8]/android.view.View[{}]'.format(i)).click() 303 | # time.sleep(1) 304 | # if d(text="").exists: 305 | # twoxontest() 306 | # elif d(text="开始比赛").exists: 307 | # fourcontest() 308 | # else:d(text="时事政治").click() 309 | d.xpath( 310 | '//*[@resource-id="app"]/android.view.View[1]/android.view.View[3]/android.view.View[8]/android.view.View[1]').click() 311 | time.sleep(1) 312 | if d(text="").exists: 313 | twoxontest() 314 | elif d(text="开始比赛").exists: 315 | fourcontest() 316 | # else: 317 | # # d(text="时事政治").click() 318 | 319 | 320 | 321 | 322 | ################################################################### 323 | 324 | 325 | ################################################################### 326 | 327 | time.sleep(1) 328 | # d.app_stop("cn.xuexi.android") 329 | 330 | --------------------------------------------------------------------------------