본문 바로가기
프로젝트 & 실습/VarChar_JSP_ver_Proj

[프로젝트] 데이터 크롤링(img 부분 업데이트)

by hhyyyjun 2023. 1. 8.

프로젝트에 사용될 데이터를 크롤링하기 위해 여러 중고차 사이트를 참고하였음

but 대부분의 사이트에서 크롤링 시 크롤링을 못하게 막아두었고,

보배드림 중고차 사이트에서 유일하게 가능하여서 진행하게 되었음

https://www.bobaedream.co.kr/cyber/CyberCar.php?sel_m_gubun=ALL&page=1&order=S11&view_size=70

 

사이버매장 | 보배드림 사이버매장

자동차 검색, 중고차 시세, 수입차, 국산차, 내차사기

www.bobaedream.co.kr

 

진행사항 - 먼저 java로직으로 데이터 크롤링 및 가공작업

추후에 리스너 서블릿으로 해당 크롤링 클래스를 호출하여 사용할 예정

총 크롤링할 데이터

  • 상품 제목
  • 상품 설명
  • 연식
  • 엔진
  • 주행거리
  • 가격
  • 지역
  • 이미지(URL로 사용예정)

1. 크롤링 준비

  • 크롤링 데이터를 삽입할 CAR 테이블 생성
  • sql문 로직(CarDAO)
  • 변수 설정(CarVO)
  • 크롤링 로직(Crawling)
  • JDBC 연동(JDBCUtil)

2. 범위 설정

이전의 크롤링 프로젝트를 했을 때 같은 class명이나 id명의 중복으로 다른 부분의 원치않는 데이터가 나왔던 경험이 있어

크롤링할 데이터를 감싸는 부분의 범위를 설정하였다.

3. 상품제목, 상품설명 크롤링

가공할 필요가 없는 데이터이므로 그대로 가져왔다.

- 상품 제목

- 상품 설명

4. 연식 크롤링

년도를 기준, 정수형으로 데이터를 삽입할 예정이므로 가공처리가 필요하였다.

년도/월 형태의 데이터

1) substring을 사용하여 맨 앞의 정수 2자리만 추출

2) 20xx 년도로 저장하기 위해 concat을 통해 문자열 합치기

3) 출력확인을 위해 정수형으로 형변환

5. 엔진 크롤링

추후 데이터를 출력하기 위해 문자열만 사용하기 위해(특수문자 제외) '+'가 붙은 부분을 가공처리 하였다.

'+'가 붙어있음

1) indexOf 함수를 이용하여 '+'의 인덱스 위치가 0보다 컸을 때

2) substring으로 처음부터 '+'전까지 잘라서 변수에 저장

6. 주행거리 크롤링

주행거리는 차량마다 다르고 정수형으로 저장하기 위해 가공처리를 하였다.

뒤의 'km'을 삭제처리, 차량마다 주행거리 단위가 다름(천, 만, 없는 경우), 또한 주행거리가 '미등록' 이라고 되어있는 경우도 있다.

1) 먼저 '미등록'인 경우 주행거리를 0으로 저장

2) 'km'를 가공하기 위해 indexOf를 이용하여 k전까지 잘라서 저장

3) indexOf를 사용하여 '만', '천'이 있는 경우 '만','천' 전까지 문자열을 자르고 '만'은 0000 문자열 합침, '천'은 000 문자열을 합쳤다.

4) 아무 문자열이 없는 경우 000 문자열을 합쳐 저장하였다.

7. 가격 크롤링

가격은 정해져있지 않은 상품들도 많았다. 상담/운용리스/금융리스/계약/팔림/보류/렌트 의 여러 형태 문자열로 존재하여 가공처리가 필요했다.

가격을 정수형으로 저장할 계획

1) 가격이 아닌 상담/운용리스/금융리스/계약/팔림/보류/렌트 문자열인 경우 int형의 최대값으로 만들어서 저장

2) xxx(가격) 만원 과 같은 형태로 되어있어 숫자와 '만원' 사이의 공백을 indexOf와 substring을 이용하여 문자열 정리

3) 가격에는 ',' 가 들어가있으므로 replace를 이용하여 빈 값으로 만들어 저장하였음

8. 지역 크롤링

지역은 앞의 도시로만 저장할 계획이므로 가공처리 하였다.

하지만 문제점이 있었다. 먼저 li 태그들로 이루어져 있고 class명이 다 같아 nth-child 속성으로 필요한 부분을 크롤링 하였는데,

지역이 아닌 '오토갤러리'라는 li태그가 위에 있는 경우가 있었다. nth-child 속성이나 인접형제 선택자로 가져오게 되면 태그와 class명이 전부 같으므로 원하는 지역 데이터만 가져오는 것이 불가능하였다. 따라서 '오토갤러리' 부분의 문자열이 있는 경우 java 로직에서 변경되도록 하였다.

지역의 앞부분만 저장

