본문 바로가기
컴퓨터관련

JSoup 이용하여 웹툰 다운로드하기

by 기록이답이다 2016. 3. 23.
반응형

jsoup의 parsing 기능을 이용하여 webtoon을 다운로드 해보자.
물론 이것은 연습용일뿐이며 웹툰을 다운로드 받은걸 어찌할것도 아니니까 저작권침해 부분은 전혀 없다.

process는 다음과 같다.
1. naver 웹툰의 html을 분석한다.
2. naver 웹툰을 다운로드 받는다.
3. 웹툰을 본후 삭제한다.

ㅎㅎㅎ 간단하구나...

그러면 우선 1번부터 naver 웹툰의 html을 분석해보자

naver 웹툰은 다음과 같은 형식으로 되어있다.
http://comic.naver.com/webtoon/list.nhn?titleId=517252&page=1 (스페이스킹)


titleId가 웹툰의 id 이며 page는 웹툰이 속해있는 page이다.

게시물의 형식은 최근웹툰이 맨 위에 표시된다.
이 말은 1화가 가장 마지막 페이지에 있다는 말이다.

그러면 1화부터 다운받으려면 마지막 페이지가 몇페이지 인지 알아야 한다.
마지막 페이지는 아래와 같은 방법으로 알아낼 수 있다.



위의 그림을 보면 pagenavigation 이라는 class를 가지고 있는 하위의 a tag 들중 class='next' 라는 tag가 있다. 그 tag가 다음페이지로 이동하는 tag이다.
그러면 그 바로 위에 있는 링크가 현재 페이지 블럭중에 마지막 블럭으로 이동하는 블럭이 된다.
우리는 끝까지 가야 하므로 다음에는 마지막 블록이 가지고 있는 페이지 번호로 이동하면 된다.
그래서 나온 페이지 에서 다시 파싱을 하여 next가 없을때까지 이동하다보면 마지막 페이지가 어디인지 알수 있게 된다.


	public Document getDocument(String url) throws IOException {
		Document doc = null;
		doc = Jsoup.connect(url).get();
		return doc;
	}

	public int getLastPageNumber(int pageNumber) throws IOException {
		String pageSelector = ".pagenavigation";
		comicUrl = "http://comic.naver.com/webtoon/list.nhn?titleId="+tableId+"&page=1";
		Document doc = getDocument(comicUrl);
		Element pageInfo = doc.select(pageSelector).get(0);
		int size = pageInfo.getElementsByTag("a").size();

		// 마지막 페이지는 '다음페이지'가 없으므로 class를 가지고 있지 않음.
		// class를 가지고 있다는 얘기는 '다음페이지'가 존재한다는 이야기임.
		// class를 가지고 있으면 size-2를 하여 다음페이지 전의 a 태그를 가져옴.
		if(!pageInfo.getElementsByTag("a").hasClass("next")) return pageNumber;
		
		return getLastPageNumber(Integer.parseInt(pageInfo.getElementsByTag("a").get(size-2).text()));
	}


위의 소스에서 페이지 블럭의 마지막 페이지를 가져오는 방법은 아래와 같이 해도 된다.
// 마지막 페이지를 가져오는 selector
// $("div.pagenavigation > a").not("div.pagenavigation > a.next").last().text();
// 나중에 내가 까먹을때를 대비해서 자세히 풀어서 설명해놓아야 겠다.
// $("div.pagenavigation > a") :


Thread t = new ImageSaver(img, imageDir, imgName);
t.start();
위 코드는 각 회차수에 표시되어 있는 썸네일 이미지를 저장하는 부분이다.
if(href != null) { parse(reprUrl + href); }
이 부분이 실제 웹툰이 있는 페이지로 이동하여 웹툰 이미지를 저장하는 부분이다.
살펴보자.





위의 그림처럼 웹툰은 wt_viewer를 가지고 있는 div 안에 있다. 그래서 아래처럼 이미지를 가져온다.

	public void parse(String url) {
		try {
			Document doc = getDocument(url);
			Elements images = doc.select("div.wt_viewer > img"); // 만화 이미지를 가져온다.
			
			for(Element e : images) {
				String img = e.attr("src");
				String imageDir = saveDir+img.substring(img.indexOf(tableId), img.lastIndexOf("/"));
				String imageName = img.substring(img.lastIndexOf("/"));
				
				Thread t = new ImageSaver(img, imageDir, imageName);
				t.start();
			} // end for
		} catch(Exception ex) {
			ex.printStackTrace();
		} finally {
		}
	}


이렇게 하면 웹툰이 다운로드 된다.

ImageSaver는 아래와 같이 작성하였다.
ImageSaver.java


package bugnote.webtoon.collector;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

public class ImageSaver extends Thread {
	private String saveImageName;
	private String imageName;
	private String saveDir;
	private String status;
	public ImageSaver(String imageName, String saveDir, String saveImageName) {
		this.saveDir = saveDir;
		this.saveImageName = saveImageName;
		this.imageName = imageName;
	}
	
