본문 바로가기

수업 정리

33일차 수업 정리(Json Parsing)

** JSON Parsing

http://swiftapi.rubypaper.co.kr:2029/hoppin/movies?version=1&page=1&count=20&genreId=&order=releasedatease 사이트의 데이터 중에서 title ratingAverage의 값만 가져와서 데이터베이스에 저장

 

1. 데이터 구조를 보고 필요한 데이터를 결정

2. Maven 기반으로 Java Application Project를 생성

    => Java Project를 만들고 MavenProject로 변환

3. JSON Parsing Library 사용을 위한 의존성 코드를 pom.xml에 작성

    => www.mvnrepository.com에서 json을 검색하여 직접 입력 or 참고파일에서 복사하여 사용

4. main 메소드를 소유한 클래스를 생성

 

5. main 메소드에 문자열을 다운받는 코드를 작성

    - 과정 : 다운받을 URL을 생성 -> HttpURLConnection 인스턴스를 생성하고 옵션 설정 -> Stream 생성하여 다운로드

    - 문자열을 한번에 읽기 힘들기 때문에 줄 단위로 읽어서 작업(합칠 때는 String 대신 StringBuilder를 사용

        * String : 자신에게 편집 작업 할수 없어 복사하여 수행하므로 메모리 낭비가 발생

        * StringBuilder : 자신에게 문자열을 추가 할 수 있음

 

//다운로드 받을 문자열을 저장 할 변수
String jsonString = null;

//웹에서 다운받기
try {
	//다운로드 받을 주소 만들기
	String addr = "http://swiftapi.rubypaper.co.kr:2029/hoppin/movies?version=1&page=1&count=20&genreId=&order=releasedatease";
	//URL에 한글이 있으면 한글부분은 utf-8로 인코딩
	//URLEncoder.encode("한글문자열","utf-8") - 지금은 없으므로 패스
	URL url = new URL(addr);
	
	//HttpURLConnection 생성
	//openConnection은 URLConnection이라는 추상 클래스 타입으로 리턴하므로 강제 형변환하여
	//일반 클래스 타입으로 변경
	HttpURLConnection con = (HttpURLConnection) url.openConnection();
	//옵션 설정
	con.setConnectTimeout(30000);		//최대 연결 시간 설정
	con.setUseCaches(false); 		//이전에 받은 데이터 사용 여부
			
	//문자열을 읽을 스트림을 생성
	//읽은 내용이 깨질때는 con.getInputStream다음에, "인코딩방식"을 추가
	//(con.getInputStream(), utf-8)
	BufferedReader br = new BufferedReader(
			new InputStreamReader(con.getInputStream()));
	//문자열을 읽기 위한 임시변수를 생성
	StringBuilder sb = new StringBuilder();
	while(true) {
		//한줄씩 읽어오기
		String line = br.readLine();
		//읽은 데이터가 없으면 종료
		if(line == null)
			break;
		//읽은 데이터가 있으면 sb에 추가
		sb.append(line + "\n");
	}
			
	//연결 종료
	br.close();
	con.disconnect();
			
	//읽은 내용을 String으로 변환
	jsonString = sb.toString();
		
}catch (Exception e) {
	System.err.println("다운로드 실패");
	System.out.println(e.getMessage());
	e.printStackTrace();
}

 

6. 데이터를 파싱해서 list에 저장하는 코드 작성

 

System.out.println(jsonString);
//파싱 결과를 저장할 변수 생성
//title과 ratingAverage를 저장(Map에 저장)
List<Map<String, Object>> list = new ArrayList<Map<String,Object>>();
		
//데이터 파싱
try {
	//텍스트가 존재하는 경우만 수행
	//&&을 만들경우 순서는 무조건 null을 검사하는 소스가 '앞'에 오도록해야 함
	//||를 만들경우 순서는 무조건 null을 검사하는 소스가 '뒤'에 오도록 해야 함
	//null은 trim을 부를 수 없으므로 NullPointException발생(튕김)
	if(jsonString != null && jsonString.trim().length() >0) {
		//첫번째는 JSON 객체
		JSONObject mainData = new JSONObject(jsonString);
		//System.out.println(mainData);
		
		//hopping이라는 key의 값을 객체로 가져옴
		JSONObject hoppin = mainData.getJSONObject("hoppin");
		//System.out.println(hoppin);
			
		JSONObject movies = hoppin.getJSONObject("movies");
		//System.out.println(movies);
				
		JSONArray movie = movies.getJSONArray("movie");
		//System.out.println(movie);
				
		for(int i=0; i<movie.length(); i+=1) {
			JSONObject imsi = movie.getJSONObject(i);
			//System.out.println(imsi);
					
			String title = imsi.getString("title");
			String ratingAverage = imsi.getString("ratingAverage");
					
			//Map으로 생성
			Map<String, Object> map = new HashMap<String, Object>();
			map.put("title", title);
			map.put("ratingAverage", Double.parseDouble(ratingAverage));
					
			//list 에 추가
			list.add(map);
					
			//파싱 데이터 출력부
			for(Map<String, Object> movieList : list) {
				System.out.println(movieList);
			}
		}
	}else {
		System.out.println("다운받을 문자열이 없음");
		//프로그램 종료
		System.exit(0);
	}
			
}catch (Exception e) {
	System.err.println("파싱 실패");
	System.out.println(e.getMessage());
	e.printStackTrace();
}

 

7. 데이터를 저장하기 위한 테이블을 생성
    => 문자열 title, 실수형 ratingAverage를 저장
    => 기본키로 사용할 code를 추가 : auto_increment로 설정
    => MySQL 데이터베이스에 접속해서 테이블을 생성

        (접속시 refuse connection 발생시 [서비스] - [mySQL]사용

 

-- 사용할 데이터베이스를 설정
use mysql;

-- 테이블 생성 구문
create table movie(
	code int primary key auto_increment,
	title varchar(100),
	ratingaverage double
	)default charset = utf8;

 

8. mysql을 java에서 사용하기 위한 의존성을 pom.xml 설정

 

<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>6.0.6</version>
</dependency>

 

9. List에 저장된 데이터를 테이블에 저장하는 코드를 작성
    => 드라이버 클래스 로드 -> 연결객체 생성 -> SQL실행 객체 생성 -> SQL 실행 -> 결과 사용 -> 생성한 객체 닫기

**Kakao 검색 API의 내용 파싱

    => developers.kakao.com에서 계정을 생성하고 로그인

    => URL : 웹서버에게 요청하는 주소

        - 요청 : request  /  응답 : response  / 서버에게 전송하는 데이터 : parameter

    => 웹 서버에게 요청을 할 때는 URL header가 필요

        - header URL에 노출되서는 안되는 정보들을 저장

    => URL의 구성

        - 프로토콜://서버IP 또는 Domain:포트번호/요청경로?파라미터명=&파라미터명=...

 

        - //로 시작하면 상황에 따라 http or https로 접속

        - 포트번호와 요청경로는 생략되는 경우가 있음(생략된 경우 없는 것이 아니라 서버설정으로 자동 입력된 것)

 

        - web에서 parameter는 클라이언트가 서버에게 전송하는 데이터

        - 이름과 값 모두 문자열만 가능(한글포함시 인코딩하여 요청)

 

    =>Kakao Open API를 사용하려면 애플리케이션을 생성

        - NativeApp : 일반 응용프로그램(SmartPhone Application에서 사용하는 경우)

        - Rest API : 서버에서 데이터를 주는 경우(데이터를 받아오고자 하는 경우)

        - Javascript : 웹화면에서 사용(Web화면(html)에서 사용하는 경우)

        - Admin : 로그인 관련

 

     => App Key : f9e2c5d96fd9f8f18c1757ee858596ec

 

    => Kakao Book 검색 API

     - url - https://dapi.kakao.com/v3/search/book?target=title&query=책제목

       header - Authorization:KakaoAK kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk

      ex) con.addRequestProperty("Authorization", "KakaoAK f9e2c5d96fd9f8f18c1757ee858596ec");

 

1. 검색어를 입력받아서 검색어에 해당하는 책이 몇권이고 몇페이지에 걸쳐 있는지 확인
    - Web에서 Scapping(수집)을 할때는 전체 데이터가 몇개인지 확인하는 것이 먼저

// 1.web에서 문자열 다운로드 받기
// 다운로드 받을 문자열을 저장 할 변수
String jsonString = null;

try {
	// 다운로드 받을 URL 생성
	Scanner sc = new Scanner(System.in);
	System.out.println("조회할 도서명");
	String keyword = sc.nextLine();
	// 영문과 숫자이외의 데이터가 있을 수 있으므로 인코딩
	keyword = URLEncoder.encode(keyword, "utf-8");

	//URL 생성
	String addr = "https://dapi.k"
			+ "akao.com/v3/search/book?target=title&size=50&query=" + keyword;
	URL url = new URL(addr);
		
	//URL연결 객체 생성
	HttpURLConnection con = (HttpURLConnection)url.openConnection();
	con.setConnectTimeout(30000);
	con.setUseCaches(false);
			
	//헤더 설정
	con.addRequestProperty("Authorization", "KakaoAK f9e2c5d96fd9f8f18c1757ee858596ec");
			
	//문자열 읽어오기
	StringBuilder sb = new StringBuilder();
	BufferedReader br = new BufferedReader(
			new InputStreamReader(con.getInputStream()));
			
	while(true) {
		String line = br.readLine();
		if(line == null)
			break;
		sb.append(line + "\n");
	}
			
	jsonString = sb.toString();
			
	//연결 해제
	br.close();
	con.disconnect();
	sc.close();
} catch (Exception e) {
	System.err.println("다운로드 실패");
	System.out.println(e.getMessage());
	e.printStackTrace();
}
//System.out.println(jsonString);


2. 조회된 데이터 건수 찾아오기
    =>JSON 문자열 : Object 형태로 리턴, 내부의 meta 속성 추출시 Object 형태가 리턴, 내부에 다시 total_count의 값을 가져오면 데이터 건수를 알수 있음

//읽어온 데이터에서 조회된 데이터 건수 찾아오기
//조회된 데이터 개수를 저장할 변수
int total_count = -1;
try {
	//문자열을 JSONObject로 변경
	JSONObject json = new JSONObject(jsonString);
			
	//meta 속성의 내용을 JSONObject로 가져오기
	JSONObject meta = json.getJSONObject("meta");
	//System.out.println(meta);
			
	//total_count 속성의 값을 정수로 가져오기
	total_count = meta.getInt("total_count");
	System.out.println(total_count);
}catch (Exception e) {
	System.err.println("데이터 개수 가져오기 실패");
	System.out.println(e.getMessage());
	e.printStackTrace();
}

3. 페이지 개수 구하기

//3. 페이지당 개수 구하기
//페이지당 출력할 개수 설정
int perPage = 50;

//페이지 개수 계산 : 전체 데이터 개수와 페이지당 출력개수를 이용
//서버프로그래밍에서는 출력할 페이지 개수를 설정하는데 이용
//클라이언트 프로그래밍에서는 읽어야할 페이지 개수를 설정하는데 이용
int pageCnt = (int)((double)total_count / perPage + (double)(perPage -1)/perPage);

System.out.println(pageCnt);

문제

int imageCnt = 10;

//위의 숫자를 이용하여

//image1.png, image2.png, image3.png..., image10.png를 출력

 

4. 전체 페이지를 순회하면서 문자열을 전부 읽어오기

    => 페이지별로 읽어서 파싱해도 됨

    => 웹 서버에서 읽어올 때는 파라미터 구성을 잘 파악하고 해야 함

        - 다른 파라미터를 추가할 경우 &로 구분

        - "https://dapi.kakao.com/v3/search/book?

           target=title&query=검색어&page=페이지번호&size=페이지당데이터개수"

    => 전체 페이지 개수를 가지고 페이지번호를 변경해가면서 데이터를 읽어야 함

 

  1) 검색어를 저장하는 변수 선언을 가장 상단으로 이동(String keyword = null;)

    => 입력받은 검색어를 다른 영역에서도 사용해야 하기 때문

  2) 페이지별로 읽어서 파싱한 후 List에 저장하기

    => title, url, price를 가져와서 저장하기

//데이터를 저장할 변수
List<Map<String, Object>> list = new ArrayList<>();
try {
	//반복하여 데이터를 가져와서 파싱
	for(int i=1; i<=pageCnt; i+=1) {
		//예외 발생시 다음작업을 수행하도록 하는 경우 - 반복문 내부에 예외처리 구문추가
		//웹의 데이터를 수집하는 경우 이런방식으로 처리하지 않을시
		//첫 조회시에는 있었으나 수집시 없어지면 예외가 발생하여 데이터 수집이 안되는 경우 발생
		try {
			//다운로드 받을 URL 만들기 - 파라미터에 한글이 있는지 확인
			String addr = "https://dapi.kakao.com/v3/search/book?target=title"
					+ "&query=" + keyword + "&page=" + i + "&size=" + perPage;
			URL url = new URL(addr);
			//연결객체 만들고 옵션 설정
			//문자열을 읽어서 출력해보고 한글이 깨지는 지 확인
			//헤더 설정을 해야하는지 확인
			HttpURLConnection con = (HttpURLConnection)url.openConnection();
			con.setConnectTimeout(30000);
			con.setUseCaches(false);
			con.addRequestProperty("Authorization", "KakaoAK f9e2c5d96fd9f8f18c1757ee858596ec");
					
			//문자열 가져오기
			String imsiString = null;
			StringBuilder sb = new StringBuilder();
			//읽어온 데이터의 한글이 깨질때 utf-8을 euc-kr로 변경
			BufferedReader br = new BufferedReader(
					new InputStreamReader(con.getInputStream(), "utf-8"));
			while(true) {
				String line = br.readLine();
				if(line == null)
					break;
				sb.append(line + "\n");
			}
			imsiString = sb.toString();
			System.out.println(imsiString);
			br.close();
			con.disconnect();
			
			//읽어온 문자열을 JSONObject로 변경
			JSONObject obj = new JSONObject(imsiString);
			
			//documents 속성의 내용을 배열로 가져오기
			JSONArray documents = obj.getJSONArray("documents");
					
			//배열 순회
			for(int j=0; j<documents.length(); j+=1) {
				//배열을 순회하면서 데이터 가져오기
				JSONObject document = documents.getJSONObject(i);
				//객체에서 데이터 읽어오기
				String title = document.getString("title");
				String urlStr = document.getString("url");
				int price = document.getInt("price");
						
				//Map으로 위의 데이터 묶기
				Map<String, Object> map = new HashMap<String, Object>();
				map.put("title", title);
				map.put("url", urlStr);
				map.put("price", price);
						
				//list에 추가
				list.add(map);
						
				//가져온 데이터 출력
				for(Map<String, Object> map2 : list) {
					System.out.println(map2.get("title"));
					System.out.println(map2.get("price"));
				}
			}
		}catch (Exception e) {
			System.err.println("실패시 다음페이지로 이동");
			System.out.println(e.getMessage());
			e.printStackTrace();
		}
	}
}catch (Exception e) {
	System.err.println("페이지 별로 가져오기 실패");
	System.out.println(e.getMessage());
	e.printStackTrace();
}

 

**데이터 사용

    => BackEnd에서는 csv, json, xml 데이터를 만들어서 넘겨주는 것이 중요

    => FrontEnd에서는 csv, json, xml 데이터를 파싱해서 출력하는 것이 중요

 

    => SmartPhone App도 대부분 FrontEnd

        - 이 부분을 구현하지 못하면 대다수의 SmartPhone App 구현 불가

 

  1) Web Data Parsing 

    => 웹 서버에서 데이터를 문자열로 받아옴(파라미터, 결과 문자열의 한글 깨짐에 유의)

    => 문자열을 읽어서 파싱해서 자료구조(DTO의 List나 Map의 List)에 저장

        - 데이터의 구성을 확인

    => Local에 저장하여 출력하거나 바로 출력