144 lines
5.8 KiB
Python
144 lines
5.8 KiB
Python
from selectolax.parser import HTMLParser
|
|
from curl_cffi import requests
|
|
import os
|
|
|
|
def parse_weather_data(html_content):
|
|
tree = HTMLParser(html_content)
|
|
|
|
print(f"{'날짜':<12} {'시간':<6} {'날씨':<8} {'기온':<6} {'체감':<6} {'강수확률':<8} {'바람':<14} {'습도':<6}")
|
|
print("-" * 80)
|
|
|
|
# [수정 1] 타겟 슬라이더 변경
|
|
# dfs-daily-slide(위쪽 요약) 대신 slide-wrap 안의 slide(아래쪽 상세)를 찾아야 함
|
|
# 구조: <div class="slide-wrap"> -> <div class="slide"> -> <div class="daily">
|
|
slides = tree.css("div.slide-wrap div.slide")
|
|
|
|
if not slides:
|
|
print(">> 데이터를 포함한 슬라이드를 찾지 못했습니다. HTML 구조가 변경되었을 수 있습니다.")
|
|
return
|
|
|
|
for slide in slides:
|
|
# 날짜는 slide 바로 아래 div.daily 태그의 data-date 속성에 있습니다.
|
|
daily_node = slide.css_first("div.daily")
|
|
if not daily_node:
|
|
continue
|
|
|
|
date_str = daily_node.attributes.get('data-date', '').strip()
|
|
|
|
# [수정 2] 상세 데이터 리스트 찾기
|
|
# <ul class="item ..."> 형태를 모두 찾습니다.
|
|
ul_items = daily_node.css("ul") # daily_node 내부에서 검색
|
|
|
|
for ul in ul_items:
|
|
# class에 'item'이 없는 ul(헤더 등)은 건너뜀
|
|
if "item" not in ul.attributes.get("class", ""):
|
|
continue
|
|
|
|
# 데이터를 저장할 딕셔너리
|
|
w_data = {
|
|
"time": "-", "weather": "-", "temp": "-",
|
|
"feels_like": "-", "prob": "-", "wind": "-", "humid": "-"
|
|
}
|
|
|
|
# 속성에서 시간 가져오기 (가장 정확함)
|
|
if 'data-time' in ul.attributes:
|
|
w_data['time'] = ul.attributes['data-time']
|
|
|
|
# li 태그 순회 (숨겨진 라벨 기반 매핑)
|
|
lis = ul.css("li")
|
|
for li in lis:
|
|
label_node = li.css_first("span.hid")
|
|
if not label_node:
|
|
continue
|
|
|
|
label_text = label_node.text(strip=True)
|
|
|
|
# 1. 시각 (속성값이 없을 경우 대비)
|
|
if "시각" in label_text and w_data['time'] == "-":
|
|
val = li.css_first("span:not(.hid)")
|
|
if val: w_data['time'] = val.text(strip=True)
|
|
|
|
# 2. 날씨
|
|
elif "날씨" in label_text:
|
|
wic = li.css_first(".wic")
|
|
if wic:
|
|
w_data['weather'] = wic.attributes.get("title") or wic.text(strip=True)
|
|
|
|
# 3. 기온 (체감온도 괄호 제거)
|
|
elif "기온" in label_text:
|
|
feel_node = li.css_first(".feel")
|
|
if feel_node:
|
|
# deep=False로 자식 노드 제외하고 순수 텍스트만 추출
|
|
w_data['temp'] = feel_node.text(deep=False, strip=True)
|
|
|
|
# 4. 체감온도
|
|
elif "체감온도" in label_text and "기온" not in label_text:
|
|
spans = li.css("span")
|
|
if len(spans) > 1:
|
|
w_data['feels_like'] = spans[-1].text(strip=True)
|
|
|
|
# 5. 강수확률
|
|
elif "강수확률" in label_text:
|
|
spans = li.css("span")
|
|
if len(spans) > 1:
|
|
prob = spans[-1].text(strip=True)
|
|
w_data['prob'] = prob if prob else "-"
|
|
|
|
# 6. 바람 (텍스트 정제)
|
|
elif "바람" in label_text:
|
|
wd_node = li.css_first(".wdic")
|
|
# 약/강 텍스트(.qwsd) 제외하고 숫자(.wspd)만
|
|
ws_node = li.css_first(".wspd:not(.qwsd)")
|
|
|
|
wd = wd_node.text(strip=True) if wd_node else ""
|
|
ws = ws_node.text(strip=True) if ws_node else ""
|
|
w_data['wind'] = f"{wd} {ws}".strip()
|
|
|
|
# 7. 습도
|
|
elif "습도" in label_text:
|
|
spans = li.css("span")
|
|
if len(spans) > 1:
|
|
w_data['humid'] = spans[-1].text(strip=True)
|
|
|
|
# 데이터 출력
|
|
if w_data['time'] != "-":
|
|
print(f"{date_str:<12} {w_data['time']:<6} {w_data['weather']:<8} "
|
|
f"{w_data['temp']:<6} {w_data['feels_like']:<6} "
|
|
f"{w_data['prob']:<8} {w_data['wind']:<14} {w_data['humid']:<6}")
|
|
|
|
# ==========================================
|
|
# 실행 부
|
|
# ==========================================
|
|
if __name__ == "__main__":
|
|
filename = "weather_debug.html"
|
|
|
|
# 1. 파일이 있으면 파일로 테스트
|
|
if os.path.exists(filename):
|
|
print(f"📂 파일 '{filename}'을 로드합니다...")
|
|
with open(filename, "r", encoding="utf-8") as f:
|
|
parse_weather_data(f.read())
|
|
|
|
# 2. 파일이 없으면 네트워크 요청 (실제 사용 시)
|
|
else:
|
|
print("🌐 네트워크 요청을 시작합니다...")
|
|
url = "https://www.weather.go.kr/w/wnuri-fct2021/main/digital-forecast.do"
|
|
params = {
|
|
"code": "2638057200",
|
|
"unit": "m/s",
|
|
"hr1": "Y",
|
|
"lat": "35.0952286648109",
|
|
"lon": "128.960618556493"
|
|
}
|
|
headers = {
|
|
"User-Agent": "Mozilla/5.0",
|
|
"Referer": "https://www.weather.go.kr/w/index.do",
|
|
"X-Requested-With": "XMLHttpRequest"
|
|
}
|
|
try:
|
|
res = requests.get(url, params=params, headers=headers, impersonate="chrome120")
|
|
if res.status_code == 200:
|
|
parse_weather_data(res.text)
|
|
else:
|
|
print("통신 오류")
|
|
except Exception as e:
|
|
print(e) |