solorpower_crawler/crawlers/hyundai.py

490 lines
17 KiB
Python

# ==========================================
# 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