	public void run() {
		// 파일이 존재하는경우 return
		File saveFile = new File(saveDir + File.separator + saveImageName);
		if(saveFile.isFile()) {
			System.out.println(saveImageName + " : 존재함");
			return;
		}
		
		try {
			URL voImageURL = new URL(imageName);
			//이미지에 해당하는 url을 통하여 커넥션을 진행
			HttpURLConnection connection = (HttpURLConnection) voImageURL.openConnection();
			connection.setRequestProperty("User-Agent", "User-Agent	Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko");
			connection.setRequestProperty("Host", "imgcomic.naver.net");
			connection.setRequestProperty("Referer", "http://comic.naver.com/webtoon/list.nhn?titleId=507638");
			connection.setRequestProperty("Accept-Encoding", "gzip, deflate");
			connection.setRequestProperty("DNT", "1");
			
			//200_OK 응답에 대해서만 처리
			if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
				InputStream is = connection.getInputStream();
				
				File f = new File(saveDir);
				if(!f.isDirectory()) f.mkdirs();
				
				//응답 코드를 C:\1.jpg에 저장
				FileOutputStream fos = new FileOutputStream(saveFile);
				BufferedInputStream bis = new BufferedInputStream(is);
				BufferedOutputStream bos = new BufferedOutputStream(fos);
				
				//응답 코드를 1024바이트 단위로 저장
				int len = 0;
				byte[] buf = new byte[1024];
				while ((len = bis.read(buf, 0, 1024)) != -1) {
					bos.write(buf, 0, len);
					bos.flush();
				}
				bos.close();
				bis.close();
				fos.close();
				status = "Success";
			} else {
				status = "fail";
				System.out.println("connection failed. code is : " + connection.getResponseCode());
				System.out.println("failed connection url : " + connection.getURL());
			}		
		} catch(Exception ex) {
			System.out.println("IMAGENAME : " + status);
			ex.printStackTrace();
		} finally {
		}
	}
}


전체 소스는 아래와 같다.
ComicImageDownMain.java

package com.web.get.comic.naver;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

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

import com.web.get.comic.service.ILogin;
import com.web.get.comic.service.NaverLogin;
import com.web.get.common.db.ConvertMap;
import com.web.get.common.db.DBManager;
import com.web.get.main.ImageSaver;
import com.web.get.model.TBComicVO;
import com.web.get.util.Util;

/**
 * 네이버의 만화 이미지를 저장
 * @author ksw
 *
 */
public class ComicImageDownMain {
	private int startNumber;
	private int endNumber;
	private String comicUrl;
	private String tableId;
	private String reprUrl;
	private String saveDir;
	public Document getDocument(String url) throws IOException {
		Document doc = null;
		doc = Jsoup.connect(url).get();
		return doc;
	}
	
	public static void main(String[] args) {
		new ComicImageDownMain("스페이스 킹", "517252");
	}

	public int getLastPageNumber(int pageNumber) throws IOException {
		String pageSelector = ".pagenavigation";
		comicUrl = "http://comic.naver.com/webtoon/list.nhn?titleId="+tableId+"&page="+pageNumber;
		Document doc = getDocument(comicUrl);
		Element pageInfo = doc.select(pageSelector).get(0);
		int size = pageInfo.getElementsByTag("a").size();

		// 마지막 페이지는 '다음페이지'가 없으므로 class를 가지고 있지 않음.
		// class를 가지고 있다는 얘기는 '다음페이지'가 존재한다는 이야기임.
		// class를 가지고 있으면 size-2를 하여 다음페이지 전의 a 태그를 가져옴.
		
		// $("div.pagenavigation > a").not("div.pagenavigation > a.next").last().text();
		if(!pageInfo.getElementsByTag("a").hasClass("next")) return pageNumber;
		return getLastPageNumber(Integer.parseInt(pageInfo.getElementsByTag("a").get(size-2).text()));
	}
	

	
 	public ComicImageDownMain(String title, String tableId) {
 		this.saveDir = "g:/webtoon/naver/";
 		this.tableId = tableId;
		try {
			startNumber = 1;
			endNumber = getLastPageNumber(startNumber);
			
			File currDir = new File(saveDir+tableId+"_"+title);
			if(!currDir.isDirectory()) currDir.mkdirs();
			
			List dtlList = new ArrayList();
			for(int pageNumber = startNumber; pageNumber<=endNumber; pageNumber++) {
				comicUrl = "http://comic.naver.com/webtoon/list.nhn?titleId="+tableId+"&page="+pageNumber;
				Document doc = getDocument(comicUrl);
				
				Elements viewList = doc.select("table.viewList > tbody > tr"); // viewList로 감싸져있는 만화 정보를 가져온다.
				for(Element e : viewList) {
					TBComicVO vo = new TBComicVO();
					String href = "";
					Elements td = e.getElementsByTag("td");
					if(td.size() == 1) continue;
					String img = td.get(0).getElementsByTag("img").attr("src");
					String imageDir = saveDir+img.substring(img.indexOf(tableId), img.lastIndexOf("/"));
					String imgName = img.substring(img.lastIndexOf("/"));

					Thread t = new ImageSaver(img, imageDir, imgName);
					t.start();
					
					if(td.get(1).hasClass("title")) {
						href = td.get(1).getElementsByTag("a").attr("href");
					}
					if(href != null) {
						parse(reprUrl + href);
					}
				}
			}			
		} catch(Exception ex) {
			ex.printStackTrace();
		} finally {
		}
	}
	
	public void parse(String url) {
		try {
			Document doc = getDocument(url);
			Elements images = doc.select("div.wt_viewer > img"); // 만화 이미지를 가져온다.
			
			for(Element e : images) {
				String img = e.attr("src");
				String imageDir = saveDir+img.substring(img.indexOf(tableId), img.lastIndexOf("/"));
				String imageName = img.substring(img.lastIndexOf("/"));
				
				Thread t = new ImageSaver(img, imageDir, imageName);
				t.start();
			} // end for
		} catch(Exception ex) {
			ex.printStackTrace();
		} finally {
		}
		
	}
}

 

ps. 네이버에는 19세이상 웹툰이 존재하기 떄문에 19세이상 웹툰을 다운로드 받기 위해서는 위의 방법대로는 처리가 되지 않는다
HtmlUnit 을 이용하여 처리를 하였다. 그 방법은 나중에...

반응형