domovoy_bot/scraper_gis/ultimate_reaper.py

312 lines
13 KiB
Python
Raw 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.

import asyncio
import os
import logging
import random
from datetime import datetime
from playwright.async_api import async_playwright
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[logging.FileHandler("logs/reaper.log"), logging.StreamHandler()])
logger = logging.getLogger("Ultimate-Reaper")
USER_DATA_DIR = "/app/browser_context"
OBSIDIAN_BASE = "/app/obsidian/Gemini/Projects/GIS_ZHKH"
NAS_DOWNLOADS = "/app/downloads/nas/gis_archive"
async def send_tg_report(message):
token = os.getenv("TELEGRAM_BOT_TOKEN")
chat_id = os.getenv("TELEGRAM_CHAT_ID")
if not token or not chat_id: return
import aiohttp
url = f"https://api.telegram.org/bot{token}/sendMessage"
try:
async with aiohttp.ClientSession() as session:
await session.post(url, json={"chat_id": chat_id, "text": message, "parse_mode": "HTML"})
except: pass
async def human_delay():
# Имитируем медлительного человека: от 5 до 22 секунд
delay = random.uniform(5, 22)
logger.info(f"Human delay: {delay:.2f}s")
await asyncio.sleep(delay)
async def take_screenshot(page, title, subfolder):
safe_name = "".join([c for c in title if c.isalnum() or c in (' ', '_', '-')]).strip().replace(" ", "_")
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"{safe_name}_{timestamp}.png"
path = f"{OBSIDIAN_BASE}/{subfolder}/{filename}"
os.makedirs(os.path.dirname(path), exist_ok=True)
await page.screenshot(path=path, full_page=True)
return filename
async def get_all_frames(page):
return [page] + page.frames
async def download_files_from_frames(page, save_folder):
files_saved = []
dest_dir = f"{NAS_DOWNLOADS}/{save_folder}"
os.makedirs(dest_dir, exist_ok=True)
selectors = [
"a[title*='Скачать']", "button:has-text('Скачать')", "a:has-text('Скачать все')",
"a[href*='.pdf']", "a[href*='.zip']", "a[href*='.doc']", "a[href*='.rar']"
]
for f in await get_all_frames(page):
for selector in selectors:
try:
elements = await f.locator(selector).all()
for el in elements:
if not await el.is_visible(): continue
try:
async with page.expect_download(timeout=15000) as di:
await el.click()
download = await di.value
fname = download.suggested_filename
dest = f"{dest_dir}/{fname}"
await download.save_as(dest)
files_saved.append(fname)
except: continue
except: continue
return files_saved
async def wait_for_user_login(page):
await send_tg_report("🔍 <b>Инициализация.</b>\nЯ открыл страницу ГИС ЖКХ и жду, когда ты залогинишься и зайдешь в Личный Кабинет.\nURL: http://192.168.10.116:6080/vnc.html")
await page.goto("https://dom.gosuslugi.ru/")
await human_delay()
for _ in range(60): # Ждем долго (около 20 минут в сумме с задержками)
content = ""
for f in await get_all_frames(page):
try: content += await f.content()
except: pass
if "Выйти" in content or "Александрович" in content:
await send_tg_report("✅ <b>Успех:</b> Я вижу Личный Кабинет! Начинаю работу.")
return True
await asyncio.sleep(20)
return False
async def map_dashboard(page):
await send_tg_report("🗺️ <b>Начинаю картографирование Личного Кабинета.</b>")
await human_delay()
scr = await take_screenshot(page, "Dashboard", "reports")
buttons = set()
for f in await get_all_frames(page):
try:
els = await f.locator("a, button, [role='button'], .menu-item").all()
for el in els:
if await el.is_visible():
txt = (await el.inner_text()).strip()
if txt and len(txt) > 3:
buttons.add(txt)
except: pass
path = f"{OBSIDIAN_BASE}/reports/Dashboard_Map.md"
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, "w", encoding="utf-8") as f:
f.write("# Карта Личного Кабинета\n\n")
f.write(f"![[Gemini/Projects/GIS_ZHKH/reports/{scr}]]\n\n")
f.write("## Найденные элементы:\n")
for b in sorted(buttons):
f.write(f"- `{b}`\n")
await send_tg_report("✅ <b>Карта составлена</b> и сохранена в Obsidian (reports/Dashboard_Map.md).")
await human_delay()
async def process_notifications(page):
await send_tg_report("📦 <b>Раздел: Информационные сообщения.</b> Захожу...")
await page.goto("https://my.dom.gosuslugi.ru/citizen-cabinet/#!/hcsi/nnl?tab=INCOMING")
await human_delay()
scr = await take_screenshot(page, "Уведомления", "notifications")
files = await download_files_from_frames(page, "notifications")
path = f"{OBSIDIAN_BASE}/reports/Notifications.md"
with open(path, "w", encoding="utf-8") as f:
f.write("# Информационные сообщения\n\n")
f.write(f"![[Gemini/Projects/GIS_ZHKH/notifications/{scr}]]\n\n")
f.write("## Файлы:\n")
for file in files: f.write(f"- `{file}`\n")
await send_tg_report(f"✅ <b>Уведомления:</b> Скриншот сделан, скачано {len(files)} файлов.")
await human_delay()
async def process_appeals(page):
await send_tg_report("✉️ <b>Раздел: Обращения и ответы.</b> Захожу...")
await page.goto("https://my.dom.gosuslugi.ru/citizen-cabinet/#!/appeals")
await human_delay()
# Ждем загрузки списка
await asyncio.sleep(5)
scr = await take_screenshot(page, "Обращения_Список", "appeals")
page_num = 1
processed = set()
while True:
await send_tg_report(f"📄 Обращения: Обрабатываю страницу {page_num}...")
while True:
targets = []
for f in await get_all_frames(page):
try:
links = await f.locator("a").all()
for l in links:
if await l.is_visible():
txt = (await l.inner_text()).strip()
# Ищем ссылки-номера обращений или темы
if len(txt) > 10 and txt not in processed and ("" in txt or "Ответ" in txt or "Обращение" in txt):
# Исключаем пагинацию и меню
if "следующая" not in txt.lower() and "предыдущая" not in txt.lower():
targets.append((l, txt))
except: pass
if targets: break
if not targets: break
el, txt = targets[0]
safe_name = "".join([c for c in txt if c.isalnum() or c in (' ', '_', '-')]).strip().replace(" ", "_")
processed.add(txt)
await send_tg_report(f"⏳ Открываю обращение: <code>{txt[:40]}...</code>")
try:
await el.click()
await human_delay() # Имитация человека
inner_scr = await take_screenshot(page, safe_name, "appeals")
files = await download_files_from_frames(page, f"appeals/{safe_name}")
path = f"{OBSIDIAN_BASE}/reports/Appeal_{safe_name}.md"
with open(path, "w", encoding="utf-8") as f_out:
f_out.write(f"# Обращение: {txt}\n\n![[Gemini/Projects/GIS_ZHKH/appeals/{inner_scr}]]\n\n## Файлы:\n")
if files:
for fn in files: f_out.write(f"- `{fn}`\n")
else:
f_out.write("Файлов нет.\n")
await send_tg_report(f"✅ Готово. Файлов: {len(files)}")
await page.go_back()
await human_delay()
except Exception as e:
logger.error(f"Error on appeal {txt}: {e}")
await page.goto("https://my.dom.gosuslugi.ru/citizen-cabinet/#!/appeals")
await human_delay()
# Пагинация
next_found = False
for f in await get_all_frames(page):
try:
btn = f.locator("a:has-text('следующая')").first
if await btn.is_visible():
await btn.click()
page_num += 1
next_found = True
await human_delay()
break
except: pass
if not next_found: break
await send_tg_report("🏆 <b>Раздел Обращений выкачан полностью!</b>")
async def process_contracts(page):
await send_tg_report("📑 <b>Раздел: Информация о договорах по дому.</b> Захожу...")
await page.goto("https://dom.gosuslugi.ru/#!/house-info")
await human_delay()
# Клик по иконке раздела
clicked = False
for f in await get_all_frames(page):
try:
btn = f.locator("text='Информация о договорах по дому'").first
if await btn.is_visible():
await btn.click()
await human_delay()
clicked = True
break
except: pass
if not clicked:
await send_tg_report("⚠️ Не смог найти раздел 'Информация о договорах по дому'.")
return
# Клик Найти
for f in await get_all_frames(page):
try:
btn = f.locator("button:has-text('Найти')").first
if await btn.is_visible():
await btn.click()
await human_delay()
break
except: pass
processed = set()
while True:
targets = []
for f in await get_all_frames(page):
try:
elements = await f.locator("a").all()
for el in elements:
if await el.is_visible():
txt = (await l.inner_text()).strip() if hasattr(el, 'inner_text') else (await el.inner_text()).strip()
if txt.startswith("Договор") and "Закон" not in txt and txt not in processed and len(txt) > 15:
targets.append((el, txt))
except: pass
if targets: break
if not targets: break
el, txt = targets[0]
safe_name = "".join([c for c in txt if c.isalnum() or c in (' ', '_', '-')]).strip().replace(" ", "_")
processed.add(txt)
await send_tg_report(f"⏳ Открываю договор: <code>{txt[:40]}...</code>")
try:
await el.click()
await human_delay()
inner_scr = await take_screenshot(page, safe_name, "contracts")
files = await download_files_from_frames(page, f"contracts/{safe_name}")
path = f"{OBSIDIAN_BASE}/reports/Contract_{safe_name}.md"
with open(path, "w", encoding="utf-8") as f_out:
f_out.write(f"# {txt}\n\n![[Gemini/Projects/GIS_ZHKH/contracts/{inner_scr}]]\n\n## Файлы:\n")
if files:
for fn in files: f_out.write(f"- `{fn}`\n")
else:
f_out.write("Файлов нет.\n")
await send_tg_report(f"✅ Готово. Файлов: {len(files)}")
await page.go_back()
await human_delay()
except Exception as e:
logger.error(f"Error on {txt}: {e}")
await page.go_back()
await human_delay()
await send_tg_report("🏆 <b>Раздел Договоров выкачан полностью!</b>")
async def ultimate_mission():
async with async_playwright() as p:
context = await p.chromium.launch_persistent_context(
user_data_dir=USER_DATA_DIR, headless=False, args=["--no-sandbox", "--disable-setuid-sandbox", "--start-maximized"]
)
page = context.pages[0] if context.pages else await context.new_page()
await page.set_viewport_size({"width": 1600, "height": 1024})
if await wait_for_user_login(page):
await map_dashboard(page)
await process_notifications(page)
await process_appeals(page)
await process_contracts(page)
await send_tg_report("🎉 <b>ЖНЕЦ-УЛЬТИМАТУМ ЗАВЕРШИЛ РАБОТУ!</b>")
else:
await send_tg_report("❌ Таймаут ожидания логина. Отбой миссии.")
while True: await asyncio.sleep(3600)
if __name__ == "__main__":
asyncio.run(ultimate_mission())