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(아래쪽 상세)를 찾아야 함 # 구조:
->
->
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_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)