428 lines
15 KiB
Python
428 lines
15 KiB
Python
# ==========================================
|
|
# crawlers/cmsolar.py - CMSolar 크롤러 (10호기)
|
|
# ==========================================
|
|
|
|
import requests
|
|
from .base import create_session, safe_float
|
|
|
|
def fetch_data(plant_info):
|
|
"""
|
|
CMSolar 발전소 데이터 수집
|
|
"""
|
|
plant_id = plant_info.get('id', 'cmsolar-10')
|
|
auth = plant_info.get('auth', {})
|
|
system = plant_info.get('system', {})
|
|
company_name = plant_info.get('company_name', '함안햇빛발전소')
|
|
plant_name = plant_info.get('name', '10호기')
|
|
|
|
login_id = auth.get('payload_id', '')
|
|
login_pw = auth.get('payload_pw', '')
|
|
site_no = auth.get('site_no', '')
|
|
login_url = system.get('login_url', '')
|
|
data_url = system.get('data_url', '')
|
|
|
|
session = create_session()
|
|
|
|
headers = {
|
|
'User-Agent': 'Mozilla/5.0',
|
|
'Content-Type': 'application/x-www-form-urlencoded'
|
|
}
|
|
|
|
# 로그인
|
|
login_data = {
|
|
'login_id': login_id,
|
|
'login_pw': login_pw,
|
|
'site_no': site_no
|
|
}
|
|
|
|
try:
|
|
res = session.post(login_url, data=login_data, headers=headers)
|
|
if res.status_code != 200:
|
|
return []
|
|
except Exception as e:
|
|
print(f"❌ {plant_name} 접속 에러: {e}")
|
|
return []
|
|
|
|
# 데이터 요청
|
|
try:
|
|
res = session.get(data_url, headers=headers)
|
|
|
|
if res.status_code == 200:
|
|
data = res.json()
|
|
curr_kw = safe_float(data.get('current', data.get('power', 0)))
|
|
today_kwh = safe_float(data.get('today', data.get('generation', 0)))
|
|
status = "🟢 정상" if curr_kw > 0 else "💤 대기"
|
|
|
|
return [{
|
|
'id': plant_id,
|
|
'name': f'{company_name} {plant_name}',
|
|
'kw': curr_kw,
|
|
'today': today_kwh,
|
|
'status': status
|
|
}]
|
|
else:
|
|
return []
|
|
|
|
except Exception as e:
|
|
print(f"❌ {plant_name} 에러: {e}")
|
|
return []
|
|
|
|
|
|
def fetch_history_hourly(plant_info, start_date, end_date):
|
|
"""
|
|
CMSolar 발전소의 시간대별 과거 데이터 수집
|
|
|
|
실제 엔드포인트: /plant/sub/report_ok.php
|
|
파라미터: mode=getPowers&type=daily&device=total&start=YYYY-MM-DD&money=
|
|
"""
|
|
from datetime import datetime, timedelta
|
|
|
|
results = []
|
|
plant_id = plant_info.get('id', 'cmsolar-10')
|
|
auth = plant_info.get('auth', {})
|
|
system = plant_info.get('system', {})
|
|
plant_name = plant_info.get('name', '10호기')
|
|
|
|
login_id = auth.get('payload_id', '')
|
|
login_pw = auth.get('payload_pw', '')
|
|
site_no = auth.get('site_no', '')
|
|
login_url = system.get('login_url', '')
|
|
|
|
# 실제 데이터 엔드포인트
|
|
base_url = system.get('api_url', 'http://www.cmsolar2.kr')
|
|
data_url = f"{base_url}/plant/sub/report_ok.php"
|
|
|
|
session = create_session()
|
|
|
|
print(f"\n{'='*60}")
|
|
print(f"[CMSolar History] {plant_name} ({start_date} ~ {end_date})")
|
|
print(f"{'='*60}")
|
|
|
|
headers = {
|
|
'User-Agent': 'Mozilla/5.0',
|
|
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
|
|
}
|
|
|
|
login_data = {
|
|
'login_id': login_id,
|
|
'login_pw': login_pw,
|
|
'site_no': site_no
|
|
}
|
|
|
|
try:
|
|
res = session.post(login_url, data=login_data, headers=headers)
|
|
if res.status_code == 200:
|
|
print(" ✓ Login successful")
|
|
else:
|
|
print(" ✗ Login failed")
|
|
return results
|
|
except Exception as e:
|
|
print(f" ✗ Login error: {e}")
|
|
return results
|
|
|
|
# 사이트 선택 (필수!)
|
|
try:
|
|
change_url = f"{base_url}/change.php?site={site_no}"
|
|
session.get(change_url, headers=headers)
|
|
print(" ✓ Site selected")
|
|
except Exception as e:
|
|
print(f" ✗ Site selection error: {e}")
|
|
return results
|
|
|
|
# 날짜 반복
|
|
current_date = datetime.strptime(start_date, '%Y-%m-%d')
|
|
end_dt = datetime.strptime(end_date, '%Y-%m-%d')
|
|
|
|
while current_date <= end_dt:
|
|
date_str = current_date.strftime('%Y-%m-%d')
|
|
|
|
# 실제 확인된 시간별 엔드포인트 (type=daily는 하루 치 시간별 데이터 반환)
|
|
params = {
|
|
'mode': 'getPowers',
|
|
'type': 'daily',
|
|
'device': 'total',
|
|
'start': date_str,
|
|
'money': ''
|
|
}
|
|
|
|
try:
|
|
res = session.get(data_url, params=params, headers=headers, timeout=10)
|
|
|
|
if res.status_code == 200:
|
|
data = res.json()
|
|
# 시간별 데이터 파싱
|
|
hourly_data = data.get('data', []) or data.get('list', []) or data.get('powers', [])
|
|
|
|
if isinstance(hourly_data, list) and len(hourly_data) > 0:
|
|
print(f" ✓ Found {len(hourly_data)} hourly records for {date_str}")
|
|
|
|
for item in hourly_data:
|
|
hour = str(item.get('hour', item.get('time', '00'))).zfill(2)
|
|
generation_kwh = safe_float(item.get('power', item.get('generation', item.get('kwh', 0))))
|
|
current_kw = safe_float(item.get('kw', 0))
|
|
|
|
timestamp = f"{date_str} {hour}:00:00"
|
|
|
|
results.append({
|
|
'plant_id': plant_id,
|
|
'timestamp': timestamp,
|
|
'generation_kwh': generation_kwh,
|
|
'current_kw': current_kw
|
|
})
|
|
else:
|
|
print(f" ⚠ No data for {date_str}")
|
|
else:
|
|
print(f" ✗ HTTP {res.status_code} for {date_str}")
|
|
|
|
except Exception as e:
|
|
print(f" ✗ Error: {e}")
|
|
|
|
current_date += timedelta(days=1)
|
|
|
|
print(f"\n{'='*60}")
|
|
print(f"[Total] Collected {len(results)} hourly records")
|
|
print(f"{'='*60}\n")
|
|
|
|
return results
|
|
|
|
|
|
def fetch_history_daily(plant_info, start_date, end_date):
|
|
"""
|
|
CMSolar 발전소의 일별 과거 데이터 수집
|
|
|
|
실제 엔드포인트: /plant/sub/report_ok.php
|
|
파라미터: mode=getPowers&type=month&device=total&start=YYYY-MM-DD&money=
|
|
"""
|
|
from datetime import datetime
|
|
from dateutil.relativedelta import relativedelta
|
|
|
|
results = []
|
|
plant_id = plant_info.get('id', 'cmsolar-10')
|
|
auth = plant_info.get('auth', {})
|
|
system = plant_info.get('system', {})
|
|
plant_name = plant_info.get('name', '10호기')
|
|
|
|
login_id = auth.get('payload_id', '')
|
|
login_pw = auth.get('payload_pw', '')
|
|
site_no = auth.get('site_no', '')
|
|
login_url = system.get('login_url', '')
|
|
|
|
# 실제 데이터 엔드포인트
|
|
base_url = system.get('api_url', 'http://www.cmsolar2.kr')
|
|
data_url = f"{base_url}/plant/sub/report_ok.php"
|
|
|
|
session = create_session()
|
|
|
|
print(f"\n{'='*60}")
|
|
print(f"[CMSolar Daily] {plant_name} ({start_date} ~ {end_date})")
|
|
print(f"{'='*60}")
|
|
|
|
headers = {
|
|
'User-Agent': 'Mozilla/5.0',
|
|
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
|
|
}
|
|
|
|
login_data = {
|
|
'login_id': login_id,
|
|
'login_pw': login_pw,
|
|
'site_no': site_no
|
|
}
|
|
|
|
try:
|
|
res = session.post(login_url, data=login_data, headers=headers)
|
|
if res.status_code == 200:
|
|
print(" ✓ Login successful")
|
|
else:
|
|
print(" ✗ Login failed")
|
|
return results
|
|
except Exception as e:
|
|
print(f" ✗ Login error: {e}")
|
|
return results
|
|
|
|
# 사이트 선택 (필수!)
|
|
try:
|
|
change_url = f"{base_url}/change.php?site={site_no}"
|
|
session.get(change_url, headers=headers)
|
|
print(" ✓ Site selected")
|
|
except Exception as e:
|
|
print(f" ✗ Site selection error: {e}")
|
|
return results
|
|
|
|
# 월 단위로 반복 (type=month는 한 달 치 일별 데이터 반환)
|
|
current_date = datetime.strptime(start_date, '%Y-%m-%d')
|
|
end_dt = datetime.strptime(end_date, '%Y-%m-%d')
|
|
|
|
while current_date <= end_dt:
|
|
month_start = current_date.strftime('%Y-%m-01')
|
|
|
|
# 실제 확인된 일별 엔드포인트 (type=month)
|
|
params = {
|
|
'mode': 'getPowers',
|
|
'type': 'month',
|
|
'device': 'total',
|
|
'start': month_start,
|
|
'money': ''
|
|
}
|
|
|
|
try:
|
|
res = session.get(data_url, params=params, headers=headers, timeout=10)
|
|
|
|
if res.status_code == 200:
|
|
data = res.json()
|
|
# 일별 데이터 파싱
|
|
daily_data = data.get('data', []) or data.get('list', []) or data.get('powers', [])
|
|
|
|
if isinstance(daily_data, list) and len(daily_data) > 0:
|
|
print(f" ✓ Found {len(daily_data)} daily records for {month_start[:7]}")
|
|
|
|
for item in daily_data:
|
|
date_str = item.get('date', item.get('day', ''))
|
|
generation_kwh = safe_float(item.get('power', item.get('generation', item.get('kwh', 0))))
|
|
current_kw = safe_float(item.get('kw', 0))
|
|
|
|
# 날짜 범위 필터링
|
|
if date_str >= start_date and date_str <= end_date:
|
|
results.append({
|
|
'plant_id': plant_id,
|
|
'date': date_str,
|
|
'generation_kwh': generation_kwh,
|
|
'current_kw': current_kw
|
|
})
|
|
print(f" ✓ {date_str}: {generation_kwh:.2f}kWh")
|
|
else:
|
|
print(f" ✗ HTTP {res.status_code} for {month_start[:7]}")
|
|
|
|
except Exception as e:
|
|
print(f" ✗ Error: {e}")
|
|
|
|
# 다음 달로 이동
|
|
current_date = (current_date.replace(day=1) + relativedelta(months=1))
|
|
|
|
print(f"[Total] Collected {len(results)} daily records\n")
|
|
return results
|
|
|
|
|
|
def fetch_history_monthly(plant_info, start_month, end_month):
|
|
"""
|
|
CMSolar 발전소의 월별 과거 데이터 수집
|
|
|
|
실제 엔드포인트: /plant/sub/report_ok.php
|
|
파라미터: mode=getPowers&type=year&device=total&start=YYYY-MM-DD&money=
|
|
"""
|
|
from datetime import datetime
|
|
from dateutil.relativedelta import relativedelta
|
|
|
|
results = []
|
|
plant_id = plant_info.get('id', 'cmsolar-10')
|
|
auth = plant_info.get('auth', {})
|
|
system = plant_info.get('system', {})
|
|
plant_name = plant_info.get('name', '10호기')
|
|
|
|
login_id = auth.get('payload_id', '')
|
|
login_pw = auth.get('payload_pw', '')
|
|
site_no = auth.get('site_no', '')
|
|
login_url = system.get('login_url', '')
|
|
|
|
# 실제 데이터 엔드포인트
|
|
base_url = system.get('api_url', 'http://www.cmsolar2.kr')
|
|
data_url = f"{base_url}/plant/sub/report_ok.php"
|
|
|
|
session = create_session()
|
|
|
|
print(f"\n{'='*60}")
|
|
print(f"[CMSolar Monthly] {plant_name} ({start_month} ~ {end_month})")
|
|
print(f"{'='*60}")
|
|
|
|
headers = {
|
|
'User-Agent': 'Mozilla/5.0',
|
|
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
|
|
}
|
|
|
|
login_data = {
|
|
'login_id': login_id,
|
|
'login_pw': login_pw,
|
|
'site_no': site_no
|
|
}
|
|
|
|
try:
|
|
res = session.post(login_url, data=login_data, headers=headers)
|
|
if res.status_code == 200:
|
|
print(" ✓ Login successful")
|
|
else:
|
|
print(" ✗ Login failed")
|
|
return results
|
|
except Exception as e:
|
|
print(f" ✗ Login error: {e}")
|
|
return results
|
|
# 사이트 선택 (필수!)
|
|
try:
|
|
change_url = f"{base_url}/change.php?site={site_no}"
|
|
session.get(change_url, headers=headers)
|
|
print(" ✓ Site selected")
|
|
except Exception as e:
|
|
print(f" ✗ Site selection error: {e}")
|
|
return results
|
|
# 연도별로 반복 (type=year는 한 해 치 월별 데이터 반환)
|
|
current_date = datetime.strptime(start_month + '-01', '%Y-%m-%d')
|
|
end_date = datetime.strptime(end_month + '-01', '%Y-%m-%d')
|
|
|
|
years_processed = set()
|
|
|
|
while current_date <= end_date:
|
|
year_start = current_date.strftime('%Y-01-01')
|
|
year = current_date.year
|
|
|
|
# 중복 연도 스킵
|
|
if year in years_processed:
|
|
current_date += relativedelta(months=1)
|
|
continue
|
|
|
|
years_processed.add(year)
|
|
|
|
# 실제 확인된 월별 엔드포인트 (type=year)
|
|
params = {
|
|
'mode': 'getPowers',
|
|
'type': 'year',
|
|
'device': 'total',
|
|
'start': year_start,
|
|
'money': ''
|
|
}
|
|
|
|
try:
|
|
res = session.get(data_url, params=params, headers=headers, timeout=10)
|
|
|
|
if res.status_code == 200:
|
|
data = res.json()
|
|
# 월별 데이터 파싱
|
|
monthly_data = data.get('data', []) or data.get('list', []) or data.get('powers', [])
|
|
|
|
if isinstance(monthly_data, list) and len(monthly_data) > 0:
|
|
print(f" ✓ Found {len(monthly_data)} monthly records for {year}")
|
|
|
|
for item in monthly_data:
|
|
month_str = item.get('month', item.get('date', ''))
|
|
generation_kwh = safe_float(item.get('power', item.get('generation', item.get('kwh', 0))))
|
|
|
|
# YYYY-MM 형식으로 정규화
|
|
if len(month_str) >= 7:
|
|
month_str = month_str[:7]
|
|
|
|
# 월 범위 필터링
|
|
if month_str >= start_month and month_str <= end_month:
|
|
results.append({
|
|
'plant_id': plant_id,
|
|
'month': month_str,
|
|
'generation_kwh': generation_kwh
|
|
})
|
|
print(f" ✓ {month_str}: {generation_kwh:.1f}kWh")
|
|
else:
|
|
print(f" ✗ HTTP {res.status_code} for {year}")
|
|
|
|
except Exception as e:
|
|
print(f" ✗ Error: {e}")
|
|
|
|
current_date += relativedelta(months=1)
|
|
|
|
print(f"[Total] Collected {len(results)} monthly records\n")
|
|
return results
|