본문 바로가기

건승하고있어요/취미생활의 구체화

2018 프로야구 시작 전 내맘대로 스탯리스트만들기[1] - 자바 jsoup 이용한 크롤링

반응형

야구!!!!!!!!!!!!!!!!! 

내가 개발을 시작하는 이유이기도 했고, 뭔가 도전하는 데에 항상 중심에 있었던 야구!!!

언제나 야구 때문에 이거저거 해보고 싶었던게 많았다. 개발도 마찬가지였다.

가장 만들어 보고 싶었던 것은 자동으로 기사를 착착 써서 알아서 슉슉 올라가는 프로그램, 실제로 MLB에서는 사실기반의 기사들은 사람이 아닌 컴퓨터가 알아서 올리고 있다고 한다. 그러니 홈런기사 득점기사, 선발이 강판 당할 때 기사 등은 선발을 내리자 마자 혹은 홈런을 치자마자 바로 기사화된다. 실제로 이런 사실기반의 기사들을 나의 경우는 1분 1초를 다투어 으아아아아아 하면서 올렸었는데, 이런 것들을 컴퓨터가 자동으로 후다닥 올려주면 여러모로 모두가  편하지 않을까 하고 생각했었다. 물론 내 생각이 들어있는 기사의 경우는 얘기가 달라지겠지만...

하지만....

그건 내가 지금 당장 만들 수 없기에... 우선 다음으로 미루고!!


예전에 야구 기사를 쓰면서 항상 만들어 보고 싶었던 두번 째는 바로 <내가 원하는 스탯들을 주루룩 한 번에 보는 사이트>였다. 스탯티즈라는 정말 좋은 사이트가 있지만, 뭔가 그냥 내가 원하는 선수들과 조금은 특이한 스탯을 내 기준으로 내가 계산해서 바로바로 보고싶었다. (여담이지만 스탯티즈를 개인이 스스로 만들어서 서버를 자비로 운영하는게 정말 멋지고 대단하다고 생각되지 말입니다ㅠ_ㅠ) 매번 숫자 계산도 못하는게 어설프게 그때그때 계산해서 하나씩 두드려보고 맞나 또 확인하고 하는 과정이 그렇~~게 고될수가 없었다. 확신이 없는 계산은 더더욱 나에게 어려움을 안겨주곤 했었다. ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ 그래서 더 만들어 보고 싶었다. 그리고 카스포인트처럼  내 기준으로 포인트같은 걸 만들어 보고 싶기도 했다. 단순히 자책점이라든가 승패, 피안타율을 넘어서 특이한 스탯을 기록해보고 싶었다. 이닝이터의 경우 6회부터의 볼넷개수라든가, 뭐 그런... 지금은 딱히 생각이 안 나네


그냥 막연하게 해야지, 라고 생각만 해서 막상 시작하려니 시작부터 턱 막혔다. 

우선 

1. 어디에서 자료들을 긁어올 것인가

2. 어떤 기록들을 위주로 긁어올 것인가

3. 순위 순으로 긁어올 것인가 

4. 투수 타자 스탯은 어떤식으로?

5. 팀 기준은?

6. ?

7. ??

8. ????


그냥 생각할게 너무 많았다. 

그래서 우선은 무엇을 긁어올지 생각하기 보다는 사이트를 먼저 보고 어떤식으로 어떻게 긁어올 수 있는게 있는지 확인했다.

KBO 사이트에서 크롤링이 우선 가능한지 확인했다. 


알아보는 방법:

https://사이트주소/robots.txt


나의 경우에는 https://www.koreabaseball.com/robots.txt를 치면 됐다.

그러면 이렇게 뜬다.


어떤걸 크롤링 할 수 있는지 뜬다. 

여기선 /Common/ /Help/ /Member/ /ws/ 를 제외하고는 다 크롤링이 가능하다. 

예전에 프로젝트를 하면서 구글 스콜라를 긁어왔었는데, 거긴 다 막혀있었다.

그래서 몇 번이상 크롤링하면 로봇으로 걸려서 며칠동안 크롤링을 할 수가 없었다..ㅠ_ㅠ 정말 슬펐어. 

그래서 어떤 아이피가 막히면 핸드폰 핫스팟을 켜서 계속 마저 하곤 했었다. 으메 지겨운거 ㅠ_ㅠ 


