Jsoup 실습 : 크롤링해서 로컬DB에 넣기

부트캠프(END)/-Web 실습|2022. 7. 11. 18:11

아래와 같은 간단한 HTML 코드가 있다고 하자. (예시 코드 출처:  w3schools)

 <div class="row">
  <div class="col-md-4">
    <div class="thumbnail">
      <a href="/w3images/lights.jpg">
        <img src="/w3images/lights.jpg" alt="Lights" style="width:100%">
        <div class="caption">
          <p>Lorem ipsum...</p>
        </div>
      </a>
    </div>
  </div>
  <div class="col-md-4">
    <div class="thumbnail">
      <a href="/w3images/nature.jpg">
        <img src="/w3images/nature.jpg" alt="Nature" style="width:100%">
        <div class="caption">
          <p>Lorem ipsum...</p>
        </div>
      </a>
    </div>
  </div>
  <div class="col-md-4">
    <div class="thumbnail">
      <a href="/w3images/fjords.jpg">
        <img src="/w3images/fjords.jpg" alt="Fjords" style="width:100%">
        <div class="caption">
          <p>Lorem ipsum...</p>
        </div>
      </a>
    </div>
  </div>
</div>

위 코드는 아래와 같은 화면을 보여 준다.

 

이 때, 이미지 아래에 있는 "Lorem ipsum..." 이하의 텍스트들을 모두 가져오려고 한다.

자바의 Jsoup이라는 라이브러리를 이용하면 텍스트 추출을 자동화할 수 있다.

*jsoup 라이브러리가 포함된 jar를 꼭 프로젝트에 추가해 주어야 한다.

 

JSoup의 클래스와 메서드

Elements, Element

: CSS 선택자를 이용해서 가져온다. 

일반적으로 태그에 부여된 id 또는 class로 구분할 수 있다. (ex) tag#id_name, tag.class_name

Elements는 여러 개의 태그를 가져올 때 사용하여 반복문을 통해 결과를 걸러 내고,

Element는 하나의 태그를 가져올 때 사용한다.

 

selectFirst(String s)

: 선택자와 일치하는 것 중 첫 번째에 있는 태그를 가져온다.

select(String s)

: 선택자와 일치하는 모든 태그를 가져온다.

select(String s)[i]

: 선택자와 일치하는 모든 태그 중 i번째 태그를 가져 온다.

 

text() : 태그 사이의 텍스트 값을 가지고 온다. 
attr(String s) : 속성에 들어간 값을 가지고 온다. (*img, a태그에서 많이 사용)
html() : 하위 태그의 코드 전체를 가지고 온다. 
data() : javascript 안에 들어간 값을 가지고 온다.

 

결과적으로, 아래처럼 코드를 작성하면 3개의 "Lorem ipsum..." 이라는 텍스트와

각 이미지의 소스를 가져와 출력할 수 있다.

public void practieData() {
  try {
    String html = "연습용 HTML 코드";
    Document doc = Jsoup.parse(html); //html코드를 파싱한다.
    //caption이라는 클래스의 div 태그 아래의 p를 가져온다.
    Elements txt = doc.select("div.caption p"); 
    //img태그를 다 가져온다.
    Elements imgsrc = doc.select("img");
    System.out.println(txt);
    System.out.println(imgsrc);
    for (int i = 0; i < txt.size(); i++) {
      //Elemetns txt 에서 text만을 가져와 출력한다.
      System.out.println(txt.get(i).text());
      //img태그에서 src 속성값을 가져와 출력한다.
      System.out.println(imgsrc.get(i).attr("src"));
    }

  } catch (Exception ex) {
    ex.printStackTrace();
  }
}

 

