Add get_screens.py
This commit is contained in:
parent
428b21e33e
commit
2e6d80c61c
232
get_screens.py
Normal file
232
get_screens.py
Normal 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()
|
||||||
Loading…
x
Reference in New Issue
Block a user