Back-End

[JAVA] 기초 공부 15 크롤링

Minch13r 2025. 1. 24. 10:19

2025.01.24 Jsoup을 이용한 웹 크롤링(Selenium이 더 좋은 것 같아요)

 

[ 웹 크롤링 ] (스크래핑 + 크롤링)

샘플 데이터를 웹 페이지로부터 추출하고 → 스크래핑

이 데이터들을 나의 프로젝트에 알맞는 형태로 “가공”할 수 있다. → 크롤링

 

  1. 타겟 웹 페이지 url

https://comic.naver.com/index

https://comic.naver.com/webtoon?tab=mon

import 할 때 swing은 옛날꺼라서 안 하는게 좋음

Jsoup은 random crawling 하는 친구

jar = 자바 아카이브 = 자바 코드 묶음

웹페이지를 코딩하는 언어

HTML

: 한쌍의 <> 태그로 이루어져 있음 → MarkUP 언어

span.DailyList_title—epGnt

.DailyList_title—epGnt → 클래스 이름

점(.) : class 속성명

<span>광마회귀</span>

span#title

<span id=”title”></span>

# : id 속성명

<a class = “ContentAuthor_author—CTAAP”>박태준</a>

이렇게 이해하면 된다.


커피빈 크롤링

package day018.class01;

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

import java.io.IOException;

public class Test01 {
    public static void main(String[] args) {
        // 크롤링 할 URL
        // 추출
        final String URL="https://www.coffeebeankorea.com/menu/list.asp?category=13";

        Document doc=null;
        try {
            doc=Jsoup.connect(URL).get();
        } catch (IOException e) {
            e.printStackTrace();
        }

        Elements elems=doc.select("span.kor");
        for(Element elem:elems) {
            String str=elem.text();
            System.out.println(str);
        }
    }
}

출력결과

 


교촌치킨 크롤링

package day018.class02;

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

import java.io.IOException;

public class Test01 {
    public static void main(String[] args) {
        // 크롤링 할 URL
        // 추출
        final String URL = "https://www.kyochon.com/menu/chicken.asp?code=18";

        Document doc = null;
        try {
            doc = Jsoup.connect(URL).get();
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 메뉴 정보가 있는 dl 태그를 선택합니다
        Elements elems = doc.select("dl.txt");
        // 각 메뉴 아이템을 순회하면서 메뉴명과 가격을 함께 출력합니다
        for (Element elem : elems) {
            // 메뉴명
            String menuName = elem.select("dt").text();
            // 설명
            String description = elem.select("dd").text();
            // 가격
            String price = elem.parent().select("p.money strong").text();

            System.out.println("================");
            System.out.println("메뉴명 : " + menuName);
            System.out.println("설명 : " + description);
            System.out.println("가격 : " + price + "원");
        }
        System.out.println("================");
    }
}

출력 결과


교촌치킨 크롤링을 Model, View, Controller로 나누어 구현해봤다. 이렇게 한 이유는 낮은 결합도, 높은 응집도를 위해서 했다.

교촌치킨 구조도

 Client(사용자, 실행파일)

package day018.class02.client;

import day018.class02.Controller.Controller;

public class KyochonClient {
    public static void main(String[] args) {
        Controller app = new Controller();

        app.appStart();
    }
}

Controller

package day018.class02.Controller;

import day018.class02.Model.Crawling;
import day018.class02.Model.KyoChonDAO;
import day018.class02.Model.KyochonDTO;
import day018.class02.View.View;

import java.util.ArrayList;
import java.util.Scanner;

public class Controller {
    private KyoChonDAO dao;
    private Crawling crawler;
    private Scanner sc;
    private View view;

    public Controller() {
        this.dao = new KyoChonDAO();
        this.crawler = new Crawling(dao);
        this.sc = new Scanner(System.in);
        this.view = new View();
    }

