# ========================================== # crawlers/hyundai.py - 현대 크롤러 (8호기) # ========================================== import requests from .base import create_session def fetch_data(plant_info): """ 현대 발전소 데이터 수집 (Hi-Smart 3.0) """ plant_id = plant_info.get('id', 'hyundai-08') auth = plant_info.get('auth', {}) system = plant_info.get('system', {}) company_name = plant_info.get('company_name', '태양과바람') plant_name = plant_info.get('name', '8호기') user_id = auth.get('user_id', '') password = auth.get('password', '') site_id = auth.get('site_id', '') base_url = system.get('base_url', '') login_path = system.get('login_path', '') data_path = system.get('data_path', '') session = create_session() headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0 Safari/537.36', 'Content-Type': 'application/json;charset=UTF-8', 'Accept': 'application/json, text/plain, */*', 'Origin': base_url, 'Referer': f'{base_url}/', 'X-ApiVersion': 'v1.0', 'X-App': 'HIWAY4VUETIFY', 'X-CallType': '0', 'X-Channel': 'WEB_PC', 'X-Lang': 'ko', 'X-Mid': 'login', 'X-VName': 'UI' } # 로그인 login_urls = [ f"{base_url}{login_path}", f"{base_url}{login_path}.json", f"{base_url}{login_path}.do" ] login_success = False for url in login_urls: try: payload = {"user_id": user_id, "password": password} res = session.post(url, json=payload, headers=headers) if res.status_code == 200: auth_token = res.headers.get('x-auth-token') if auth_token: headers['x-auth-token'] = auth_token print(f" [현대] 로그인 성공 & 토큰 확보!") login_success = True break except Exception: continue if not login_success: print(f"❌ 현대 {plant_name} 로그인 실패") return [] # 데이터 요청 try: data_url = f"{base_url}{data_path}" params = {'site_id': site_id} # 데이터 요청용 헤더 업데이트 headers['X-Channel'] = 'WEB_PCWeb' headers['X-Mid'] = 'siteWork' res = session.get(data_url, params=params, headers=headers) if res.status_code != 200: print(f"❌ 현대 데이터 요청 실패 (코드: {res.status_code})") return [] data = res.json() if 'datas' in data and 'unitedSiteInfo' in data['datas']: info = data['datas']['unitedSiteInfo'] curr_kw = float(info.get('PVPCS_Pac', '0').replace(',', '')) today_kwh = float(info.get('PVPCS_Daily_P', '0').replace(',', '')) print(f" [현대] {plant_name} 데이터: {curr_kw}kW / {today_kwh}kWh") return [{ 'id': plant_id, 'name': f'{company_name} {plant_name}', 'kw': curr_kw, 'today': today_kwh, 'status': "🟢 정상" if curr_kw > 0 else "💤 대기" }] else: print(f"⚠️ 현대 데이터 구조가 다릅니다.") return [] except Exception as e: print(f"❌ 현대 파싱 에러: {e}") return [] def fetch_history_hourly(plant_info, start_date, end_date): """ 현대 발전소의 시간대별 과거 데이터 수집 Args: plant_info: { 'id': 'hyundai-08', 'name': '8호기', 'type': 'hyundai', 'auth': {'user_id': '...', 'password': '...', 'site_id': '...'}, 'system': {'base_url': '...', 'login_path': '...', 'data_path': '...'}, 'company_name': '태양과바람' } start_date: str, 시작일 (YYYY-MM-DD) end_date: str, 종료일 (YYYY-MM-DD) Returns: list: [{ 'plant_id': 'hyundai-08', 'timestamp': '2026-01-15 14:00:00', 'generation_kwh': 123.5, 'current_kw': 15.2 }, ...] """ from datetime import datetime, timedelta from .base import safe_float results = [] # 설정 추출 plant_id = plant_info.get('id', 'hyundai-08') auth = plant_info.get('auth', {}) system = plant_info.get('system', {}) plant_name = plant_info.get('name', '8호기') user_id = auth.get('user_id', '') password = auth.get('password', '') site_id = auth.get('site_id', '') base_url = system.get('base_url', '') login_path = system.get('login_path', '') session = create_session() print(f"\n{'='*60}") print(f"[Hyundai History] {plant_name} ({start_date} ~ {end_date})") print(f"{'='*60}") # 로그인 headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0 Safari/537.36', 'Content-Type': 'application/json;charset=UTF-8', 'Accept': 'application/json, text/plain, */*', 'Origin': base_url, 'Referer': f'{base_url}/', 'X-ApiVersion': 'v1.0', 'X-App': 'HIWAY4VUETIFY', 'X-CallType': '0', 'X-Channel': 'WEB_PC', 'X-Lang': 'ko', 'X-Mid': 'login', 'X-VName': 'UI' } login_urls = [ f"{base_url}{login_path}", f"{base_url}{login_path}.json", f"{base_url}{login_path}.do" ] login_success = False for url in login_urls: try: payload = {"user_id": user_id, "password": password} res = session.post(url, json=payload, headers=headers) if res.status_code == 200: auth_token = res.headers.get('x-auth-token') if auth_token: headers['x-auth-token'] = auth_token print(f" ✓ Login successful") login_success = True break except Exception: continue if not login_success: print(f" ✗ Login failed") return results # 날짜 범위 반복 current_date = datetime.strptime(start_date, '%Y-%m-%d') end_dt = datetime.strptime(end_date, '%Y-%m-%d') headers['X-Mid'] = 'siteWork' while current_date <= end_dt: date_str = current_date.strftime('%Y-%m-%d') print(f"\n[Processing Date] {date_str}") # getSolraDayWork 엔드포인트 사용 (20분 간격 데이터) url = f"{base_url}/hismart/site/getSolraDayWork" params = { 'site_id': site_id, 'startDate': date_str # YYYY-MM-DD 형식 } try: res = session.get(url, params=params, headers=headers, timeout=10) if res.status_code == 200: data = res.json() # solraDayWork 구조 파싱 day_work = data.get('datas', {}).get('solraDayWork', {}) run_data = day_work.get('runData', []) run_time = day_work.get('runTime', []) if run_data and run_time and len(run_data) == len(run_time): print(f" ✓ Found {len(run_data)} records (20-min intervals)") # runData와 runTime을 조합하여 시간대별 데이터 생성 for i in range(len(run_data)): time_str = run_time[i] # "14:20" 형식 generation_kw = safe_float(run_data[i]) # kW 값 # timestamp 생성 timestamp = f"{date_str} {time_str}:00" # 20분 간격 데이터를 그대로 저장 (또는 시간 단위로 집계 가능) results.append({ 'plant_id': plant_id, 'timestamp': timestamp, 'generation_kwh': generation_kw, # 실제로는 순간 kW값 'current_kw': generation_kw }) print(f" → Collected {len(run_data)} records") else: print(f" ⚠ No data for {date_str}") else: print(f" ✗ HTTP {res.status_code}") except Exception as e: print(f" ✗ Error: {e}") # 다음 날짜로 current_date += timedelta(days=1) print(f"\n{'='*60}") print(f"[Total] Collected {len(results)} records") print(f"{'='*60}\n") return results def fetch_history_daily(plant_info, start_date, end_date): """ 현대 발전소의 일별 과거 데이터 수집 (월 단위 최적화) getSolraMonthWork API를 사용하여 한 달치 일별 데이터를 한 번에 가져옴 """ from datetime import datetime from dateutil.relativedelta import relativedelta from .base import safe_float import calendar results = [] plant_id = plant_info.get('id', 'hyundai-08') auth = plant_info.get('auth', {}) system = plant_info.get('system', {}) plant_name = plant_info.get('name', '8호기') user_id = auth.get('user_id', '') password = auth.get('password', '') site_id = auth.get('site_id', '') base_url = system.get('base_url', '') login_path = system.get('login_path', '') session = create_session() print(f"\n{'='*60}") print(f"[Hyundai Daily] {plant_name} ({start_date} ~ {end_date}) - Looping by Month") print(f"{'='*60}") # 로그인 headers = { 'User-Agent': 'Mozilla/5.0', 'Content-Type': 'application/json;charset=UTF-8', 'X-ApiVersion': 'v1.0', 'X-App': 'HIWAY4VUETIFY', 'X-Channel': 'WEB_PC', 'X-Lang': 'ko', 'X-Mid': 'login', 'X-VName': 'UI' } login_url = f"{base_url}{login_path}" payload = {"user_id": user_id, "password": password} try: res = session.post(login_url, json=payload, headers=headers) auth_token = res.headers.get('x-auth-token') if not auth_token: print(" ✗ Login failed") return results headers['x-auth-token'] = auth_token headers['X-Mid'] = 'siteWork' print(" ✓ Login successful") except Exception as e: print(f" ✗ Login error: {e}") return results # 월 단위 반복 current_month = datetime.strptime(start_date[:7], '%Y-%m') # YYYY-MM-01 end_month_dt = datetime.strptime(end_date[:7], '%Y-%m') while current_month <= end_month_dt: month_str = current_month.strftime('%Y-%m') year = current_month.year month = current_month.month print(f" [Fetching] {month_str} ...", end="", flush=True) url = f"{base_url}/hismart/site/getSolraMonthWork" params = {'site_id': site_id, 'month': month_str} try: res = session.get(url, params=params, headers=headers, timeout=10) if res.status_code == 200: data = res.json() day_work = data.get('datas', {}).get('solraMonthWork', {}) run_data = day_work.get('runData', []) if run_data: count = 0 for day_idx, val in enumerate(run_data): day = day_idx + 1 daily_total = safe_float(val) # 유효한 날짜인지 확인 (예: 2월 30일 방지) try: # 해당 월의 마지막 날짜 확인 last_day = calendar.monthrange(year, month)[1] if day > last_day: continue date_str = f"{year}-{month:02d}-{day:02d}" # 요청된 날짜 범위 내인지 확인 if date_str >= start_date and date_str <= end_date: results.append({ 'plant_id': plant_id, 'date': date_str, 'generation_kwh': round(daily_total, 2) }) count += 1 except ValueError: continue print(f" OK ({count} days)") else: print(f" No data") else: print(f" HTTP {res.status_code}") except Exception as e: print(f" Error: {e}") current_month += relativedelta(months=1) print(f"\n[Total] Collected {len(results)} daily records\n") return results def fetch_history_monthly(plant_info, start_month, end_month): """ 현대 발전소의 월별 과거 데이터 수집 Args: plant_info: 발전소 정보 start_month: str, 시작월 (YYYY-MM) end_month: str, 종료월 (YYYY-MM) Returns: list: [{'plant_id': '...', 'month': '2026-01', 'generation_kwh': 12345.6}, ...] """ from datetime import datetime from dateutil.relativedelta import relativedelta from .base import safe_float results = [] plant_id = plant_info.get('id', 'hyundai-08') auth = plant_info.get('auth', {}) system = plant_info.get('system', {}) plant_name = plant_info.get('name', '8호기') user_id = auth.get('user_id', '') password = auth.get('password', '') site_id = auth.get('site_id', '') base_url = system.get('base_url', '') login_path = system.get('login_path', '') session = create_session() print(f"\n{'='*60}") print(f"[Hyundai Monthly] {plant_name} ({start_month} ~ {end_month})") print(f"{'='*60}") # 로그인 headers = { 'User-Agent': 'Mozilla/5.0', 'Content-Type': 'application/json;charset=UTF-8', 'X-ApiVersion': 'v1.0', 'X-App': 'HIWAY4VUETIFY', 'X-Channel': 'WEB_PC', 'X-Lang': 'ko', 'X-Mid': 'login', 'X-VName': 'UI' } login_url = f"{base_url}{login_path}" payload = {"user_id": user_id, "password": password} res = session.post(login_url, json=payload, headers=headers) auth_token = res.headers.get('x-auth-token') if not auth_token: print(" ✗ Login failed") return results headers['x-auth-token'] = auth_token headers['X-Mid'] = 'siteWork' print(" ✓ Login successful") current_month = datetime.strptime(start_month, '%Y-%m') end_month_dt = datetime.strptime(end_month, '%Y-%m') while current_month <= end_month_dt: month_str = current_month.strftime('%Y-%m') try: # 실제 확인된 월별 엔드포인트: getSolraMonthWork url = f"{base_url}/hismart/site/getSolraMonthWork" params = { 'site_id': site_id, 'month': month_str # YYYY-MM 형식 } res = session.get(url, params=params, headers=headers, verify=False, timeout=10) if res.status_code == 200: data = res.json() # 응답 구조: datas.solraMonthWork.runData = 일별 발전량 배열 if 'datas' in data and 'solraMonthWork' in data['datas']: month_data = data['datas']['solraMonthWork'] run_data = month_data.get('runData', []) # runData는 해당 월의 일별 발전량 배열 → 합산 monthly_kwh = sum(run_data) if run_data else 0.0 print(f" ✓ {month_str}: {monthly_kwh:.1f}kWh (from {len(run_data)} days)") results.append({ 'plant_id': plant_id, 'month': month_str, 'generation_kwh': monthly_kwh }) except Exception as e: print(f" ✗ {month_str}: {e}") current_month += relativedelta(months=1) print(f"[Total] Collected {len(results)} monthly records\n") return results