공공데이터 CSV 변환

개요

전 직장 이사님이 논문을 준비하시는데 공공데이터 자료가 필요하시다고 하셔서 조회 후 저장하는 작업을 해보았습니다.

공공데이터는 https://www.data.go.kr/ 에서 API 신청 후 serviceKey를 지급받아 REST로 요청하면 됩니다. 응답값은 XML 또는 JSON 이지만, 논문에 사용할 자료라서 CSV로 변환하였습니다. 응답포멧은 조금 다르지만, 처리로직은 거의 동일하여http://www.smartfarmkorea.net/ 의 데이터도 처리해봤습니다.

공공데이터를 사용함에 있어서 가장 큰 문제점은.. 명세서가 hwp 포멧이여서 문서 확인이 힘들었습니다.

토마토 생산량

REST로 API 호출하고, 응답값인 JSON을 Parsing하여 Item만 취하고, 이를 CSV로 변환하는 코드를 작성해 봤습니다.

해당 API는 농가 코드를 입력받아 생산량을 응답합니다.
Request / Response는 Pagenation 되어 있지만, 귀찮아서 1000개 정도씩 요청하고 1 Page의 정보만 취합했습니다.

package kr.kimstar.opendata;

import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
import kr.kimstar.opendata.constant.SmartFarmConst;
import kr.kimstar.opendata.dto.DataGoKrResponse;
import kr.kimstar.opendata.dto.Item;
import kr.kimstar.opendata.dto.TomatoItem;
import kr.kimstar.opendata.helper.APIConnector;
import kr.kimstar.opendata.helper.FileWriter;
import kr.kimstar.opendata.helper.JsonHelper;

import java.io.IOException;
import java.lang.reflect.Type;
import java.text.ParseException;
import java.util.*;

public class Tomato {
    public static void main(String[] args) throws IOException {

        // request
        List<Item> items = new ArrayList<>();

        for (String code : SmartFarmConst.SEARCH_FRMHS_CODE_LIST) {
            List<Item> resultItems = fetchItems(code);
            items.addAll(resultItems);
        }

        if (items.size() == 0) {
            return;
        }

        // save
        List<String> csvItems = new ArrayList<>();
        csvItems.add(items.get(0).getTitle());
        items.forEach(i -> csvItems.add(i.getCSV()));

        FileWriter.write(csvItems, "C:\\tomato.csv");
    }

    private static List<Item> fetchItems(String frmhsCode) {

        try {
            int pageSize = 1000;
            String apiUrl = "http://apis.data.go.kr/1390000/SmartFarmDATA/prddatarqst";

            Map<String, String> params = new HashMap<>();
            params.put("searchFrmhsCode", frmhsCode);
            params.put("pageNo", "1");
            params.put("pageSize", String.valueOf(pageSize));
            params.put("returnType", "json");
            params.put("serviceKey", "블라블라블라");

            String response = APIConnector.fetch(apiUrl, params);
            JsonObject jsonObj = JsonHelper.convert(response);
            // JsonHelper.printJson(jsonObj);

            Type type = new TypeToken<DataGoKrResponse<TomatoItem>>() {}.getType();
            DataGoKrResponse dto = (new Gson()).fromJson(jsonObj, type);

            if (!"OK".equalsIgnoreCase(dto.getResponse().getHeader().getResultMsg())) {
                return Collections.EMPTY_LIST;
            }

            if (dto.getResponse().getBody().getTotalCount() > pageSize) {
                System.out.println("!!!!!!!!! totalCount is " + dto.getResponse().getBody().getTotalCount());
                JsonHelper.printJson(jsonObj);
            }

            Thread.sleep(100);
            return dto.getResponse().getBody().getItems().getItem();
        } catch (Exception e) {
            return Collections.EMPTY_LIST;
        }
    }
}

REST 로 호출하기 위해 HttpURLConnection 를 사용하였습니다. 지금 보니 try-with-resource 형태로 수정이 필요해 보이네요. 한글이 포함된 정보가 있어서 UTF-8로 처리하였습니다.

