átlagos kiegészítséek jó sok
This commit is contained in:
214
backend/app/workers/vehicle/R2_generation_scout.py
Normal file
214
backend/app/workers/vehicle/R2_generation_scout.py
Normal file
@@ -0,0 +1,214 @@
|
||||
import asyncio
|
||||
import logging
|
||||
import random
|
||||
import re
|
||||
from playwright.async_api import async_playwright
|
||||
from sqlalchemy import text
|
||||
from app.database import AsyncSessionLocal
|
||||
|
||||
# --- NAPLÓZÁS KONFIGURÁCIÓ ---
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s [R2-AUTOS-ONLY] %(message)s',
|
||||
handlers=[logging.StreamHandler()]
|
||||
)
|
||||
logger = logging.getLogger("R2")
|
||||
|
||||
async def get_page_safe(page, url):
|
||||
"""
|
||||
Gondolatmenet: Az anti-bot védelem (Cloudflare) kijátszása érdekében
|
||||
véletlenszerű várakozást és valós User-Agent viselkedést szimulálunk.
|
||||
"""
|
||||
delay = random.uniform(4, 7)
|
||||
await asyncio.sleep(delay)
|
||||
|
||||
try:
|
||||
# A domcontentloaded gyorsabb, mint a networkidle, de elég a linkgyűjtéshez
|
||||
await page.goto(url, wait_until="domcontentloaded", timeout=60000)
|
||||
|
||||
# Ellenőrizzük, hogy nem kaptunk-e blokkoló oldalt
|
||||
title = await page.title()
|
||||
if "Just a moment" in title or "Cloudflare" in title:
|
||||
raise Exception(f"Bot védelem észlelve az URL-en: {url}")
|
||||
|
||||
return page
|
||||
except Exception as e:
|
||||
logger.error(f"Hiba az oldal betöltésekor: {url} -> {e}")
|
||||
raise
|
||||
|
||||
async def extract_scoped_links(page, p_id, current_url):
|
||||
"""
|
||||
Gondolatmenet: A 'Scope-Lock' technika lényege, hogy az URL-kből kinyert
|
||||
márkanév horgony (anchor) segítségével megakadályozzuk, hogy a robot
|
||||
kilépjen a jelenlegi autócsalád környezetéből.
|
||||
|
||||
Javítás: Beépített nyelvi szűrő és 'Language Shield' a nem kívánt (görög, spanyol, bolgár stb.)
|
||||
változatok elkerülésére. Minden talált új linket 'car' kategóriával mentünk el.
|
||||
"""
|
||||
# Kinyerjük a márka/típus nevét az URL-ből (pl. 'alfa-romeo')
|
||||
url_parts = current_url.split('/')[-1].split('-')
|
||||
brand_anchor = "-".join(url_parts[:2])
|
||||
|
||||
# Csak azokat a linkeket gyűjtjük, amik valódi navigációt jelentenek
|
||||
hrefs = await page.eval_on_selector_all(
|
||||
"a",
|
||||
"nodes => nodes.map(n => ({ 'name': n.innerText.trim(), 'url': n.href }))"
|
||||
)
|
||||
|
||||
found_count = 0
|
||||
async with AsyncSessionLocal() as db:
|
||||
for link in hrefs:
|
||||
url = link['url']
|
||||
name = link['name'].replace('\n', ' ').strip()
|
||||
|
||||
# --- 1. ALAPVETŐ ÉRVÉNYESSÉG ---
|
||||
if not name or len(name) < 2:
|
||||
continue
|
||||
|
||||
# --- 2. LANGUAGE SHIELD (ÚJ VÉDELEM) ---
|
||||
# Karakterkészlet ellenőrzés: Ha görög, cirill vagy egyéb nem latin karakter van benne, eldobjuk.
|
||||
if re.search(r'[^\x00-\x7F]+', name):
|
||||
continue
|
||||
|
||||
# Szigorított angol-kényszerítés az URL-ben
|
||||
if '/en/' not in url:
|
||||
continue
|
||||
|
||||
# Szövegalapú zajszűrés (Meta-linkek kizárása)
|
||||
junk_keywords = [
|
||||
'privacy', 'configuracion', 'ρυθμίσεις', 'cookie', 'settings',
|
||||
'contact', 'about us', 'terms', 'advertising', 'login', 'registration',
|
||||
'pribatutasun', 'configuració', 'naslovnica', 'stisni',
|
||||
'personvern', 'prywatnosci', 'ustawienia', 'endre', 'zmień'
|
||||
]
|
||||
if any(junk in name.lower() for junk in junk_keywords):
|
||||
continue
|
||||
|
||||
# --- 3. EREDETI NYELVI SZŰRŐ (Language Lock) ---
|
||||
# Megtartva az eredeti logikát: domain.com/bg/..., domain.com/se/...
|
||||
path_segments = url.split('/')
|
||||
if len(path_segments) > 3:
|
||||
lang_segment = path_segments[3]
|
||||
if len(lang_segment) == 2 and lang_segment != 'en':
|
||||
continue
|
||||
|
||||
# --- 4. SCOPE SZŰRÉS ---
|
||||
# Csak az adott márkához tartozó linkeket engedjük át
|
||||
if brand_anchor not in url:
|
||||
continue
|
||||
|
||||
# --- 5. NAVIGÁCIÓS SZŰRÉS ---
|
||||
# Ne lépjen vissza a listákhoz, és zárjuk ki az idegen nyelvű könyvtárakat (teljes lista)
|
||||
excluded_patterns = [
|
||||
'-brand-', 'allbrands', 'en/brands',
|
||||
'/bg/', '/ru/', '/de/', '/it/', '/fr/', '/es/',
|
||||
'/tr/', '/ro/', '/fi/', '/se/', '/no/', '/pl/', '/gr/',
|
||||
'/hr/', '/cz/', '/sk/', '/ua/'
|
||||
]
|
||||
if any(x in url for x in excluded_patterns):
|
||||
continue
|
||||
|
||||
# --- 6. ÖNHIVATKOZÁS SZŰRÉS ---
|
||||
if url.strip('/') == current_url.strip('/'):
|
||||
continue
|
||||
|
||||
# --- 7. SZINT MEGHATÁROZÁSA MINTÁZAT ALAPJÁN ---
|
||||
if '-generation-' in url:
|
||||
target_level = 'generation'
|
||||
elif re.search(r'-\d+$', url) and '-model-' not in url:
|
||||
target_level = 'engine'
|
||||
else:
|
||||
continue
|
||||
|
||||
# --- 8. MENTÉS AZ ADATBÁZISBA ---
|
||||
await db.execute(text("""
|
||||
INSERT INTO vehicle.auto_data_crawler_queue (url, level, parent_id, name, status, category)
|
||||
VALUES (:url, :level, :p_id, :name, 'pending', 'car')
|
||||
ON CONFLICT (url) DO NOTHING
|
||||
"""), {"url": url, "level": target_level, "p_id": p_id, "name": name})
|
||||
found_count += 1
|
||||
|
||||
await db.commit()
|
||||
return found_count
|
||||
|
||||
async def process_target(context, t_id, t_url, t_name, t_level):
|
||||
"""
|
||||
Gondolatmenet: Egy adott feladat (URL) teljes körű feldolgozása.
|
||||
A volume mapping miatt a módosítás azonnal látszik a konténerben is.
|
||||
"""
|
||||
page = await context.new_page()
|
||||
try:
|
||||
logger.info(f"🚀 Autós felderítés indítása [{t_level}]: {t_name}")
|
||||
await get_page_safe(page, t_url)
|
||||
|
||||
# Linkek kinyerése és mentése
|
||||
found = await extract_scoped_links(page, t_id, t_url)
|
||||
|
||||
async with AsyncSessionLocal() as db:
|
||||
new_status = 'completed' if found > 0 else 'completed_leaf'
|
||||
await db.execute(text("""
|
||||
UPDATE vehicle.auto_data_crawler_queue
|
||||
SET status = :s, error_msg = NULL, updated_at = NOW()
|
||||
WHERE id = :id
|
||||
"""), {"s": new_status, "id": t_id})
|
||||
await db.commit()
|
||||
|
||||
logger.info(f"✅ Befejezve: {t_name} -> {found} új link.")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Kritikus hiba feldolgozás közben ({t_name}): {e}")
|
||||
async with AsyncSessionLocal() as db:
|
||||
await db.execute(text("""
|
||||
UPDATE vehicle.auto_data_crawler_queue
|
||||
SET status = 'error', error_msg = :msg, updated_at = NOW()
|
||||
WHERE id = :id
|
||||
"""), {"msg": str(e), "id": t_id})
|
||||
await db.commit()
|
||||
finally:
|
||||
await page.close()
|
||||
|
||||
async def main():
|
||||
"""
|
||||
Gondolatmenet: A fő vezérlő hurok.
|
||||
STRATÉGIA: Csak a 'car' kategóriájú feladatokat vesszük fel (category='car').
|
||||
"""
|
||||
async with async_playwright() as p:
|
||||
browser = await p.chromium.launch(headless=True)
|
||||
context = await browser.new_context(
|
||||
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
|
||||
viewport={'width': 1920, 'height': 1080}
|
||||
)
|
||||
|
||||
logger.info("🤖 R2 Autós Felderítő Robot aktív. (Filter: category='car')")
|
||||
|
||||
while True:
|
||||
async with AsyncSessionLocal() as db:
|
||||
# Csak 'car' kategóriájú, pending feladatok lekérése
|
||||
res = await db.execute(text("""
|
||||
UPDATE vehicle.auto_data_crawler_queue SET status = 'processing'
|
||||
WHERE id = (
|
||||
SELECT id FROM vehicle.auto_data_crawler_queue
|
||||
WHERE status = 'pending'
|
||||
AND level IN ('model', 'generation')
|
||||
AND category = 'car'
|
||||
ORDER BY level ASC, id ASC
|
||||
LIMIT 1 FOR UPDATE SKIP LOCKED
|
||||
) RETURNING id, url, name, level
|
||||
"""))
|
||||
target = res.fetchone()
|
||||
await db.commit()
|
||||
|
||||
if not target:
|
||||
logger.info("🏁 Nincs több autós feladat (car). Alvás 60mp...")
|
||||
await asyncio.sleep(60)
|
||||
continue
|
||||
|
||||
await process_target(context, target[0], target[1], target[2], target[3])
|
||||
|
||||
await browser.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
asyncio.run(main())
|
||||
except KeyboardInterrupt:
|
||||
logger.info("🛑 Felhasználói leállítás (Ctrl+C).")
|
||||
Reference in New Issue
Block a user