자바로 네이버 주식가격 크롤링하기 두번째 포스팅입니다.
기본 개발 환경은 아래 포스팅 확인해주시면 됩니다.
[springboot] java로 크롤링 하기 - Jsoup , 네이버 주식 가격 조회 -01 - 오오코딩 (tistory.com)
이번 포스팅에선 실제 로컬 서버를 띄우고 브라우저에서 크롤링한 데이터를 출력해보도록 하겠습니다.
기본 패키지 구조 입니다.
아래 구조로 구성해두었습니다.
Controller - url정보 셋팅
Service - 컨트롤러에서 호출할 메소드 구현
domain - 리턴할 dto 경로
component - 실제 크롤링할 로직 처리
#Controller
아래와 같이 localhost:8080/kospi/all 을 호출했을때 처리를 위한 컨트롤러 생성. Json형태로 리턴함.
package com.oogo.api.controller.stock;
import com.oogo.api.domain.dto.stock.KospiStockDto;
import com.oogo.api.service.stock.StockService;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequiredArgsConstructor
public class StockController {
private final StockService stockService;
@GetMapping("/kospi/all")
public List<KospiStockDto> getKosPiStockList(HttpServletRequest request) {
return stockService.getKosPiStockList();
}
}
#Service
- 컨트롤러에서 호출할 메소드로 component 메소드를 호출해서 결과값을 컨트롤러로 전달함 (중간 다리 역할)
package com.oogo.api.service.stock;
import com.oogo.api.component.stock.JsoupComponent;
import com.oogo.api.domain.dto.stock.KospiStockDto;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class StockService {
private final JsoupComponent jsoupComponent;
public List<KospiStockDto> getKosPiStockList() {
return jsoupComponent.getKosPiStockList();
}
}
#domain
크롤링할 결과 값을 저장할 Dto 클래스 토론방url(discussionRoomUrl) 은 뒷 uri만 크롤링되므로 앞에 도메인까지 포함 될 수 있도록 getter를 새로 추가해준다.
package com.oogo.api.domain.dto.stock;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class KospiStockDto {
private String no;
private String stockName;
private String price; // 현재가
private String diffAmount; // 전일비
private String dayRange; // 등락률
private String parValue; // 액면가
private String marketCap; // 시가총액
private String numberOfListedShares; // 상장 주식 수
private String foreignOwnRate; // 외국인 비율
private String turnover; // 거래량
private String per; // per
private String roe; // roe
private String discussionRoomUrl; // 토론방 url
public String getDiscussionRoomUrl() {
return "https://finance.naver.com"+discussionRoomUrl;
}
}
#Component
실제 크롤링 로직 처리를 하는 클래스
package com.oogo.api.component.stock;
import com.oogo.api.domain.dto.stock.KospiStockDto;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import org.jsoup.Connection;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.stereotype.Component;
@Component
public class JsoupComponent {
public List<KospiStockDto> getKosPiStockList() {
final String stockList = "https://finance.naver.com/sise/sise_market_sum.nhn?&page=1";
Connection conn = Jsoup.connect(stockList);
try {
Document document = conn.get();
return getKosPiStockList(document);
} catch (IOException ignored) {
}
return null;
}
public List<KospiStockDto> getKosPiStockList(Document document) {
Elements kosPiTable = document.select("table.type_2 tbody tr");
List<KospiStockDto> list = new ArrayList<>();
for (Element element : kosPiTable) {
if (element.attr("onmouseover").isEmpty()) {
continue;
}
list.add(createKosPiStockDto(element.select("td")));
}
return list;
}
public KospiStockDto createKosPiStockDto(Elements td) {
KospiStockDto kospiStockDto = KospiStockDto.builder().build();
Class<?> clazz = kospiStockDto.getClass();
Field[] fields = clazz.getDeclaredFields();
for (int i = 0; i < td.size(); i++) {
String text;
if(td.get(i).select(".center a").attr("href").isEmpty()){
text = td.get(i).text();
}else{
text = "https://finance.naver.com" + td.get(i).select(".center a").attr("href");
}
fields[i].setAccessible(true);
try{
fields[i].set(kospiStockDto,text);
}catch (Exception ignored){
}
}
return kospiStockDto;
}
}
--Component 상세 설명--
1. getKosPiStockList()
- 서비스에서 호출할 메소드로
- 타겟 URL을 기준으로 크롤링하기 위한 커넥션을 생성하고 document 객체를 생성한다.
- getKosPiStockList(document) 를 호출해 결과값을 받아 리턴한다.
public List<KospiStockDto> getKosPiStockList() {
final String stockList = "https://finance.naver.com/sise/sise_market_sum.nhn?&page=1";
Connection conn = Jsoup.connect(stockList);
try {
Document document = conn.get();
return getKosPiStockList(document);
} catch (IOException ignored) {
}
return null;
}
2. getKosPiStockList(Document document)
- 전체 결과값을 1차적으로 전달받는다. (document.select() 메소드로 태그 접근함)
- 이제 전달 받은 결과값 kosPiTable 변수에 담긴 데이터를 for loop를 통해 하나씩 KospiStockDto 객체로 변환해준다.
- KospiStockDto 객체 변환은 createKosPiStockDto() 메소를 활용한다.
- 변환된 객체는 List<KospiStockDto> list; 에 저장한다.
- 저장한 list를 리턴한다.
public List<KospiStockDto> getKosPiStockList(Document document) {
Elements kosPiTable = document.select("table.type_2 tbody tr");
List<KospiStockDto> list = new ArrayList<>();
for (Element element : kosPiTable) {
if (element.attr("onmouseover").isEmpty()) {
continue;
}
list.add(createKosPiStockDto(element.select("td")));
}
return list;
}
3. createKosPiStockDto(Elements td)
- 전달 받은 객체를 dto 객체로 변환한다.
- java 의 리플렉션을 활용해 객체로 변환해준다.
- KospiStockDto kospiStockDto = KospiStockDto.builder().build(); 기본 객체를 하나 생성해준다.
-> KospiStockDto kospiStockDto = new KospiStockDto() 와 동일한 의미이다.
- kospiStockDto.getClass(); 해당객체의 Class 타입으로 접근한다.
- clazz.getDeclaredFields(); 전체 필드를 배열로 가져온다.
-> KospiStockDto 에서 생성한 필드 순서로 (위에서 부터 차례로) 배열을 생성한다.
- 전달받은 파라미터인 "Element td" 는 앞서 크롤링시 <tr> 태그 하위에 있는 td들을 모두 갖는 리스트라고 생각하면 된다.
- 아래 캡처화면의 <tr></tr> 영역 정보라고 보면된다.
<tr>
<td>..</td>
<td></td>
</tr>
- Element td 를 기준으로 for문을 돌리며 i=0 부터 순서대로 Field[] 배열의 0번째 데이터부터 변경해준다.
- field[i].setAccessible(true); private 변수에 대한 접근을 허용하도록 한다.(KospiStockDto 필드가 private 이기 때문)
- fields[i].set(kospiStockDto,text); set 메소드로 필드의 값을 셋팅한다.
파라미터로 set(셋팅할 원본 객체, 변경할 값) 을 전달하면 된다.
- 셋팅된 KospiStockDto 객체를 리턴한다.
public KospiStockDto createKosPiStockDto(Elements td) {
KospiStockDto kospiStockDto = KospiStockDto.builder().build();
Class<?> clazz = kospiStockDto.getClass();
Field[] fields = clazz.getDeclaredFields();
for (int i = 0; i < td.size(); i++) {
String text;
if(td.get(i).select(".center a").attr("href").isEmpty()){
text = td.get(i).text();
}else{
text = "https://finance.naver.com" + td.get(i).select(".center a").attr("href");
}
fields[i].setAccessible(true);
try{
fields[i].set(kospiStockDto,text);
}catch (Exception ignored){
}
}
return kospiStockDto;
}
이제 빌드해서 실제 브라우저에서 어떤결과가 출력되는지 확인해보도록 하겠습니다.
http://localhost:8080/kospi/all 호출 결과
아래와 같이 json array 결과를 확인 할 수 있습니다.
이 데이터를 db에 저장할 수도 있고 html을 만들어서 예쁘게 화면으로 출력 할 수도 있을 것 같습니다.
다음 포스팅에선 db 저장 및 html 화면 출력까지 한 번 해보도록 하겠습니다.
전체소스는 아래 Github 참고 부탁드립니다.
build99k/spring-boot-buildup (github.com)
'springboot' 카테고리의 다른 글
[springboot] java로 크롤링 하기 - Jsoup , 네이버 주식 가격 조회 -01 (0) | 2021.09.16 |
---|---|
[비트코인] 업비트 API로 시세 조회 (1) | 2021.09.15 |
[springboot]Feign client 사용하기 - REST api 간편 호출 (1) | 2021.09.12 |
[스프링 스터디] IoC 컨테이너와 빈 (1) | 2021.04.12 |
[SpringBoot] 스프링부트 aop Aspectj 적용하기 - 메소드 실행시 특정 로직 수행 (0) | 2020.02.03 |
최근댓글