아무튼, kbo는 문제 없겠습니다. 


또 한 가지 문제점.

선수들을 긁어와야 하는데, 지금 당장 이 사람들을 무슨 기준으로 긁어와야 할지 매우 난감했다.

이 많은 선수들을 어떻게 또 왜 긁어와야 하는지에 대한 나 스스로의 기준이 필요했다.

파이썬의 경우 여러 크롤링 라이브러리를 제공하기 때문에 기준과 방식만 정한다면 구현하는데 크게 문제가 없을거라고 생각했지만, 

나는 지금 당장은 자바로 우선 구현해보기로 생각했기 때문에, 

페이지는 얼마 없지만 스프링을 적용시켜보고 싶은 마음이 커서, 

조금은 제한된, 일정한 숫자의 긁어오기를 해야할 것 같았다.


어쨌든 시작은 자바로, 차후에는 파이썬으로 나가기로!!


자바에서 크롤링을 돕는 라이브러리가 있다. 

바로 Jsoup!

파이썬의 BeautifulSoup에서 이름을 따온 듯한 이름이다. 

라이브러리를 사용하기 전에 jar 파일을 받아서 클래스 패스에 넣어주든지,

maven을 사용중이라면 pom에 요거슬 넣어주면 된다. 


<!-- https://mvnrepository.com/artifact/org.jsoup/jsoup -->

<dependency>

    <groupId>org.jsoup</groupId>

    <artifactId>jsoup</artifactId>

    <version>1.9.1</version>

</dependency>


그럼 사용 가느으응ㅇ! 


자, 그 다음부터 나는 어떻게 해야할지 고민했다.

모든 선수를 데려올 순 없다. 그렇다면 우선은 한화 선수들을 데려오자.

한화 선수들의 누구를 데려오지.

그래서 우선은 투수를 데려오기로 했다.

근데 kbo에서 선수들의 기록을 보려면 직접 한 명씩 찾아야만 한다.


그리고 그 선수마다 페이지 주소가 다르다.

그래서 난관에 봉착했다. 

우선은 단순히 몇 명의 투수들의 정보를 긁어오는 걸로 시작해보기로 했다.


크롤링을 할 때는 Html의 DOM구조를 이해하면 정보를 가져오기가 훨씬 쉽다.


우선 크롤링해오고 싶은 페이지의 주소를 가져온다. 배영수선수의 통산기록을 가져와 보려고 한다.

https://www.koreabaseball.com/Record/Player/PitcherDetail/Total.aspx?playerId=70425

이게 바로 내가 크롤링해오고 싶은 페이지의 주소다. 

크롬이든 익스플로러든 F12를 누르면 그 페이지가 어떻게 이루어져 있는지 소스가 좌르륵 뜬다. 그러면 그걸 보고 어떤걸 긁어올지 내가 잘 적어주면 된다. ㅋㅋㅋㅋㅋㅋ 


이 부분을 긁어오고 싶었다. 배영수 선수의 통산기록! 

F12를 누르고 이부분이 어떻게 돼 있는지 확인한다.


play_record라는 클래스를 가진 tfoot의 자식들로 모조리 모여있다. 

이렇게 고맙게 자상하게 해주면 너모너모 고마오. 테이블이라서 보기가 더 쉬운거 같당.

아무튼 이걸 확인하면, 여기있는 애들을 긁어오면 된다.

나의 경우는 VO에 한 번에 넣어주고 싶어서 자식들의 자식들.. 뭐 이런식으로 다 받아왔다. 그래서 코드가 길당..

정석의 방법이 아니라 컨트롤 스페이스 누르고 있는 메소드들로  하나씩 이거저거 누르다가 발견한 방법이라서, 이게 제대로 된 방법인지는 잘 모르겠다. 어쨌든 확실히 긁어오긴 한다는거..

ㅋㅋㅋㅋㅋㅋㅋ


항목이 다 들어있는 VO를 먼저 만들고, 크롤링을 했다. 아직 DB에는 넣지 않았다. 

예시를 들어보자면


     double ERA = Double.parseDouble(elem.get(j).child(1).child(1).text());

방어율을 긁어오기 위한 코드 한 줄.

위에 보면 <tfoot class="play_record"> 

