资讯详情

资讯详情

Python+Appium实现抖音自动刷视频:环境配置到脚本落地

做App自动化测试这几年经常有人问我能不能用Python和Appium直接操作抖音把上滑刷视频、记录视频信息、清理弹窗这些重复事交给脚本。我的答案是能但整个过程会比操作普通App更考验细节。抖音页面是一个无限滑动的动态信息流控件树里大量节点没有文本、资源ID重复加上各种弹窗和权限申请脚本想一次跑通并不容易。这篇文章是我踩了一圈坑之后整理出来的完整思路和可落地代码从环境配置讲到元素定位再讲到完整的自动刷视频脚本适合正在学App自动化、或者想用Python处理日常重复App操作的朋友。需要先说明的是本文所有内容仅用于自动化测试技术学习、个人效率工具等合规场景不建议也不鼓励拿去做刷量、刷评论、批量关注这类违规操作。1. 项目定位与整体思路拆解1.1 为什么选择Appium而不是其他方案市面上能驱动安卓App做自动化的方案有好几种最常见的是Appium、Airtest、uiautomator2往下还有纯ADB命令。选择Appium做抖音自动化主要是因为它生态成熟背后是WebDriver标准几乎所有元素交互都有一套通用APIPython、Java、JavaScript都能写。拿Airtest来说它偏向图像识别不需要控件信息也能操作看起来很省事。但实际跑抖音这种信息流会发现一旦视频封面颜色相近图像识别很容易发生误判而且同一张截图在不同分辨率设备上坐标完全不同脚本换一台手机就要重新调。uiautomator2虽然轻量只支持Android但对于需要处理复杂等待、多弹窗、输入中文这类场景它的封装没有Appium那么完整。纯ADB命令更是只能在坐标层面点击滑动没办法根据页面内容做判断遇到弹窗就只能盲点不实用。Appium的优势在于它把控件的查找、等待、点击、滑动都抽象成了统一接口既能拿到页面XML里的真实节点也能通过WebDriverWait做条件等待。对于抖音这种动态加载页面这一点非常关键我们可以等待某个控件出现再操作而不是盲目sleep若干秒。而且Appium同时支持iOS和Android今天学会控制安卓App以后换到iOS端思路基本可以复用。1.2 抖音自动化要解决的三个核心问题抖音和其他普通App相比有几个非常明显的自动化难点做之前必须想清楚。第一个问题是控件树信息稀薄。很多图标的resource-id是同一个text属性是空的只看单个节点根本分辨不出来它到底是点赞、评论还是分享。这种情况占很大比例所以定位策略不能只靠ID。第二个问题是页面无限下滑。你滑一下新的视频会在几百毫秒到几秒内加载出来加载期间如果脚本急着抓取页面信息很可能抓到上一屏的残留内容。怎么判断当前屏幕是否已经换成新视频是整个脚本能否顺畅跑起来的关键。第三个问题是弹窗和权限干扰。首次启动App可能弹隐私协议、通知授权中间还会时不时出现青少年模式提醒、活动弹窗、运营广告。这些弹窗如果不处理元素定位就会被遮住点击也会点到错误位置。把这三个问题作为核心设计点后面写的每一步代码都依赖这个思路能等就等、能判断就判断、弹窗统一拦截。脚本结构清晰后期维护也就不会乱。2. 环境搭建与关键配置2.1 工具清单与版本选择正式开始写脚本之前先把环境准备好。我推荐的一套组合是Python 3.8以上建议直接用3.10或3.11省得处理旧版本库冲突。Node.js 12以上因为Appium Server本身是Node包。Appium Server 2.x安装命令是npm install -g appium。Appium Python客户端安装命令是pip install Appium-Python-Client。Android SDK Platform-Tools里面包含adb命令。一台闲置Android手机Android 7及以上版本都可以系统版本太老可能对UiAutomator2支持不好。Appium 2.x和1.x最大的区别是驱动机制变了。1.x还内置了各种端驱动2.x需要单独安装Android驱动appium driver install uiautomator2安装完可以用appium driver list确认驱动状态。如果不装这个驱动连接Android设备时会报“Could not find a driver for automationName UiAutomator2”之类的错误。另外要注意Appium Server和Python客户端的大版本尽量匹配。如果Server用的是2.xAppium-Python-Client最好也用2.x以上避免老API写法失效。比如旧版常用的appium.webdriver.common.appiumby.AppiumBy在2.x依然保留但一些TouchAction类已经被标记废弃新代码里我会尽量避开。2.2 真机连接与ADB调试的三步配置连接真机是三步固定流程每一步都不能省。第一步开启开发者模式。在Android手机的“设置-关于手机”里连续点击“版本号”七次直到系统提示已经进入开发者模式。第二步打开USB调试。回到设置找到“开发者选项”打开“USB调试”。如果手机有“仅充电模式下允许ADB调试”之类的选项也顺手打开。第三步用数据线连接电脑。手机弹窗出现“允许USB调试吗”时勾选“始终允许”然后确认。接着在电脑终端执行adb devices看到类似ABCDEF12345 device的输出就说明连接正常。如果显示unauthorized说明没有点允许弹窗如果显示offline一般重插数据线或者重启adb服务就能解决。拿到设备连接状态之后还要确认抖音对应的包名和入口Activity。因为不同渠道包的包名可能不一样我不建议直接抄网上的固定值。最可靠的办法是自己在设备上查adb shell pm list packages -3这个命令会列出第三方应用包名找到你的抖音应用。如果想确认当前正在运行的Activity可以先在手机上手动打开抖音然后执行adb shell dumpsys window | grep mCurrentFocus输出里会有一行类似mCurrentFocusWindow{... 你的包名/你的入口Activity}的内容把对应的包名和Activity记下来后面写到desired_caps里用。2.3 第一个最小化启动脚本环境就绪之后先用一个最简单的脚本验证能不能启动抖音页面。这一步排错最方便不要一上来就堆几百行逻辑。from appium import webdriver from appium.webdriver.common.appiumby import AppiumBy desired_caps { platformName: Android, automationName: UiAutomator2, deviceName: 你的设备名, appPackage: 你的抖音包名, appActivity: 你查到的入口Activity, noReset: True, autoGrantPermissions: True, unicodeKeyboard: True, resetKeyboard: True, newCommandTimeout: 120, } driver webdriver.Remote(http://127.0.0.1:4723/wd/hub, desired_caps) print(driver.current_package) print(driver.page_source[:500]) driver.quit()先解释几个重点配置。noReset设为True意思是每次启动不清除App数据这样权限授权、登录状态都能保留。autoGrantPermissions设为True可以自动同意弹出来的系统权限申请比如存储权限、麦克风权限不用每次手动点。unicodeKeyboard和resetKeyboard是专门给中文输入准备的后面如果需要输入评论内容这两个配置能帮你省掉很多麻烦。执行脚本前先在终端启动Appium Serverappium确认Server监听在4723端口再运行Python脚本。如果driver.current_package能打印出你的抖音包名说明启动成功。如果报错说找不到Activity就回到上一步用dumpsys window重新确认入口Activity。3. 抖音页面元素定位与输入操作3.1 用UIAutomator2拿到控件树编写定位代码前一定要先看看抖音当前页面的控件树长什么样。不要凭感觉写XPath那样大概率找不到。在命令行执行adb shell uiautomator dump /sdcard/ui.xml adb pull /sdcard/ui.xml ./ui.xml然后用文本编辑器打开ui.xml可以看到完整的控件层级。每个节点都有text、resource-id、class、content-desc、bounds这些属性。例如android.widget.TextView text首页 resource-idcom.xxx:id/xxx bounds[0,1800][270,1980]/观察几次之后你会发现抖音信息流里大量节点的resource-id是一样的而且text往往是空字符串。真正能用的信息可能只有content-desc或者某些文字节点的text。所以定位策略不能只依赖ID。如果觉得命令行dump不够直观也可以打开Appium Server自带的Inspector输入设备信息和desired_caps它会把你手机当前界面的控件树渲染出来支持点击查看节点属性还能直接生成XPath对新手非常友好。3.2 三种定位优先级我写抖音自动化时定位控件的优先级一般是这样排的。第一优先是text或content-desc。比如底部导航的“首页”“朋友”“消息”“我”这些文字节点直接用AppiumBy.XPATH配合文本匹配就能稳定拿到。driver.find_element(AppiumBy.XPATH, //android.widget.TextView[text首页])第二优先是resource-id。如果节点ID在页面里是唯一的用它最省事。但抖音经常出现几十个相同ID的节点这时单独用ID就会落到一个无法预测的位置需要配合index或者父级节点缩小范围。第三优先是XPath层级关系。比如某个按钮没有文本但它所在的父容器在页面的固定位置可以通过层级路径定位。这里要注意XPath里不要写死过多的绝对路径因为App改版或者视频卡片结构变化都会导致脚本挂掉。尽量用contains这类模糊匹配driver.find_element(AppiumBy.XPATH, //*[contains(content-desc, 点赞)])如果页面刷新比较慢还需要给定位加上显式等待。我习惯封装一个通用方法from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC def wait_click(driver, by, locator, timeout10): el WebDriverWait(driver, timeout).until( EC.element_to_be_clickable((by, locator)) ) el.click() return el这样每次点击前都会等到元素可点击能明显减少因为页面加载慢导致的失败。3.3 点击、滑动、输入文本示例抖音信息流的核心操作就是三个点击、滑动、输入。先写一个通用的滑动手势函数。因为不同手机屏幕分辨率不同直接写绝对坐标不合适最好按屏幕比例的相对坐标来操作def swipe_up(driver, duration300): width driver.get_window_size()[width] height driver.get_window_size()[height] start_x width * 0.5 start_y height * 0.8 end_y height * 0.2 driver.swipe(start_x, start_y, start_x, end_y, duration)这里把起始点放在屏幕下方20%的位置结束点放在屏幕上方20%模拟常见的上滑手势。duration控制滑动速度默认300毫秒比较贴近真实手感。点击操作尽量用元素定位不要用坐标。比如要点击评论按钮可以先拿到控件树里评论节点的content-desc再点击。如果确实遇到只有坐标能点的情况可以用driver.tap([(x, y)], duration100)不过需要提醒一下tap的写法在不同版本Appium Python Client里略有差异新版如果提示方法签名变化优先改用TouchAction或者干脆用driver.execute_script来执行原生的mobile: clickGesture。这些底层接口虽然繁琐但很实用。输入文本主要用于评论框场景。点击评论区后输入框才会出现。然后再用android.widget.EditText定位输入框并输入driver.find_element(AppiumBy.XPATH, //*[contains(text,评论)]).click() time.sleep(1) edit_box driver.find_element(AppiumBy.CLASS_NAME, android.widget.EditText) edit_box.send_keys(这是一段自动化测试文本)这里我故意用了sleep因为弹起输入法本身不受元素加载控制短时间等待是合理的。unicodeKeyboard配置会保证中文能正常输入否则很多设备上中文会丢失。4. 自动刷视频脚本的完整实现4.1 目标场景与脚本设计我先说清楚这个脚本的目标模拟真实用户逐条向上滑每滑到新视频就抓取当前屏幕上的文字信息并保存一份截图。它不做点赞、不写评论、不关注任何人只是“看”和“记录”用于自动测试UI和了解信息流结构。设计思路分五步启动抖音等待首页加载出来。打开一个CSV文件用来记录每条时间戳和页面提取到的文本。循环指定次数每次先处理弹窗再提取文本和截图。上滑切到下一个视频等待几秒让页面加载完成。结束后回到桌面并退出驱动。这个结构是抖音信息流自动化的最小框架后面不管扩展成什么样核心都在循环里。4.2 怎么判断当前视频已经切换上滑动作做完后短视频不会立刻出现完整内容。如果脚本马上抓取页面文本很可能抓到的是上一屏的残留节点。解决办法是记录滑动前后的页面文本特征进行对比轮询。可以从driver.page_source里提取所有可见文本import re def visible_texts(driver): page_source driver.page_source matches re.findall(rtext([^]*), page_source) return [t for t in matches if t.strip()]然后在上滑前保存当前文本列表上滑后每隔1秒重新提取一次直到文本列表发生变化或者超过最大等待时间。def wait_new_page(driver, old_texts, timeout8): start time.time() while time.time() - start timeout: new_texts visible_texts(driver) if new_texts ! old_texts: return True time.sleep(1) return False当然如果某些视频信息没有文字文本列表可能不变这时候可以用截图对比或者OCR辅助。但作为最基础的方案文本变化判断已经能覆盖大部分场景。4.3 一个完整可运行的脚本下面这个脚本是核心参考版本我把代码量控制在容易理解的范围内实际生产环境可以在这个基础上封装成类。import csv import re import time from appium import webdriver from appium.webdriver.common.appiumby import AppiumBy from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC desired_caps { platformName: Android, automationName: UiAutomator2, deviceName: 你的设备名, appPackage: 你的抖音包名, appActivity: 你查到的入口Activity, noReset: True, autoGrantPermissions: True, unicodeKeyboard: True, resetKeyboard: True, } driver webdriver.Remote(http://127.0.0.1:4723/wd/hub, desired_caps) wait WebDriverWait(driver, 15) def visible_texts(): page_source driver.page_source matches re.findall(rtext([^]*), page_source) return [t for t in matches if t.strip()] def close_popup(): for keyword in (同意, 我知道了, 以后再说, 取消, 确定): try: el driver.find_element(AppiumBy.XPATH, f//*[text{keyword}]) el.click() return True except Exception: continue return False def swipe_up(): width driver.get_window_size()[width] height driver.get_window_size()[height] driver.swipe(width * 0.5, height * 0.8, width * 0.5, height * 0.2, 300) def wait_new_page(old_texts, timeout8): start_time time.time() while time.time() - start_time timeout: new_texts visible_texts() if new_texts ! old_texts: return True time.sleep(1) return False with open(video_log.csv, a, newline, encodingutf-8) as f: writer csv.writer(f) writer.writerow([时间, 屏幕文本摘要]) wait.until(EC.presence_of_element_located((AppiumBy.XPATH, //*[text首页]))) print(首页加载完成开始自动浏览) for i in range(10): close_popup() texts visible_texts() summary | .join(texts[:10]) writer.writerow([time.strftime(%Y-%m-%d %H:%M:%S), summary]) driver.get_screenshot_as_file(fscreen_{time.strftime(%Y%m%d_%H%M%S)}.png) print(f已记录第{i 1}屏文本摘要: {summary[:50]}) old_texts texts swipe_up() wait_new_page(old_texts) close_popup() driver.press_keycode(3) driver.quit() print(自动浏览结束)这个脚本里最值得学习的是循环顺序先处理弹窗再提取信息再截图然后滑动最后等待新页面出现。这个顺序经过多次实践调整能最大限度避免信息抓取到重复页面。每一屏只抓前10个文本作为摘要避免CSV被XML冗余信息撑爆截图则作为完整兜底方便后续人工查看。4.4 为什么用显式等待而不是长时间sleep新手很容易写出time.sleep(5)这种代码看起来省事实际隐患很大。抖音的加载速度受网络影响非常明显。网络好的时候1秒就出新视频网络差的时候可能要5秒。如果固定sleep 3秒网络慢就会抓到旧页面网络快则白白浪费大量时间。显式等待会根据实际条件动态调整时间页面元素已经到位就立刻继续不需要人为估算。脚本设计里我用wait_new_page等待页面文本发生变化这个函数本质上是一个轮询循环它可以处理1秒到8秒的网络波动。如果8秒还没变化就跳过当前屏继续不会把脚本卡死。5. 常见问题与避坑指南5.1 弹窗、权限和青少年模式怎么处理抖音在不同状态下会弹各种东西常见的有隐私协议、通知权限、青少年模式、活动弹窗。处理思路是写一个统一弹窗清理函数把可能出现的关键词都放进去每轮循环先调用一次。autoGrantPermissions能解决系统权限弹窗但解决不了App内部弹窗。App内部弹窗一般都有一个可点击按钮关键词是“同意”“我知道了”“以后再说”“取消”这几个。把这些词写进close_popup里每轮循环都尝试点击一次找不到就跳过既不干扰正常流程也不会漏掉弹窗。需要特别留意的是青少年模式。这个弹窗出现后如果设备没有登录往往需要家长验证才能关闭自动脚本处理不了。所以实操中我会在首次手动登录并做好设置再开启noReset让登录状态保留。脚本运行期间尽量保持账号已经完成基础设置能省掉很多麻烦。5.2 元素找不到、定位失效的排查顺序当find_element报NoSuchElementException时先不要急着改XPath按下面顺序排查。第一步看元素是否存在。用adb shell uiautomator dump导出当前页面XML搜索目标文本或ID。如果搜索不到说明元素确实不在控件树里。第二步判断当前是不是WebView。抖音很多页面是原生和WebView混合的比如一些活动页、直播间部分区域。原生节点用driver.page_source能看到WebView里的内容看不到。执行driver.contexts会输出当前所有可用上下文如果里面有WEBVIEW_xxx需要切换到对应WebView再定位。第三步考虑App是否用了自绘渲染。抖音部分信息流会用到高度自定义的渲染方式有时节点是存在的但信息不完整甚至只有一个大容器内部细节无法拿到。这种情况下不建议死磕XPath直接用坐标滑动和截图判断更实在。自动化不是非要拿到每个控件很多场景用屏幕比例坐标反而更省心。5.3 长时间运行后卡死、黑屏怎么办脚本跑的时间长了偶尔会遇到设备休眠导致Session失效、App进程被系统回收、甚至服务端报错。解决思路是在主流程外做好恢复动作。最简单可靠的方法是每次循环都用try/finally兜底确保最终一定会关闭驱动。如果循环内异常太频繁可以在外层捕获到特定异常后重新激活Apptry: driver.activate_app(你的抖音包名) except Exception: driver.launch_app()另外可以在手机设置里关闭自动锁屏和屏幕超时或者用充电线保持供电否则设备一旦锁屏Appium的控件树就取不到了。脚本结束前尽量执行一次driver.press_keycode(3)回到桌面不会让手机停留在抖音界面造成误触。5.4 关于账号使用边界的提醒我实测跑自动化刷视频时如果频率过高、持续时间过长会遇到平台要求进行安全验证的情况。这说明大规模、高频率的自动化行为是可能触发限制策略的。因此脚本里一定要控制频率和次数建议每次运行不超过20屏滑动间隔不要太短不要涉及点赞、评论、关注等交互行为。同时建议使用自己日常使用的设备并且保持App更新后及时检查控件树变化。平台App是不断更新的今天能用的XPath下次升级可能就失效了。把脚本当成测试学习项目看不要当成能长期跑的黑盒工具。6. 把脚本扩展成顺手小工具的几点建议6.1 做成命令行工具跑测试的时候总得改循环次数、输出文件名老改代码很烦。可以用argparse把这些参数暴露成命令行参数python run_douyin_auto.py --times 20 --output video_log.csv然后脚本里用args.times替代固定的range(10)用args.output替代写死的CSV文件名。这样整个工具就可以交给别人用哪怕看不懂代码也能操作。6.2 加上日志和运行监控刷视频过程中偶尔会碰到某一屏什么都没抓到如果不做日志事后很难排查。我习惯用logging模块把每次滑屏、每次异常都记录到本地文件import logging logging.basicConfig(filenameauto_run.log, levellogging.INFO, format%(asctime)s %(levelname)s %(message)s) logging.info(开始本轮自动浏览) logging.info(f当前第 {i 1} 屏) logging.exception(发生异常)日志和截图放在同一个目录里出问题时按时间戳一一对应能很快定位是哪一步出错。6.3 用pytest组织测试用例如果本身是做测试工作的可以把这套脚本改造成pytest用例。启动App和关闭App分别放在setup_method和teardown_method里把“滑动一次并等待新视频加载”作为一个用例循环操作通过参数化来执行。这样做的好处是测试报告清晰每个步骤都有关联的断言方便集成到现有测试框架里。最后分享一个个人体会。不要一上来就想着用Appium搞定抖音全部页面Appium的能力边界在控件树可见的范围内。像抖音这种重刷新的信息流应用脚本最可靠的写法不是每一步都精确控点而是在几个关键节点卡住等待剩下的交给滑动和重试。先跑通10次再考虑扩展成命令行工具这是我一直用的节奏。只要每一步都按“判断状态再操作”这个思路来这套脚本就能稳稳当当地长久用下去。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →