수업 과제 & 실습/운영체제
멀티스레드 - Web Crawler 구현
ddongyeonn
2020. 5. 23. 22:20
멀티스레드를 두 번 이용하여 40개의 웹페이지를 크롤링하는 프로그램을 만들어보자.
웹 크롤링이란 웹페이지네 데이터를 추출하여 가공하는 행위를 말한다.
JAVA JSOUP Library를 이용하여 1980년 ~ 2019년까지 genie차트에서 원하는 가수의 랭킹을 출력하는 프로그램을 만들어 보았다.
1. 멀티스레드를 이용하는 지점
- 웹 페이지를 크롤링할때
- 각각의 스레드는 1개의 URL 주소를 할당받고 크롤링하는 작업을 진행한다.
- 웹 페이지에서 크롤링한 데이터를 이용하여 원하는 검색어를 탐색할 때
- 각각의 스레드는 1개의 URL 주소를 크롤링한 결과에서 원하는 검색어를 탐색하는 작업을 진행한다.
2. 프로그램 구성 클래스
- WebCrawling Class
- WebCrawling Class는 프로그램의 전반적인 진행을 담당한다.
- 크롤링 결과를 저장해둘 2차원 벡터 result를 static으로 선언하여 가지고 있고 각각의 페이지에서 크롤링하여 가공한 데이터를 SongInfrom형 객체를 원소로 가지는 Vector로 만들어 저장 및 유지한다.
- 크롤링을 진행하는 CrawlingThread와 탐색을 진행하는 SearchThread를 각각 40개씩 생성하고 진행시키며 동기화한다.
package OSReport1;
import java.io.IOException;
import java.util.*;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
// 웹 크롤링 실행 관리
public class WebCrawling {
public static int searchnum = 0; // 찾은 단어 개수
public static void main(String[] args) {
String singer = null;
String title = null;
int choice;
// 스레드 주소를 저장할 배열 선언
Thread thread[] = new Thread[40];
while (true) {
searchnum = 0;
System.out.println("\n========== 1980년 부터 2019년 까지 지니차트에서 50위 이내에 든 노래 찾기 ==========");
System.out.println("1. 노래 제목으로 찾기 2. 가수 이름으로 찾기");
System.out.println("번호 입력: ");
// 크롤링-----------------------------------------------------------------------------
// 크롤링 결과를 저장해둘 리스트
Vector<Vector<SongInform>> result = new Vector<Vector<SongInform>>();
// 40개의 페이지 크롤링
for (int i = 0; i < 40; i++) {
// 각각의 스레드 생성
Runnable task = new CrawlingThread(i);
thread[i] = new Thread(task);
thread[i].start();
// 각 년도별 크롤링 결과물을 2차원 배열에 저장
result.add(((CrawlingThread) task).getInform());
}
// 다른 스레드 기다리도록 동기화
for (int i = 0; i < 40; i++) {
try {
thread[i].join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 사용자로부터
// 입력받기--------------------------------------------------------------------
Scanner scanner = new Scanner(System.in);
while (true) {
choice = scanner.nextInt();
if (choice == 1) {
System.out.println("노래 제목 입력(2글자 이상)");
title = scanner.next();
System.out.println("==============" + title + " 탐색 결과==============");
break;
} else if (choice == 2) {
System.out.println("가수 이름 입력 ");
singer = scanner.next();
System.out.println("==============" + singer + " 탐색 결과==============");
break;
} else
System.out.println("입력 오류! 다시 입력하세요.");
continue;
}
// 1980년 ~ 2019년 음악차트
// 탐색하기---------------------------------------------------------
for (int i = 0; i < 40; i++) {
Runnable task;
// 각각의 스레드 생성
if (choice == 1)
task = new SearchThread(result.get(i), title, choice);
else
task = new SearchThread(result.get(i), singer, choice);
thread[i] = new Thread(task);
thread[i].start();
}
// 다른 스레드 기다리도록 동기화
for (int i = 0; i < 40; i++) {
try {
thread[i].join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 결과 출력
if (searchnum == 0) {
System.out.println("입력이 잘못되었거나 검색 결과가 차트에 없습니다.");
System.out.println("=================================");
} else {
if (choice == 1)
System.out.println(title + "는 총 " + searchnum + "회 검색되었습니다.");
else
System.out.println(singer + "는 총 " + searchnum + "회 검색되었습니다.");
System.out.println("==========================================");
}
// 프로그램을 계속 진행할지 확인
System.out.println("\n계속 진행하시겠습니까? [Y/N]");
String step = scanner.next();
if (step.equals("Y") || step.equals("y"))
continue;
else {
System.out.println("프로그램 종료");
break;
}
}
}
}
- CrawlingThread Class
- Crawling을 진행하는 Thread 자체를 나타내는 Class이다.
- CrawlingThread의 Run() 메소드 내부에 1개의 페이지를 Crawlilng 하는 소스코드가 포함된다.
- 총 40개의 CrawlingThread가 생성되어 40개의 웹페이지가 크롤링된다.
package OSReport1;
import java.io.IOException;
import java.util.*;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
//Document 클래스 : 연결해서 얻어온 HTML 전체 문서
//Element 클래스 : Document의 HTML 요소
//Elements 클래스 : Element가 모인 자료형
//select(자료형.클래스이름)
public class CrawlingThread implements Runnable {
private String url = "https://www.genie.co.kr/chart/musicHistory?year=2019&category=0"; // 크롤링할 URL
private int threadnum = 0; // 스레드 번호
private int year = 0; // 스레드가 크롤링하는 년도
private Vector<SongInform> inform = new Vector<SongInform>(); // 0위 ~ 50위 곡정보 저장할 벡터
// 생성자
public CrawlingThread(int num) {
threadnum = num; // 스레드 번호 지정
// 연도별 url 설정
year = 2019 - threadnum;
String tmp = Integer.toString(year);
url = url.replace("2019", tmp); // url 중간 연도부분을 해당 연도로 바꿈
}
public void run() {
Document doc = null; // Document에는 페이지의 전체 소스가 저장된다
try {
// connect를 통해 url 접속 -> get을 통해 페이지 소스를 얻어옴
doc = Jsoup.connect(url).get();
} catch (IOException e) {
System.out.println(e.getMessage()); // 예외가 발생한 위치와 호출된 메소드 정보 출력
}
// select를 이용하여 원하는 태그를 선택한다.
Elements element = doc.select("table.list-wrap");
// element로부터 원하는 데이터를 저장한다.
Iterator<Element> title = element.select("a.title").iterator();
Iterator<Element> singer = element.select("a.artist").iterator();
Iterator<Element> rank = element.select("td.number").iterator();
// 추출한 정보를 모아 객체를 생성한다
while (rank.hasNext()) {
SongInform tmp = new SongInform(year, rank.next().text(), title.next().text(), singer.next().text(), url);
// 가수이름 문자열 공백제거
tmp.singer = tmp.singer.trim();
tmp.singer = tmp.singer.replace(" ", "");
inform.add(tmp);
}
}
public Vector<SongInform> getInform() {
return inform;
}
}
- SearchThread Class
- Crawling결과를 저장해둔 SongInfrom형 벡터를 탐색하는 Class이다.
- SearchThread의 Run() 메소드 내부에 1개의 페이지에 대한 Crawling 결과를 탐색하는 소스코드가 포함된다.
- 총 40개의 SearchThread가 생성되어 40개의 SongInfrom형 벡터를 탐색하고 검색어와 일치하는 정보가 있으면 곡의 정보를 출력한다.
package OSReport1;
import java.util.*;
public class SearchThread implements Runnable {
int choice; // 1 - title로 탐색 2 - 가수명으로 탐색
String inform;
Vector<SongInform> songlist; // 해당 년도 곡들을 저장한 리스트
SearchThread(Vector<SongInform> songlist, String inform, int choice) {
this.songlist = songlist;
this.inform = inform;
this.choice = choice;
}
public void run() {
// 탐색하기
for (int i = 0; i < songlist.size(); i++) {
// 곡명으로 검색
if (choice == 1 && songlist.get(i).title.contains(inform)) {
int year = songlist.get(i).year;
String singer = songlist.get(i).singer;
String rank = songlist.get(i).rank;
String title = songlist.get(i).title;
WebCrawling.searchnum++;
// 출력
System.out.println(title + "(은)는 " + year + "년에 " + rank + "위를 한 " + singer + "의 노래입니다.\n"
+ "검색어를 찾은 url: " + songlist.get(i).url + "\n");
}
// 가수명으로 검색
else if (choice == 2 && songlist.get(i).singer.equalsIgnoreCase(inform)) {
int year = songlist.get(i).year;
String rank = songlist.get(i).rank;
String title = songlist.get(i).title;
WebCrawling.searchnum++;
// 출력
System.out.println(inform + "(은)는 " + year + "년에 " + title + "(으)로 " + rank + "위를 했습니다.\n"
+ "검색어를 찾은 url: " + songlist.get(i).url + "\n");
}
}
}
}
- SongInfrom Class
- 크롤링한 결과를 가공하여 저장해두기 위해 생성한 Class이다.
- SongInfrom Class에는 인스턴스로 URL, 음원 발매 연도, 가수 이름, 노래 제목, 랭킹을 포함하고 있다.
package OSReport1;
// 노래 정보를 담은 클래스
public class SongInform {
int year; // 노래가 출시된 년도
String rank, title, singer, url; // 노래의 순위, 제목, 가수, 찾은 url 정보
// 생성자
SongInform(int year, String rank, String title, String singer, String url) {
this.year = year;
this.rank = rank;
this.title = title;
this.singer = singer;
this.url = url;
}
}
3. 결과화면
4. 첨부파일 (소스코드, 실행파일, 보고서)