1) '오토갤러리' 가 있는 경우 replace 함수로 '경기' 로 변경하여 저장

2) indexOf와 substring을 사용하여 지역 사이의 공백부분까지 문자열 편집 및 저장

9. 이미지 크롤링

이미지는 컴퓨터의 파일에서 가져오지 않고 url로 불러올 예정

1) 이미지의 url을 가져오기 위해 attr을 사용하여 속성 src를 불러온다.

10. 여러 페이지 크롤링

상품 페이지들의 url을 보니 사진의 빨강박스 내의 숫자만 바뀌는 것을 확인하였다.

for문을 사용하고 url의 숫자가 있는 부분을 변수로 두어 원하는 페이지 수 만큼 데이터를 크롤링 할 수 있게 코드를 작성하였다.

페이지 별 크롤링 데이터 개수 = 70개

크롤링 실행

로그처리를 한 결과 총 70개의 데이터가 제대로 크롤링된 것을 확인!

DB확인 결과

  • 연식데이터 연도별로 가공되어 처리된 것 확인
  • 엔진 데이터 '+ ~' 문자열 가공처리 확인
  • 주행거리데이터 조건별로 가공처리 확인
  • 가격데이터 숫자 이외의 문자열은 int형의 최대값으로 처리된 것 확인, ',' 정리 확인
  • 지역데이터 '오토갤러리' > '경기' 가공처리 확인, 앞부분 지역만 출력 확인

전체코드

package controller;

import java.io.IOException;
import java.util.Iterator;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import car.CarDAO;
import car.CarVO;


