https://overmt.com/yahoo-finance에서-주식-정보-가져오기-1/
의 코드 중 코드 2를 기준으로 fetch_stock_data과 fetch_multiple_stocks 함수에 대해 알아보겠습니다.
Ⅰ. fetch_stock_data 함수에 대해 알아보기
async fn fetch_stock_data(symbol: &str) -> Result<StockData, Box<dyn std::error::Error + Send + Sync>> {
let url = format!("https://query1.finance.yahoo.com/v8/finance/chart/{}", symbol);
let client = reqwest::Client::new();
let response = client
.get(&url)
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
.send()
.await?;
let text = response.text().await?;
let yahoo_response: YahooResponse = serde_json::from_str(&text)?;
if let Some(result) = yahoo_response.chart.result.first() {
let meta = &result.meta;
Ok(StockData {
symbol: symbol.to_string(),
long_name: meta.long_name.clone().unwrap_or_else(|| "N/A".to_string()),
regular_market_price: meta.regular_market_price.unwrap_or(0.0),
currency: meta.currency.clone(),
regular_market_time: meta.regular_market_time.unwrap_or(0),
})
} else {
Err(format!("No data found for symbol: {}", symbol).into())
}
}
1. 함수 시그니처
async fn fetch_stock_data(symbol: &str)
-> Result<StockData, Box<dyn std::error::Error + Send + Sync>>
- async fn → 비동기 함수, await를 사용할 수 있음
- 입력값: symbol → “AAPL”, “TSLA” 같은 종목 코드
- 반환값은 성공 시는 StockData 구조체, 실패 시는 에러(Box<dyn std::error::Error + Send + Sync>를 반환하는데, 멀티스레드 환경이라 Box<dyn std::error::Error >에 Send(스레드간 이동)와 Sync(동시 접근) trait를 추가한 것임
2. 함수 동작 흐름
가. API URL 만들기
let url = format!(
"https://query1.finance.yahoo.com/v8/finance/chart/{}",
symbol
);
- symbol을 이용해 Yahoo Finance의 차트 데이터 API 주소 구성
예) https://query1.finance.yahoo.com/v8/finance/chart/AAPL
나. HTTP 클라이언트 준비
let client = reqwest::Client::new();
- reqwest 라이브러리의 비동기 HTTP 클라이언트를 생성
다. GET 요청 보내기
let response = client
.get(&url)
.header(
"User-Agent",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
)
.send()
.await?;
- Yahoo Finance API 서버에 GET 요청
- 브라우저처럼보이게 하려고 User-Agent 헤더를 추가 (봇 차단 방지 목적)
- .await? → 요청 완료를 기다리고, 실패 시 에러 전파
라. 응답 본문(JSON) 텍스트 추출
let text = response.text().await?;
- HTTP 응답을 문자열 형태로 읽어옴
마. JSON 파싱
let yahoo_response: YahooResponse = serde_json::from_str(&text)?;
- serde_json을 사용해 JSON 문자열을 YahooResponse구조체로 변환
- 여기서 YahooResponse와 내부 구조(chart.result, meta 등)는 미리 serde를 이용해 파싱할 수 있도록 정의되어 있어야 함
바. 데이터 꺼내서 StockData 만들기
if let Some(result) = yahoo_response.chart.result.first() {
let meta = &result.meta;
Ok(StockData {
symbol: symbol.to_string(),
long_name: meta.long_name.clone().unwrap_or_else(|| "N/A".to_string()),
regular_market_price: meta.regular_market_price.unwrap_or(0.0),
currency: meta.currency.clone(),
regular_market_time: meta.regular_market_time.unwrap_or(0),
})
} else {
Err(format!("No data found for symbol: {}", symbol).into())
}
- chart.result의 첫 번째 요소를 가져옴
- 거기서 meta 정보를 추출
- 회사명, 현재 시장 가격, 통화 단위, 마지막 거래 시간 등을 꺼내 StockData에 담음
- 데이터가 없으면 Err로 반환
위 예에서, unwrap_or는 Option이 None일 때 값을 0.0 또는 0으로 지정하는데,
unwrap_or_else는 Option이 None일 때 클로저가 실행되고, clone()이 추가된 차이점이 있음
Ⅱ. fetch_multiple_stocks 함수에 대해 알아보기
async fn fetch_multiple_stocks(symbols: &[&str]) -> Vec<Result<StockData, Box<dyn std::error::Error + Send + Sync>>> {
let mut handles = Vec::new();
for &symbol in symbols {
let symbol_owned = symbol.to_string();
let handle = tokio::spawn(async move {
fetch_stock_data(&symbol_owned).await
});
handles.push(handle);
}
let mut results = Vec::new();
for handle in handles {
match handle.await {
Ok(result) => results.push(result),
Err(e) => results.push(Err(e.into())),
}
}
results
}
이 함수는 여러 주식 종목(symbol)을 동시에 비동기로 조회(fetch) 하기 위해 tokio::spawn을 사용하는 구조입니다.
1. 함수 시그니처
async fn fetch_multiple_stocks(
symbols: &[&str]
) -> Vec<Result<StockData, Box<dyn std::error::Error + Send + Sync>>>
- async fn → 비동기 함수이므로 호출 시 .await 필요.
- 입력값은 &[&str]로 &str 슬라이스 (예: &[“AAPL”, “GOOG”, “TSLA”])
- 반환값: Result의 벡터
– 각 종목(symbol)에 대해 Ok(StockData) 또는 Err(에러)가 담긴 리스트.
– 즉, 한 종목 실패해도 다른 종목은 결과를 받을 수 있음.
2. 주요 동작 흐름
가. handles 벡터 생성
let mut handles = Vec::new();
- 비동기 작업(태스크) 핸들을 저장해 둘 벡터.
나. 종목별 비동기 작업 생성
for &symbol in symbols {
let symbol_owned = symbol.to_string(); // 소유권 있는 String으로 변환
let handle = tokio::spawn(async move {
fetch_stock_data(&symbol_owned).await
});
handles.push(handle);
}
- for 루프를 돌면서 각 종목 기호(&str)를 String으로 복사(to_string)
→ 이유: tokio::spawn의 async move 블록은 ‘static 라이프타임을 요구하기 때문.
원본 &str는 반복문이 끝나면 사라질 수 있으니, 안전하게 소유권 있는 String 사용. - tokio::spawn(…) → 배경(백그라운드)에서 새로운 비동기 태스크 생성
- async move → 클로저에 캡처되는 값(symbol_owned)을 이동(move)시켜 사용.
- 결과: 각 종목을 조회하는 여러 비동기 태스크가 동시에 실행됨.
다. 모든 태스크 완료 대기
let mut results = Vec::new();
for handle in handles {
match handle.await {
Ok(result) => results.push(result),
Err(e) => results.push(Err(e.into())),
}
}
- handle.await → 해당 비동기 태스크가 끝날 때까지 대기.
- handle.await의 반환값:
– Ok(result) → 작업이 정상 종료 → result(= Result)를 results에 저장.
– Err(e) → 태스크 자체가 패닉 또는 취소 → 에러를 Box로 변환해 저장.
라. 결과 반환
results
- 벡터에는 각 종목별 Result<StockData, Error>가 순서대로 저장됨.
마. 주의점
종목 수가 매우 많으면 동시에 많은 태스크가 실행되어 서버나 네트워크에 부하 발생 가능 → tokio::task::JoinSet이나 futures::stream::FuturesUnordered로 동시 실행 수를 제한하는 방법 고려 가능.