Add get_screens.py

This commit is contained in:
dimon 2026-05-27 21:28:42 +00:00
parent 428b21e33e
commit 2e6d80c61c

232
get_screens.py Normal file
View File

@ -0,0 +1,232 @@
import os
import re
import time
import csv
import cv2
import xml.etree.ElementTree as ET
from pyzbar.pyzbar import decode
import shutil
# --- НАСТРОЙКИ ---
ADB_PATH = "adb"
OUTPUT_CSV = "stocard_export.csv"
MANUAL_FOLDER = "pdf417_manual_cards"
# Увеличенные задержки для старых или медленных телефонов (в секундах)
DELAY_OPEN_CARD = 1.0 # Сколько ждать, пока откроется карта и появится штрихкод
DELAY_BACK = 0.5 # Сколько ждать после нажатия кнопки "Назад"
DELAY_SCROLL = 0.5 # Сколько ждать после прокрутки списка
# ------------------
def run_adb(command):
return os.popen(f"{ADB_PATH} {command}").read()
def ensure_stocard_is_open():
"""Проверяет, открыт ли Stocard, и если нет — запускает его MainActivity"""
activity_info = run_adb("shell dumpsys window displays | grep mCurrentFocus")
if "de.stocard.stocard" not in activity_info:
print("[ЗАЩИТА] Похоже, мы вылетели из приложения. Возвращаю Stocard...")
# Запуск через обход блокировки (MainActivity)
run_adb("shell am start -n de.stocard.stocard/de.stocard.ui.main.MainActivity")
time.sleep(3.0)
def get_current_xml():
"""Стягивает актуальный снимок экрана XML с телефона"""
ensure_stocard_is_open()
run_adb("shell uiautomator dump /sdcard/current_dump.xml")
run_adb("pull /sdcard/current_dump.xml .")
run_adb("shell rm /sdcard/current_dump.xml")
return "current_dump.xml"
def click_at(x, y):
ensure_stocard_is_open()
run_adb(f"shell input tap {x} {y}")
time.sleep(DELAY_OPEN_CARD) # Медленное ожидание открытия штрихкода
def press_back():
run_adb("shell input keyevent 4")
time.sleep(DELAY_BACK)
def scroll_down():
"""Листает список карт вниз, чтобы подгрузить новые"""
ensure_stocard_is_open()
print("Прокручиваю список вниз...")
run_adb("shell input swipe 540 1800 540 600 500")
time.sleep(DELAY_SCROLL)
def parse_card_title():
"""Делает дамп экрана ОТКРЫТОЙ карты и пытается вытащить название магазина"""
run_adb("shell uiautomator dump /sdcard/card_dump.xml")
run_adb("pull /sdcard/card_dump.xml .")
run_adb("shell rm /sdcard/card_dump.xml")
title = "Неизвестный магазин"
if not os.path.exists("card_dump.xml"):
return title
try:
tree = ET.parse("card_dump.xml")
root = tree.getroot()
# Собираем все текстовые строки с экрана
possible_titles = []
for elem in root.iter('node'):
text = elem.get('text', '').strip()
package = elem.get('package', '')
# Отсекаем служебные тексты, цифры (номера карт) и пустые строки
if text and 'stocard' in package.lower():
if not text.isdigit() and len(text) > 1 and text.lower() not in ['назад', 'back', 'share', 'поделиться', 'меню', 'menu']:
possible_titles.append(text)
# Обычно название магазина — это самый первый или самый верхний текст на экране карточки
if possible_titles:
title = possible_titles[0]
except Exception as e:
print(f"[ОШИБКА парсинга названия]: {e}")
if os.path.exists("card_dump.xml"):
os.remove("card_dump.xml")
return title
def capture_and_scan(card_number_id):
"""Делает скриншот открытой карты, распознает штрихкод и вытаскивает название"""
# 1. Сначала вытаскиваем название из XML открытой карты
card_title = parse_card_title()
# 2. Делаем скриншот для распознавания штрихкода
run_adb("shell screencap -p /sdcard/screen.png")
run_adb("pull /sdcard/screen.png .")
run_adb("shell rm /sdcard/screen.png")
img = cv2.imread("screen.png")
if img is None:
return card_title, None, None
# 3. Пробуем распознать стандартный штрихкод
detected = decode(img)
for barcode in detected:
return card_title, barcode.data.decode('utf-8'), barcode.type
# 4. ПЛАН Б: Если код не распознан (например, PDF417), сохраняем скриншот
# Используем название магазина в имени файла, чтобы было удобно смотреть глазами
safe_title = "".join([c for c in card_title if c.isalpha() or c.isdigit() or c==' ']).rstrip()
manual_screenshot_path = os.path.join(MANUAL_FOLDER, f"{card_number_id}_{safe_title}.png")
shutil.copy("screen.png", manual_screenshot_path)
return card_title, f"РУЧНОЙ_ВВОД (См. скриншот {card_number_id}_{safe_title}.png)", "PDF417_OR_UNKNOWN"
def extract_coordinates(xml_path):
"""Ищет координаты только видимых на экране контейнеров карт и фильтрует дубликаты"""
centers = []
try:
tree = ET.parse(xml_path)
root = tree.getroot()
except Exception as e:
return centers
for elem in root.iter('node'):
package = elem.get('package', '')
if 'stocard' in package.lower():
bounds = elem.get('bounds', '')
resource_id = elem.get('resource-id', '')
if bounds and ('card' in resource_id.lower() or 'item' in resource_id.lower() or 'container' in resource_id.lower()):
match = re.findall(r'\d+', bounds)
if match and len(match) == 4:
x1, y1, x2, y2 = map(int, match)
if y1 > 250 and y2 < 2100:
center_x = int((x1 + x2) / 2)
center_y = int((y1 + y2) / 2)
# --- УМНАЯ ФИЛЬТРАЦИЯ ПО ДИСТАНЦИИ ---
# Проверяем, нет ли уже в списке точки, которая находится слишком близко
is_duplicate = False
for (existing_x, existing_y) in centers:
# Если расстояние по X меньше 100 и по Y меньше 200 — это та же самая карта
if abs(existing_x - center_x) < 100 and abs(existing_y - center_y) < 200:
is_duplicate = True
break
if not is_duplicate:
centers.append((center_x, center_y))
return centers
def main():
# Создаем папку для проблемных карт, если её нет
if not os.path.exists(MANUAL_FOLDER):
os.makedirs(MANUAL_FOLDER)
print("=== ЗАПУСК ПОЛНОЙ АВТОМАТИЗАЦИИ СБОРА КАРТ (МЕДЛЕННЫЙ РЕЖИМ) ===")
print("Инструкция: Откройте самый верх главного списка в Stocard.")
input("Нажмите Enter для старта...")
file_exists = os.path.isfile(OUTPUT_CSV)
with open(OUTPUT_CSV, mode="a", encoding="utf-8", newline="") as csv_file:
writer = csv.writer(csv_file)
if not file_exists:
writer.writerow(["Название", "Номер карты", "Тип штрихкода"])
saved_numbers = set()
consecutive_duplicates = 0
card_counter = 1
while True:
xml_file = get_current_xml()
coordinates = extract_coordinates(xml_file)
if not coordinates:
print("[ВНИМАНИЕ] На этом экране карты не найдены. Пробую прокрутить...")
scroll_down()
continue
new_cards_on_screen = 0
print(f"На текущем экране обнаружено точек для проверки: {len(coordinates)}")
for (x, y) in coordinates:
print(f"Кликаю на карту в точку ({x}, {y})...")
click_at(x, y)
# Получаем Название, Номер и Тип из обновленной функции
card_title, card_number, card_type = capture_and_scan(card_counter)
if card_number:
# Проверяем на дубликаты (для обычных карт) или сохраняем, если это PDF417
if card_number not in saved_numbers or card_type == "PDF417_OR_UNKNOWN":
saved_numbers.add(card_number)
# Записываем РЕАЛЬНОЕ НАЗВАНИЕ вместо "Карта Х"
writer.writerow([card_title, card_number, card_type])
csv_file.flush()
print(f"[СОХРАНЕНО] #{card_counter}: {card_title} -> {card_number} ({card_type})")
card_counter += 1
new_cards_on_screen += 1
consecutive_duplicates = 0
else:
print(f"[ДУБЛИКАТ] Карта магазина {card_title} уже в базе.")
else:
print("[ПРОПУСК] Экран пустой или произошла ошибка чтения.")
print("Возвращаюсь обратно...")
press_back()
if os.path.exists(xml_file):
os.remove(xml_file)
if new_cards_on_screen == 0:
consecutive_duplicates += 1
if consecutive_duplicates >= 2:
print("\n[УСПЕХ] Новые карты больше не появляются. Список полностью обработан!")
break
scroll_down()
print(f"\nСбор завершен! Всего сохранено строк: {card_counter - 1}")
print(f"Данные сохранены в: {os.path.abspath(OUTPUT_CSV)}")
if __name__ == "__main__":
main()