package kr.kimstar.opendata.helper;

import org.apache.commons.lang3.StringUtils;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Map;

public final class APIConnector {

    private static final String ENC_UTF8 = "UTF-8";

    public static String fetch(String apiUrl, Map<String, String> params) throws IOException {

        // request
        URL url = new URL(buildUrl(apiUrl, params));
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("GET");
        conn.setRequestProperty("Content-type", "application/json");
        System.out.println("Response code: " + conn.getResponseCode());

        // response
        BufferedReader reader;
        if (conn.getResponseCode() >= 200 && conn.getResponseCode() <= 300) {
            reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8));
        } else {
            reader = new BufferedReader(new InputStreamReader(conn.getErrorStream(), StandardCharsets.UTF_8));
        }

        // result
        StringBuilder sb = new StringBuilder();
        String line;
        while ((line = reader.readLine()) != null) {
            sb.append(line);
        }
        reader.close();
        conn.disconnect();

        return sb.toString();
    }

    private static String buildUrl(String apiUrl, Map<String, String> params) throws UnsupportedEncodingException {
        if (StringUtils.isEmpty(apiUrl)) {
            return StringUtils.EMPTY;
        }

        StringBuilder sb = new StringBuilder(apiUrl);
        for (String key : params.keySet()) {
            sb.append("&" + URLEncoder.encode(key, ENC_UTF8) + "=" + params.get(key));
        }

        return sb.toString().replaceFirst("&", "?");
    }
}

데이터는 JSON 형태로 처리하였으며, Parsing 및 Mapping을 위해 Gson 을 사용하였습니다.

package kr.kimstar.opendata.helper;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

public final class JsonHelper {

    public static JsonObject convert(String jsonString) {
        return JsonParser.parseString(jsonString).getAsJsonObject();
    }

    public static void printJson(JsonObject jsonObject) {
        Gson gson = new GsonBuilder().setPrettyPrinting().create();
        System.out.println(gson.toJson(jsonObject));
    }
}

data.or.kr 의 데이터들은 표준화된 포멧을 사용하고 items 내용만 다르므로, Generic으로 item을 처리하여, 다양한 응답값을 처리할 수 있도록 하였습니다.

@Getter
@Setter
@ToString
public class DataGoKrResponse<T> {
    private Response<T> response;
    private String resultCode;
    private String resultMessage;
}

각 Item은 CSV 생성시 사용할 title과 value 값들을 응답하는 method를 정의하였습니다.

package kr.kimstar.opendata.dto;

public interface Item {
    String getTitle();
    String getCSV();
}
@Getter
@Setter
@ToString
public class TomatoItem implements Item {
    private String frmYear;
    private String frmMonth;
    private String frmAr;
    private String frmhsId;
    private String outtrn;
    private String frmWeek;

    @Override
    public String getTitle() {
        return "frmYear,frmMonth,frmAr,frmhsId,outtrn,frmWeek";
    }

    @Override
    public String getCSV() {
        StringBuilder sb = new StringBuilder();
        sb.append(frmYear + ",");
        sb.append(frmMonth + ",");
        sb.append(frmAr + ",");
        sb.append(frmhsId + ",");
        sb.append(outtrn + ",");
        sb.append(frmWeek);
        return sb.toString();
    }
}

처리결과는 다음과 같이 CSV 파일로 저장됩니다.

frmYear,frmMonth,frmAr,frmhsId,outtrn,frmWeek
2017,8,3400.0,9,0.0,34
2017,8,3400.0,9,0.0,35
2017,9,3400.0,9,0.0,36
2017,9,3400.0,9,0.0,37

Notice

  • 이 저작물은 크리에이티브 커먼즈 저작자표시-비영리-변경금지 2.0 대한민국 라이선스에 따라 이용할 수 있습니다. 크리에이티브 커먼즈 라이선스
  • 저작권과 관련된 파일요청 및 작업요청을 받지 않습니다.
  • 댓글에 대한 답변은 늦을 수도 있습니다.
  • 댓글 남기기

    이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다