    public void appStart(){
        while(true){
            // 메뉴 보여줌
            view.showMainMenu();

            // 숫자 입력받기
            int num = view.inputNum();
            if(num == 0){
                view.printExit();
                break;
            }

            // 1. 메뉴 크롤링하기
            else if(num == 1){
                if(crawler.crawlMenuData()) {
                    view.printTrue();
                } else {
                    view.printFalse();
                }
            }

            // 2. 전체 메뉴 보기
            else if(num == 2){
                ArrayList<KyochonDTO> menus = dao.selectAll();
                if(menus.isEmpty()) {
                    view.printEmpty();
                } else {
                    view.printMenuList(menus);
                }
            }

            // 3. 메뉴 검색하기
            else if(num == 3){
                // view로부터 이름 입력받기
                String menuName = view.inputMenuName();

                // 이름 입력받은거 model에게 반환
                ArrayList<KyochonDTO> menus = dao.selectOne(menuName);
                // 메뉴가 해당하지 않으면
                if(menus == null) {
                    // 에러 메세지 출력
                    view.printNotFound();
                }
                else {
                    // 해당 내용 메뉴 출력
                    view.printMenu(menus);
                }
            }

            // 4. 메뉴 삭제하기
            else if(num == 4) {
                // view에 이름 입력하기
                String menuName = view.inputMenuName();
                // model에 이름 입력받은거 반환해서 선택
                ArrayList<KyochonDTO> menus = dao.selectOne(menuName);

                // 입력받으면 삭제 실행
                if(menus.isEmpty()) {
                    // 메뉴가 아무것도 없을 때 내용 출력
                    view.printNotFound();
                }
                else {
                    // 삭제 메서드 실행
                    dao.delete(menuName);
                    // 성공 메세지 출력
                    view.printTrue();
                }
            }
            // 다른거 입력했을 때
            else {
                view.inputElse();
            }

        }
    }
}

Model

 

크롤링 하는 모델

package day018.class02.Model;

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

import java.io.IOException;

public class Crawling {
    private KyoChonDAO dao;
    final String URL = "https://www.kyochon.com/menu/chicken.asp";

    public Crawling(KyoChonDAO dao) {
        this.dao = dao;
    }

    public boolean crawlMenuData() {
        try {
            // 웹 페이지 연결
            Document doc = Jsoup.connect(URL).get();

            // 메뉴 정보 추출
            Elements menuItems = doc.select("dl.txt");

            for (Element item : menuItems) {
                // 메뉴명, 설명, 가격 추출
                String menuName = item.select("dt").text();
                String description = item.select("dd").text();
                String price = item.parent().select("p.money strong").text();

                KyochonDTO menuDTO = new KyochonDTO(menuName, description, price);
                dao.insert(menuDTO);
            }
            // 호출 성공하면 true
            return true;

        } catch (IOException e) {
            System.out.println("[로그] 크롤링 중 오류 발생: " + e.getMessage());
            // 호출 실패하면 false
            return false;
        }
    }
}

 

KyochonDAO

package day018.class02.Model;

import java.util.ArrayList;
import java.util.Iterator;

public class KyoChonDAO {
    private ArrayList<KyochonDTO> datas;

    // 생성자
    public KyoChonDAO() {
        datas = new ArrayList<>();
    }

    // 전체 메뉴 검색
    public ArrayList<KyochonDTO> selectAll() {
        return new ArrayList<>(datas);
    }

    // 단일 메뉴 검색
    public ArrayList<KyochonDTO> selectOne(String menuName) {
        ArrayList<KyochonDTO> result = new ArrayList<>();
        for (KyochonDTO menu : datas) {
            if (menu.getMenuName().contains(menuName)) {
                result.add(menu);
            }
        }
        return result;
    }

    // 새로운 메뉴 추가
    public void insert(KyochonDTO dto) {
        datas.add(dto);
    }

    // 메뉴 정보 수정
    public void update(KyochonDTO dto) {
        for (int i = 0; i < datas.size(); i++) {
            if (datas.get(i).getMenuName().equals(dto.getMenuName())) {
                datas.set(i, dto);
                break;
            }
        }
    }

