Bootstrap

uiautomator2实现自动化商品发布实践

import os
import re
import time
from appium import webdriver
import uiautomator2 as u2
import logging
import subprocess
from datetime import datetime
import queue
import threading
import random
now = datetime.now()
# logging.basicConfig(level = logging.INFO,format = '%(asctime)s - %(name)s - %(funcName)s - %(message)s')
# logger = logging.getLogger(__name__)
# logger.info(d)
log_path = r'./log'
img_path = r'./img_screen'
titles_dir = './device_title'
os.makedirs(titles_dir,exist_ok=True)
os.makedirs(log_path,exist_ok=True)
os.makedirs(img_path,exist_ok=True)

def get_current_time():
    return str(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())+' : ')
def append_file(d_sn,content):
    with open(os.path.join(log_path,f'{d_sn}.txt'),"a",encoding='utf-8') as f:
        f.write(f'{get_current_time()} {content}')
        f.write('\n')
def append_title_file(file,content):
    with open(file,"a",encoding='utf-8') as f:
        f.write(content)
        f.write('\n')
def get_devices():
    s_d = ''
    devices_info = os.popen('adb devices').readlines()
    for i in range(len(devices_info)):
        s_d+=devices_info[i]
    device_sn = re.findall('\n(.+?)\t',s_d,re.S)
    return device_sn
devices = get_devices()
def clean_screen_shots(driver,flag=True):
    screen_files = run_adb(f'adb -s {driver.serial} shell ls /sdcard/DCIM/Screenshots').split('\r\n')
    print('screen_files:',screen_files)
    for f in screen_files: 
        if not len(f) > 0 :continue
        run_adb(f'adb -s {driver.serial} shell rm -f /sdcard/DCIM/Screenshots/{f}')
        # run_adb(f'adb -s {device} shell am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_FILE -d file:///sdcard/DCIM/Screenshots/*.png')
        time.sleep(1)
        run_adb(f'adb -s {driver.serial} shell am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_FILE -d file:///sdcard/DCIM/Screenshots/{f}')
        time.sleep(1)
    if flag:
        driver.app_stop('com.smile.gifmaker') 
# 判断屏幕是否锁屏,如果屏幕锁定就解锁
def get_screen_status(device):
    status = device.info['screenOn'] 
    if not status:
        device.screen_on()
        device.swipe_ext('up',scale=0.9)
    return status
def run_adb(cmd):
    process = subprocess.Popen(cmd.split(),stdout=subprocess.PIPE)
    output ,error = process.communicate()
    print(output,error)
    return output.decode('utf-8')
def click_(driver,element='',text_='',resourceId_='', content='',error_path='',pkg='com.smile.gifmaker',sleepTime=3):
    try:
        if str(element).startswith("com"):  # 若开头是com则使用ID定位
            driver(resourceId=element).click()  # 点击定位元素
        elif re.findall("//", str(element)):  # 若//开头则使用正则表达式匹配后用xpath定位
            driver.xpath(element).click()  # 点击定位元素
        elif text_:  # 若以上两种情况都不是,则使用描述定位
            driver(text=text_).click()  # 点击定位元素
        elif resourceId_:
            driver(resourceId=resourceId_).click()
        else:
           print('***********无操作************') 
        if content:
           append_file(driver.serial,content)
        time.sleep(sleepTime)
        driver.implicitly_wait(15)
    except Exception as e:
        append_file(driver.serial,'异常中断本次商品发布' + content)
        if error_path:
            driver.screenshot(f'{error_path}/error_{content}.png')
        time.sleep(sleepTime)
        clean_screen_shots(driver,flag=True)
        driver.app_stop(pkg) 
