全国免费咨询:

13245491521

VR图标白色 VR图标黑色
X

中高端软件定制开发服务商

与我们取得联系

13245491521     13245491521

2022-10-10_【玩转Android自动化】学亿点有备无患的“姿势“

您的位置:首页 >> 新闻 >> 行业资讯

【玩转Android自动化】学亿点有备无患的“姿势“ 本文为稀土掘金技术社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究! 1、引言Hi,我是杰哥,上节《学穿ADB》带着大家: 对ADB相关的姿势进行了深入学习,并写了一个 "某办公软件打卡自动化" 的简单练手案例。 不知道读者朋友们,有没有跟着动手试试看,纸上得来终觉浅,绝知此事要躬行,收藏夹吃灰是大忌,没试过的赶紧玩起来! 文尾处说道:这个自动打卡脚本的实现过于简单粗暴 (定时+adb包名启动应用),存在一系列问题。 而单靠ADB并不能解决,所以本节我们再学点有备无患的 "自动化姿势",拓宽认知,为下节自动打卡脚本的「赋能」做准备。 本节学习路线安排如下: 了解下OCR文字识别的相关概念,使用pytesseract库和chineseocr_lite库识别图片文字;了解消息推送相关,使用Server酱推送消息到微信;学习自动化中Python常用的图片处理操作;利用Android系统中的system/bin/uiautomator.jar包,导出当前页面的所有控件信息,并对xml内容进行解析;话不多说,直接开冲~ 2、OCR文字识别OCR,Optical Character Recognition,译作光学字符识别,亦称计算机文字识别,指的是利用光学技术和计算机技术,对文本资料的图像文件进行分析识别处理,获取文字及版面信息的过程。 说人话就是:利用这项技术,从图片中直接提取文字。 OCR技术是实现文字快速录入的一项关键技术,应用场景可就多了: 车牌路牌识别;证件卡片识别;验证码识别;古籍识别;PDF转Word等...如今大部分手机和APP都自带OCR,读者们应该都玩过吧?没玩过的可以打开微信体验下:点开图片 → 长按 → 提取文字: 简单介绍下印刷体文字的主要识别流程 (详细讲解可见【独家】一文读懂文字识别(OCR)) 前期处理 灰度化→ 彩色图像中每个像素的颜色由RGB三个分量决定,取值范围0-255,对于计算机来说,这样一个像素点会有256256256=16777216种颜色的变化范围。而灰度图是一种RGB分量相同的特殊色图像,一个像素点的变化范围只有0-255这256种。图像灰度化的目的是为了简化矩阵,提高运算速度。二值化→ 将文字与背景进一步分开,将灰度值图像信号转换为只有黑(1)和白(0)的二值图像信号,可简单理解为 "黑白化",常用方法有:分量法、最大值法、平均值法、加权平均法等;降噪→ 根据噪点的特征进行去噪;倾斜校正→ 倾斜的文档图像对后期的字符分割、识别和图像压缩等工作会产生较大影响,为了保证后续处理的正确性,需要对文本图像进行倾斜检测和校正;大小规范化→ 将输入的任意尺寸的文字都处理成统一尺寸的标准文字,以便与预先存储在字典中的参考模板相匹配;图像平滑→ 去掉笔划上的孤立白点和笔划外部的孤立黑点,以及笔划边缘的凹凸点,使得笔划边缘变得平滑;中期处理 版面分析→ 将文本图像分割为不同部分,并标定各部分属性,如:文本、图像、表格;版面理解→ 获取文章逻辑结构,包括各区域的逻辑属性、文章的层次关系和阅读顺序;图像切分→ 对文本图像进行切分,方便对单个文字进行识别处理,有两个类型:行(列)切分和字切分;特征提取→ 从单个字符图像上提取统计特征或结构特征,匹配特征库中与待识别文字相似度最高的文字;模型训练→ 从大量标记预料中自动学习出图像的特征;识别后处理 版面恢复→ 根据版面分析和OCR的结果,重构出包含文字信息和版面信息的电子文档;识别校正→ 特定的语言上下文的关系,对识别结果进行校正;上述内容看得一脸懵逼,云里雾里?放心,这些技术我们无需掌握,专业的事情交给专业的人,大概了解下就好。 我们只关注能提取图片文字的OCR工具及这个工具该怎么用? ① pytesseract (不推荐)网上随手搜下关键字Python OCR识别文字,一堆烂大街的教程都是教你用pytesseract这个基于Google开源的Tesseract-OCR引擎封装的库来进行文字识别。 用法确实简单,先装两个东西: #安装pytesseract pipinstallpytesseract #安装Tesseract(区分系统) #Windows系统→可到:https://github.com/UB-Mannheim/tesseract/wiki下载exe安装包 #注:记得选中文包,默认只支持英文识别 #安装完,来到tesseract.exe所在的目录,复制路径,在PATH环境变量中新增此路径。 #macOS系统可用brew安装→brewinstalltesseract 安装配置完,打开命令行键入tesseract -v可以查看对应版本: 如果安装时没勾选中文包也没关系,可以到tessdata中下载中文数据集chi_sim.traineddata,然后放到tessdata目录下 (其它语言也是如此): 随手拿个图片试试水: 识别代码如下: importpytesseract fromPILimportImage if__name__=='__main__': image=Image.open("test_ocr.jpg") text=pytesseract.image_to_string(image,lang='chi_sim') print(text) 输出结果如下: em...识别率好像不是很高?尝试把文字部分抠出来 (手动截图),再识别一下: 第一行文本算是完整识别了,但第二行文本依旧识别不出来。想提高识别率,除了对图片进行处理(二值化、灰度等),还可以自己训练数据模型,对训练方法感兴趣可移步至Training for Tesseract 5自行查阅尝试。 得自己训练模型,这波算是劝退我们了,毕竟我们的期望是:简单调下API,就能获取较为准确的识别结果。 ② chineseocr_lite (推荐)自己不想训练模型,直接用别人训练好的模型,不就好了? 随手搜下"OCR平台",铺天盖地的供应服务商,以百度OCR为例,官网控制台开通OCR相关服务,接着pip install baidu-aip安装模块,然后直接调用: fromaipimportAipOcr #读取文件内容 defget_file_content(file_path): withopen(file_path,'rb')asfp: returnfp.read() classBaiDuOCR: #初始化,相关字段到官网控制台自行获取 def__init__(self): self.APP_ID="xxx" self.API_KEY="xxx" self.SECRET_KEY="xxx" self.client=AipOcr(self.APP_ID,self.API_KEY,self.SECRET_KEY) #识别图片 defgeneral(self,pic_path): orc_result=self.client.basicGeneral(get_file_content(pic_path)) iforc_resultisnotNone: print("识别结果:"+str(orc_result)) else: print("识别失败") raiseException("识别失败异常") if__name__=='__main__': ocr_client=BaiDuOCR() ocr_client.general('test_ocr.jpg') 运行识别结果如下: 啧啧,完美识别,其它OCR识别平台的集成方法也是类似,参照对应官方文档即可。 当然,识别服务是要收费的的 (有提供少量的白嫖次数),毕竟,人家也是要盈利的,有企业采购需求可以考虑下~ 除了付费的服务供应商外,还有一些优秀的开源OCR识别库,比如笔者在《破大防!这个开源库,竟能让APP日常任务自动化变得如此简单》中用到的DayBreak-u/chineseocr_lite,识别速度和准确率都非常高。 使用方法很简单,先把项目clone到本地: gitclonehttps://github.com/DayBreak-u/chineseocr_lite.git 接着cd到目录下,键入启动命令: pythonbackend/main.py 一般是运行不起来的,相关依赖都没有装,报缺啥,你就pip装啥,比如笔者依次就装了这些: #Python高性能Web框架 pipinstalltornado #opencv→cv2 pipinstallopencv-python #ONNX格式的机器学习模型的高性能推理引擎 pipinstallonnxruntime #小型动态图形计算库,将输入的图形路径进行处理 pipinstallpyclipper #空间几何对象库,支持点线面等集合对象及相关空间操作 pipinstallshapely 该装的都装完了,执行启动命令,终端最后会输出一个内网的ip地址: 复制到浏览器打开,把要识别的图片传入,接着点击识别,静待识别完成: 识别结果准确无误不说,文字区域相对图片的位置也标记出来了: 接着要做的事情就是:抓包、**编写代码 (模拟上传图片 + 解析识别结果)**,而这部分折腾过程在《模拟上传 & 结果解析》中已经写得巨详细了,不再复述了,直接给出工具代码: importsocket importrequestsasr fromcollectionsimportOrderedDict local_ocr_base_url="http://{}:8089".format(socket.gethostbyname(socket.gethostname())) local_ocr_tr_run_url=local_ocr_base_url+"/api/tr-run/" defpicture_local_ocr(pic_path): upload_files={'file':open(pic_path,'rb'),'compress':960} #发送请求会自动加上Content-Type,不要手多加上,加了会报错 resp=r.post(local_ocr_tr_run_url,files=upload_files) returnextract_text(resp.json()) defextract_text(origin_data_dict): text_dict=OrderedDict() raw_out=origin_data_dict['data']['raw_out'] ifraw_outisnotNone: forrawinraw_out: text_dict[raw[1]]=(raw[0][0][0],raw[0][0][1],raw[0][27][0],raw[0][28][1]) returntext_dict else: print("Json数据解析异常") if__name__=='__main__': print(picture_local_ocr('test_ocr.jpg')) 运行打印输出结果如下: 行吧,想用OCR文字识别的时候,先把服务跑起来,然后调下picture_local_ocr()就好了,非常简单~ 3、消息推送在自动化脚本运行过程中,有时需要将一些消息及时告知我们,以便进行一些决策,比如:自动打卡成功或失败。 而笔者知道的关于消息推送的方案有这些: 发送邮件(免费,python中主要用到两个内置库smtplib-登录邮箱,email-构建邮件内容)发送短信(付费,调短信平台提供的API,各种传自己的信息,而且有限制);集成第三方消息推送SDK(花钱不说,可能还得自己写个接收端的APP);各种群机器人(企业微信、钉钉、飞书、tg等);微信消息(Server酱、微信测试号等)上述方案都可以,按照自己实际情况来就好,这里只提一嘴笔者一直在用的Server酱。用法比较简单,打开官网扫码登录 (要接收消息的微信): 然后点击通道配置,选择方糖服务号: 接着点SendKey跳转,可以在此测试发送消息: 手机微信立马到推送: 接着就是写代码调调API咯,直接给出工具代码: importrequestsasr send_key="xxx"#SendKey,官网自行获取 send_url="https://sctapi.ftqq.com/%s.send"%send_key defsend_wx_message(title,desp,short,channel=9): """ 发送微信消息 :paramtitle:标题,必填,最大长度32 :paramdesp:消息内容,选填,最大长度为32KB :paramshort:消息卡片内容,选填,最大长度64,不指定会自动截图desp的前30个显示 :paramchannel:渠道,支持最多两个通道,用竖线隔开,9为方糖服务号 :return:None """ resp=r.post(send_url,data={'title':title,'desp':desp,'short':short,'channel':channel}) ifresp: ifresp.status_code==200: print("消息发送成功") else: print("消息发送失败") print(resp.text) if__name__=='__main__': send_wx_message("测试标题","测试消息内容\n\n"*16,"测试卡片") 运行输出结果如下: 微信同样收到消息推送,细心的你可能发现了 **[1/5]**,这是每天只能发5条? 确切点来说是新版免费额度每天只有5条,对于我们的自动打卡场景来说是够用的了。 如果你还有其他自动化的需求,需要频繁用到消息推送的,也可以按需订阅会员,丰俭由人~ 当然,你还可以使用微信测试号或企业微信动手搭建一个类似的推送平台,具体实践可以参考:《使用python推送消息至手机微信最全版》 4、图片处理就是一些自动化里经常用到的图片处理操作,比如:区域裁剪、分辨率调整、转灰度、二值化等,也没啥好讲,直接上工具代码,读者按需Copy即可: importos importtime fromPILimportImage defget_picture_size(pic_path): """ 获得图片尺寸 :parampic_path:图片路径 :return:图片宽度、图片高度,图片格式,返回样例:(1080,1080,'JPEG') """ img=Image.open(pic_path) returnimg.width,img.height,img.format defcrop_area(pic_path,start_x,start_y,end_x,end_y): """ 裁剪图片 :parampic_path:图片路径 :paramstart_x:x轴起始坐标 :paramstart_y:y轴起始坐标 :paramend_x:x轴终点坐标 :paramend_y:y轴终点坐标 :return:生成的截图路径 """ img=Image.open(pic_path) region=img.crop((start_x,start_y,end_x,end_y)) save_path=os.path.join(os.getcwd(),"crop_"+str(round(time.time()*1000))+".jpg") region.save(save_path) returnsave_path defresize_picture(pic_path,width,height): """ 调整图片分辨率 :parampic_path:图片路径 :paramwidth:调整后的图片宽 :paramheight:调整后的图片高 :return:调整后的图片路径 """ img=Image.open(pic_path) resized_img=img.resize((width,height),Image.ANTIALIAS) save_path=os.path.join(os.getcwd(),"resized_"+str(round(time.time()*1000))+".jpg") resized_img.save(save_path) returnsave_path defresize_picture_percent(pic_path,percent): """ 按比例调整图片分辨率 :parampic_path:图片路径 :parampercent:缩放比例 :return:调整后的图片路径 """ img=Image.open(pic_path) resized_img=img.resize((int(img.width*percent),int(img.height*percent)),Image.ANTIALIAS) save_path=os.path.join(os.getcwd(),"resized_"+str(round(time.time()*1000))+".jpg") resized_img.save(save_path) returnsave_path defpicture_to_gray(pic_path): """ 转灰度图 :parampic_path:图片路径 :return:转换后的图片路径 """ img=Image.open(pic_path) gray_img=img.convert('L') save_path=os.path.join(os.getcwd(),"gray_"+str(round(time.time()*1000))+".jpg") gray_img.save(save_path) returnsave_path defpicture_to_black_white(pic_path): """ 图片二值化(黑白) :parampic_path:图片路径 :return:转换后的图片路径 """ img=Image.open(pic_path) gray_img=img.convert('1') save_path=os.path.join(os.getcwd(),"bw_"+str(round(time.time()*1000))+".jpg") gray_img.save(save_path) returnsave_path if__name__=='__main__': print(get_picture_size('test_ocr.jpg')) print(crop_area('test_ocr.jpg',0,0,100,200)) print(resize_picture('test_ocr.jpg',300,600)) print(resize_picture_percent('test_ocr.jpg',0.5)) print(picture_to_gray('test_ocr.jpg')) print(picture_to_black_white('test_ocr.jpg')) 运行输出结果如下: 5、获取当前页面所有控件信息有时,我们需要定位到页面中的某个控件的所在区域,然后触发一些交互,比如登录时: 先定位到点击账号/密码输入文本框,点击获得焦点,然后进行输入;先定位到同意协议单选框,点击勾选;有文字的控件可以通过上面的OCR文字识别大概定位到位置区域,而对于没有文字的控件这种方式就不太行得通了。 当然你不嫌麻烦,直接截图,然后用PS抠像素点坐标也可以。 当然,有更高效便捷的方法,如果是原生控件堆砌的页面,可以利用Android系统中的system/bin/uiautomator.jar包,把当前屏幕上所有控件信息直接dump到xml中。调用命令如下: #dump出所有控件信息到xml中 adbshell/system/bin/uiautomatordump--compressed/手机存储路径/ui.xml #将文件从手机导出到PC adbpull/手机存储路径/ui.xml本地路径 直接在Python中调用这两行命令: defcurrent_ui_xml(save_dir=None): """ 获取当前页面的布局xml :paramsave_dir:文件保存根目录 :return:布局xml文件的本地路径 """ ui_xml_name="ui_%d.xml"%(int(round(t*1000))) start_cmd('adbshell/system/bin/uiautomatordump--compressed/sdcard/%s'%ui_xml_name) ui_xml_path=os.path.join(os.getcwd()ifsave_dirisNoneelsesave_dir,ui_xml_name) start_cmd('adbpull/sdcard/%s%s'%(ui_xml_name,ui_xml_path)) returnui_xml_path 运行输出结果如下: 打开这个xml文件康康:(此处以掘金APP为例~) 结构很简单,最外层是hierarchy标签,然后是node标签(代表一个控件) 的嵌套,接着要做的事情就是解析这个xml,提取所需的数据了。先挑选可能要用的属性,直接定义一个Node类: classNode: """ XML节点类 """ def__init__(self,index=None,text=None,resource_id=None,class_name=None,package=None,content_desc=None, bounds=None): self.index=index self.text=text self.resource_id=resource_id self.class_name=class_name self.package=package self.content_desc=content_desc self.bounds=bounds self.nodes=[]#存储子节点 defadd_node(self,node): self.nodes.append(node) 使用lxml库解析xml,难点应该就是:遍历子node节点,这里直接递归进行遍历,看不懂的多看几遍就好,还算简单。顺手写个递归打印出所有节点,以便结果更直观,直接给出完整代码: fromlxmlimportetree importre bounds_pattern=re.compile(r"\[(\d+),(\d+)\]\[(\d+),(\d+)\]")#将坐标区域格式化为元组的正则 defanalysis_ui_xml(xml_path): """ 解析ui.xml文件 :paramxml_path:xml文件路径 :return:节点实例 """ root=etree.parse(xml_path,parser=etree.XMLParser(encoding="utf-8")) root_node_element=root.xpath('/hierarchy/node')[0]#定位到根node节点 node=analysis_element(root_node_element) print_node(node)#打印看看效果 returnnode defanalysis_element(element): """ 递归分析结点(转换为node对象) :paramelement: :return: """ ifelementisnotNoneandelement.tag=="node": #解析当前节点 bounds_result=re.search(bounds_pattern,element.attrib['bounds']) node=Node( int(element.attrib['index']), element.attrib['text'], element.attrib['resource-id'], element.attrib['class'], element.attrib['package'], element.attrib['content-desc'], (int(bounds_result[1]),int(bounds_result[2]),int(bounds_result[3]),int(bounds_result[4])) ) #解析子节点,递归调用 child_node_elements=element.xpath('node') iflen(child_node_elements)0: forchild_node_elementinchild_node_elements: node_result=analysis_element(child_node_element) ifnode_result: node.nodes.append(node_result) returnnode defprint_node(node,space_count=0): """ 递归打印结点信息 :paramnode:当前节点 :paramspace_count:前面的空格数,区分不同层级用 :return: """ widget_info="%d-%s-%s-%s-%s-%s-%s"%( node.index,node.text,node.resource_id,node.class_name,node.package,node.content_desc,node.bounds) print(""*(2*space_count),widget_info) forchild_nodeinnode.nodes: print_node(child_node,space_count+1) if__name__=='__main__': #测试解析效果 analysis_ui_xml('ui_1665224943842.xml') 运行输出结果如下: 效果杠杠滴,读者Copy下,根据自己的具体业务按需修改即可~ 6、小结不知不觉又到文尾,本节简要学习了亿点和自动化有关的 "姿势(知识)",包括:OCR文字识别、消息推送、图片处理、获取当前页面的所有控件信息。 看似轻松愉快的一节,但实操性极强,杰哥建议:即使不自己跟着写一遍,也要Copy下代码,运行一下! 啧啧,杰哥将在下一节中用上这些姿势,把我们的打卡jio本打磨得blingbling的,敬请期待~ 阅读原文

上一篇:2022-01-30_正月初二启!国际AI顶会、重要SOTA工作温故知新,机器之心「虎卷 er 行动」春节一起「来一卷 er」! 下一篇:2022-04-24_「转」许锦波团队开发蛋白逆折叠深度学习框架,用更少结构数据训练获得更准确序列预测

TAG标签:

16
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设网站改版域名注册主机空间手机网站建设网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。
项目经理在线

相关阅读 更多>>

猜您喜欢更多>>

我们已经准备好了,你呢?
2022我们与您携手共赢,为您的企业营销保驾护航!

不达标就退款

高性价比建站

免费网站代备案

1对1原创设计服务

7×24小时售后支持

 

全国免费咨询:

13245491521

业务咨询:13245491521 / 13245491521

节假值班:13245491521()

联系地址:

Copyright © 2019-2025      ICP备案:沪ICP备19027192号-6 法律顾问:律师XXX支持

在线
客服

技术在线服务时间:9:00-20:00

在网站开发,您对接的直接是技术员,而非客服传话!

电话
咨询

13245491521
7*24小时客服热线

13245491521
项目经理手机

微信
咨询

加微信获取报价