Tự động chụp màn hình thiết bị học tập đọc sách bằng Appium và lập lịch thực thi

Việc giám sát hoạt động sử dụng máy tính bảng trong học tập trực tuyến là nhu cầu thực tế của nhiều phụ huynh. Dù ứng dụng quản lý của thương hiệu Đọc Sách (Readboy) cung cấp chức năng chụp màn hình từ xa, thao tác thủ công thường xuyên gây bất tiện và thiếu tính chủ động. Giải pháp được triển khai là xây dựng một hệ thống tự động hóa dựa trên Appium để định kỳ truy cập giao diện quản trị, kích hoạt chức năng chụp màn hình, xử lý ảnh và lưu trữ có tổ chức — tất cả đều chạy ổn định trên thiết bị Android giả lập hoặc thật qua ADB.

import os
import time
from datetime import datetime
from appium import webdriver
from appium.webdriver.common.appiumby import AppiumBy
from appium.webdriver.common.touch_action import TouchAction
from PIL import Image

# Cấu hình kết nối thiết bị (Nox Player hoặc thiết bị vật lý)
DEVICE_ID = "127.0.0.1:62001"
APP_PACKAGE = "com.readboy.rbmanager"
APP_ACTIVITY = "com.readboy.rbmanager.ui.activity.SplashActivity"

def generate_timestamp():
    return datetime.now().strftime("%Y%m%d%H%M%S")

def generate_daily_folder():
    date_str = datetime.now().strftime("%Y%m%d")
    folder_path = os.path.join("screenshots", date_str)
    os.makedirs(folder_path, exist_ok=True)
    return folder_path

def clear_device_gallery():
    """Xóa toàn bộ ảnh JPG trong thư mục Pictures trên thiết bị"""
    try:
        result = os.popen("adb shell ls /sdcard/Pictures/*.jpg 2>/dev/null").read()
        for img_path in result.strip().splitlines():
            if img_path.strip():
                os.system(f"adb shell rm -f '{img_path.strip()}'")
        print("[INFO] Đã dọn dẹp thư viện ảnh trên thiết bị.")
    except Exception as e:
        print(f"[WARN] Không thể dọn dẹp ảnh: {e}")

def pull_latest_screenshot(save_path):
    """Kéo ảnh mới nhất từ /sdcard/Pictures về máy chủ"""
    try:
        result = os.popen("adb shell ls -t /sdcard/Pictures/*.jpg 2>/dev/null | head -n 1").read().strip()
        if result:
            os.system(f'adb pull "{result}" "{save_path}" >nul 2>&1')
            print(f"[INFO] Đã lưu ảnh: {save_path}")
        else:
            print("[WARN] Không tìm thấy ảnh JPG trong thư mục thiết bị.")
    except Exception as e:
        print(f"[ERROR] Lỗi khi kéo ảnh: {e}")

def wait_for_element(driver, by, value, timeout=60):
    """Chờ tối đa `timeout` giây cho phần tử xuất hiện"""
    start = time.time()
    while time.time() - start < timeout:
        try:
            element = driver.find_element(by, value)
            return element
        except:
            time.sleep(1)
    return None

def detect_screen_state(driver):
    """Phát hiện trạng thái màn hình: đang hoạt động / tắt / mất kết nối"""
    states = {
        "active": wait_for_element(driver, AppiumBy.ID, "com.readboy.rbmanager:id/app_name"),
        "black": wait_for_element(driver, AppiumBy.ID, "com.readboy.rbmanager:id/app_title"),
        "offline": wait_for_element(driver, AppiumBy.XPATH, '//*[@text="Thiết bị ngoại tuyến hoặc mạng không ổn định, vui lòng thử lại sau"]')
    }
    return states