# 点击详情图片,然后根据图片张数进行截图,并保存至/DCIM
def img_info_screen_shot(driver,s_title,current_index):
    # 获取大图上面的图片index
    img_info = driver(textContains='/').get_text().split('/')
    left_step = int(img_info[1]) - int(img_info[0])
    print(img_info,left_step)
    # 每次截图前先讲Screenshots下的图片清空
    if int(img_info[1]) ==1:
        pass
    else:
        for j in range(int(img_info[0])):#将图片滑到第一张
            driver.swipe_ext('right',scale=0.9)
    time.sleep(2)
    current_time = time.strftime('%Y_%m_%d_%H_%M_%S', time.localtime())
    img_path_tmp =f'{img_path}/{driver.serial}/{current_time}_{current_index}'
    title_file_tmp = f'{img_path}/{driver.serial}/{current_time}_{current_index}/{driver.serial}_title_{current_index}.txt'
    
    os.makedirs(img_path_tmp,exist_ok=True)
    append_title_file(title_file_tmp,s_title)
    img_counts = driver(textContains='/').get_text().split('/')[-1] # 获取商品详情总共图片张数
    append_file(driver.serial,f'Step 10 : 当前商品张数:{img_counts}')
    for i in range(int(img_info[1])):     
        tt = time.time()
        timestamp = int(round(tt * 1000))
        time.sleep(1)
        current_element = driver(textContains='/').get_text().split('/')
        print('timestamp',current_element)
        driver.screenshot(f'{img_path_tmp}/{timestamp}_{i+1}.png')
        # append_file(device,f'{get_current_time()}当前商品截图title:{current_element}')
        time.sleep(1)
        append_file(driver.serial,f'Step 11 : 当前商品截图:{timestamp}_{i+1}.png')
        time.sleep(1)
        run_adb(f'adb -s {driver.serial} push {img_path_tmp}/{timestamp}_{i+1}.png /sdcard/DCIM/Screenshots')
        time.sleep(1)
        driver.swipe_ext('left',scale=0.9)
        time.sleep(2)
        # 截图后发送广播更新相册
        run_adb(f'adb -s {driver.serial} shell am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_FILE -d file:///sdcard/DCIM/Screenshots/{timestamp}_{i+1}.png')
    # d.xpath('//android.app.Dialog/android.view.View[1]/android.view.View[1]/android.view.View[1]/android.view.View[1]/android.view.View[1]/android.view.View[4]').click()
    driver.press("back")
    # 刷新商品列表至最顶部
    # driver.xpath('//*[@text="在售 "]').click()
    click_(driver,element='//*[@text="在售 "]',content='Step 12 : 双击在售',error_path=img_path_tmp)
    click_(driver,element='//*[@text="在售 "]',content='Step 12 : 双击在售',error_path=img_path_tmp)
    # append_file(device,f'双击在售',12)
    time.sleep(8)
    driver.click(0.889, 0.377)
    time.sleep(2)
    # driver(text='拍短视频').click()
    if not driver(text='拍短视频').exists():
        driver.click(0.163, 0.056)#点空白处,隐藏弹框
        time.sleep(1)
        driver.click(0.856, 0.532)# 点击第二个item
        time.sleep(1)
        driver.click(0.494, 0.63)# 点击拍短视频按钮
        append_file(driver.serial,'Step 13-1 : 拍短视频')
        # click_(driver,text='拍短视频',content='Step 13-1 : 拍短视频')
    else:
        click_(driver,text_='拍短视频',content='Step 13 : 拍短视频',error_path=img_path_tmp)
    # append_file(device,f'拍短视频',13)
    time.sleep(2)
    # driver(text='模板').click()
    click_(driver,text_='模板',content='Step 14 : 选择模板',error_path=img_path_tmp)
    # append_file(device,f'选择模板',14)
    time.sleep(2)
    # 收藏的模板
    # driver(text='收藏').click()
    click_(driver,text_='收藏',content='Step 15 : 选择收藏模板',error_path=img_path_tmp)
    # append_file(device,f'选择收藏模板',15)
    # d.xpath('//*[@resource-id="com.smile.gifmaker:id/ks_root_view"]/android.widget.HorizontalScrollView[1]/android.widget.LinearLayout[1]/android.widget.FrameLayout[1]').click(0)
    time.sleep(1)
    r = random.randint(1, 10)
    # 随机选择模板
    for s_r in range(r):
        driver.swipe_ext('up',scale=0.5)
    # driver(text='使用').click()
    click_(driver,text_='使用',content=f'Step 16 : 选择收藏的模板,drag {r} 次',error_path=img_path_tmp)
    # append_file(device,f'选择收藏的模板,drag {r} 次',16)
    # driver.xpath(f'//*[@resource-id="com.smile.gifmaker:id/recycler_view"]/android.widget.RelativeLayout[{r}]/android.widget.RelativeLayout[1]').click()# 第一个收藏的模板
    # d.swipe_ext('down',scale=0.9)
    time.sleep(3)
    driver.implicitly_wait(15)
    # append_file(device,'Step 17 : 开始制作 - 开始选择图片')
    # driver(text='开始制作').click()  
    click_(driver,text_='开始制作',content='Step 17 : 开始制作 - 开始选择图片',error_path=img_path_tmp)
    print('需要截图张数:',img_counts,s_title)
    # 根据截图数量选择图片
    # //*[@resource-id="com.smile.gifmaker:id/album_view_list"]/android.widget.FrameLayout[6]
    for c_i in  range(int(img_counts)):
        # driver.xpath(f'//*[@resource-id="com.smile.gifmaker:id/album_view_list"]/android.widget.FrameLayout[{c_i+1}]').click()
        click_(driver,element=f'//*[@resource-id="com.smile.gifmaker:id/album_view_list"]/android.widget.FrameLayout[{c_i+1}]',content=f'Step 18 : 选择第 {c_i+1} 张图片',error_path=img_path_tmp)
        # append_file(device,f'Step 18 : 选择第 {c_i+1} 张图片')
        time.sleep(1)
    # 选好图片后确认
    time.sleep(2)
    # append_file(device,'选好了',19)
    # driver(resourceId="com.smile.gifmaker:id/right_container").click()# 选好了
    click_(driver,resourceId_="com.smile.gifmaker:id/right_container",content='Step 18 : 选好了',sleepTime=15,error_path=img_path_tmp)
    # time.sleep(10)
    if not driver(text="下一步").exists():
        append_file(driver.serial,'Step 19 : 下一步 控件没有找到,请检查资源是否违规')
        return 
    # append_file(device,'Step 18 : 下一步',20)
    # driver(text='下一步').click()
    click_(driver,text_='下一步',content='Step 20 : 下一步',error_path=img_path_tmp)
    time.sleep(1)
    driver.xpath('//*[@resource-id="com.smile.gifmaker:id/editor_container"]').set_text(f'{s_title}')
    time.sleep(5)
    driver.press("back")
    time.sleep(2)
    # append_file(device,'Step 21 : 发布作品')
    # driver.xpath('//*[@resource-id="com.smile.gifmaker:id/publish_button"]').click()
    click_(driver,element='//*[@resource-id="com.smile.gifmaker:id/publish_button"]',content='发布作品',sleepTime=5,error_path=img_path_tmp)
    sn_title_file = f'{titles_dir}/{driver.serial}_title.txt'
    append_title_file(sn_title_file,s_title)
