코딩항해기

[실습/JAVA] 쇼핑몰 프로그램에 웹크롤링 묻히기 (+풀이 0726) 본문

problem solving/과제&실습 코딩

[실습/JAVA] 쇼핑몰 프로그램에 웹크롤링 묻히기 (+풀이 0726)

miniBcake 2024. 7. 25. 16:36

 

 

 

 

[TEAM] 쇼핑몰 프로그램 만들기

쇼핑몰 프로그램 만들기java / Eclipse5인참여 (팀명 : 삼부삼조)24/07/19 ~ 24/07/24 MVC 패턴 연습을 위한 팀 프로젝트를 진행하였다.  [요구사항]쇼핑몰 프로그램을 MVC 패턴으로 구현해주세요!회원 속

minibcake.tistory.com

 

 

 

java 외부 API인 Jsoup을 사용하여 쇼핑몰 프로그램에 사용한 샘플데이터를 웹크롤링한 내용으로 바꾸고, 파일로 출력할 수 있도록 기능을 추가해보자.

 

먼저, 웹크롤링이 정상적으로 되는지 확인하기 위해 크롤링을 따로 연습했다.

package crawling;

import java.io.IOException;

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

public class Crawling {
	public static void main(String[] args) {
		String url = "https://www.theskinfood.com/"; //크롤링할 대상 사이트
		
		Connection conn = Jsoup.connect(url);
		try {
			Document doc = conn.get();
//			System.out.println(doc);
			Elements nameElems = doc.select("span.goods_name"); 
			Elements priceElems = doc.select("span.sale_price"); 
			//요소보기
			for(Element elem : nameElems) {
				System.out.println(elem.text());
			}
			for(Element elem : priceElems) {
				System.out.println(elem.text().replaceAll("[^\\d]", "")); //앞의 인자에 해당하는 부분을 뒤의 인자로 대신하는 정규표현식
//				\\d: 숫자 (digit)
//				\\D: 숫자가 아닌 문자 (non-digit)
//				\\s: 공백 문자 (space)
//				\\S: 공백이 아닌 문자 (non-space)
//				\\w: 단어 문자 (word character, 알파벳과 숫자 및 밑줄)
//				\\W: 단어 문자가 아닌 문자 (non-word character)
//				[^abc]: a, b, c가 아닌 문자 (negation)
			}
//			System.out.println(nameElems.size());
//			System.out.println(priceElems.size());
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

 

상품과 가격만 웹크롤링한 데이터로 넣고 상품의 재고는 랜덤값으로 넣을 예정이기 때문에 

상품 이름을 보여주는 요소를 확인해보니 span태그에 goods_name 클래스명을 가지고 있었고,

가격 을 보여주는 요소를 확인해보니 span태그에 sale_price 클래스명을 가지고 있어 해당 명칭으로 요소를 지정했다.

 

for each문을 통해 확인해보니 데이터 정제가 필요했고, 각각 .text()를 통해 태그 제거, replaceAll()로 필요없는 문자열 요소를 제거하여 데이터가 의도한 대로 나오는 것을 확인했다.

 

 

MVC 쇼핑몰 프로젝트에 적용

샘플 데이터를 손대기 위해서는 DAO 생성자에 있는 샘플데이터 부분만 작업하면 되기 때문에 다른 부분은 건들지 않았다.

public ProductDAO() {
		this.datas = new ArrayList<ProductDTO>(); // 멤버변수 초기화 

		// ▼ 샘플 데이터  
		String url = "https://www.theskinfood.com/";
		Connection conn = Jsoup.connect(url);
		try {
			Document doc = conn.get();
			Elements nameElems = doc.select("span.goods_name"); 
			Elements priceElems = doc.select("span.sale_price"); 
			//요소보기
			for(int i=0; i<nameElems.size(); i++) {
				ProductDTO product = new ProductDTO();
				product.setProductId(ProductDAO.PK++);
				product.setProductName(nameElems.get(i).text());
				product.setProductInventory(ProductDAO.rand.nextInt(6));
				product.setProductPrice(Integer.parseInt(priceElems.get(i).text().replaceAll("[^\\d]", "")));
				this.datas.add(product);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

 

상품의 이름과 가격의 개수가 다를 수 없으므로 반복 길이는 둘 중의 하나인 상품 이름의 길이를 잡아 설정했다.

상품 생성, 값 설정, 상품 리스트에 추가하는 것을 요소 길이만큼 반복하며, 재고는 0~5개 사이로 랜덤 설정된다.

 

실행하여 상품 목록을 출력해보면, 정상적으로 출력된다.

 

 

샘플데이터가 반영된 것을 확인했으니, 기능을 추가하면 된다.

 

상품 리스트 출력 기능 추가

상품 리스트 출력 기능을 추가하기 전 메뉴 추가 위치를 설계하였다.

출력 파일명과 파일 내용에 출력하는 사용자 이름을 추가하고자 하는 개인적인 요구사항이 있었기 때문에 로그인 메뉴에 6번으로 추가하게 되었다.

 

LoginView 메뉴출력 수정

public void printLoginMenuList() {	// 로그인 메뉴
		System.out.println(" 4. 장바구니 ");
		System.out.println(" 5. 로그아웃 ");
		System.out.println(" 6. 상품목록 프린트 ");
	}

 

메뉴를 추가했다면 MainView에서 입력값 유효성을 검사하는 부분도 수정해야한다.

public int insertMainMenu(MemberDTO user) {	//메인 메뉴 입력 받기
		int insertMainMenu;

		while(true) {		
			System.out.print(" 번호 입력 >> ");
			insertMainMenu= sc.nextInt();
			if(user!=null) {//로그인 상태라면
				if(!(2<=insertMainMenu&&insertMainMenu<=3)
						&&(0<=insertMainMenu&&insertMainMenu<=6)) {
					//입력값이 2번,3번을 선택타지 않았고, 
					//0~6 사이의 숫자를 선택받았을때 
					break;
				}
			}
			else {//로그인 상태가 아니라면
				if(0<=insertMainMenu&&insertMainMenu<=3) {
					//0~3사이를 입력받기
					break;
				}

			}//위에가 다 아니라면 
			System.out.println("다시 입력바랍니다!");
		}
		return insertMainMenu;
	}

 

로그인 상태 입력값 유효범위만 수정해주면된다.

이후 출력 성공했을 때의 안내 문구가 필요하기 때문에 LoginView에 새로운 메서드(화면)을 추가해주었다.

public void printTruePrint() {	
		System.out.println(" 상품 목록 프린트가 완료 되었습니다.");
	}

 

 

Controller 파트 기능 추가

이제 View와 Model은 준비가 완료되었으므로, Controller 작업을 마무리한다.

else if(menu == 6) {
	String url = "D:\\koreaIT\\hjy2406\\javaworkspace\\0725shoppingmalCrawling\\";
	String file = user.getMid()+"product_list.txt";
	ProductDTO product = new ProductDTO();
	product.setCondition("ALL");
	ArrayList<ProductDTO> datas = productDAO.selectAll(product);
				
	try {
		BufferedWriter writer = new BufferedWriter(new FileWriter(url+file));
		writer.write(user.getMid()+"님이 출력한 상품 리스트입니다.");
		writer.newLine();
					
		for(ProductDTO data : datas) {
			writer.write("[" + data.getProductId() +" | "+ data.getProductName() +" | "+data.getProductPrice()+"원 | "+data.getProductInventory()+ "개 ]");
			writer.newLine();
		}
		writer.close();
		loginView.printTruePrint();			
		} 
    catch (IOException e) {
		System.out.println("    log printProductList false IOException");
		mainView.printFalse();
	}
}

 

저장할 경로와 파일명과 확장자를 지정해주고, model로부터 상품리스트 전체를 받아온다.

writer에서 오류가 발생하지 않는다면 상품 리스트 출력이 실행되며, 실패할 경우 실패화면이 출력된다.

상품 리스트는 write()메서드를 통해 받아온 상품 리스트를 순회하며 차례로 버퍼에 값을 쌓고,

close()되며 해당 경로에 지정된 파일을 생성하고 해당 파일에 버퍼에 올라가 있던 값을 전부 출력한다.

완료되면 출력 성공 화면을 보여주게 된다.

 

123product_list.txt
0.01MB

 

 

 

 

[피드백]

현재는 크롤링하는 코드가 바로 DAO 생성자에 들어있는데, 이 부분을 따로 빼서 크롤링만 하고 데이터를 받아와 샘플데이터를 제작하는 식으로 하면 더욱 깔끔해진다는 피드백을 들었다.

 

DAO에서 CRUD로 값을 Controller에서 받아오듯이 Crawling과 DAO의 관계를 짜면 되는 것으로 보인다.

Crawling은 상품이름 Elements를 반환하는 메서드와 가격 Elements를 반환하는 메서드를 지니게 된다.

	public Elements productNameData() {
		Document doc;
		Elements nameElems;
		try {
			doc = conn.get();
			nameElems = doc.select("span.goods_name"); 
		} catch (IOException e) {
			nameElems = null;
		}
		return nameElems;
	}
	
	public Elements productPriceData() {
		Document doc;
		Elements priceElems;
		try {
			doc = conn.get();
			priceElems = doc.select("span.sale_price"); 
		} catch (IOException e) {
			priceElems = null;
		}
		return priceElems;
	}
	public ProductDAO() {
		this.datas = new ArrayList<ProductDTO>(); // 멤버변수 초기화 

		// ▼ 샘플 데이터  
		this.crawling = new Crawling();
		this.nameElems = crawling.productNameData(); 
		this.priceElems = crawling.productPriceData(); 
		//요소보기
		for(int i=0; i<nameElems.size(); i++) {
			ProductDTO product = new ProductDTO();
			product.setProductId(ProductDAO.PK++);
			product.setProductName(this.nameElems.get(i).text());
			product.setProductInventory(ProductDAO.rand.nextInt(6));
			product.setProductPrice(Integer.parseInt(this.priceElems.get(i).text().replaceAll("[^\\d]", "")));
			this.datas.add(product);
		}
	}

 

 

[풀이 후기]

반환할 때 객체에 담아 넘기는 것도 좋았을 것 같다.

Crawling 불러올 때 객체화가 굳이 필요하지 않은 부분이므로 메서드에 static을 붙여 new를 줄이면 좋았을 것 같다.