Files
wiki/tasks/internet-orders/windows_client.py
2026-04-12 21:55:33 +03:00

235 lines
9.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
vprok.ru Windows Playwright Client
====================================
Каждые 30 секунд проверяет сервер-реле на наличие задания.
Если задание есть — открывает vprok.ru и добавляет товары в корзину.
Требования:
pip install playwright requests
playwright install chromium
"""
import time
import requests
from playwright.sync_api import sync_playwright, TimeoutError as PwTimeout
# ─── Настройки ───────────────────────────────────────────────────────────────
SERVER_URL = "http://185.130.212.192:5000"
API_KEY = "vprok2024secret"
POLL_INTERVAL = 30 # секунды между проверками
# ─────────────────────────────────────────────────────────────────────────────
HEADERS = {"X-Api-Key": API_KEY, "Content-Type": "application/json"}
def log(msg: str):
ts = time.strftime("%H:%M:%S")
print(f"[{ts}] {msg}", flush=True)
def fetch_task():
"""Забрать задание с сервера. Возвращает dict или None."""
try:
r = requests.get(f"{SERVER_URL}/task", headers=HEADERS, timeout=10)
if r.status_code == 204:
return None
if r.status_code == 200:
return r.json()
log(f"⚠️ Неожиданный статус от сервера: {r.status_code}")
return None
except Exception as e:
log(f"❌ Ошибка подключения к серверу: {e}")
return None
def report_done(status: str, message: str):
"""Отправить результат на сервер."""
try:
requests.post(
f"{SERVER_URL}/task/done",
headers=HEADERS,
json={"status": status, "message": message},
timeout=10
)
log(f"✅ Результат отправлен: [{status}] {message}")
except Exception as e:
log(f"Не удалось отправить результат: {e}")
def add_item_to_cart(page, query: str, qty: int) -> tuple[bool, str]:
"""
Ищет товар на vprok.ru и добавляет qty раз в корзину.
Возвращает (success, message).
"""
log(f"🔍 Ищу: «{query}» (кол-во: {qty})")
try:
# Переходим на главную и кликаем поиск
page.goto("https://www.vprok.ru/", wait_until="domcontentloaded", timeout=30000)
page.wait_for_timeout(1500)
# Находим поле поиска и вводим запрос
search_input = page.locator(
"input[placeholder*='оиск'], input[type='search'], [data-testid='search-input'], .search__input"
).first
search_input.click()
search_input.fill("")
search_input.type(query, delay=60)
search_input.press("Enter")
log(f" ⏳ Жду результаты поиска...")
page.wait_for_load_state("domcontentloaded")
page.wait_for_timeout(2500)
# Ищем кнопки добавления — берём первый доступный товар
# vprok.ru использует разные классы, пробуем несколько вариантов
add_button_selectors = [
"button:has-text('Добавить'):not([disabled])",
"button:has-text('В корзину'):not([disabled])",
"[class*='add-to-cart']:not([disabled])",
"[data-testid*='add']:not([disabled])",
]
add_btn = None
for selector in add_button_selectors:
btns = page.locator(selector)
count = btns.count()
if count > 0:
# Проверяем что рядом нет текста "Нет в наличии"
for i in range(min(count, 5)):
btn = btns.nth(i)
try:
# Ищем ближайший контейнер товара
product_card = btn.locator("xpath=ancestor::*[contains(@class,'product') or contains(@class,'item') or contains(@class,'card')][1]")
card_text = product_card.inner_text(timeout=1000) if product_card.count() > 0 else ""
if "нет в наличии" in card_text.lower() or "недоступен" in card_text.lower():
log(f" ⏭️ Товар #{i+1} недоступен, пропускаю")
continue
add_btn = btn
break
except Exception:
add_btn = btn
break
if add_btn:
break
if add_btn is None:
return False, f"Кнопка 'Добавить' не найдена для «{query}»"
# Добавляем нужное количество раз
for n in range(qty):
try:
add_btn.scroll_into_view_if_needed()
add_btn.click(timeout=5000)
log(f" Добавлено ({n+1}/{qty})")
page.wait_for_timeout(800)
# После первого клика кнопка может смениться на +/-
if n < qty - 1:
plus_btn = page.locator(
"button:has-text('+'), [aria-label*='увеличить'], [class*='plus']:not([disabled])"
).first
if plus_btn.count() > 0 and plus_btn.is_visible():
add_btn = plus_btn
except PwTimeout:
log(f" ⚠️ Таймаут при клике на кнопку (попытка {n+1})")
return True, f"«{query}» добавлен(о) ×{qty}"
except Exception as e:
return False, f"Ошибка при обработке «{query}»: {e}"
def process_task(task: dict):
"""Обрабатывает задание: открывает браузер и добавляет все товары."""
items = task.get("items", [])
task_id = task.get("id", "?")
log(f"📦 Задание #{task_id} получено. Товаров: {len(items)}")
results = []
errors = []
with sync_playwright() as pw:
log("🌐 Запускаю браузер...")
browser = pw.chromium.launch(
headless=False,
args=["--start-maximized"]
)
context = browser.new_context(
viewport=None, # использовать размер окна
locale="ru-RU",
timezone_id="Europe/Moscow"
)
page = context.new_page()
for item in items:
query = item.get("query", "").strip()
qty = int(item.get("qty", 1))
if not query:
continue
success, msg = add_item_to_cart(page, query, qty)
results.append(msg)
if not success:
errors.append(msg)
log(f" {'' if success else ''} {msg}")
time.sleep(1)
log("🛒 Все товары обработаны. Перехожу в корзину...")
try:
# Пытаемся открыть корзину
cart_link = page.locator("a[href*='cart'], a[href*='basket'], [data-testid*='cart']").first
if cart_link.count() > 0:
cart_link.click()
page.wait_for_load_state("domcontentloaded")
else:
page.goto("https://www.vprok.ru/cart", wait_until="domcontentloaded", timeout=15000)
except Exception:
pass
log("👁️ Браузер оставлен открытым — проверьте корзину!")
log(" (закройте браузер вручную когда будете готовы)")
# Ждём пока пользователь не закроет браузер
try:
page.wait_for_event("close", timeout=0) # бесконечно
except Exception:
pass
# Отправляем результат
if errors:
status_str = "partial"
message = f"Выполнено с ошибками: {'; '.join(errors)}"
else:
status_str = "ok"
message = f"Все {len(results)} товар(ов) добавлены: {', '.join(results)}"
report_done(status_str, message)
try:
browser.close()
except Exception:
pass
def main():
log("🚀 vprok.ru клиент запущен")
log(f" Сервер: {SERVER_URL}")
log(f" Интервал проверки: {POLL_INTERVAL} сек")
log(" Нажмите Ctrl+C для остановки\n")
while True:
task = fetch_task()
if task:
process_task(task)
else:
log(f"💤 Нет заданий, жду {POLL_INTERVAL} сек...")
time.sleep(POLL_INTERVAL)
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
log("\n👋 Остановка по запросу пользователя")