queue = queue.Queue()
lock = threading.Lock()
# 将每个设备添加至线程池
def add_device_2_queue():
    for d_sn in devices:
        queue.put(d_sn)
# 判断该商品是否发布过
def get_device_title(d_sn):
    sn_title_file = f'{titles_dir}/{d_sn}_title.txt'
    if not os.path.exists(sn_title_file):
        return []
    with open(sn_title_file, 'r', encoding='utf-8') as f:
        data = [d.replace('\n','') for d in f.readlines()]
        print(data)
        return data
def exc_consum(q):
    while not q.empty():
        current_device = q.get()
        # try:
        for c_i in range(50):
            print(current_device,c_i)
            d = u2.connect(current_device)
            s = d.info['screenOn']
            clean_screen_shots(d,flag=False)
            append_file(current_device,f'Step 0 : 当前连接设备:{current_device} {c_i}')
            print('当前屏幕状态:',s)
            append_file(current_device,f'Step 1 : 当前屏幕状态:{s}')
            if not s:
                d.screen_on()
                d.swipe_ext('up',scale=0.9)
            d.app_start('com.smile.gifmaker')
            append_file(current_device,f'Step 2 : 启动快手应用')
            # d.implicitly_wait(15)
            time.sleep(10) 
            try:
                d(resourceId="android:id/text1", text="我").click()
                append_file(current_device,f'Step 3 : 进入我的')

            except Exception as e:
                print('e1',e)
                append_file(current_device,f'Step 3-1 : Exception:点击我\n{e}')
                # d(description="我").click()
                
                continue

            try:
                d(text='我').click()
                d(text='我').click()
                time.sleep(3)
                zuopin = d.xpath('//*[@resource-id="com.smile.gifmaker:id/tab_text"]').all()
                # print(type(zuopin))
                zuopins = [x.text for x in zuopin]
                append_file(current_device,f'Step 4 : 作品详情:{zuopins}')
                print('zuopin:',zuopins)
                d.implicitly_wait(10)
                # 点击进入快手小店
                # d(text='快手小店').click()
                click_(d,element='//*[@resource-id="com.smile.gifmaker:id/im_group_list"]/android.view.ViewGroup[1]')
            except Exception as e2:
                append_file(current_device,f'Step 5-1 : Exception : 进入快手小店')
                print('exception2:',e2)
                continue
            append_file(current_device,f'Step 5 : 进入快手小店')
            time.sleep(5)
            # 点击货架
            try:
                # d.xpath('//*[@resource-id="com.smile.gifmaker:id/root_view"]/android.widget.LinearLayout[1]/android.widget.FrameLayout[1]/android.widget.FrameLayout[1]/android.view.ViewGroup[1]/android.view.ViewGroup[1]/android.view.ViewGroup[3]').click()
                click_(d,element='//*[@resource-id="com.smile.gifmaker:id/root_view"]/android.widget.LinearLayout[1]/android.widget.FrameLayout[1]/android.widget.FrameLayout[1]/android.view.ViewGroup[1]/android.view.ViewGroup[1]/android.view.ViewGroup[3]',content='Step 6 : 进入货架')
                # append_file(current_device,f'Step 6 : 进入货架')
            except Exception as e3:
                print('exception3:',e3)
                append_file(current_device,f'Step 6-1 : Exception:点击货架\n{e3}')
                continue
            time.sleep(5) 

            # 获取商品布局宽高尺寸
            info = d.xpath('//*[@resource-id="com.smile.gifmaker:id/root_view"]/android.widget.LinearLayout[1]/android.widget.LinearLayout[1]').info
            wedith,high = info['bounds']['right'],info['bounds']['bottom']
            
            # 商品列表向上滑动一个item,点击第一个item进入
            for hj in range(c_i + 2):
                d.swipe_ext('up',scale=0.5)
            append_file(current_device,f'Step 7 : 进入列表第一个item')
            d.click(0.074, 0.281) 
            time.sleep(5) 
            # 获取商品详情title
            # title = d(className="android.widget.TextView").get_text()
            for elem in d.xpath('//*[@resource-id="root"]/android.view.View[1]/android.view.View[1]/android.view.View[1]/android.view.View[1]/android.view.View[1]/android.view.View[1]/android.view.View[1]/android.view.View[1]/android.view.View[3]/android.view.View[1]').all():
                children = elem.elem.getchildren()
                if children:
                    title = children[0].get('text')
                    # print(children[0].get('text'),type(children))
            print('商品标题:',title)
            titles_tmp = get_device_title(current_device)
            if not title:continue
            if title in titles_tmp:
                append_file(current_device,f'Step 8-1 : 该商品已经发布过:{title}')
                clean_screen_shots(d,flag=True)
                continue
            append_file(current_device,f'Step 8 : 获取商品标题:{title}')
            
            time.sleep(1) 
            # 点击商品大图
            info = d.xpath('//*[@resource-id="root"]/android.view.View[1]/android.view.View[1]/android.view.View[1]/android.view.View[1]/android.view.View[1]/android.view.View[1]/android.view.View[1]/android.view.View[1]/android.view.View[2]').click()
            append_file(current_device,f'Step 9 : 获取商品大图:{title}')
            time.sleep(1)
            # 获取当前图片显示的张数,截图
            img_info_screen_shot(d,title,c_i)
            time.sleep(5)
            # d.app_stop('com.smile.gifmaker') 
            lock.acquire()
            lock.release()
            clean_screen_shots(d,flag=True)
        # except Exception as ee:
        #     print('Exception:',ee)
        #     append_file(current_device,'异常状态,请检查设备连接状态')
        #     clean_screen_shots(d,flag=True)
add_device_2_queue()
thrds = []
for _ in range(len(devices)):
    t = threading.Thread(target=exc_consum,args=(queue,))
    t.start()
    thrds.append(t)
for t in thrds:
    t.join()

;