class AirtestPoco(object): """ Airtest和Poco的方法集合 airtest-api self.api,methods poco-Selector text, textMatches """ def __init__(self, device): """ init初始化 """ # 设置日志目录 set_logdir(AIRTEST_LOG) # 删除旧日志 clear_log(ST.LOG_DIR) # 初始化日志 init_logging() # 等待显示时间 self.timeout = ST.FIND_TIMEOUT # airtest-api self.api = api self.poco = AndroidUiautomationPoco(device, use_airtest_input=True, screenshot_each_action=False) self.UIObj = UIObjectProxy(poco=self.poco) """ AirTest-Method 封装的都是跟图片相关的 """ @classmethod def temp(cls, img_name: str, rgb: bool = True, record_pos: tuple = (0.5, -0.5), resolution: tuple = airDev.screen, target_pos=TargetPos.MID): """CV识别主函数 :param rgb: 灰度识别还是色彩识别 :param record_pos: 图片坐标 :param img_name: 图片名称 :param target_pos: :param resolution: 设备分辨率 :return: """ temp = Template(r"%s" % img_name, target_pos=target_pos, record_pos=record_pos, resolution=resolution, rgb=rgb) return temp @allure.step("元素点击:") def touch(self, v: Template, **kwargs): """ 在设备屏幕上执行触摸操作 :param v: 要触摸的目标,可以是Template实例,也可以是绝对坐标(x,y) :param kwargs: [times 要执行多少次触摸] """ self.api.touch(v, **kwargs) @allure.step("输入文本:") def text(self, text, enter=True, **kwargs): """ 目标设备上的输入文本。文本输入部件必须首先是活动的。 :param text: 输入文本,支持unicode :param enter:输入' enter '键事件后文本输入,默认为真 :param kwargs: :return: :platforms: Android, Windows, iOS """ self.api.text(text, enter=enter, **kwargs) self.api.sleep() @allure.step("双击元素:") def double_click(self, v: Template): """双击""" self.api.double_click(v) @allure.step("滑动元素:") def swipe(self, v1, v2=None, vector=None, **kwargs): """ 在设备屏幕上执行滑动操作。 分配参数有两种方法 swipe(v1, v2=Template(...)) #从v1滑动到v2 swipe(v1, vector=(x, y)) #滑动从v1开始并沿向量移动。 :param:v1 –滑动的起点,可以是Template实例,也可以是绝对坐标(x,y) :param:v2 –滑动的终点,可以是Template实例,也可以是绝对坐标(x,y) :param:vector - 向量 –滑动动作的向量坐标,可以是绝对坐标(x,y)或屏幕百分比,例如(0.5,0.5) :param:**kwargs – 平台特定的kwargs,请参考相应的文档 :exception: 异常 –如果提供的参数不足以执行交换操作,则为一般异常 平台: Android,Windows,iOS :return: 原点位置和目标位置 """ if v1.endswith('.png'): v1 = self.temp(v1) if v2.endswith('.png'): v2 = self.temp(v2) return self.api.swipe(v1, v2, vector, **kwargs) @allure.step("等待元素:") def wait(self, v: Template, **kwargs): """ 等待与设备屏幕上的模板匹配 :param v –等待的目标对象,模板实例 :param 超时 –等待比赛的时间间隔,默认为无,即ST.FIND_TIMEOUT :param interval –尝试找到匹配项的时间间隔(以秒为单位) :param intervalfunc –在每次未成功尝试找到相应匹配项后调用 """ self.api.wait(v, **kwargs) @allure.step("元素存在:") def exists(self, v: Template): """ 检查设备屏幕上是否存在给定目标 :param v –检查对象 :return 如果找不到目标,则为False,否则返回目标的坐标 """ return self.api.exists(v) @allure.step("断言目标存在:") def assert_exists(self, v: Template, msg: str = None): """ 断言目标存在于设备屏幕上 :param v –要检查的目标 :param msg –断言的简短描述,它将记录在报告中 """ self.api.assert_exists(v, msg) @allure.step("断言目标不存在:") def assert_not_exists(self, v: Template, msg: str = None): """ 断言目标在设备屏幕上不存在 :param v –要检查的目标 :param msg –断言的简短描述,它将记录在报告中 """ self.api.assert_not_exists(v, msg) @allure.step("查找所有匹配的:") def find_all(self, v: Template): """ 在设备屏幕上查找目标的所有位置并返回其坐标 :param v:要查找的目标 :return:坐标列表,[(x,y),(x1,y1),…] :平台:Android、Windows、iOS """ return self.api.find_all(v) def capture_screenshot(self, bs64=True): """ 截图保存为base64 :return: """ filename = self.api.snapshot()['screen'] filepath = os.path.join(ST.LOG_DIR, filename) allure.attach.file(filepath, "截图" + filename, allure.attachment_type.JPG) if bs64: with open(filepath, 'rb') as f: imagebase64 = base64.b64encode(f.read()) return imagebase64.decode() """ poco-method """ @allure.step("poco等待一个元素显示:") def poco_wait_any(self, objects: list): """ 等待,直到所有给定的一个 UI 代理在超时之前显示。将定期轮询所有 UI 代理。 :param objects: :return: bool """ try: return self.poco.wait_for_any(objects, timeout=self.timeout) except poco_exception.PocoTargetTimeout: return False @allure.step("poco等待多个元素显示:") def poco_wait_all(self, objects: list): """ 等待,直到所有给定的所有 UI 代理在超时之前显示。将定期轮询所有 UI 代理。 :param objects: :return: """ try: self.poco.wait_for_all(objects, timeout=self.timeout) return True except poco_exception.PocoTargetTimeout: return False def poco_obj(self, **kwargs): """poco实例""" if 'index' in kwargs: index = kwargs.pop('index') ele = self.poco(**kwargs)[index] else: ele = self.poco(**kwargs) ele.wait_for_appearance(timeout=self.timeout) return ele @allure.step("poco点击元素:") def poco_click(self, **kwargs): """ 对由UI代理表示的UI元素执行click操作。如果这个UI代理代表一组 UI元素,单击集合中的第一个元素,并将UI元素的定位点用作默认值 一个。还可以通过提供“focus”参数单击另一个点偏移。 :param kwargs: [text, name] """ log("点击元素:{}".format(kwargs)) self.poco_obj(**kwargs).click() self.poco.sleep_for_polling_interval() @allure.step("poco点击pos:") def poco_click_pos(self, pos): """ 在给定坐标下对目标设备执行单击(触摸,轻击等)操作。坐标(x, y)是一个2-列表或2-元组。 x和y的坐标值必须在0 ~ 1之间,以表示屏幕的百分比。 例如: 坐标[0.5,0.5]表示屏幕的中心,坐标[0,0]表示左上角。 有关坐标系统的详细信息,请参阅CoordinateSystem。 实际案例: 单击分辨率为(1920,1080)的屏幕的(100,100)点: poco.click([100.0 / 1920, 100.0 / 1080]) :param pos: (list(float, float) / tuple(float, float)) – coordinates (x, y) in range of 0 to 1 :return: """ self.poco.click(pos) self.poco.sleep_for_polling_interval() @allure.step("poco获取元素文本:") def poco_text(self, **kwargs): """ 获取 UI 元素的文本属性。如果没有此类属性,则返回"无"。 :param kwargs: :return: txt """ txt = self.poco_obj(**kwargs).get_text() log("获取元素{}文本:{}".format(kwargs, txt)) return txt @allure.step("poco获取元素属性:") def poco_attr(self, name, **kwargs): """ 按给定属性名称检索 UI 元素的属性。如果属性不存在,则返回"无"。 visible:用户是否可见 text:UI 元素的字符串值 type:远程运行时的 UI 元素的类型名称 pos:UI 元素的位置 size:根据屏幕,0+1 范围内的百分比大小 [宽度、高度] name:UI 元素的名称 ...: other sdk 实现的属性 :return: """ return self.poco_obj(**kwargs).attr(name) def poco_freeze(self, **kwargs): """冻结UI树并返回当前的UI结果树""" with self.poco.freeze() as freeze: return freeze(**kwargs) def poco_hierarchy_dict(self): """获取当前结构树的字典""" frozen_poco = self.poco.freeze() hierarchy_dict = frozen_poco.agent.hierarchy.dump() return hierarchy_dict @allure.step("poco元素存在:") def poco_exists(self, **kwargs): """ 测试UI元素是否在层次结构中 :param kwargs: [text,name] """ result = self.poco_freeze(**kwargs).exists() log("元素{}验证结果: {}".format(kwargs, result)) return result @allure.step("poco滚动屏幕:") def poco_scroll(self, direction: str = 'vertical', percent: float = 0.5, duration: float = 2.0): """ 从整个屏幕的下部滚动到上部 默认的 direction='vertical', percent=0.6, duration=2.0 :param direction: 方向:滚动方向。垂直(vertical)或“水平”(horizontal) :param duration: 百分比:根据 :param percent: 持续时间:执行操作的时间间隔 """ self.poco.scroll(direction=direction, percent=percent, duration=duration) @allure.step("poco滑动:") def poco_swipe(self, p1, p2=None, direction=None, duration: float = 2.0): """ 在目标设备上通过起点和终点或方向向量指定的点到点执行滑动操作。必须至少提供端点或方向之一。 点的坐标(x,y)定义与click事件的定义相同。方向矢量(x,y)的分量也以0到1的屏幕范围表示。 请参阅CoordinateSystem以获取有关坐标系的更多详细信息。 实际案例 以下示例显示了如何在分辨率为1920x1080的屏幕上执行从(100,100)到(100,200)的滑动动作: poco.swipe([100.0 / 1920, 100.0 / 1080], [100.0 / 1920, 200.0 / 1080]) 或由特定方向而非终点给定: poco.swipe([100.0 / 1920, 100.0 / 1080], direction=[0, 100.0 / 1080]) :param p1: 起点 :param p2: 终点 :param direction: 滑动方向 :param duration: 持续时间(float)–执行滑动操作的时间间隔 """ self.poco.swipe(p1=p1, p2=p2, direction=direction, duration=duration) """ aircv-method """ @allure.step("元素截图:") def crop_image(self, rect: list): """局部截图 :param rect = [x_min, y_min, x_max ,y_max]. :return filepath 图片路径 """ # 局部截图 img = G.DEVICE.snapshot() crop_screen = crop_image(img, rect) # 生成截图路径 filename = "%(time)d.jpg" % {'time': timestamp() * 1000} filepath = os.path.join(ST.LOG_DIR, filename) # 保存局部截图到logs文件夹中 pil_image = cv2_2_pil(crop_screen) pil_image.save(filepath, quality=99, optimize=True) return filepath @allure.step("图片文字识别:") def tesseract_string(self, filepath, lang='eng+chi_sim', config='--psm 6'): """识别图片文字""" # 读取图片 im = Image.open(filepath) # 识别图片文字 # 进行置灰处理 im = im.convert('L') # 这个是二值化阈值 threshold = 150 table = [] for i in range(256): if i < threshold: table.append(0) else: table.append(1) # 通过表格转换成二进制图片,1的作用是白色,0就是黑色 im = im.point(table, "1") result = pytesseract.image_to_string(im, lang=lang, config=config) # 返回并清除结果的空格 return result.replace(" ", "")
class XHSFollow(object): def __init__(self): connect_device( "android://127.0.0.1:5037/DRGGAM0850527807?cap_method=MINICAP_STREAM&&ori_method=MINICAPORI&&touch_method=MINITOUCH") self.poco = AndroidUiautomationPoco(use_airtest_input=True, screenshot_each_action=False) self.screenWidth, self.screenHeight = self.poco.get_screen_size() self.follow() def follow(self): start_app("com.xingin.xhs") temp_name_list = [] while True: sleep(1) state = self.poco.freeze() fans_list = state(type="androidx.recyclerview.widget.RecyclerView") sleep(1) user_name_list = [] for item in fans_list.children(): sleep(1) item.click() sleep(1) post_frame = self.poco(type="androidx.recyclerview.widget.RecyclerView") sleep(1) if post_frame.exists(): if post_frame[-1].children().exists(): post = post_frame[-1].children()[0] if post.exists(): post_name = post.get_name() if re.match(r"^com.xingin.xhs:id.*", post_name): sleep(1) like = post.offspring("com.xingin.xhs:id/afi") if like.exists(): like.click() print("已经点赞") else: print("用户没有发过帖子") message_btn = self.poco("com.xingin.xhs:id/cav") username = "" user_name_btn = self.poco("com.xingin.xhs:id/bhz") if user_name_btn.exists(): username = user_name_btn.get_text() user_name_list.append(username) if message_btn.exists(): if message_btn.get_text()=="关注": message_btn.click() print("已经关注 ", username) else: print("已经关注过 %s 了,跳过" % username) sleep(1) if self.poco("com.xingin.xhs:id/bha").exists(): self.poco("com.xingin.xhs:id/bha").click() print("下一个") date = time.strftime("%Y%m%d", time.localtime()) total = collection.count_documents({"record_date":date}) if user_name_list == temp_name_list: print("-----------------\n到头了\n-----------------") break elif total > 500: print("今天任务完成了") break else: temp_name_list = user_name_list swipe((self.screenWidth * 0.5, self.screenHeight * 0.8), vector=[0, -1], duration=1) sleep(3)
class Follow(object): def __init__(self): connect_device( "android://127.0.0.1:5037/4f74b1cc?cap_method=MINICAP_STREAM&&ori_method=MINICAPORI&&touch_method=MINITOUCH" ) self.poco = AndroidUiautomationPoco(use_airtest_input=True, screenshot_each_action=False) self.s_width, self.s_height = self.poco.get_screen_size() self.fans_json = "" self.validate_config() def judge_lite(self): sleep(3) return # sleep(3) # if exists(Template(r"tpl1616916148778.png", threshold=0.9000000000000001, rgb=True, record_pos=(-0.193, -0.033), resolution=(1080, 2340))): # touch(Template(r"tpl1616916171046.png", threshold=0.9000000000000001, rgb=True, record_pos=(0.153, 0.212), resolution=(1080, 2340))) # sleep(1) def fetch_fans(self): frzz = self.poco.freeze() frzz("android.widget.LinearLayout").offspring( "com.android.chrome:id/compositor_view_holder").offspring( "app")[0].child("android.view.View")[0].child( "android.view.View")[7].click() self.only_scrol() keyevent("BACK") sleep(3) def fetch_follows(self): frzz = self.poco.freeze() button = frzz("android.widget.LinearLayout").offspring( "com.android.chrome:id/compositor_view_holder").offspring("app")[ 0].child("android.view.View")[0].child("android.view.View")[6] if button: button.click() else: swipe((self.s_width * 0.5, self.s_height * 0.5), vector=[0, -0.4]) sleep(5) frzz = self.poco.freeze() button = frzz("android.widget.LinearLayout").offspring( "com.android.chrome:id/compositor_view_holder").offspring( "app")[0].child("android.view.View")[0].child( "android.view.View")[6] self.only_scrol() keyevent("BACK") sleep(3) self.fetch_fans() def only_scrol(self): path = Path("./end_user.png") if path.exists(): path.unlink() while True: sleep(2) scr = self.scrol_crop() is_same = self.compaire_image(scr) if is_same: # 二次滚动防止提前退出 second_pic = self.scrol_crop() second = self.compaire_image(second_pic) if second: print("确实已经到达底部,下一轮开始了") break else: self.save_caputure(second_pic) else: self.save_caputure(scr) def save_caputure(self, pic): end_user = cv2_2_pil(pic) end_user.save(r"./end_user.png") print("继续往下滑") def scrol_crop(self): # self.judge_lite() swipe((self.s_width * 0.5, self.s_height * 0.9), vector=[0, -1]) sleep(5) scr = crop_image(G.DEVICE.snapshot(), (0, self.s_height / 2, 450, self.s_height / 2 + 250)) sleep(1) return scr def compaire_image(self, scr): path = Path("./end_user.png") if path.exists(): tempalte = Template(r"./end_user.png", threshold=0.90) pos = tempalte.match_in(scr) if pos: print("貌似到达底部了", pos) return True else: return False else: return False def start(self): owner = list(source.find({}, {"user.id": 1, "user.screen_name": 1})) current = [ item for item in owner if item["user"]["id"] == self.fans_json["userid"] ] index = owner.index(current[0]) user_list = [item["user"] for item in owner][index:] for user in user_list: start_app("com.android.chrome") sleep(5) swipe((self.s_width * 0.5, self.s_height * 0.5), vector=[0, -0.2]) userid = user["id"] username = user["screen_name"] url = "https://m.weibo.cn/profile/" + str(userid) swipe((self.s_width * 0.5, self.s_height * 0.5), vector=[0, 0.2]) sleep(5) self.poco("com.android.chrome:id/url_bar").click() self.poco("com.android.chrome:id/url_bar").set_text("") sleep(1) text(url, search=True) sleep(5) # self.judge_lite() print("当前是 %s 的粉丝,id是 %s \n" % (username, userid)) self.write_data(userid) self.fetch_fans() stop_app("com.android.chrome") def validate_config(self): path = Path("fans.json") if not path.is_file(): sys.exit(u'当前路径:%s 不存在fans.json' % path.resolve()) with open("fans.json") as file: try: self.fans_json = json.loads(file.read()) print(self.fans_json) except ValueError: sys.exit(u'fans.json 格式不正确') def write_data(self, id): self.fans_json["userid"] = id with open("fans.json", "w") as f: json.dump(self.fans_json, f)
class XHSUnfollow(object): def __init__(self): connect_device( "android://127.0.0.1:5037/DRGGAM0850527807?cap_method=MINICAP_STREAM&&ori_method=MINICAPORI&&touch_method=MINITOUCH") self.poco = AndroidUiautomationPoco(use_airtest_input=True, screenshot_each_action=False) self.screenWidth, self.screenHeight = self.poco.get_screen_size() self.follow() def sleep_radom(self): sleep(random.randint(1,3)) def follow(self): start_app("com.xingin.xhs") total = 500 while total > 1: self.sleep_radom() state = self.poco.freeze() fans_list = state("com.xingin.xhs:id/cx1") self.sleep_radom() for item in fans_list.children(): self.sleep_radom() follow_state = item.offspring("com.xingin.xhs:id/ex1") if follow_state.exists(): state_str = follow_state.get_text() if state_str == "互相关注": continue item.click() self.sleep_radom() post_frame = self.poco(type="androidx.recyclerview.widget.RecyclerView") self.sleep_radom() if post_frame.exists(): if post_frame[-1].children().exists(): post = post_frame[-1].children()[-1] if post.exists(): post_name = post.get_name() if not re.match(r"^com.xingin.xhs:id.*", post_name): message_btn = self.poco("com.xingin.xhs:id/f21") username = "" user_name_btn = self.poco("com.xingin.xhs:id/de8") if user_name_btn.exists(): username = user_name_btn.get_text() if message_btn.exists(): if message_btn.get_text() == "发消息": unfollow_btn = self.poco("com.xingin.xhs:id/f22") if unfollow_btn.exists(): self.sleep_radom() unfollow_btn.click() self.sleep_radom() self.poco("android:id/button1").click() self.sleep_radom() print("已经取关 ", username) total = total - 1 else: print("没有操作 %s 了,跳过" % username) else: print("用户发过帖子") if self.poco("com.xingin.xhs:id/ddi").exists(): self.poco("com.xingin.xhs:id/ddi").click() print("下一个") swipe((self.screenWidth * 0.5, self.screenHeight * 0.2), vector=[0, 1], duration=random.randint(1,3)) self.sleep_radom()
# -*- coding: utf-8 -*- """ Description: Author:henly Date:2021/3/2 """ from airtest.core.api import * auto_setup(__file__) from poco.drivers.android.uiautomation import AndroidUiautomationPoco poco = AndroidUiautomationPoco(use_airtest_input=True, screenshot_each_action=False) s_width, s_height = poco.get_screen_size() poco("豆瓣").click() sleep(5) freeze_po = poco.freeze() group_list = freeze_po("com.douban.frodo:id/list_view").children() for item in group_list: group_name = item.child("com.douban.frodo:id/tvTitle").get_text() print("group name is :", group_name) item.click() sleep(2) poco("com.douban.frodo:id/count").click() pre_name_list = [] while True: sleep(5) freeze_user = poco.freeze() user_list = freeze_user("com.douban.frodo:id/name") user_name_list = [] for user in user_list: