본문 바로가기

수업 정리

34일차 수업 정리(XML Parsing, HTML)

**XML Parsing

    => 태그를 이용해서 데이터를 표현하는 포맷

        <시작태그 속성=값 속성=값 /> : 빈 태그 - 내용이 없는 태그

        - 속성=값은 생략될 수 있음

ex) <age>50</age> : 우리나라

     <age value=50/> : google이 선호하는 방식

 

ex2) 이름, 나이, 주소를 저장

<person>

    <name>최성권</name>

    <age>30</age>

    <address>경기도 시흥시</address>

</person>

 

1. XML의 용도

    => 실시간으로 변하는 데이터를 전송하는 RSS에 많이 사용, 프로그램의 설정 파일에도 많이 사용

2. 경향신문 스포츠 기사 rss에서 title과 link태그의 내용을 전부 가져오기

    => 전부 가져와서 MySQL에 저장

    => http://www.khan.co.kr/rss/rssdata/kh_sports.xml 

  1) 웹에서 텍스트 다운받기

    => 웹에서 다운로드 받는 경우 파라미터를 호가인하고 파라미터에 한글이 포함되는지 확인 

        - 파라미터에 한글이 포함되어 있다면 utf-8로 인코딩

        - java.net.URLEncode.encode(인코딩할 문자열, "utf-8")을 호출하면 인코딩 된 문자열을 리턴 받을 수 있음

    => header를 필요로 하는지 확인

        - header를 필요로 한다면 HttpURLConnection 객체를 가지고 addProperty를 호출하여 header 값 대입

        - Open API에서 헤더를 설정해야 하는 경우가 많음

    => 다운로드 받은 결과에 한글이 포함된 경우 한글이 깨졌는지 확인

        - 한글이 깨진경우 BufferedReader 만드는 문장을 수정

        - new BufferedReader(new InputStreamReader(connection.getInputStream(), "인코딩방식")))

  2) XML Parsing

    => 방법은 DOM Parser와 SAX Parser

    => DOM(Document Object Model) Parser : 태그의 내용을 메모리에 전부 펼쳐두고 원하는 태그를 찾는 방식

        - 메모리에 전부 펼치기 때문에 메모리 사용량은 증가하지만 속도가 빠름

    => SAX Parser : 태그의 내용을 부분부분 읽어가면서 파싱하는 방식

        - 부분적으로 파싱하기 때문에 메모리 사용량은 적지만 속도는 DOM보다 느림

    => 대다수의 프로그래밍 언어가 2가지 방법 모두 제공

 

    => DOM Parser

        // 파싱을 수행해주는 DocumentBuilder 인스턴스를 생성하기 위한 DocumentBuilderFactory 인스턴스 생성

        - DocumentBuilderFactory 팩토리 = DocumentBuilderFactory.newInstance();

 

        // 팩토리 인스턴스를 이용해서 DocumentBuilder 인스턴스를 생성

        - DocumentBuilder 빌더 = 팩토리.newDocumentBuilder();

 

        // 파싱을 수행 - 메로리에 펼침

        - Document 도큐먼트 = 빌더.parse(new ByteArrayInputStream(xml문자열.getBytes()));

 

        //루트를 찾기

        - Element 루트엘리먼트 = 도큐먼트.getDocumentElement();

 

        // 원하는 태그를 찾아오기

        - NodeList 노드리스트 = 루트엘리멘트.getElementsByTagName(String tag);

 

        // 노드리스트 순회

for(int i=0; i<노드리스트.getLength(); i+=1){  
	//하나의 태그를 가져옴
    Node 태그 = 노드리스트.get(i);
    Node 자식 = 태그.getFirstChild();
    String 내용 = 자식.getNodeValue();
}

 

  3) 스마트폰 애플리케이션이라면 이미 다운받은 경우 다시 받지 않도록 해주는 것이 중요

    => 중복된 데이터를 다운로드 받기 때문에 불필요한 트래픽이 증가

    => 스마트폰은 네트워크가 불안정하므로 언제 네트워크가 해제될지 예측 불가(불필요한 다운로드 최소화)

    => 되도록 다운로드 받은 데이터를 로컬에 저장해두고 데이터가 변경된 경우에만 다시 다운로드 받게 제작

        - 변경여부 판단 방법 : 데이터를 가져온 날짜, 시간을 파일에 기록해두고, 서버의 업데이트 날짜와 비교

 

3. 기상청 XML 정보를 가져와서 파싱한 후 데이터베이스에 저장

    => 스레드를 이용

  1) Java에서 스레드 생성 및 시작(비동기 수행 - 순서대로 수행되지 않음)

    => Thread 클래스로부터 상속 받는 클래스를 만든 후 public void run 메소드에 스레드로 수행할 동작을 작성

        - 인스턴스 생성 후, Start() 메소드 호출

    => Runnable 인터페이스를 구현한 클래스를 만들 후 public void run 메소드에 스레드로 수행할 동작을 작성

        - 인스턴스 생성 후 Thread 클래스의 생성자에 대입하여 Thread 클래스의 인스턴스를 만들고 start()를 호출

    => 네트워크에서 다운로드 받아 작업을 수행, 업로드하는 코드는 스레드를 이용하는 것을 권장.

        - 업, 다운로드는 시간이 오래 걸리는 작업이라 스레드 비사용시 다음 작업이 무한 대기상태에 빠질 수 있음

    => 클래스를 상속, 인터페이스 구현시, 별도 클래스 생성없이 인스턴스를 바로 만들어서 사용가능(anonymous class)

        - 구현해야 할 메소드가 1개인 경우 람다 문법으로 표현 가능

    => 람다와 스트림, GUI 프로그래밍은 안드로이드 하기전 자바 복습 겸하여 학습

  2) 기상청 RSS URL을 확인하고 데이터 확인

    => url : http://www.weather.go.kr/weather/forecast/mid-term-rss3.jsp?stnId=108 

    => 도시별 각 날짜의 날씨, 최저온도, 최고온도를 읽어서 저장

 

  3) 스레드를 만들어서 실행하는 코드를 작성

public static void main(String[] args) {
	//Anonymous Class를 이용해서 Thread 클래스로 부터 상속 받는 클래스의 인스턴스 생성
	Thread th = new Thread() {
		//스레드로 수행할 내용
		public void run() {
			//run 메소드 안에서 예외 발생시 return 하도록 만들면 thread 중지 가능
			try {
					
			}catch (Exception e) {
				return;
			}
		}
	};
        
	//스레드 시작
	th.start();
}

 

  4) 스레드 메소드 안에 다운로드 받는 코드를 작성

    => 파라미터의 한글여부, header 추가여부, 가져온 문자열의 한글 깨짐을 확인

String weatherString = null;
try {
	//URL 만들기
	String addr = "http://www.weather.go.kr/weather/forecast/mid-term-rss3.jsp?stnId=108";
	URL url = new URL(addr);
			
	//연결 객체 만들기
	HttpURLConnection con = (HttpURLConnection)url.openConnection();
	con.setConnectTimeout(30000);
	con.setUseCaches(false);
				
	//스트림을 생성하고 줄 단위로 읽어서 저장하기
	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");
	}
	weatherString = sb.toString();
						
	//연결 해제
	br.close();
	con.disconnect();
}catch (Exception e) {
	System.out.println("다운로드 예외");
	System.out.println(e.getMessage());
	e.printStackTrace();
}
//데이터 확인
System.out.println(weatherString);

 

5) 도시이름과 날짜 그리고 날씨, 최고온도, 최저온도 데이터를 가져와서 List에 저장

    => 도시이름 1개에 13개씩 데이터가 존재

    => 도시이름 리스트와 날짜, 날씨 그리고 최고온도, 최저온도 별 리스트를 생성하여 데이터 파싱하여 저장

        - 저장 후, 도시명, 날짜, 날씨, 최고온도, 최저온도를 갖는 Map을 만들어 List로 저장

//데이터를 저장할 자료구조 생성
List<Map<String, Object>> list = new ArrayList<>();
					
// 도시이름 찾아오기
List<String> cities = new ArrayList<String>();
					
//데이터를 파싱하여 List에 저장
if(weatherString != null && weatherString.trim().length() > 0) {
	try {
		// XMl 문자열에서 루트 태그를 찾기
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		DocumentBuilder builder = factory.newDocumentBuilder();
		Document document = builder.parse(new ByteArrayInputStream(
				weatherString.getBytes()));
		Element root = document.getDocumentElement();

		NodeList cityList = root.getElementsByTagName("city");
		for(int i=0; i<cityList.getLength(); i+=1) {
			Node node = cityList.item(i);
			Node city = node.getFirstChild();
			cities.add(city.getNodeValue());
		}
		//System.out.println(cities);
							
		//날짜, 날씨, 최고온도, 최저온도 가져오기
		NodeList dateList = root.getElementsByTagName("tmEf");
		NodeList wfList = root.getElementsByTagName("wf");
		NodeList tmxList = root.getElementsByTagName("tmx");
		NodeList tmnList = root.getElementsByTagName("tmn");
							
		//날짜, 날씨, 최고온도, 최저온도를 저장할 임시 리스트
		List<String> list1 = new ArrayList<String>();
		List<String> list2 = new ArrayList<String>();
		List<String> list3 = new ArrayList<String>();
		List<String> list4 = new ArrayList<String>();
							
		for(int i=0; i<dateList.getLength(); i+=1) {
			//날짜를 list1에 저장하기
			Node node = dateList.item(i);
			Node temp = node.getFirstChild();
			list1.add(temp.getNodeValue());
								
			//날씨를 list2에 저장하기
			//wf만 초기데이터가 1개 더 있어서 하나 뒤의 데이터 가져오기
			node = wfList.item(i+1);
			temp = node.getFirstChild();
			list2.add(temp.getNodeValue());
								
			//최고온도를 list3에 저장하기
			node = tmxList.item(i);
			temp = node.getFirstChild();
			list3.add(temp.getNodeValue());
								
			//최저온도를 list4에 저장하기
			node = tmnList.item(i);
			temp = node.getFirstChild();
			list4.add(temp.getNodeValue());
		}
							
//		System.out.println(list1);
//		System.out.println(list2);
//		System.out.println(list3);
//		System.out.println(list4);

		//cityList와 list1, list2, list3, list4의 데이터를 모아서 하나의 list로 만들기
		//city 1개에 각 데이터 13개씩 존재
		for(int i=0; i<cities.size(); i+=1) {
			//도시이름 1개 가져오기
			String city = cities.get(i);
			//도시이름 1개당 13개의 날씨, 날짜, 최고온도, 최저온도 가져오기
			for(int j=0; j<13; j+=1) {
				String date = list1.get(i*13+j);
				String wf = list2.get(i*13+j);
				String tmx = list3.get(i*13+j);
				String tmn = list4.get(i*13+j);
									
				//맵생성
				Map<String, Object> map = new HashMap<String, Object>();
				map.put("city", city);
				map.put("date", date);
				map.put("wf", wf);
				map.put("tmx", tmx);
				map.put("tmn", tmn);
									
				list.add(map);
			}
		}
	} catch (Exception e) {
		System.err.println("xml 파싱 실패");
		System.out.println(e.getMessage());
		e.printStackTrace();
	}

}else {
	System.out.println("읽어온 데이터가 없습니다.");
}
//System.out.println(list);
for(Map<String, Object> map : list) {
	if(map.get("city").equals("서귀포")) {
		System.out.println(map);
	}
}

 

6) 위의 데이터베이스에 저장하기 위하여 테이블 생성

    => "city", "date", "wf", "tmx", "tmn" : 전부 문자열

create table weather(
	weatherNum int primary key auto_increment,
	weatherCity varchar(20),
	weatherDate varchar(50),
	weatherwf varchar(30),
	weathertmx varchar(5),
	weathertmn varchar(5)
)default charset=utf8;

 

  7) 프로젝트에서 MySQL이 사용 가능한지 확인

    => MySQL 드라이버가 프로젝트에 포함되어 있는지 확인

 

  8) 읽어온 데이터를 MySQL에 저장

    => MySQL 사용시 "characterEncoding=utf8useUnicode=true&characterEncoding=utf8&serverTimezone=UTC"

        - 비 사용시 한글을 사용할 수 없음(깨짐)

//MySQL에 저장
try {
	//드라이버 클래스 로드
	Class.forName("com.mysql.cj.jdbc.Driver");
				
	//데이터베이스 연결
	Connection con = DriverManager.getConnection(
			"jdbc:mysql://localhost:3306/mysql?useUnicode=true"
			+ "&characterEncoding=utf8&serverTimezone=UTC"
			+ "&useSSL=false", "root", "900826");
						
	for(Map<String, Object> map : list) {
		PreparedStatement pstmt = con.prepareStatement(""
				+ "insert into weather(weatherCity, weatherDate, weatherwf, "
				+ "weathertmx, weathertmn) values(?,?,?,?,?)");
							
		pstmt.setString(1, (String)map.get("city"));
		pstmt.setString(2, (String)map.get("date"));
		pstmt.setString(3, (String)map.get("wf"));
		pstmt.setString(4, (String)map.get("tmx"));
		pstmt.setString(5, (String)map.get("tmn"));
							
		//SQl실행
		pstmt.executeUpdate();
							
		pstmt.close();
	}
						
	//사용한 객체 정리
	con.close();
						
}catch (Exception e) {
	System.err.println("데이터 저장 실패");
	System.out.println(e.getMessage());
	e.printStackTrace();
}

 

**HTML Parsing

    => jsoup 라이브러리 이용

    => html : 웹 브라우저에 문서를 출력하기 위한 마크업 언어

        - xml과 json은 출력을 위한 포맷이 아니고 데이터 포맷

        - 웹 사이트에서 보여지지만 open api로 제공되지 않는 경우 html을 수집하여 원하는 데이터만 사용

    => xml은 구조적이라 태그만 가지고 충분한 데이터 추출 가능

         html은 비구조적이라 원하는 데이터를 가져올 때 태그 대신에 여러가지를 이용

 

1. 용어

    => tag : 문서의 구조를 나타내기 위한 명령어 - 중복될 수 있음

    => id : 자바스크립트에서 사용하기 위해 부여하는 식별자(문서에 1개만 존재)

    => class : CSS에서 동일한 디자인을 적용하기 위해 부여하는 이름(그룹화하여 사용하므로 중복 가능)

    => name : 서버에서 구분하기 위한 이름 - 서버에게 데이터를 전달하는 태그에만 부여(중복 가능)

 

    => select : HTML 문서내의 객체의 선택을 다양화 하기 위해 만든 문법(중복가능)

         - 앞에서부터 순서대로 만들어지므로 앞부분을 생략해도 동일한 객체를 선택가능(중복 가능성 증가)

    => xpath : xml 문서에서 하나의 객체를 가리키기 위한 언어(절대 중복되지 않음(=id))

 

    => 크롬의 검사기능을 사용하면 원하는 객체의 경로를 알아내는게 쉬움

        - 선택자 : 중복되는 경우가 있음(웹 프론트앤드에서는 중요)

          #NM_FAVORITE > div.group_nav > ul.list_nav.type_fix > li:nth-child(3) > a

        - xpath : 중복되는 경우가 없음

          //*[@id="NM_FAVORITE"]/div[1]/ul[1]/li[3]/a

 

 

연습 문제

1. 하나의 반복문으로 ar의 모든 요소를 출력

int [][] ar = new int[5][5];
int cnt = 1;
for(int i=0; i<5; i=i+1){
	for(int j=0; j<5; j=j+1){
		ar[i][j] = cnt;
		cnt = cnt + 1;
	}
}

//답안
for(int i=0; i<(5*5); i+=1) {
	System.out.print(ar[i/5][i%5] + " ");
}

2. k의 모든 내용을 ar에 순서대로 대입해서 출력

int [] k = new int[25];
cnt = 101;
for(int i=0; i<25; i=i+1){
	k[i] = cnt;
	cnt = cnt + 1;
    
    //답안
    ar[i / 5][i % 5] = k[i];
}

//답안 확인
for (int i = 0; i < (5 * 5); i += 1) {
	System.out.print(k[i] + " ");
}