public class Crawling {
//	public static void main(String[] args) {
		public static void data(){
	
		int count = 0;
		for(int i=1;i<2;i++) {
			String url = "https://www.bobaedream.co.kr/cyber/CyberCar.php?sel_m_gubun=ALL&page="+i+"&order=S11&view_size=70";
			Document doc = null;
			try {
				doc = Jsoup.connect(url).get(); 
			} catch (IOException e) {
				e.printStackTrace();
			}

			String ctitle1 = "p.tit.ellipsis > a"; // 차량명
			String csubtitle1 = "p.stxt.ellipsis > a"; // 설명
			String cyear1 = "div.mode-cell.year > span.text"; // 연식
			String cfuel1 = "div.mode-cell.fuel > span.text"; //엔진
			String ckm1 = "div.mode-cell.km > span.text"; //주행거리
			String cprice1 = "div.mode-cell.price"; //가격
			String ccity1 = "ul.content-list > li:nth-child(1) > span.text"; // 지역
			String cimg1 = "div.list-inner > div.mode-cell.thumb > a.img.w132 > img"; // 이미지

			Elements eles = doc.select("ul.clearfix"); // 아래 정보들을 포함하는 div의 정보 가져온다
			Elements ctitle2 = eles.select(ctitle1); 
			Elements csubtitle2 = eles.select(csubtitle1); 
			Elements cyear2 = eles.select(cyear1); 
			Elements cfuel2 = eles.select(cfuel1); 
			Elements ckm2 = eles.select(ckm1);
			Elements cprice2 = eles.select(cprice1); 
			Elements ccity2 = eles.select(ccity1);
			Elements cimg2 = eles.select(cimg1);

			Iterator<Element> ctitle3 = ctitle2.iterator();
			Iterator<Element> csubtitle3 = csubtitle2.iterator();
			Iterator<Element> cyear3 = cyear2.iterator();
			Iterator<Element> cfuel3 = cfuel2.iterator();
			Iterator<Element> ckm3 = ckm2.iterator();
			Iterator<Element> cprice3 = cprice2.iterator();
			Iterator<Element> ccity3 = ccity2.iterator();
			Iterator<Element> cimg3 = cimg2.iterator();
			CarDAO cDAO = new CarDAO();
			while (ctitle3.hasNext()) {
				//자동차 명
				String ctitle4 = ctitle3.next().text();
				System.out.println("차 : " + ctitle4);
				//자동차설명
				String csubtitle4 = csubtitle3.next().text();
				System.out.println("설명 : " + csubtitle4);

				//연식 : 데이터 가공
				String cyear4 = cyear3.next().text(); 
				cyear4 = cyear4.substring(cyear4.length()-5, cyear4.length()-3); //연식 xx/xx 중 '/' 앞부분의 2자리수만 추출
				cyear4 = "20".concat(cyear4); //20xx 년도로 저장하기 위해 concat으로 문자열 합침
				System.out.println("연식 : " + Integer.parseInt(cyear4));

				//엔진 : 데이터 가공
				String cfuel4 = cfuel3.next().text();
				if(cfuel4.indexOf("+") > 0) { // 문자열 '+'의 인덱스가 0보다 클 경우
					cfuel4 = cfuel4.substring(0,cfuel4.indexOf("+")); //문자열 처음부터 '+' 전까지 잘라 저장
				}
				System.out.println("엔진 : " + cfuel4);

				//주행거리 : 데이터 가공
				String ckm4 = ckm3.next().text();
				if(ckm4.equals("미등록")) {
					ckm4 = "0";
				}
				else if(ckm4.indexOf("k") > 0) { // 문자열 'k'의 인덱스가 0보다 클 경우
					ckm4 = ckm4.substring(0, ckm4.indexOf("k")); //문자열 처음부터 'k' 전까지 잘라 저장
					if(ckm4.indexOf("만")>0) { // 문자열 '만'의 인덱스가 0보다 클 경우
						ckm4 = ckm4.substring(0,ckm4.indexOf("만")); //문자열 처음부터 '만' 전까지 잘라 저장
						ckm4 = ckm4.concat("0000"); //concat으로 0000 합침
					}else if(ckm4.indexOf("천")>0) { // 문자열 '천'의 인덱스가 0보다 클 경우
						ckm4 = ckm4.substring(0,ckm4.indexOf("천")); //문자열 처음부터 '천' 전까지 잘라 저장
						ckm4 = ckm4.concat("000"); //concat으로 000 합침
					}else {
						ckm4 = ckm4.concat("000"); //concat으로 000 합침
					}
				}
				else if(ckm4.indexOf("m")>0) { //문자열 'm'의 인덱스가 0보다 클 경우
		               ckm4 = ckm4.substring(0, ckm4.indexOf("m")); //문자열 처음부터 'm' 전까지 잘라 저장
		               if(ckm4.indexOf("만")>0) { // 문자열 '만'의 인덱스가 0보다 클 경우
		                  ckm4 = ckm4.substring(0,ckm4.indexOf("만")); //문자열 처음부터 '만' 전까지 잘라 저장
		                  ckm4 = ckm4.concat("0000"); //concat으로 0000 합침
		               }else if(ckm4.indexOf("천")>0) { // 문자열 '천'의 인덱스가 0보다 클 경우
		                  ckm4 = ckm4.substring(0,ckm4.indexOf("천")); //문자열 처음부터 '천' 전까지 잘라 저장
		                  ckm4 = ckm4.concat("000"); //concat으로 000 합침
		               }else {
		                  ckm4 = ckm4.concat("000"); //concat으로 000 합침
		               }
		            }
				System.out.println("주행거리 : " + Integer.parseInt(ckm4));

				//가격 : 데이터 가공
				String cprice4 = cprice3.next().text();
				if(cprice4.equals("상담") || cprice4.equals("운용리스") || cprice4.equals("금융리스") || cprice4.equals("계약") || cprice4.equals("팔림") || cprice4.equals("보류") || cprice4.equals("렌트")) {
					cprice4 = "" + Integer.MAX_VALUE; // 가격이 기재되지 않은 경우 int형 최대값으로 만듦
				}
				else if(cprice4.indexOf(" ") > 0) { // indexOf로 공백이 있는 위치의 인덱스가 0보다 클 경우
					cprice4 = cprice4.substring(0, cprice4.indexOf(" ")); //문자열 처음부터 공백 전까지 잘라 저장
					cprice4 = cprice4.replace(",", ""); // 문자열에서 ',' 를 빈 값으로 설정
				}
				System.out.println("가격 : " + Integer.parseInt(cprice4));

				//지역 : 데이터 가공
				String ccity4 = ccity3.next().text();
				if(ccity4.equals("오토갤러리")) { //지역이 오토갤러리로 나올 경우 '경기'로 변경하여 저장
					ccity4 = ccity4.replace("오토갤러리", "경기"); 
				}
				else if(ccity4.indexOf(" ") > 0) { // indexOf로 공백이 있는 위치의 인덱스가 0보다 클 경우
					ccity4 = ccity4.substring(0,ccity4.indexOf(" ")); //문자열 처음부터 공백 전까지 잘라 저장
				}
				System.out.println("지역 : " + ccity4);
				
				//이미지
				String cimg4 = cimg3.next().attr("src"); //이미지의 URL을 가져오기 위해 속성 src를 불러옴
				System.out.println("이미지 url : " + cimg4);
				
				count++;
				System.out.println("순서 : "+count);
				System.out.println();
				CarVO vo = new CarVO();
				vo.setCtitle(ctitle4);
				vo.setCsubtitle(csubtitle4);
				vo.setCyear(Integer.parseInt(cyear4)); 
				vo.setCfuel(cfuel4);
				vo.setCkm(Integer.parseInt(ckm4));
				vo.setCprice(Integer.parseInt(cprice4)); 
				vo.setCcity(ccity4); 
				vo.setCimg(cimg4);
				cDAO.insert(vo); // dao insert함수 호출
			}
		}
		System.out.println("총 크롤링된 데이터 수 : "+count);
		System.out.println("로그: DB에 저장완료!");
		System.out.println();
	}
}

댓글