공공데이터포탈 접속 : https://www.data.go.kr/index.do
포탈 접속 이후 회원가입/로그인 진행을 먼저합니다
그리고 사용하고 싶은 데이터를 검색하여 선정, 상세정보 페이지로 들어갑니다
작성자는 주식 차트를 그리고 싶기 때문에, 금융위원회에서 제공하는 주식시세정보 데이터를 가져오기로 했습니다. (링크 : https://www.data.go.kr/data/15094808/openapi.do)
그리고 이 페이지에서 활용신청 버튼을 누릅니다
신청 페이지에서 내리다보면, 무언가 입력 작성하는 부분이 있는데,
내용을 보고 심사하거나 하지는 않는 것 같습니다.
첨부파일도 필수가 아니니, 적당한 내용으로 채워넣고 활용신청 빠르게 진행합니다
마이페이지 > 활용신청 현황 > 활용 n건 > 아래 스크롤하여 신청한 데이터 클릭
상세한 API 정보를 볼 수 있습니다
중요한 것은 하단의 End Point와 인증키!
End Point 는 데이터 요청 url을 만들 때 base url이 됩니다.
(End Point로 제공된 url 뒤에 '?' 을 붙이고, &[항목명]=[값] 형식으로 뒤로 계속 붙여줍니다.)
Encoding을 한 것과 하지 않은 버전 두가지가 제공되는데,
우리는 일반인증키(Decoding) 을 사용할 것입니다.
(위 페이지는 활용기간동안 만료되거나 하지 않고 계속 계속 볼 수 있습니다)
그러면 공공데이터 API 사용을 위한 준비는 끝났습니다!
이 데이터를 받기 위한 JAVA 코드를 작성해보도록 합시다
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
private String callAPI(String requestUrl) {
// 결과 값을 받기 위한 String 변수들
StringBuilder result = new StringBuilder();
String returnLine;
try {
// REST 통신을 위해, 이미 만들어진 url 이용하여 URL 구조체 선언
URL url = new URL(requestUrl);
// url 스트림 open 및 연결
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.connect();
// REST 통신 결과값 받기 위한 Buffer 선언
BufferedInputStream bufferedInputStream = new BufferedInputStream(urlConnection.getInputStream());
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(bufferedInputStream, "UTF-8"));
// 마지막줄이 나올 때까지 전송 결과 result 에 계속 작성
while ((returnLine = bufferedReader.readLine()) != null) {
result.append(returnLine);
}
} catch (Exception e) {
e.printStackTrace();
}
return result.toString();
}
HTTP 프로토콜로 통신을 위해 함수 input으로 완성된 링크를 받고, 위와 같은 순서로 통신을 진행합니다.
참고로,
URLConnection
은 애플리케이션과 URL 간의 통신 링크를 나타내는 모든 클래스의 슈퍼 클래스입니다. URL이 리키는 리소스와 관련된 연결을 나타냅니다.
HttpURLConnection
은 URLConnection의 하위 클래스로, HTTP 프로토콜을 사용하여 특정 웹 서버와 통신합니다. HTTP 메서드 GET, POST, PUT, DELETE 등의 HTTP 요청과 응답을 처리할 수 있는 메서드들을 제공합니다.
이후 해당 리소스를 GET방식(urlConnection.getInputStream)으로 읽어오고, 마지막줄이 나올 때까지 StringBuilder 객체로 정의된 전송 결과(result)에 계속 작성하며 받아옵니다.
앞서 API 쿼리에 parameter에서 resultType을 json으로 잘 넣었다면, 결과값은 json형태로 나올 것입니다.
JSON 형식의 데이터 분석을 위해 StringBuilder 객체의 result 를 String 형식으로 다시 parse 하였습니다.
웹에서 종목코드(itemId) 와 조회 일수(day)를 input으로 넘겨주면,
해당 종목코드의 해당하는 데이터 중 최근 day 일수 만큼의 주식 차트를 그려주려 합니다.
또한, 휘발성이긴 하지만 임시 Repository를 만들어 주식데이터를 저장,
이 데이터들을 불러와 차트를 그릴겁니다.
(Repository와 Controller 관련 내용은 코드에 포함되어 있지만, 별도 포스트로 따로 다루도록 하겠습니다.)
@RestController
public class APIController {
private final PriceService priceService;
@Autowired
public APIController(PriceService priceService) {
this.priceService = priceService;
}
...
Controller 외에도 주식 데이터를 일별로 나눠 Class(Price) 형식으로 받고, 이를 사용하는 함수들을 PriceService에 저장하였기에 위와 같이 Controller 정의를 시작하였습니다.
/* 퍼센트 인코딩된 쿼리에 + 를 %2B 로 직접 변환해 줌으로써 해결 */
// String SERVICEKEY = "n6~~~+Lvw==";
String SERVICEKEY = "n6~~~%2BLvw==";
공공데이터포털에서 제공하는 apiKey는 'W3C recommendations for URI addressing'에 따라 +를 %2B로 변환하는데,
URLComponents에서는 그냥 + 그대로 뒀기 때문에 키가 틀리는 오류가 아래와 같이 발생합니다.
퍼센트 인코딩된 쿼리에 + 를 %2B 로 직접 변환해 줌으로써 해결합니다...
@GetMapping("/api/apiRequest")
public String getStockPrice(
@RequestParam String itemId, // 종목코드
@RequestParam String day // 불러올 데이터 일 수
) throws IOException, ParseException {
// 결과 값을 받기 위한 String 변수들
String returnString;
String resultString;
// 주식데이터 Repository 초기화
priceService.clearStore();
// API 호출할 url 만들기
String requestUrl = makePriceUrl(SERVICEKEY, "400", "1", itemId, day);
// API 호출하기
returnString = callAPI(requestUrl);
// Repository 에 투자정보 저장하기
resultString = savePriceData(returnString);
return resultString;
}
공공데이터 API를 호출하기 위한 url을 만들고, 호출한 후, 이를 저장하는 순서로 진행됩니다.
@GetMapping
을 통해 요청을 받을 것입니다.
RequestMethod의 경우, GET, POST, PUT, DELETE 등 총 8가지가 있고,
@RequestMapping(method = RequestMethod.GET, value = "/input")
위와 같이 쓰는 방법도 있지만,
@GetMapping
과 같이 메서드 옵션이 적용된 어노테이션이 있기 때문에 이 편이 더 효율적이고 유용합니다.
// REST 전송을 위한 url 생성
private String makePriceUrl(String serviceKey, String numOfRows, String pageNo, String itemId, String day) {
String baseUrl = "https://apis.data.go.kr/1160100/service/GetStockSecuritiesInfoService/getStockPriceInfo?";
String returnUrl = baseUrl + "serviceKey=" + serviceKey + "&numOfRows=" + numOfRows + "&pageNo=" + pageNo;
String todayDate = todayDate();
String prevDate = prevDate(Integer.parseInt(day));
returnUrl = returnUrl + "&resultType=json";
returnUrl = returnUrl + "&likeSrtnCd=" + itemId;
returnUrl = returnUrl + "&beginBasDt=" + prevDate + "&endBasDt=" + todayDate;
return returnUrl;
}
기본적으로 공공데이터API는 serviceKey
(인증키) , numOfRows
(한 페이지에 제공받을 데이터 수), pageNo
(페이지 수) 를 필수로 받습니다.
또한, 여러 타입의 데이터(XML, JSON 등) 를 제공하는 API의 경우, resultType
를 통해 제공받을 데이터의 형식을 지정할 수 있습니다.
추가로, 작성자가 선택한 데이터는 likeSrtnCd
(종목코드) beginBasDt
(시작일자) endBasDt
(종료일자) 등의 선택 데이터를 통해 제공 데이터에 조건 필터를 줄 수 있었습니다.
input을 더 추가하고 싶으면, url 뒤에 &[항목명]=[값] 형식으로 url 을 늘려줍니다.
private String callAPI(String requestUrl) {
// 결과 값을 받기 위한 String 변수들
StringBuilder result = new StringBuilder();
String returnLine;
try {
// REST 통신을 위해, 이미 만들어진 url 이용하여 URL 구조체 선언
URL url = new URL(requestUrl);
// url 스트림 open 및 연결
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.connect();
// REST 통신 결과값 받기 위한 Buffer 선언
BufferedInputStream bufferedInputStream = new BufferedInputStream(urlConnection.getInputStream());
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(bufferedInputStream, "UTF-8"));
// 마지막줄이 나올 때까지 전송 결과 result 에 계속 작성
while ((returnLine = bufferedReader.readLine()) != null) {
result.append(returnLine);
}
} catch (Exception e) {
e.printStackTrace();
}
return result.toString();
}
HTTP 프로토콜로 통신을 위해 함수 input으로 완성된 링크를 받고, 위와 같은 순서로 통신을 진행합니다.
위에 설명이 있으니 자세한 사항은 pass
// API에서 받은 데이터 repository 에 차례대로 저장
private String savePriceData(String data) throws ParseException {
JSONObject dataJson = jsonParser(data);
Object response = dataJson.get("response");
Object body = ((JSONObject) response).get("body");
// 결과 데이터 중 JSONObject Array의 길이 output
int totalCount = Integer.parseInt(((JSONObject) body).get("totalCount").toString());
List<Price> outputList;
Object items = ((JSONObject) body).get("items");
Object item = ((JSONObject) items).get("item");
// 결과 데이터 중 JSONObject Array 형식의 결과값
JSONArray itemConverted = (JSONArray) item;
int i;
for (i = 0; i < totalCount; i++) {
JSONObject element = (JSONObject) itemConverted.get(i);
// 미리 정의된 class안 Price 에 필요한 인자만 get하여 저장
Price add = new Price();
add.setItemId((String) element.get("srtnCd"));
add.setName((String) element.get("itmsNm"));
add.setDate((String) element.get("basDt"));
add.setOpen(Integer.parseInt((String) element.get("mkp")));
add.setClose(Integer.parseInt((String) element.get("clpr")));
add.setHigh(Integer.parseInt((String) element.get("hipr")));
add.setLow(Integer.parseInt((String) element.get("lopr")));
priceService.addDayPrice(add);
}
outputList = priceService.listAll();
List<Object> result;
result = chartParser(outputList, totalCount);
return result.toString();
}
사용자별로 API 결과값 사용방법이 다르겠지만, 작성자는 Spring 프레임워크의 Repository에 결과값들을 저장 후 이를 불러오는 방식으로 사용하였습니다.
........ Springboot 공부목적이었습니다
이후 chartParser 라는 자체 함수를 통해 내가 원하는 방식의 JSONObject를 String으로 적어 List로 만들고, return 하였습니다.
// 오늘 날짜 데이터 형식에 맞게 받아 return
private String todayDate() {
LocalDate today = LocalDate.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
return formatter.format(today);
}
// 오늘 기준 day 일 이전의 날짜 데이터 형식에 맞게 받아 return
private String prevDate(int day) {
LocalDate dateVal = LocalDate.now();
dateVal = dateVal.minusDays(day);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
return formatter.format(dateVal);
}
// String 으로 된 JSON 오브젝트 내용 받아 JSON 오브젝트 형식으로 parse
private JSONObject jsonParser(String data) throws ParseException {
JSONParser jsonParser = new JSONParser();
Object obj = jsonParser.parse(data);
JSONObject jsonObj = (JSONObject) obj;
return jsonObj;
}
// List<Price> 형식으로 정리된 데이터 웹에서 필요하고 받을 수 있는 Sting 형식으로 parse
private List<Object> chartParser(List<Price> listPrice, int cnt) {
List<Object> result = new ArrayList<>(cnt);
System.out.println("cnt : " + cnt);
int i;
for (i = 0; i < cnt; i++) {
String obj = "{\"name\":\"" + listPrice.get(i).getName() +
"\",\"date\":\"" + listPrice.get(i).getDate() +
"\",\"openClose\":["
+ listPrice.get(i).getOpen() + "," + listPrice.get(i).getClose()
+ "],\"highLow\":["
+ listPrice.get(i).getHigh() + "," + listPrice.get(i).getLow()
+ "]}";
result.add(obj);
}
return result;
}
이로써 공공데이터포탈 API 사용하여 Springboot 데이터 조회 Controller 만든 과정을 정리하였습니다.
이후 포스트에서는 이 결과 데이터를 받는 Repository와 이 데이터로 차트를 그리는 코드에 대한 리뷰를 남기겠습니다.
:)
@RestController
public class APIController {
private final PriceService priceService;
@Autowired
public APIController(PriceService priceService) {
this.priceService = priceService;
}
/* 공공데이터포털에서 제공하는 apiKey는 'W3C recommendations for URI addressing'에 따라 +를 %2B로 변환하는데,
URLComponents에서는 그냥 + 그대로 뒀기 때문에 키가 틀리는 오류가 발생.
퍼센트 인코딩된 쿼리에 + 를 %2B 로 직접 변환해 줌으로써 해결 */
// String SERVICEKEY = "n6~~~+Lvw==";
String SERVICEKEY = "n6~~~%2BLvw==";
@GetMapping("/api/apiRequest")
public String getStockPrice(
@RequestParam String itemId, // 종목코드
@RequestParam String day // 불러올 데이터 일 수
) throws IOException, ParseException {
// 결과 값을 받기 위한 String 변수들
String returnString;
String resultString;
// 주식데이터 Repository 초기화
priceService.clearStore();
// API 호출할 url 만들기
String requestUrl = makePriceUrl(SERVICEKEY, "400", "1", itemId, day);
// API 호출하기
returnString = callAPI(requestUrl);
// Repository 에 투자정보 저장하기
resultString = savePriceData(returnString);
return resultString;
}
private String callAPI(String requestUrl) {
// 결과 값을 받기 위한 String 변수들
StringBuilder result = new StringBuilder();
String returnLine;
try {
// REST 통신을 위해, 이미 만들어진 url 이용하여 URL 구조체 선언
URL url = new URL(requestUrl);
// url 스트림 open 및 연결
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.connect();
// REST 통신 결과값 받기 위한 Buffer 선언
BufferedInputStream bufferedInputStream = new BufferedInputStream(urlConnection.getInputStream());
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(bufferedInputStream, "UTF-8"));
// 마지막줄이 나올 때까지 전송 결과 result 에 계속 작성
while ((returnLine = bufferedReader.readLine()) != null) {
result.append(returnLine);
}
} catch (Exception e) {
e.printStackTrace();
}
return result.toString();
}
// REST 전송을 위한 url 생성
private String makePriceUrl(String serviceKey, String numOfRows, String pageNo, String itemId, String day) {
String baseUrl = "https://apis.data.go.kr/1160100/service/GetStockSecuritiesInfoService/getStockPriceInfo?";
String returnUrl = baseUrl + "serviceKey=" + serviceKey + "&numOfRows=" + numOfRows + "&pageNo=" + pageNo;
String todayDate = todayDate();
String prevDate = prevDate(Integer.parseInt(day));
returnUrl = returnUrl + "&resultType=json";
returnUrl = returnUrl + "&likeSrtnCd=" + itemId;
returnUrl = returnUrl + "&beginBasDt=" + prevDate + "&endBasDt=" + todayDate;
System.out.println("url : " + returnUrl);
return returnUrl;
}
// 오늘 날짜 데이터 형식에 맞게 받아 return
private String todayDate() {
LocalDate today = LocalDate.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
return formatter.format(today);
}
// 오늘 기준 day 일 이전의 날짜 데이터 형식에 맞게 받아 return
private String prevDate(int day) {
LocalDate dateVal = LocalDate.now();
dateVal = dateVal.minusDays(day);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
return formatter.format(dateVal);
}
// String 으로 된 JSON 오브젝트 내용 받아 JSON 오브젝트 형식으로 parse
private JSONObject jsonParser(String data) throws ParseException {
JSONParser jsonParser = new JSONParser();
Object obj = jsonParser.parse(data);
JSONObject jsonObj = (JSONObject) obj;
return jsonObj;
}
// List<Price> 형식으로 정리된 데이터 웹에서 필요하고 받을 수 있는 Sting 형식으로 parse
private List<Object> chartParser(List<Price> listPrice, int cnt) {
List<Object> result = new ArrayList<>(cnt);
System.out.println("cnt : " + cnt);
int i;
for (i = 0; i < cnt; i++) {
String obj = "{\"name\":\"" + listPrice.get(i).getName() +
"\",\"date\":\"" + listPrice.get(i).getDate() +
"\",\"openClose\":["
+ listPrice.get(i).getOpen() + "," + listPrice.get(i).getClose()
+ "],\"highLow\":["
+ listPrice.get(i).getHigh() + "," + listPrice.get(i).getLow()
+ "]}";
result.add(obj);
}
return result;
}
// API에서 받은 데이터 repository 에 차례대로 저장
private String savePriceData(String data) throws ParseException {
JSONObject dataJson = jsonParser(data);
Object response = dataJson.get("response");
Object body = ((JSONObject) response).get("body");
int totalCount = Integer.parseInt(((JSONObject) body).get("totalCount").toString());
List<Price> outputList;
Object items = ((JSONObject) body).get("items");
Object item = ((JSONObject) items).get("item");
JSONArray itemConverted = (JSONArray) item;
int i;
for (i = 0; i < totalCount; i++) {
JSONObject element = (JSONObject) itemConverted.get(i);
Price add = new Price();
add.setItemId((String) element.get("srtnCd"));
add.setName((String) element.get("itmsNm"));
add.setDate((String) element.get("basDt"));
add.setOpen(Integer.parseInt((String) element.get("mkp")));
add.setClose(Integer.parseInt((String) element.get("clpr")));
add.setHigh(Integer.parseInt((String) element.get("hipr")));
add.setLow(Integer.parseInt((String) element.get("lopr")));
priceService.addDayPrice(add);
}
outputList = priceService.listAll();
List<Object> result;
result = chartParser(outputList, totalCount);
return result.toString();
}
}