    // 메뉴 삭제
    public void delete(String menuName) {
        Iterator<KyochonDTO> iterator = datas.iterator();
        while (iterator.hasNext()) {
            KyochonDTO menu = iterator.next();
            if (menu.getMenuName().equals(menuName)) {
                iterator.remove();
            }
        }
    }
}

 

KyochonDTO

package day018.class02.Model;

public class KyochonDTO {
    private String menuName;    // 메뉴명
    private String description; // 설명
    private String price;      // 가격

    // 생성자
    public KyochonDTO(String menuName, String description, String price) {
        this.menuName = menuName;
        this.description = description;
        this.price = price;
    }

    // Getter,Setter
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }

    public String getMenuName() {
        return menuName;
    }
    public void setMenuName(String menuName) {
        this.menuName = menuName;
    }

    public String getPrice() {
        return price;
    }
    public void setPrice(String price) {
        this.price = price;
    }


    @Override
    public String toString() {
        return "================\n" +
                "메뉴명 : " + menuName + "\n" +
                "설명 : " + description + "\n" +
                "가격 : " + price + "원";
    }
}

View

package day018.class02.View;

import java.util.ArrayList;
import day018.class02.Model.KyoChonDAO;
import day018.class02.Model.KyochonDTO;
import java.util.Scanner;

public class View {
    Scanner sc;

    public View() {
        // 공통 인자 생성자로 선언
        this.sc=new Scanner(System.in);
    }

    // 메뉴출력
    public void showMainMenu(){
        System.out.println("\n=== 교촌치킨 메뉴 관리 시스템 ===");
        System.out.println("1. 메뉴 크롤링하기");
        System.out.println("2. 전체 메뉴 보기");
        System.out.println("3. 메뉴 검색하기");
        System.out.println("4. 메뉴 삭제하기");
        System.out.println("0. 종료");
    }

    // 번호 입력
    public int inputNum(){
        System.out.print("번호입력 >> ");
        int num = sc.nextInt();
        sc.nextLine(); // 버퍼 비우기
        return num;    }

    // 종료 안내
    public void printExit(){
        System.out.println("종료됩니다.");
    }

    public void printTrue() {
        System.out.println("성공!");
    }

    public void printFalse() {
        System.out.println("실패...");
    }

    public void printMenuList(ArrayList<KyochonDTO> menus) {
        menus.forEach(System.out::println);
    }

    public void printEmpty() {
        System.out.println("등록된 메뉴가 없습니다. 먼저 크롤링을 실행해주세요.");
    }

    public String inputMenuName() {
        while(true) {
            System.out.print("메뉴명을 입력하세요: ");
            String input = sc.nextLine().trim();

            if(input.isEmpty()) {
                System.out.println("메뉴명을 반드시 입력해주세요!");
                continue;
            }
            return input;
        }
    }

    public void printNotFound() {
        System.out.println("해당 메뉴를 찾을 수 없습니다.");
    }

    public void printMenu(ArrayList<KyochonDTO> menus) {
        for(KyochonDTO menu : menus) {
            System.out.println("================");
            System.out.println("메뉴명 : " + menu.getMenuName());
            System.out.println("설명 : " + menu.getDescription());
            System.out.println("가격 : " + menu.getPrice());
            System.out.println("================");
        }
    }

    public void inputElse(){
        System.out.println("잘못 입력했습니다. 0~4번 사이 숫자를 입력해주세요");
    }
}

 


실행결과
전체출력

 

메뉴는 더 있지만 짤려서 안 나온다

 

이름 검색

 

Contains 함수를 써서 스틱만 검색해도 스틱이 들어가는 내용들이 다 나온다

 

추가로 이름 전체를 다 입력하면 삭제도 가능하다. 레드스틱과 레드스틱[S]는 다른거니 이름을 정확히 다 입력해야 원하는 게 삭제된다

'Back-End' 카테고리의 다른 글

[JAVA] Toy-Project  (1) 2025.02.07
[크롤링] Jsoup과 Selenium  (3) 2025.02.01
[JAVA] 기초 공부 14 MVC-1  (1) 2025.01.23
[JAVA] 기초 공부 13 MVC  (1) 2025.01.22
[JAVA] 기초 공부 12 File I/O  (2) 2025.01.21