-
Rust: scraper를 이용한 네이버 날씨 파싱컴퓨터/Rust 2021. 7. 28. 23:17728x90반응형
Rust
1. 크롤링
우선 데이터를 가져올 사이트는 다음과 같다. ?cpName=ACCUWEATHER을 넘겨서 아큐웨더 제공자 사용함
(아주대 지역 날씨)
Rust언어에서 Python의 beautifulsoup4처럼 html 요소를 쉽게 선택할 수 있는 라이브러리는
scraper가 가장 괜찮은 것 같다.
Cargo.toml
scraper = "0.12.0"
2. Rust언어 소스
맨 위에 보이는 현재 온도 = <strong class="current">"12"</strong>
현재 날씨 = <span class="weather">흐림</span>
기타 정보 = <em class="level_text"></em> 4개가 있으므로 FindAll (select)
([미세먼지, 초미세먼지, 자외선, 일몰 시간])
(기상청은 자정에서 정오 전이 오전, 정오에서 자정 전이 오후이고,
해외 제공사는 일출부터 일몰 전이 낮, 일몰부터 일출 전이 밤입니다.)
주간예보에 있는 오늘 낮, 오늘 밤 부분에 강수 확률과 최고/최저 온도가 있다.
최고, 최저 온도 = <span class="data"></span> 2개가 있으므로 FindAll (select)
강수 확률 = <span class="rainfall"></strong> 2개가 있으므로 FindAll (select)
크롤링
Weather란 struct에 정보를 다 string으로 저장할 것이다.
use reqwest::header::USER_AGENT; use scraper::{Html, Selector}; #[derive(Debug, Default)] pub struct Weather { pub max_temp: String, pub min_temp: String, pub current_temp: String, pub current_status: String, pub wind_chill: String, // 체감온도 pub rain_day: String, pub rain_night: String, pub fine_dust: String, pub ultra_dust: String, pub uv: String, pub icon: String, } const NAVER_WEATHER: &'static str = "https://weather.naver.com/today/02117530?cpName=ACCUWEATHER"; // 아주대 지역 날씨 pub fn weather_parse() -> Result<Weather, reqwest::Error> { // Blocking NON-ASYNC let client = reqwest::blocking::Client::builder() .danger_accept_invalid_certs(true) .build()?; let res = client.get(NAVER_WEATHER).header(USER_AGENT, "User-Agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36").send()?; let body = res.text()?; // HTML Parse let mut weather: Weather = Default::default(); let document = Html::parse_document(&body); // scraper let current_temp = Selector::parse("strong.current").unwrap(); let current_stat = Selector::parse("span.weather").unwrap(); let temps = Selector::parse("span.data").unwrap(); let rains = Selector::parse("span.rainfall").unwrap(); let stats = Selector::parse("em.level_text").unwrap(); let icon = Selector::parse("div.today_weather > i").unwrap(); let wind_chill = Selector::parse("div.weather_area > dl > dd:nth-child(6)").unwrap(); let current_temp_element = document.select(¤t_temp).next().unwrap(); let current_stat_element = document.select(¤t_stat).next().unwrap(); let mut temps_element = document.select(&temps); let mut stats_element = document.select(&stats); let mut rains_element = document.select(&rains); let icon_element = document.select(&icon).next().unwrap(); let wind_chill_element = document.select(&wind_chill).next().unwrap(); let current_temp = current_temp_element.text().collect::<Vec<_>>()[1] .trim() .to_string() + "도"; // "28도" let current_stat = current_stat_element.text().collect::<Vec<_>>()[0] .trim() .to_string(); // "구름조금" let day_temp = temps_element.next().unwrap().text().collect::<Vec<_>>()[1] .trim() .to_string() + "도"; // "최고 온도" let night_temp = temps_element.next().unwrap().text().collect::<Vec<_>>()[1] .trim() .to_string() + "도"; // "최저 온도" let fine_dust = stats_element.next().unwrap().text().collect::<Vec<_>>()[0] .trim() .to_string(); // "보통" let ultra_dust = stats_element.next().unwrap().text().collect::<Vec<_>>()[0] .trim() .to_string(); // "나쁨" let uv = stats_element.next().unwrap().text().collect::<Vec<_>>()[0] .trim() .to_string(); // "높음" let day_rain = rains_element.next().unwrap().text().collect::<Vec<_>>()[1] .trim() .to_string(); // "55%" let night_rain = rains_element.next().unwrap().text().collect::<Vec<_>>()[1] .trim() .to_string(); // "8%" let wind_chill = wind_chill_element.text().collect::<Vec<_>>()[0].trim(); // "33" let wind_chill = wind_chill.replace("°", "") + "도"; let mut icon = icon_element.value().attr("data-ico").unwrap().to_string(); let icon_classes = icon_element.value().attr("class").unwrap(); // struct Weather init weather.current_temp = current_temp; weather.wind_chill = wind_chill; weather.current_status = current_stat; weather.max_temp = day_temp; weather.min_temp = night_temp; weather.rain_day = day_rain; weather.rain_night = night_rain; weather.fine_dust = fine_dust; weather.ultra_dust = ultra_dust; weather.uv = uv; if icon_classes.contains("night") { icon = icon + "_night"; } weather.icon = format!( "https://raw.githubusercontent.com/Alfex4936/KakaoChatBot-Golang/main/imgs/{}.png?raw=true", icon ); Ok(weather) } #[cfg(test)] mod test { use super::*; #[test] fn weather_test() { let weather = weather_parse().unwrap(); println!("{:#?}", weather); } } /* 결과 Weather { max_temp: "33도", min_temp: "26도", current_temp: "27도", current_status: "구름많음", wind_chill: "33도", rain_day: "55%", rain_night: "8%", fine_dust: "보통", ultra_dust: "보통", uv: "높음", icon: "https://raw.githubusercontent.com/Alfex4936/KakaoChatBot-Golang/main/imgs/ico_animation_wt5_night.png?raw=true", } */
카카오톡 챗봇 예제
#[post("/weather")] pub async fn ask_weather(_: web::Json
) -> impl Responder { let weather = weather_parse().await.unwrap(); let description = format!("현재 날씨는 {}, {} (체감 {})\n최저기온 {}, 최고기온은 {}\n\n낮, 밤 강수 확률은 {}, {}\n미세먼지 농도는 {}\n자외선 지수는 {}", weather.current_status, weather.current_temp, weather.wind_chill, weather.min_temp, weather.max_temp, weather.rain_day, weather.rain_night, weather.fine_dust, weather.uv); let mut result = Template::new(); let basic_card = BasicCard::new() .set_title("[수원 영통구 기준]".to_string()) .set_desc(description) .set_thumbnail(weather.icon) .add_button(Button::Link( LinkButton::new("자세히".to_string()).set_link(NAVER_WEATHER.to_string()), )); result.add_output(basic_card.build()); HttpResponse::Ok() .content_type("application/json") .body(serde_json::to_string(&result).unwrap()) } 풀소스
728x90'컴퓨터 > Rust' 카테고리의 다른 글
Rust: actix를 이용하여 카카오 챗봇 만들기 (1) 2021.08.02 Rust: serde json Enum 공부 (0) 2021.07.28 Rust: Javascript POST json 데이터 consume (0) 2021.07.25