여기서(https://try.jsoup.org/) HTML 코드를 넣은 후 원하는 데이터를 가져오는 선택자 연습을 할 수 있다.

 

 

실습

위 연습을 토대로 망고플레이트에서 실습을 해 본다.

 

메인 페이지의 "믿고 보는 맛집 리스트" 메뉴에서

각 추천페이지별 문구와 설명글, 이미지 소스를 가져오려 한다.

빨간박스 영역 내의 요소들을 크롤링할 것이다.

 

개발자 도구를 열어 태그를 확인해 보면, "착즙주스 맛집 베스트 7곳" 이라는 텍스트는

div.top_list_slide 아래의 ... ul.list-toplist-slider 아래의 li요소 안에 있는 span.title에 들어 있다.

이런 식으로 각 텍스트와 이미지 링크들의 위치를 파악하여 선택자로 지정하면 요소 값을 가져올 수 있다.

 

foodcategory 라는 테이블에는 기획전의 타이틀과 설명글, 배경사진, 링크 요소가 들어 있다.

 

FoodCategoryVO 라는 클래스에 해당 내용을 작성한다.

package doodoo.dao;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class FoodCategoryVO {
  private int cno;
  private String title, subject, poster, link;
}

Jsoup을 이용해서 망고플레이트에서 원하는 데이터를 골라 가져온다.

가져온 데이터는 FoodDAO를 통해 데이터베이스의 테이블에 삽입하고,

다시 불러와서 FoodCategoryVO로 생성한 인스턴스에 값들을 부여하고 화면에 출력할 수 있다.

 

FoodMain.java (Class)

package doodoo.main;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.util.*;
import doodoo.dao.*;

public class FoodMain {
  public void categoryData() {
    try {
      FoodDAO dao = new FoodDAO();
      Document doc = Jsoup.connect("https://www.mangoplate.com/").get();
      Elements title = doc.select("div.top_list_slide span.title");
      Elements subject = doc.select("div.top_list_slide p.desc");
      Elements poster = doc.select("div.top_list_slide img.center-croping");
      Elements link = doc.select("div.top_list_slide a");

      for (int i = 0; i < title.size(); i++) {
        System.out.println(title.get(i).text()); //태그 사이의 값을 가져올 때 .text()
        System.out.println(subject.get(i).text());
        System.out.println(poster.get(i).attr("data-lazy")); //속성값을 가져올 때는 .attr(속성이름)
        System.out.println(link.get(i).attr("href"));
        System.out.println("------------------------");
        FoodCategoryVO vo = new FoodCategoryVO();
        vo.setLink(link.get(i).attr("href"));
        vo.setTitle(title.get(i).text());
        vo.setSubject(subject.get(i).text());
        vo.setPoster(poster.get(i).attr("data-lazy"));
        dao.categoryInsert(vo);
      }
    } catch (Exception ex) {
      ex.printStackTrace();
    }
  }
  public void foodData() {
    try {
      FoodDAO dao = new FoodDAO();
      List < FoodCategoryVO > list = dao.foodCategoryInfoData();
      for (FoodCategoryVO vo: list) {
        System.out.println(vo.getTitle());
        //각 카테고리별 음식점 목록에서 음식점 하나하나의 상세보기 링크를 가져온다.
        Document doc = Jsoup.connect("https://www.mangoplate.com" + vo.getLink()).get();
        System.out.println("=====" + vo.getTitle() + "=====");
        Elements link = doc.select("ul.list-restaurants figure.restaurant-item span.title a");
        for (int i = 0; i < link.size(); i++) {
          //각 음식점의 상세보기 페이지 링크로 들어간다.
          //System.out.println(link.get(i).attr("href"));
          Document doc2 = Jsoup.connect("https://www.mangoplate.com" + link.get(i).attr("href")).get();
          Element title = doc2.selectFirst("span.title h1.restaurant_name");
          System.out.println(title.text());
        }
      }
    } catch (Exception ex) {
      ex.printStackTrace();
    }
  }
  public static void main(String[] args) {
    FoodMain m = new FoodMain();
    //		m.categoryData();
    m.foodData();
  }

}

 

FoodDAO.java (Class)

package doodoo.dao;
import java.util.*;
import java.sql.*;

public class FoodDAO {
  private Connection conn;
  private PreparedStatement ps;
  private final String URL = "jdbc:oracle:thin:@localhost:1521:XE";
  public FoodDAO() {
    try {
      Class.forName("oracle.jdbc.driver.OracleDriver");
      //reflection(Spring)
    } catch (Exception ex) {
      ex.printStackTrace();
    }
  }

  public void getConnection() {
    try {
      conn = DriverManager.getConnection(URL, "user", "pwd");
    } catch (Exception ex) {}
  }
  public void disConnection() {
    try {
      if (ps != null) ps.close();
      if (conn != null) conn.close();
    } catch (Exception ex) {}
  }

  public void categoryInsert(FoodCategoryVO vo) {
    try {
      getConnection();
      String sql = "INSERT INTO food_category " +
        "VALUES ((SELECT NVL(MAX(cno)+1,1) FROM food_category)," +
        "?,?,?,?)";
      ps = conn.prepareStatement(sql);
      ps.setString(1, vo.getTitle());
      ps.setString(2, vo.getSubject());
      ps.setString(3, vo.getPoster());
      ps.setString(4, vo.getLink());

      ps.executeUpdate();
    } catch (Exception ex) {
      ex.printStackTrace();
    } finally {
      disConnection();
    }
  }

  public List < FoodCategoryVO > foodCategoryInfoData() {
    List < FoodCategoryVO > list = new ArrayList < FoodCategoryVO > ();
    //상세보기 데이터 가져오기
    try {
      getConnection();
      String sql = "SELECT cno, link, title " +
        "FROM food_category " +
        "ORDER BY cno ASC";
      ps = conn.prepareStatement(sql);
      ResultSet rs = ps.executeQuery();
      while (rs.next()) {
        FoodCategoryVO vo = new FoodCategoryVO();
        vo.setCno(rs.getInt(1));
        vo.setLink(rs.getString(2));
        vo.setTitle(rs.getString(3));
        list.add(vo);
      }
      rs.close();
    } catch (Exception ex) {
      ex.printStackTrace();
    } finally {
      disConnection();
    }
    return list;
  }
}

 

댓글()