자식0-0<tr></tr>

 0-1<tr>

1-0<th~></th>

1-1<th>4.40</th>

</tr> 요렇게 

...무슨 말인지 잘 이해가 안 되겠지ㅠ_ㅠ 돔구조 돔구조 ㅠ_ㅠ


암튼 그렇게 긁어오면 

이렇게 뜬다. VO를 toString한거.


여러 선수들을 가져오고 싶어서 선수들의 주소를 각각 가져와서 배열에 넣고 포문으로 돌렸다.

지금 당장 내가 할 수 있는 방법이 요것 뿐인듯!?


 int player [] = {72447704257774860768};
        
        //크롤링할 주소
        for(int i=0 ; i<4 ; i++) {
                String URL = "https://www.koreabaseball.com/Record/Player/PitcherDetail/Total.aspx?playerId=" + player[i];
                Document doc = Jsoup.connect(URL).get();


선수들의 주소가 선수들에게 부여된 어떤 번호로 되어 있어서 다행스럽게도 이런 식으로 구현할 수 있었다.

파이썬이 아닌 이상은 우선 이렇게 돌려봐야 할 것 같다.


4명의 기록을 가져온 결과.

이름 긁어올 생각은 또 안해서리....-ㅠ-;


어떻게 무엇을 구현하고 싶은지 정확하게 규정하는 것이 지금 당장 나에게 필요한 부분인 것 같다. 으으 으으응으으



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package ahahah.control;
 
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;
 
public class HanhwaPitcher {
 
    public static void main(String[] args) throws Exception {
        
        int player [] = {72447704257774860768};
        
        //크롤링할 주소
        for(int i=0 ; i<4 ; i++) {
                String URL = "https://www.koreabaseball.com/Record/Player/PitcherDetail/Total.aspx?playerId=" + player[i];
                Document doc = Jsoup.connect(URL).get();
                
                
                Elements elem = doc.select("tfoot.play_record");
                PitcherVO PV = new PitcherVO();
                for(int j=0 ; j<elem.size() ; j++) {
                    double ERA = Double.parseDouble(elem.get(j).child(1).child(1).text());
                    int G = Integer.parseInt(elem.get(j).child(1).child(2).text());
                    int CG = Integer.parseInt(elem.get(j).child(1).child(3).text());
                    int SHO = Integer.parseInt(elem.get(j).child(1).child(4).text());
                    int W = Integer.parseInt(elem.get(j).child(1).child(5).text());
                    int L = Integer.parseInt(elem.get(j).child(1).child(6).text());
                    int SV =Integer.parseInt(elem.get(j).child(1).child(7).text());
                    int HLD =Integer.parseInt(elem.get(j).child(1).child(8).text());
                    double WPCT =Double.parseDouble(elem.get(j).child(1).child(9).text());
                    int TBF =Integer.parseInt(elem.get(j).child(1).child(10).text());
                    String IP = elem.get(j).child(1).child(11).text();
                    int H =Integer.parseInt(elem.get(j).child(1).child(12).text());
                    int HR =Integer.parseInt(elem.get(j).child(1).child(13).text());
                    int BB =Integer.parseInt(elem.get(j).child(1).child(14).text());
                    int HBP =Integer.parseInt(elem.get(j).child(1).child(15).text());
                    int SO =Integer.parseInt(elem.get(j).child(1).child(16).text());
                    int R = Integer.parseInt(elem.get(j).child(1).child(17).text());
                    int ER =Integer.parseInt(elem.get(j).child(1).child(18).text());
                    
                    PV.setBB(BB);
                    PV.setCG(CG);
                    PV.setER(ER);
                    PV.setERA(ERA);
                    PV.setG(G);
                    PV.setH(H);
                    PV.setHLD(HLD);
                    PV.setHPB(HBP);
                    PV.setHR(HR);
                    PV.setIP(IP);
                    PV.setL(L);
                    PV.setR(R);
                    PV.setSHO(SHO);
                    PV.setSO(SO);
                    PV.setSV(SV);
                    PV.setTBF(TBF);
                    PV.setW(W);
                    PV.setWPCT(WPCT);
                }
                System.out.println(PV.toString());
 
        }
        
 
            }
        
 
    
 
}
 
cs





반응형