🍁jSoup
jsoup-1.16.2.jar 파일을 다운로드 받아서 lib 폴더에 넣는다.
jsoup: Java HTML Parser
jsoup의 클래스를 가지고 클래스에 주소를 알려주면 전체 소스를 인식할 수 있는데, jsoup은 HTML을 해석할 수 있는 능력을 가지고 있기 때문에 전체 소스를 인식한 다음에 HTML을 구문분석하여 처리한다.
Document 객체
Document doc = JspOutput.connect("http://localhost:8090/memo/list.do");
jSoup은 브라우저처럼 사이트를 접속한 뒤에 접속한 페이지의 소스를 읽고 분석 및 탐색한다. 그리고 원하는 부분을 추출한다. 이는 JavaScript의 DOM을 조작하는 느낌이다. 이때 외부 작업이므로 try-catch 구문이 필수이다.
connect 메서드에 접속할 페이지의 링크를 넣는다. 그러면 반환된 객체에 get이라는 메서드가 있는데, get 메서드는 Document 객체를 반환한다. Document 객체는 접속해서 읽어온 페이지 소스를 관리하는 문서 객체이다.
html 메서드
System.out.println(doc.html);
html 메서드를 사용하면 웹 사이트긴 하지만 콘솔로 출력이 된다. 이는 페이지를 소스보기한 것과 같다.
select, selectFirst 메서드
//selectFirst
Element h1 = doc.selectFirst("body > h1");
System.out.println(h1.text());
System.out.println("---");
//select
Elements item = doc.select(".item > div:nth-child(2)");
for (Element ele : item) {
System.out.println(ele.text());
}
selectFirst는 태그를 하나만 찾을 때 사용하며, 여러 태그를 가져오려고 할 때에는 select를 사용한다.
select로 데이터를 긁어오면 Elements로 가져오며, 이때 향상된 for문을 사용할 수 있다.
다음 영화 크롤링
https://movie.daum.net/ranking/boxoffice/weekly
다음 영화 박스오피스 랭킹을 크롤링 해보도록 하자.
li 태그가 15개 반복이 되면서 내부에 영화가 들어 있는 것을 확인할 수 있다. li 태그는 공통적인 부모인 item_poster에서 찾도록 한다.
이중 영화 제목, 개봉 일자, 관객 수 데이터와 이미지 등의 데이터를 뽑아보도록 하자.
select 메서드의 특징
Elements list = doc.select(".item_poster");
for (Element movie : list) {
movie.select("link_txt");
}
select 메서드는 앞에 무엇이 있느냐에 따라서 그 전체에서 자식을 찾기 때문에 첫 번째 select는 문서 전체에서 찾고, 두 번째 select는 영화에서만 찾는다.
영화 제목, 개봉 일자, 관객 수 데이터 추출
package com.test.java;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
public class Ex02 {
public static void main(String[] args) {
try {
String url = "https://movie.daum.net/ranking/boxoffice/weekly";
Document doc = Jsoup.connect(url).get();
//System.out.println(doc.html());
Elements list = doc.select(".item_poster");
//System.out.println(list.size());
for (Element movie : list) {
Element title = movie.selectFirst(".link_txt");
System.out.println(title.text());
Element date = movie.selectFirst(".txt_num");
System.out.println(date.text());
Element count = movie.selectFirst(".screen_out");
System.out.println(count.nextSibling());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
이미지 추출
Element poster = movie.selectFirst(".img_thumb");
if (poster != null) {
System.out.println(poster.attr("src"));
}
attr 메소드로 src를 추출하면 포스터 썸네일의 주소를 반환한다.
에러가 발생하는 이유는 포스터가 없는 영화가 있기 때문이다. (괴담만찬의 포스터가 없다.) 그래서 if 문으로 poster가 있을 경우에만 출력을 해야 에러 없이 데이터를 가져온다.
정형화된 데이터의 테이블을 access 하는 것이 아니기 때문에 패턴이 우리 생각을 벗어날 수 있음에 주의하도록 한다.
이미지 다운로드
Element poster = movie.selectFirst(".img_thumb");
if (poster != null) {
System.out.println(poster.attr("src"));
getImage(poster.attr("src"), title.text());
//Thread.sleep(2000);
}
private static void getImage(String imgUrl, String filename) {
URL url = null;
InputStream in = null;
OutputStream out = null;
try {
url = new URL(imgUrl);
in = url.openStream(); //이미지 읽기
out = new FileOutputStream("C:\\Class\\code\\server\\CrawlingTest\\poster\\" + filename + ".png"); //파일 저장하기(쓰기)
while (true) {
int data = in.read();
if (data == -1) break;
out.write(data);
}
in.close();
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
그리고 이미지 url을 가져오면 하드 디스크에 저장하는 메서드를 만들었다. 해당 url로부터 데이터를 읽을 수 있는 읽기 스크림과 내보낼 수 있는 쓰기 스트림을 만들 수 있다.
너무 빨라서 다운로드 되지 않는 경우 Thread.sleep(2000)를 통해 해결할 수 있다.
전체 코드
package com.test.java;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
public class Ex02 {
public static void main(String[] args) {
try {
String url = "https://movie.daum.net/ranking/boxoffice/weekly";
Document doc = Jsoup.connect(url).get();
//System.out.println(doc.html());
Elements list = doc.select(".item_poster");
//System.out.println(list.size());
for (Element movie : list) {
//영화 제목, 개봉 일자, 관객 수 데이터 추출
Element title = movie.selectFirst(".link_txt");
System.out.println(title.text());
Element date = movie.selectFirst(".txt_num");
System.out.println(date.text());
Element count = movie.selectFirst(".screen_out");
System.out.println(count.nextSibling());
//이미지 추출
Element poster = movie.selectFirst(".img_thumb");
if (poster != null) {
System.out.println(poster.attr("src"));
getImage(poster.attr("src"), title.text());
//Thread.sleep(2000);
}
//DTO > 포장
//dao.add(dto) > insert
}
} catch (Exception e) {
e.printStackTrace();
}
//getImage("https://img1.daumcdn.net/thumb/C408x596/?fname=https%3A%2F%2Ft1.daumcdn.net%2Fmovie%2F34fa90f0ddcf26586771d10cc89b31b7c9488045");
}
private static void getImage(String imgUrl, String filename) {
URL url = null;
InputStream in = null;
OutputStream out = null;
try {
url = new URL(imgUrl);
in = url.openStream(); //이미지 읽기
out = new FileOutputStream("C:\\Class\\code\\server\\CrawlingTest\\poster\\" + filename + ".png"); //파일 저장하기(쓰기)
while (true) {
int data = in.read();
if (data == -1) break;
out.write(data);
}
in.close();
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
jSoup의 한계
이전 페이지에 로그인해야 접근할 수 있는 Private 페이지의 경우 Direct로 접속할 수 없다.
jSoup은 로그인한 뒤에 접근하는 페이지는 절대로 접근할 수 없으며, 바로 접속해서 볼 수 있는 페이지만 가져올 수 있다.
<div id="result"></div>
<div>
<input type="button" value="클릭" id="btn1">
</div>
<script>
$('#btn1').click(function() {
$('#result').text('Isaac');
});
</script>
Element result = doc.selectFirst("result");
System.out.println("result: " + result.text());
그리고 jSoup은 브라우저가 아니기 때문에 JavaScript를 실행할 수 없다.
이때의 'Isaac'은 버튼을 누르는 등의 행동으로 인해 나타나는 동적의 값을 가져올 수 없다.
다른 방법으로 이걸 긁어오는 작업을 해보도록 하자.