def capture_and_save_screenshot(driver):
    # Bắt đầu từ màn hình chính → vào mục "Thiết bị" → chọn "Chụp màn hình"
    wait_for_element(driver, AppiumBy.ANDROID_UIAUTOMATOR, 'new UiSelector().text("Thiết bị")').click()
    time.sleep(1.5)
    wait_for_element(driver, AppiumBy.XPATH, '//*[@text="Chụp màn hình"]').click()
    time.sleep(2)

    # Nhấn vào vùng xem trước ảnh để mở chế độ xem chi tiết
    preview_img = wait_for_element(driver, AppiumBy.ID, "com.readboy.rbmanager:id/imageview")
    if not preview_img:
        return False

    center_x = preview_img.location['x'] + preview_img.size['width'] // 2
    center_y = preview_img.location['y'] + preview_img.size['height'] // 2

    # Nhấn để phóng to → giữ lâu để hiện nút "Lưu ảnh"
    TouchAction(driver).tap(x=center_x, y=center_y).perform()
    time.sleep(3)
    TouchAction(driver).long_press(x=center_x, y=center_y, duration=2000).release().perform()
    time.sleep(1)

    # Nhấn nút lưu
    save_btn = wait_for_element(driver, AppiumBy.XPATH, '//*[@text="Lưu ảnh"]')
    if save_btn:
        save_btn.click()
        time.sleep(2)
        # Tạo tên tệp theo ứng dụng đang chạy và thời gian
        app_name_elem = wait_for_element(driver, AppiumBy.ID, "com.readboy.rbmanager:id/app_name")
        app_name = app_name_elem.text.strip().replace(" ", "_").replace("/", "-") if app_name_elem else "unknown"
        file_path = os.path.join(generate_daily_folder(), f"{app_name}_{generate_timestamp()}.jpg")
        pull_latest_screenshot(file_path)
        return True
    return False

def run_capture_cycle():
    clear_device_gallery()

    caps = {
        "platformName": "Android",
        "deviceName": DEVICE_ID,
        "platformVersion": "7.1.2",
        "appPackage": APP_PACKAGE,
        "appActivity": APP_ACTIVITY,
        "noReset": True,
        "unicodeKeyboard": True,
        "resetKeyboard": True,
        "newCommandTimeout": 6000
    }

    driver = None
    try:
        driver = webdriver.Remote("http://localhost:4723/wd/hub", caps)
        driver.implicitly_wait(5)

        time.sleep(8)  # Chờ khởi động ứng dụng

        screen_states = detect_screen_state(driver)
        if screen_states["active"]:
            capture_and_save_screenshot(driver)
        elif screen_states["black"]:
            print("[STATUS] Màn hình đang tắt.")
        elif screen_states["offline"]:
            print("[STATUS] Thiết bị không kết nối mạng.")
        else:
            print("[WARN] Không xác định được trạng thái màn hình.")

    except Exception as e:
        print(f"[FATAL] Lỗi trong chu kỳ chụp: {e}")
    finally:
        if driver:
            driver.quit()

if __name__ == "__main__":
    run_capture_cycle()

Để chạy tự động theo lịch, tạo file batch run_scheduler.bat với nội dung sau:

@echo off
setlocal enabledelayedexpansion

echo Bắt đầu giám sát định kỳ...
for /l %%i in (1, 1, 1000) do (
    echo [Vòng %%i] Thực thi chụp màn hình...
    python readboy_monitor.py
    echo Chờ 120 giây trước vòng tiếp theo...
    timeout /t 120 /nobreak >nul
)

Hệ thống đã được kiểm thử trong môi trường thực tế và xử lý thành công các tình huống như: ứng dụng nền đang chạy, màn hình tắt, mất kết nối mạng, hoặc giao diện lỗi. Ngoài ra, dữ liệu ảnh được phân loại theo ngày và đặt tên rõ ràng theo ứng dụng đang sử dụng — hỗ trợ việc phân tích hành vi học tập và phát hiện sớm các hành vi ngoài mục đích học tập (ví dụ: xâm nhập trái phép vào buổi học trực tuyến).

Thẻ: appium Android-Automation ADB screenshot-capture python-automation

Đăng vào ngày 16 tháng 7 lúc 21:56