1. 项目概述为什么爬虫需要 Appium如果你刚开始接触爬虫可能觉得用requests库抓抓网页数据就够用了。但现实是现在大量的核心数据都“藏”在手机App里比如电商的商品详情、短视频的评论、社交媒体的动态这些数据往往在网页端要么不完整要么干脆没有。传统的网页爬虫面对这些App数据就像用鱼竿去捞深海鱼根本够不着。这时候Appium 就登场了。很多人第一次听说 Appium都以为它只是个“自动化测试工具”跟爬虫八竿子打不着。这其实是个巨大的误解。Appium 的核心价值在于它提供了一套标准协议WebDriver能让你用写代码的方式像真人一样去“操作”手机App——点击、滑动、输入文字、获取屏幕上的元素信息。这不正是我们爬虫想要的能力吗把 App 里“看”得到的数据通过程序“拿”出来。所以这篇内容就是带你从零开始把 Appium 这个“测试工具”变成你的“爬虫利器”。我会把整个环境搭建、核心原理、代码编写到实战避坑的完整链条都拆开揉碎了讲清楚。哪怕你之前没写过一行自动化代码跟着走一遍也能自己写出能自动翻 App、抓数据的脚本。2. 环境搭建从零开始的“踩坑”指南环境搭建是劝退新手的第一个门槛网上教程很多但往往缺了关键细节导致你跟着做却总报错。这里我结合自己多次重装系统的经验给你一个清晰、可复现的步骤。2.1 核心三件套JDK、Android SDK 与 Appium Server这三者是 Appium 运行的基石缺一不可且版本有讲究。1. JDK 安装与配置Appium 底层是 Node.js但其驱动 Android 设备需要调用 Java 工具。必须安装JDK 8或JDK 11的 LTS长期支持版本。不推荐使用最新的 JDK 17因为部分 Android 构建工具链可能存在兼容性问题。下载去 Oracle 官网或 Adoptium 下载 JDK 8 的安装包。配置关键点JAVA_HOME这个变量必须指向 JDK 的安装根目录例如C:\Program Files\Java\jdk1.8.0_301。注意是jdk文件夹不是jre文件夹。Path需要添加%JAVA_HOME%\bin。这是为了让系统在任何位置都能识别java和javac命令。验证打开命令行输入java -version和javac -version。两者都能正确显示版本号才算成功。如果只有java生效而javac报错说明JAVA_HOME可能指向了 JRE。2. Android SDK 安装最易出错环节Android SDK 不是指一个具体的软件而是一套工具集。现在谷歌官方推荐通过 Android Studio 来管理但对于我们爬虫场景装个完整的 IDE 太臃肿。我推荐使用独立的SDK 命令行工具。下载访问 Android 开发者网站下载 “Command line tools only”。这是一个很小的 zip 包。安装与配置解压到一个没有中文和空格的路径比如D:\android-sdk。在这个目录下新建platforms和platform-tools文件夹。打开解压出来的tools\bin目录在这里打开命令行。使用 SDK 管理器安装必要组件# 接受所有许可 sdkmanager --licenses # 安装平台工具、构建工具和一个安卓系统镜像用于模拟器 sdkmanager “platform-tools” “build-tools;30.0.3” “platforms;android-30” “system-images;android-30;google_apis;x86_64”这里的android-30对应 Android 11你可以根据要测试的 App 兼容性选择其他版本但platform-tools包含 adb和build-tools是必须的。环境变量ANDROID_HOME指向你的 SDK 根目录如D:\android-sdk。Path添加%ANDROID_HOME%\platform-tools和%ANDROID_HOME%\tools\bin。验证命令行输入adb version。能输出 ADB 的版本信息即成功。adbAndroid Debug Bridge是我们连接真机或模拟器的桥梁至关重要。3. Appium Server 的两种选择Appium Server 是通信中枢你的 Python 脚本通过它向手机发送指令。方案一Appium Desktop新手推荐这是一个带图形界面的桌面应用非常适合调试。从 GitHub 的 Appium Desktop 发布页下载安装。启动后只需点击 “Start Server” 按钮它就在本地启动了一个服务。它的 Inspector 功能后来独立为 Appium Inspector可以直观地查看 App 界面元素生成定位代码是学习元素定位的神器。方案二通过 Node.js 安装更灵活如果你熟悉命令行或者需要在无界面的服务器上运行这是更好的选择。# 1. 安装 Node.js # 2. 通过 npm 安装 Appium npm install -g appium # 3. 安装驱动程序必须 npm install -g appium-doctor appium-uiautomator2-driver # 4. 启动 Appium Server appium使用此方案务必运行appium-doctor来检查环境它会清晰地告诉你哪里没配置对。实操心得环境变量配置后一定要关闭并重新打开命令行窗口才能生效。90%的“命令找不到”问题都是因为这个。另外建议将所有工具JDK, Android SDK, Appium都安装在英文路径下避免各种奇葩问题。2.2 连接测试设备真机与模拟器抉择设备是自动化操作的对象你可以选择真机或模拟器。真机连接手机开启“开发者模式”通常是在“关于手机”里连续点击“版本号”7次。在开发者选项中开启“USB调试”。用数据线连接电脑。在命令行输入adb devices如果看到设备序列号后面跟着device表示连接成功。如果显示unauthorized去手机上看一下是否弹出了“允许USB调试”的提示勾选“始终允许”后确定。模拟器使用 对于爬虫来说模拟器有时比真机更方便可以同时运行多个实例且不受物理设备限制。可以使用 Android Studio 自带的 AVD Manager 创建模拟器但比较重。我推荐使用Genymotion或网易 MuMu这类性能较好的第三方模拟器。启动后同样用adb devices命令查看是否连接。关键点确保模拟器的 Android 版本与你 SDK 中安装的系统镜像版本匹配。注意事项有些国产手机如华为、小米需要额外在开发者选项里开启“允许模拟位置”或“禁止权限监控”等否则自动化操作可能被拦截。如果遇到点击无效的情况这是首要排查点。2.3 Python 客户端与依赖安装环境搭好了我们需要用 Python 来编写控制脚本。# 安装 Appium 的 Python 客户端库 pip install Appium-Python-Client这个库封装了与 Appium Server 通信的所有细节让你能用简单的 Python 代码发送自动化指令。此外建议安装pytest来组织你的测试用例这样结构更清晰也方便管理前置和后置操作。pip install pytest至此你的“武器库”就准备齐全了。接下来我们理解一下这些武器是如何协同工作的。3. 核心原理与关键概念剖析只知道怎么装不知道为啥这么装出了问题肯定懵。理解 Appium 的工作原理能让你在遇到问题时快速定位。3.1 Appium 的架构与通信流程你可以把 Appium 想象成一个翻译官。你的 Python 脚本Client你用 Python 写命令比如“点击登录按钮”。Appium Server它监听 4723 端口。收到你的 Python 命令后它并不直接操作手机而是把这些命令翻译成手机能听懂的语言JSON Wire Protocol并通过adb转发。Bootstrap 或 UIAutomator2在手机端这是 Appium 安装在手机上的一个“小助手”。它接收来自 Server 的指令并将其转化为手机操作系统级别的原生操作指令。对于 Android这个“小助手”就是UIAutomator2。手机操作系统执行这些原生指令最终完成点击、滑动等操作并将结果如页面元素信息一层层返回给你的 Python 脚本。整个流程是Python代码 - HTTP请求 - Appium Server - ADB - 手机端Bootstrap - 操作系统 - 操作App。3.2 Desired Capabilities启动会话的“身份证”这是 Appium 中最核心的配置它是一个字典Dictionary告诉 Appium Server 你要如何启动和操作哪个 App。desired_caps { platformName: Android, # 平台必须是 ‘Android‘ 或 ‘iOS’ platformVersion: 11, # 手机系统的版本号 deviceName: emulator-5554, # 设备名通过 adb devices 获取 appPackage: com.zhihu.android, # 要启动的 App 的包名 appActivity: .app.ui.activity.MainActivity, # App 启动后首先打开的界面 automationName: Uiautomator2, # 自动化引擎Android 上首选 noReset: True, # 是否在会话开始前重置 App如清除数据 unicodeKeyboard: True, # 是否启用 Unicode 输入法用于输入中文 resetKeyboard: True, # 在测试结束后重置回默认输入法 }如何获取appPackage和appActivity最准确的方法是打开你要爬取的 App然后在命令行输入adb shell dumpsys activity activities | findstr “mResumedActivity”输出结果中com.zhihu.android/.app.ui.activity.MainActivity前面就是appPackage后面就是appActivity。deviceName就是adb devices命令列出的设备标识符。automationName对于现代 Android App务必使用‘Uiautomator2’它比老旧的‘Uiautomator1’更稳定支持更多手势操作。3.3 元素定位爬虫的“眼睛”爬虫的核心是找到数据所在的位置。在 App 里数据都附着在界面元素如文本框、按钮、列表项上。Appium 提供了多种定位元素的方法相当于给你提供了不同的“寻路工具”。定位方式示例代码适用场景优点缺点IDfind_element(By.ID, “com.zhihu.android:id/login”)元素有唯一资源ID时定位最快、最准确很多 App 元素ID是动态生成的Accessibility IDfind_element(By.ACCESSIBILITY_ID, “登录”)元素有content-desc属性语义化不易变依赖开发是否添加XPathfind_element(By.XPATH, ‘//android.widget.TextView[text“热搜”]’)万能定位法尤其适合通过文本定位极其灵活功能强大速度相对较慢路径可能随UI改动而失效Class Namefind_element(By.CLASS_NAME, “android.widget.EditText”)定位某一类元素如所有输入框写法简单通常不唯一需结合其他条件Android UIAutomatorfind_element(By.ANDROID_UIAUTOMATOR, ‘new UiSelector().text(“确认”)’)Android 专属复杂条件定位语法强大支持滚动查找语法稍复杂仅限 Android实操心得对于爬虫XPath 和 Accessibility ID 是主力。XPath 可以通过文本直接定位非常直观比如定位“搜索按钮”。在写爬虫脚本前务必先用Appium Inspector工具连接上你的 App用它来查看元素的各种属性并试验哪种定位方式最稳定。不要依赖那些可能变化的资源ID。4. 实战编写你的第一个 Appium 爬虫脚本理论说再多不如动手写一行代码。我们以“打开知乎搜索一个关键词并获取第一条回答的摘要”为例完成一个完整的爬虫流程。4.1 项目结构与初始化首先创建一个清晰的目录结构zhihu_crawler/ ├── config/ # 配置文件 │ └── capabilities.py ├── pages/ # 页面对象模型 │ ├── __init__.py │ ├── main_page.py │ └── search_page.py ├── utils/ # 工具函数 │ ├── __init__.py │ └── driver.py ├── test_search.py # 主测试/爬虫脚本 └── requirements.txt在requirements.txt中写明依赖Appium-Python-Client和pytest。4.2 封装驱动初始化在utils/driver.py中创建全局的 WebDriver 实例避免重复初始化。# utils/driver.py from appium import webdriver import threading local threading.local() def init_driver(): from config.capabilities import get_desired_capabilities caps get_desired_capabilities() # 注意这里的地址和端口要与你启动的 Appium Server 一致 driver webdriver.Remote(http://localhost:4723/wd/hub, caps) driver.implicitly_wait(10) # 设置隐式等待全局生效 local.driver driver return driver def get_driver(): 获取当前线程的driver实例 if not hasattr(local, driver) or local.driver is None: return init_driver() return local.driver def quit_driver(): if hasattr(local, driver) and local.driver is not None: local.driver.quit() local.driver None在config/capabilities.py中集中管理配置# config/capabilities.py def get_desired_capabilities(): return { platformName: Android, platformVersion: 11, deviceName: 你的设备名, # 从 adb devices 获取 appPackage: com.zhihu.android, appActivity: .app.ui.activity.MainActivity, automationName: Uiautomator2, noReset: True, # 不清除数据保留登录状态 unicodeKeyboard: True, resetKeyboard: True, newCommandTimeout: 600, # 命令超时时间设长一些 }4.3 实现页面操作与数据抓取使用 “Page Object Model” 设计模式将每个页面封装成一个类使代码更易维护。1. 主页面操作# pages/main_page.py from appium.webdriver.common.appiumby import AppiumBy from utils.driver import get_driver class MainPage: def __init__(self): self.driver get_driver() def go_to_search(self): 从首页点击搜索框进入搜索页 # 通过 Accessibility ID 定位搜索框假设其content-desc为‘搜索’ # 如果不行可以尝试用XPath//*[contains(text, ‘搜索’)] search_box self.driver.find_element(AppiumBy.ACCESSIBILITY_ID, ‘搜索’) search_box.click() # 返回下一个页面的对象便于链式调用 from pages.search_page import SearchPage return SearchPage()2. 搜索与数据抓取页面# pages/search_page.py from appium.webdriver.common.appiumby import AppiumBy import time class SearchPage: def __init__(self): from utils.driver import get_driver self.driver get_driver() def input_keyword_and_search(self, keyword): 在搜索框输入关键词并执行搜索 # 定位输入框通常是一个 EditText input_element self.driver.find_element(AppiumBy.CLASS_NAME, ‘android.widget.EditText’) input_element.clear() input_element.send_keys(keyword) # 点击键盘上的搜索键假设是回车搜索 self.driver.execute_script(‘mobile: performEditorAction’, {‘action’: ‘search’}) time.sleep(2) # 等待搜索结果加载更好的做法是使用显式等待 return self def get_first_answer_summary(self): 获取第一个搜索结果的摘要文本 # 这是一个示例XPath实际需要你用Inspector查看知乎搜索结果列表的结构 # 可能的结构每个结果项是一个ViewGroup里面包含标题和摘要的TextView try: # 假设摘要的控件特征class为TextView且可能包含特定文字或位于特定位置 # 这里用更通用的方法先找到结果列表再取第一个结果的摘要子元素 # 注意此XPath仅为示例需根据实际UI结构调整 first_item self.driver.find_element(AppiumBy.XPATH, ‘(//android.widget.ListView/android.widget.RelativeLayout)[1]’) summary_element first_item.find_element(AppiumBy.XPATH, ‘.//android.widget.TextView[contains(text, “…”)]’) # 摘要通常有省略号 return summary_element.text except Exception as e: print(f“获取摘要失败: {e}”) # 如果上述定位失败尝试获取整个第一条结果的文本 first_item self.driver.find_element(AppiumBy.XPATH, ‘(//android.widget.ListView//android.widget.TextView)[2]’) # 可能需要调整索引 return first_item.text4.4 组装主爬虫脚本最后在test_search.py中像搭积木一样组合这些页面对象完成整个业务流程。# test_search.py import pytest from utils.driver import init_driver, quit_driver from pages.main_page import MainPage class TestZhihuSearch: def setup_class(self): 整个测试类开始前执行一次 self.driver init_driver() def teardown_class(self): 整个测试类结束后执行一次 quit_driver() def test_search_and_crawl(self): # 1. 初始化主页面 main_page MainPage() # 2. 进入搜索页 search_page main_page.go_to_search() # 3. 输入关键词并搜索 search_page.input_keyword_and_search(“Python 爬虫”) # 4. 获取并打印第一条结果的摘要 summary search_page.get_first_answer_summary() print(f“抓取到的第一条回答摘要\n{summary}”) # 这里可以添加将数据保存到文件或数据库的代码 # with open(‘result.txt’, ‘a’, encoding‘utf-8’) as f: # f.write(summary ‘\n’) assert summary is not None and len(summary) 10 # 简单断言确保抓到了内容 if __name__ “__main__”: # 直接运行脚本 test TestZhihuSearch() test.setup_class() try: test.test_search_and_crawl() print(“[INFO] 爬虫执行成功”) except Exception as e: print(f“[ERROR] 爬虫执行失败: {e}”) finally: test.teardown_class()运行这个脚本你就能看到程序自动打开知乎、搜索、并打印出第一条结果的文本。这就是一个最基础的 Appium 爬虫雏形。5. 高级技巧与稳定性优化基础的跑通只是第一步要让爬虫稳定、高效地长期运行还需要很多技巧。5.1 等待策略告别time.sleep的笨办法在自动化脚本中使用time.sleep(固定秒数)是极不推荐的它会让脚本变得又慢又不可靠。隐式等待在初始化 Driver 时设置driver.implicitly_wait(10)。它会在查找元素时如果没立刻找到会在指定时间内轮询查找。这是全局设置。显式等待针对某个特定条件进行等待更加灵活精准。使用WebDriverWait。from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from appium.webdriver.common.appiumby import AppiumBy # 等待“搜索框”出现最多等10秒每0.5秒检查一次 wait WebDriverWait(driver, 10, poll_frequency0.5) search_box wait.until( EC.presence_of_element_located((AppiumBy.ACCESSIBILITY_ID, “搜索”)) ) # 等待某个元素可点击 button wait.until( EC.element_to_be_clickable((AppiumBy.ID, “com.example:id/btn”)) ) button.click()显式等待是编写健壮爬虫脚本的关键。5.2 复杂交互与手势操作爬虫不只是点击还需要滑动、长按等。from appium.webdriver.common.touch_action import TouchAction action TouchAction(driver) # 1. 滑动屏幕从屏幕中间向下滑动 window_size driver.get_window_size() start_x window_size[‘width’] * 0.5 start_y window_size[‘height’] * 0.8 end_x window_size[‘width’] * 0.5 end_y window_size[‘height’] * 0.2 action.press(xstart_x, ystart_y).wait(500).move_to(xend_x, yend_y).release().perform() # 2. 长按某个元素 element driver.find_element(AppiumBy.ID, “some_id”) action.long_press(element).wait(2000).release().perform() # 3. 更复杂的手势可以使用W3C Actions API (推荐) from selenium.webdriver.common.action_chains import ActionChains actions ActionChains(driver) actions.w3c_actions.pointer_action.move_to_location(x100, y200) actions.w3c_actions.pointer_action.pointer_down() actions.w3c_actions.pointer_action.pause(1) actions.w3c_actions.pointer_action.move_to_location(x300, y200) actions.w3c_actions.pointer_action.pointer_up() actions.perform()5.3 处理弹窗、权限与登录态这是爬虫稳定性的最大挑战。弹窗处理写一个通用的弹窗监控和关闭函数在关键操作前调用。def handle_random_popup(driver): 尝试关闭常见的弹窗 popup_selectors [ (AppiumBy.ID, “com.zhihu.android:id/btClose”), # 关闭按钮ID (AppiumBy.XPATH, “//*[text‘以后再说’]”), (AppiumBy.XPATH, “//*[text‘拒绝’]”), (AppiumBy.XPATH, “//*[text‘允许’]”), # 有时需要点击允许 ] for by, selector in popup_selectors: try: # 快速查找不等待 element driver.find_element(by, selector) element.click() print(f“已关闭弹窗: {selector}”) time.sleep(1) # 给关闭动画一点时间 return True except: continue return False在每次find_element或click操作前都先运行一下这个函数。登录态维持在desired_capabilities中设置‘noReset’: True可以避免每次启动都清除 App 数据。对于需要登录的 App你可以先手动登录一次后续脚本启动就会保持登录状态。更高级的做法是将登录的session或cookies保存下来但 App 的登录态通常比 Web 复杂手动登录一次是最简单的方案。5.4 数据抓取与解析策略定位到元素后获取数据有多种方式.text属性获取元素显示的文本内容最常用。.get_attribute(‘attributeName’)获取元素的其他属性如content-desc,resource-id,bounds坐标等。页面源码driver.page_source可以获取当前页面的完整 XML 结构UIAutomator 的布局文件。对于复杂列表可以先获取源码然后用lxml或BeautifulSoup进行离线解析效率可能比多次调用find_element更高。from lxml import etree source driver.page_source tree etree.HTML(source) # 注意etree.HTML 用于解析 HTMLApp 的 XML 可能需要用 etree.XML # 使用 XPath 进行解析 items tree.xpath(‘//android.widget.TextView/text’)6. 常见问题排查与性能优化在实际运行中你一定会遇到各种问题。这里列一个速查表。问题现象可能原因排查步骤与解决方案WebDriverException: Unable to create new serviceAppium Server 未启动或端口被占用1. 检查 Appium Server 是否成功启动看到listening on 0.0.0.0:4723。2. 检查端口冲突netstat -anoSessionNotCreatedExceptiondesired_capabilities配置错误1. 检查appPackage和appActivity名称是否正确。2. 检查platformVersion是否与设备系统版本匹配。3. 确认设备已通过adb devices连接。元素找不到 (NoSuchElementException)1. 定位表达式写错。2. 页面未加载完。3. 元素在WebView中。1. 用 Appium Inspector 重新确认元素属性。2. 添加显式等待等待元素出现。3. 如果是 H5 页面需要切换上下文driver.switch_to.context(‘WEBVIEW_com.xxx’)再用 Selenium 的方式定位。点击无效1. 元素不可点击。2. 被其他元素遮挡。3. 手机权限拦截。1. 使用element_to_be_clickable等待。2. 尝试用driver.tap([(x, y)])坐标点击。3. 检查手机开发者选项中的“指针位置”显示点击坐标是否准确。4. 关闭手机上的“权限监控”或“显示悬浮窗”限制。脚本运行慢1. 大量使用time.sleep。2. 查找元素策略低效。3. 截图或日志过多。1. 用显式等待替代固定等待。2. 优先使用 ID 或 Accessibility ID 定位减少 XPath 使用。3. 在desired_capabilities中设置‘disableAndroidWatchers’: True和‘skipDeviceInitialization’: True来加速启动。4. 减少不必要的get_screenshot_as_file操作。在某个页面后无法继续操作可能进入了WebView或混合应用环境。1. 打印当前所有上下文print(driver.contexts)。2. 切换到正确的WEBVIEW上下文driver.switch_to.context(‘WEBVIEW_xxx’)。3. 操作完后再切回原生上下文driver.switch_to.context(‘NATIVE_APP’)。内存溢出 (OOM)长时间运行Driver 或 Appium Server 未释放资源。1. 确保每个测试用例结束后调用driver.quit()。2. 定期重启 Appium Server。3. 对于 Mac/Linux考虑使用ulimit增加文件描述符限制。性能优化小技巧复用 Session如果只是爬取同一个 App 的不同页面不要频繁启动和关闭 Session。一个 Session 可以执行多个测试用例。并行执行如果需要爬取大量独立任务可以考虑使用多设备并行。为每个设备启动一个独立的 Appium Server 端口如 4723, 4725, 4727然后编写多线程脚本每个线程连接一个端口和设备。减少依赖脚本应尽可能少地依赖特定设备的绝对坐标或非常脆弱的 XPath。多使用相对定位和通过文本来定位提高脚本的健壮性。最后记住 Appium 爬虫的本质是模拟真人操作所以你的脚本逻辑应该尽量贴近人的操作习惯在关键步骤加入合理的等待和容错判断。它不像 HTTP 爬虫那样快如闪电但其价值在于能拿到那些仅存在于 App 交互流程中的数据。从环境搭建到写出第一个能稳定运行的脚本这个过程你会遇到很多坑但每解决一个你对移动端自动化和数据抓取的理解就会更深一层。
Appium从零到一:环境搭建、核心原理与移动端数据抓取实战
发布时间:2026/7/8 17:49:06
1. 项目概述为什么爬虫需要 Appium如果你刚开始接触爬虫可能觉得用requests库抓抓网页数据就够用了。但现实是现在大量的核心数据都“藏”在手机App里比如电商的商品详情、短视频的评论、社交媒体的动态这些数据往往在网页端要么不完整要么干脆没有。传统的网页爬虫面对这些App数据就像用鱼竿去捞深海鱼根本够不着。这时候Appium 就登场了。很多人第一次听说 Appium都以为它只是个“自动化测试工具”跟爬虫八竿子打不着。这其实是个巨大的误解。Appium 的核心价值在于它提供了一套标准协议WebDriver能让你用写代码的方式像真人一样去“操作”手机App——点击、滑动、输入文字、获取屏幕上的元素信息。这不正是我们爬虫想要的能力吗把 App 里“看”得到的数据通过程序“拿”出来。所以这篇内容就是带你从零开始把 Appium 这个“测试工具”变成你的“爬虫利器”。我会把整个环境搭建、核心原理、代码编写到实战避坑的完整链条都拆开揉碎了讲清楚。哪怕你之前没写过一行自动化代码跟着走一遍也能自己写出能自动翻 App、抓数据的脚本。2. 环境搭建从零开始的“踩坑”指南环境搭建是劝退新手的第一个门槛网上教程很多但往往缺了关键细节导致你跟着做却总报错。这里我结合自己多次重装系统的经验给你一个清晰、可复现的步骤。2.1 核心三件套JDK、Android SDK 与 Appium Server这三者是 Appium 运行的基石缺一不可且版本有讲究。1. JDK 安装与配置Appium 底层是 Node.js但其驱动 Android 设备需要调用 Java 工具。必须安装JDK 8或JDK 11的 LTS长期支持版本。不推荐使用最新的 JDK 17因为部分 Android 构建工具链可能存在兼容性问题。下载去 Oracle 官网或 Adoptium 下载 JDK 8 的安装包。配置关键点JAVA_HOME这个变量必须指向 JDK 的安装根目录例如C:\Program Files\Java\jdk1.8.0_301。注意是jdk文件夹不是jre文件夹。Path需要添加%JAVA_HOME%\bin。这是为了让系统在任何位置都能识别java和javac命令。验证打开命令行输入java -version和javac -version。两者都能正确显示版本号才算成功。如果只有java生效而javac报错说明JAVA_HOME可能指向了 JRE。2. Android SDK 安装最易出错环节Android SDK 不是指一个具体的软件而是一套工具集。现在谷歌官方推荐通过 Android Studio 来管理但对于我们爬虫场景装个完整的 IDE 太臃肿。我推荐使用独立的SDK 命令行工具。下载访问 Android 开发者网站下载 “Command line tools only”。这是一个很小的 zip 包。安装与配置解压到一个没有中文和空格的路径比如D:\android-sdk。在这个目录下新建platforms和platform-tools文件夹。打开解压出来的tools\bin目录在这里打开命令行。使用 SDK 管理器安装必要组件# 接受所有许可 sdkmanager --licenses # 安装平台工具、构建工具和一个安卓系统镜像用于模拟器 sdkmanager “platform-tools” “build-tools;30.0.3” “platforms;android-30” “system-images;android-30;google_apis;x86_64”这里的android-30对应 Android 11你可以根据要测试的 App 兼容性选择其他版本但platform-tools包含 adb和build-tools是必须的。环境变量ANDROID_HOME指向你的 SDK 根目录如D:\android-sdk。Path添加%ANDROID_HOME%\platform-tools和%ANDROID_HOME%\tools\bin。验证命令行输入adb version。能输出 ADB 的版本信息即成功。adbAndroid Debug Bridge是我们连接真机或模拟器的桥梁至关重要。3. Appium Server 的两种选择Appium Server 是通信中枢你的 Python 脚本通过它向手机发送指令。方案一Appium Desktop新手推荐这是一个带图形界面的桌面应用非常适合调试。从 GitHub 的 Appium Desktop 发布页下载安装。启动后只需点击 “Start Server” 按钮它就在本地启动了一个服务。它的 Inspector 功能后来独立为 Appium Inspector可以直观地查看 App 界面元素生成定位代码是学习元素定位的神器。方案二通过 Node.js 安装更灵活如果你熟悉命令行或者需要在无界面的服务器上运行这是更好的选择。# 1. 安装 Node.js # 2. 通过 npm 安装 Appium npm install -g appium # 3. 安装驱动程序必须 npm install -g appium-doctor appium-uiautomator2-driver # 4. 启动 Appium Server appium使用此方案务必运行appium-doctor来检查环境它会清晰地告诉你哪里没配置对。实操心得环境变量配置后一定要关闭并重新打开命令行窗口才能生效。90%的“命令找不到”问题都是因为这个。另外建议将所有工具JDK, Android SDK, Appium都安装在英文路径下避免各种奇葩问题。2.2 连接测试设备真机与模拟器抉择设备是自动化操作的对象你可以选择真机或模拟器。真机连接手机开启“开发者模式”通常是在“关于手机”里连续点击“版本号”7次。在开发者选项中开启“USB调试”。用数据线连接电脑。在命令行输入adb devices如果看到设备序列号后面跟着device表示连接成功。如果显示unauthorized去手机上看一下是否弹出了“允许USB调试”的提示勾选“始终允许”后确定。模拟器使用 对于爬虫来说模拟器有时比真机更方便可以同时运行多个实例且不受物理设备限制。可以使用 Android Studio 自带的 AVD Manager 创建模拟器但比较重。我推荐使用Genymotion或网易 MuMu这类性能较好的第三方模拟器。启动后同样用adb devices命令查看是否连接。关键点确保模拟器的 Android 版本与你 SDK 中安装的系统镜像版本匹配。注意事项有些国产手机如华为、小米需要额外在开发者选项里开启“允许模拟位置”或“禁止权限监控”等否则自动化操作可能被拦截。如果遇到点击无效的情况这是首要排查点。2.3 Python 客户端与依赖安装环境搭好了我们需要用 Python 来编写控制脚本。# 安装 Appium 的 Python 客户端库 pip install Appium-Python-Client这个库封装了与 Appium Server 通信的所有细节让你能用简单的 Python 代码发送自动化指令。此外建议安装pytest来组织你的测试用例这样结构更清晰也方便管理前置和后置操作。pip install pytest至此你的“武器库”就准备齐全了。接下来我们理解一下这些武器是如何协同工作的。3. 核心原理与关键概念剖析只知道怎么装不知道为啥这么装出了问题肯定懵。理解 Appium 的工作原理能让你在遇到问题时快速定位。3.1 Appium 的架构与通信流程你可以把 Appium 想象成一个翻译官。你的 Python 脚本Client你用 Python 写命令比如“点击登录按钮”。Appium Server它监听 4723 端口。收到你的 Python 命令后它并不直接操作手机而是把这些命令翻译成手机能听懂的语言JSON Wire Protocol并通过adb转发。Bootstrap 或 UIAutomator2在手机端这是 Appium 安装在手机上的一个“小助手”。它接收来自 Server 的指令并将其转化为手机操作系统级别的原生操作指令。对于 Android这个“小助手”就是UIAutomator2。手机操作系统执行这些原生指令最终完成点击、滑动等操作并将结果如页面元素信息一层层返回给你的 Python 脚本。整个流程是Python代码 - HTTP请求 - Appium Server - ADB - 手机端Bootstrap - 操作系统 - 操作App。3.2 Desired Capabilities启动会话的“身份证”这是 Appium 中最核心的配置它是一个字典Dictionary告诉 Appium Server 你要如何启动和操作哪个 App。desired_caps { platformName: Android, # 平台必须是 ‘Android‘ 或 ‘iOS’ platformVersion: 11, # 手机系统的版本号 deviceName: emulator-5554, # 设备名通过 adb devices 获取 appPackage: com.zhihu.android, # 要启动的 App 的包名 appActivity: .app.ui.activity.MainActivity, # App 启动后首先打开的界面 automationName: Uiautomator2, # 自动化引擎Android 上首选 noReset: True, # 是否在会话开始前重置 App如清除数据 unicodeKeyboard: True, # 是否启用 Unicode 输入法用于输入中文 resetKeyboard: True, # 在测试结束后重置回默认输入法 }如何获取appPackage和appActivity最准确的方法是打开你要爬取的 App然后在命令行输入adb shell dumpsys activity activities | findstr “mResumedActivity”输出结果中com.zhihu.android/.app.ui.activity.MainActivity前面就是appPackage后面就是appActivity。deviceName就是adb devices命令列出的设备标识符。automationName对于现代 Android App务必使用‘Uiautomator2’它比老旧的‘Uiautomator1’更稳定支持更多手势操作。3.3 元素定位爬虫的“眼睛”爬虫的核心是找到数据所在的位置。在 App 里数据都附着在界面元素如文本框、按钮、列表项上。Appium 提供了多种定位元素的方法相当于给你提供了不同的“寻路工具”。定位方式示例代码适用场景优点缺点IDfind_element(By.ID, “com.zhihu.android:id/login”)元素有唯一资源ID时定位最快、最准确很多 App 元素ID是动态生成的Accessibility IDfind_element(By.ACCESSIBILITY_ID, “登录”)元素有content-desc属性语义化不易变依赖开发是否添加XPathfind_element(By.XPATH, ‘//android.widget.TextView[text“热搜”]’)万能定位法尤其适合通过文本定位极其灵活功能强大速度相对较慢路径可能随UI改动而失效Class Namefind_element(By.CLASS_NAME, “android.widget.EditText”)定位某一类元素如所有输入框写法简单通常不唯一需结合其他条件Android UIAutomatorfind_element(By.ANDROID_UIAUTOMATOR, ‘new UiSelector().text(“确认”)’)Android 专属复杂条件定位语法强大支持滚动查找语法稍复杂仅限 Android实操心得对于爬虫XPath 和 Accessibility ID 是主力。XPath 可以通过文本直接定位非常直观比如定位“搜索按钮”。在写爬虫脚本前务必先用Appium Inspector工具连接上你的 App用它来查看元素的各种属性并试验哪种定位方式最稳定。不要依赖那些可能变化的资源ID。4. 实战编写你的第一个 Appium 爬虫脚本理论说再多不如动手写一行代码。我们以“打开知乎搜索一个关键词并获取第一条回答的摘要”为例完成一个完整的爬虫流程。4.1 项目结构与初始化首先创建一个清晰的目录结构zhihu_crawler/ ├── config/ # 配置文件 │ └── capabilities.py ├── pages/ # 页面对象模型 │ ├── __init__.py │ ├── main_page.py │ └── search_page.py ├── utils/ # 工具函数 │ ├── __init__.py │ └── driver.py ├── test_search.py # 主测试/爬虫脚本 └── requirements.txt在requirements.txt中写明依赖Appium-Python-Client和pytest。4.2 封装驱动初始化在utils/driver.py中创建全局的 WebDriver 实例避免重复初始化。# utils/driver.py from appium import webdriver import threading local threading.local() def init_driver(): from config.capabilities import get_desired_capabilities caps get_desired_capabilities() # 注意这里的地址和端口要与你启动的 Appium Server 一致 driver webdriver.Remote(http://localhost:4723/wd/hub, caps) driver.implicitly_wait(10) # 设置隐式等待全局生效 local.driver driver return driver def get_driver(): 获取当前线程的driver实例 if not hasattr(local, driver) or local.driver is None: return init_driver() return local.driver def quit_driver(): if hasattr(local, driver) and local.driver is not None: local.driver.quit() local.driver None在config/capabilities.py中集中管理配置# config/capabilities.py def get_desired_capabilities(): return { platformName: Android, platformVersion: 11, deviceName: 你的设备名, # 从 adb devices 获取 appPackage: com.zhihu.android, appActivity: .app.ui.activity.MainActivity, automationName: Uiautomator2, noReset: True, # 不清除数据保留登录状态 unicodeKeyboard: True, resetKeyboard: True, newCommandTimeout: 600, # 命令超时时间设长一些 }4.3 实现页面操作与数据抓取使用 “Page Object Model” 设计模式将每个页面封装成一个类使代码更易维护。1. 主页面操作# pages/main_page.py from appium.webdriver.common.appiumby import AppiumBy from utils.driver import get_driver class MainPage: def __init__(self): self.driver get_driver() def go_to_search(self): 从首页点击搜索框进入搜索页 # 通过 Accessibility ID 定位搜索框假设其content-desc为‘搜索’ # 如果不行可以尝试用XPath//*[contains(text, ‘搜索’)] search_box self.driver.find_element(AppiumBy.ACCESSIBILITY_ID, ‘搜索’) search_box.click() # 返回下一个页面的对象便于链式调用 from pages.search_page import SearchPage return SearchPage()2. 搜索与数据抓取页面# pages/search_page.py from appium.webdriver.common.appiumby import AppiumBy import time class SearchPage: def __init__(self): from utils.driver import get_driver self.driver get_driver() def input_keyword_and_search(self, keyword): 在搜索框输入关键词并执行搜索 # 定位输入框通常是一个 EditText input_element self.driver.find_element(AppiumBy.CLASS_NAME, ‘android.widget.EditText’) input_element.clear() input_element.send_keys(keyword) # 点击键盘上的搜索键假设是回车搜索 self.driver.execute_script(‘mobile: performEditorAction’, {‘action’: ‘search’}) time.sleep(2) # 等待搜索结果加载更好的做法是使用显式等待 return self def get_first_answer_summary(self): 获取第一个搜索结果的摘要文本 # 这是一个示例XPath实际需要你用Inspector查看知乎搜索结果列表的结构 # 可能的结构每个结果项是一个ViewGroup里面包含标题和摘要的TextView try: # 假设摘要的控件特征class为TextView且可能包含特定文字或位于特定位置 # 这里用更通用的方法先找到结果列表再取第一个结果的摘要子元素 # 注意此XPath仅为示例需根据实际UI结构调整 first_item self.driver.find_element(AppiumBy.XPATH, ‘(//android.widget.ListView/android.widget.RelativeLayout)[1]’) summary_element first_item.find_element(AppiumBy.XPATH, ‘.//android.widget.TextView[contains(text, “…”)]’) # 摘要通常有省略号 return summary_element.text except Exception as e: print(f“获取摘要失败: {e}”) # 如果上述定位失败尝试获取整个第一条结果的文本 first_item self.driver.find_element(AppiumBy.XPATH, ‘(//android.widget.ListView//android.widget.TextView)[2]’) # 可能需要调整索引 return first_item.text4.4 组装主爬虫脚本最后在test_search.py中像搭积木一样组合这些页面对象完成整个业务流程。# test_search.py import pytest from utils.driver import init_driver, quit_driver from pages.main_page import MainPage class TestZhihuSearch: def setup_class(self): 整个测试类开始前执行一次 self.driver init_driver() def teardown_class(self): 整个测试类结束后执行一次 quit_driver() def test_search_and_crawl(self): # 1. 初始化主页面 main_page MainPage() # 2. 进入搜索页 search_page main_page.go_to_search() # 3. 输入关键词并搜索 search_page.input_keyword_and_search(“Python 爬虫”) # 4. 获取并打印第一条结果的摘要 summary search_page.get_first_answer_summary() print(f“抓取到的第一条回答摘要\n{summary}”) # 这里可以添加将数据保存到文件或数据库的代码 # with open(‘result.txt’, ‘a’, encoding‘utf-8’) as f: # f.write(summary ‘\n’) assert summary is not None and len(summary) 10 # 简单断言确保抓到了内容 if __name__ “__main__”: # 直接运行脚本 test TestZhihuSearch() test.setup_class() try: test.test_search_and_crawl() print(“[INFO] 爬虫执行成功”) except Exception as e: print(f“[ERROR] 爬虫执行失败: {e}”) finally: test.teardown_class()运行这个脚本你就能看到程序自动打开知乎、搜索、并打印出第一条结果的文本。这就是一个最基础的 Appium 爬虫雏形。5. 高级技巧与稳定性优化基础的跑通只是第一步要让爬虫稳定、高效地长期运行还需要很多技巧。5.1 等待策略告别time.sleep的笨办法在自动化脚本中使用time.sleep(固定秒数)是极不推荐的它会让脚本变得又慢又不可靠。隐式等待在初始化 Driver 时设置driver.implicitly_wait(10)。它会在查找元素时如果没立刻找到会在指定时间内轮询查找。这是全局设置。显式等待针对某个特定条件进行等待更加灵活精准。使用WebDriverWait。from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from appium.webdriver.common.appiumby import AppiumBy # 等待“搜索框”出现最多等10秒每0.5秒检查一次 wait WebDriverWait(driver, 10, poll_frequency0.5) search_box wait.until( EC.presence_of_element_located((AppiumBy.ACCESSIBILITY_ID, “搜索”)) ) # 等待某个元素可点击 button wait.until( EC.element_to_be_clickable((AppiumBy.ID, “com.example:id/btn”)) ) button.click()显式等待是编写健壮爬虫脚本的关键。5.2 复杂交互与手势操作爬虫不只是点击还需要滑动、长按等。from appium.webdriver.common.touch_action import TouchAction action TouchAction(driver) # 1. 滑动屏幕从屏幕中间向下滑动 window_size driver.get_window_size() start_x window_size[‘width’] * 0.5 start_y window_size[‘height’] * 0.8 end_x window_size[‘width’] * 0.5 end_y window_size[‘height’] * 0.2 action.press(xstart_x, ystart_y).wait(500).move_to(xend_x, yend_y).release().perform() # 2. 长按某个元素 element driver.find_element(AppiumBy.ID, “some_id”) action.long_press(element).wait(2000).release().perform() # 3. 更复杂的手势可以使用W3C Actions API (推荐) from selenium.webdriver.common.action_chains import ActionChains actions ActionChains(driver) actions.w3c_actions.pointer_action.move_to_location(x100, y200) actions.w3c_actions.pointer_action.pointer_down() actions.w3c_actions.pointer_action.pause(1) actions.w3c_actions.pointer_action.move_to_location(x300, y200) actions.w3c_actions.pointer_action.pointer_up() actions.perform()5.3 处理弹窗、权限与登录态这是爬虫稳定性的最大挑战。弹窗处理写一个通用的弹窗监控和关闭函数在关键操作前调用。def handle_random_popup(driver): 尝试关闭常见的弹窗 popup_selectors [ (AppiumBy.ID, “com.zhihu.android:id/btClose”), # 关闭按钮ID (AppiumBy.XPATH, “//*[text‘以后再说’]”), (AppiumBy.XPATH, “//*[text‘拒绝’]”), (AppiumBy.XPATH, “//*[text‘允许’]”), # 有时需要点击允许 ] for by, selector in popup_selectors: try: # 快速查找不等待 element driver.find_element(by, selector) element.click() print(f“已关闭弹窗: {selector}”) time.sleep(1) # 给关闭动画一点时间 return True except: continue return False在每次find_element或click操作前都先运行一下这个函数。登录态维持在desired_capabilities中设置‘noReset’: True可以避免每次启动都清除 App 数据。对于需要登录的 App你可以先手动登录一次后续脚本启动就会保持登录状态。更高级的做法是将登录的session或cookies保存下来但 App 的登录态通常比 Web 复杂手动登录一次是最简单的方案。5.4 数据抓取与解析策略定位到元素后获取数据有多种方式.text属性获取元素显示的文本内容最常用。.get_attribute(‘attributeName’)获取元素的其他属性如content-desc,resource-id,bounds坐标等。页面源码driver.page_source可以获取当前页面的完整 XML 结构UIAutomator 的布局文件。对于复杂列表可以先获取源码然后用lxml或BeautifulSoup进行离线解析效率可能比多次调用find_element更高。from lxml import etree source driver.page_source tree etree.HTML(source) # 注意etree.HTML 用于解析 HTMLApp 的 XML 可能需要用 etree.XML # 使用 XPath 进行解析 items tree.xpath(‘//android.widget.TextView/text’)6. 常见问题排查与性能优化在实际运行中你一定会遇到各种问题。这里列一个速查表。问题现象可能原因排查步骤与解决方案WebDriverException: Unable to create new serviceAppium Server 未启动或端口被占用1. 检查 Appium Server 是否成功启动看到listening on 0.0.0.0:4723。2. 检查端口冲突netstat -anoSessionNotCreatedExceptiondesired_capabilities配置错误1. 检查appPackage和appActivity名称是否正确。2. 检查platformVersion是否与设备系统版本匹配。3. 确认设备已通过adb devices连接。元素找不到 (NoSuchElementException)1. 定位表达式写错。2. 页面未加载完。3. 元素在WebView中。1. 用 Appium Inspector 重新确认元素属性。2. 添加显式等待等待元素出现。3. 如果是 H5 页面需要切换上下文driver.switch_to.context(‘WEBVIEW_com.xxx’)再用 Selenium 的方式定位。点击无效1. 元素不可点击。2. 被其他元素遮挡。3. 手机权限拦截。1. 使用element_to_be_clickable等待。2. 尝试用driver.tap([(x, y)])坐标点击。3. 检查手机开发者选项中的“指针位置”显示点击坐标是否准确。4. 关闭手机上的“权限监控”或“显示悬浮窗”限制。脚本运行慢1. 大量使用time.sleep。2. 查找元素策略低效。3. 截图或日志过多。1. 用显式等待替代固定等待。2. 优先使用 ID 或 Accessibility ID 定位减少 XPath 使用。3. 在desired_capabilities中设置‘disableAndroidWatchers’: True和‘skipDeviceInitialization’: True来加速启动。4. 减少不必要的get_screenshot_as_file操作。在某个页面后无法继续操作可能进入了WebView或混合应用环境。1. 打印当前所有上下文print(driver.contexts)。2. 切换到正确的WEBVIEW上下文driver.switch_to.context(‘WEBVIEW_xxx’)。3. 操作完后再切回原生上下文driver.switch_to.context(‘NATIVE_APP’)。内存溢出 (OOM)长时间运行Driver 或 Appium Server 未释放资源。1. 确保每个测试用例结束后调用driver.quit()。2. 定期重启 Appium Server。3. 对于 Mac/Linux考虑使用ulimit增加文件描述符限制。性能优化小技巧复用 Session如果只是爬取同一个 App 的不同页面不要频繁启动和关闭 Session。一个 Session 可以执行多个测试用例。并行执行如果需要爬取大量独立任务可以考虑使用多设备并行。为每个设备启动一个独立的 Appium Server 端口如 4723, 4725, 4727然后编写多线程脚本每个线程连接一个端口和设备。减少依赖脚本应尽可能少地依赖特定设备的绝对坐标或非常脆弱的 XPath。多使用相对定位和通过文本来定位提高脚本的健壮性。最后记住 Appium 爬虫的本质是模拟真人操作所以你的脚本逻辑应该尽量贴近人的操作习惯在关键步骤加入合理的等待和容错判断。它不像 HTTP 爬虫那样快如闪电但其价值在于能拿到那些仅存在于 App 交互流程中的数据。从环境搭建到写出第一个能稳定运行的脚本这个过程你会遇到很多坑但每解决一个你对移动端自动化和数据抓取的理解就会更深一层。