파이썬 웹크롤링, 경마 경기결과 데이터 수집하는 방법은?!
목차
경마 경기 결과는 렛츠런 파크 싸이트에 공개된다. 이 데이터를 수집하는 방법은 여러가지가 있지만, 개인적으로 파이썬을 이용하는 것을 추천한다. 파이썬을 이용하면 웹크롤링을 통해 원하는 데이터를 쉽게 수집할 수 있다.
필자가 파이썬을 이용해서 제일 처음 했던 것이 경마 경기결과를 수집하는 것이었다. 예전에 포스팅도 했지만, 그동안 알게 된 내용들을 바탕으로 코드를 수정해보았다.
오늘은 파이썬 웹크롤링, 경마 경기결과 데이터를 수집하는 방법에 대해서 알아보도록 하겠다.
코드를 알아보기 전에 수집 프로세스에 대해 알아보도록 하겠다.
1. 수집 데이터는 MySQL에 저장한다.
MySQL은 무료로 사용할 수 있는 DBMS이다. 무료 DBMS는 여러가지가 있지만, MySQL은 서버에 올려서 사용할 수도 있기 때문에 유용하다. MySQL DB를 사용하려면 설치가 필요하다. 이는 이전 포스팅에서도 다루었으니 참고하기 바란다.
( 참조: MySQL 설치하기, sqlite3와 MySQL의 차이는? )
MySQL DB를 사용하기 불편하다면 SQLite3를 사용하거나, 엑셀 등의 다른 자료형으로 저장하는 것도 좋겠다.
( 참조: #1-6 경마 데이터 수집하기 - SQLite라는 DB로 저장하기 )
2. 경마경기결과를 수집한다.
경마경기는 매주 금,토,일에 열린다. 서울, 부산, 제주 3곳에 경기가 개최된다. 날짜와 지역, 경기번호를 특정 URL에 입력파라미터로 넣으면, 해당 날짜의 경기결과를 확인할 수 있다. 확인된 결과는 웹크롤링으로 수집하였다.
3. 수집한 결과를 MySQL DB에 놓는다.
DB에 데이터를 넣기 위해서는 SQL에 대해서 알아두는 것이 좋다. SQL은 Structured Query Language의 약자로 DB에서 사용하는 표준화된 프로그래밍 언어라고 보면 되겠다. DB에 넣을 때는 REPLACE INTO구문을 이용하였다. REPLACE INTO문은 중복되는 키가 없을 때 값을 입력한다. 그리고, 키가 중복되면 새로운 값으로 변경한다.
4. 중간중간 확인해야 되는 내용은 logging모듈을 이용하여 출력하였다.
print함수를 이용해도 된다. 하지만, logging모듈을 이용하면 더 쉽게 로그를 관리할 수 있다. 쉽게 파일로 저장할 수 있으며, 레벨별로 다른 출력을 할 수도 있다. logging모듈에 대한 자세한 내용은 아래 포스팅을 참조해보자.
( 참조: 파이썬 실행 로그를 남겨보자, logging 모듈 이용하기! )
5. 데이터 수집에 예외처리 구문을 추가하였다.
경마경기 결과를 크롤링하다 보면 네트워크 오류가 발생할 때가 있다. 네트워크 오류를 특정할 수도 있지만, 귀찮아서 어떤 에러든 발생하면 2번 더 데이터 수집을 시도하는 것으로 수정하였다.
전체 코드를 살펴보면 아래와 같다.
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 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 | # coding=utf-8 from urllib.request import urlopen from bs4 import BeautifulSoup from html_table_parser import parser_functions as parser import pandas as pd import numpy as np from datetime import datetime, timedelta import logging import pymysql import time import random db_conf = { "host": "127.0.0.1", "user": "test", "password": "test11", "database": "horse_racing", } def create_table(db_conf): """ 데이터를 저장할 mysql테이블을 만듭니다. :param db_conf: DB접속정보 :return: """ con=pymysql.connect(**db_conf) cur=con.cursor() cur.execute(""" CREATE TABLE IF NOT EXISTS hr_result ( 순위 VARCHAR(2), 마번 VARCHAR(2), 마명 VARCHAR(30), 산지 VARCHAR(20), 성별 VARCHAR(10), 연령 VARCHAR(5), 중량 VARCHAR(5), 레이팅 VARCHAR(5), 기수명 VARCHAR(30), 조교사명 VARCHAR(30), 마주명 VARCHAR(30), 도착차 VARCHAR(30), 마체중 VARCHAR(10), 단승 VARCHAR(10), 연승 VARCHAR(10), 복승 VARCHAR(10), 쌍승 VARCHAR(10), 복연승 VARCHAR(10), 삼복승 VARCHAR(10), 삼쌍승 VARCHAR(10), 장구현황 VARCHAR(20), S1F_G1F VARCHAR(20), S_1F VARCHAR(10), 1코너 VARCHAR(10), 2코너 VARCHAR(10), 3코너 VARCHAR(10), G_3F VARCHAR(10), 4코너 VARCHAR(10), G_1F VARCHAR(10), 3F_G VARCHAR(10), 1F_G VARCHAR(10), 10_8F VARCHAR(10), 8_6F VARCHAR(10), 6_4F VARCHAR(10), 4_2F VARCHAR(10), 2F_G VARCHAR(10), 경주기록 VARCHAR(10), day VARCHAR(30), day_th VARCHAR(20), weather VARCHAR(20), race_st VARCHAR(10), race_time VARCHAR(10), race_infor1 VARCHAR(10), distance VARCHAR(10), race_infor2 VARCHAR(10), race_infor3 VARCHAR(30), race_infor4 VARCHAR(10), race_infor5 VARCHAR(10), primary key (day,day_th,마명) ) """) con.commit() con.close() return 1 def collect_race(location,date,rc_no,try_cnt): """ 경기결과 수집 :param location: 서울1, 부산3 :param date: 날짜 :param rc_no: 경기번호 :return: 1: 수집결과, 정상수집이 안 되면 행이 0인 데이터프레임을 return """ if try_cnt<3: time.sleep(random.randint(3,6)) pass else: logging.info("데이터 수집 시도 횟수가 3회 초과하였습니다.") return pd.DataFrame() try: url="http://race.kra.co.kr/raceScore/ScoretableDetailList.do?meet={}&realRcDate={}&realRcNo={}".format(location,date,rc_no) logging.info("{} data collect start".format(url)) seo_col_list=['순위', '마번', 'S1F-1C-2C-3C-4C-G1F', 'S-1F', '1코너', '2코너', '3코너', 'G-3F','4코너','G-1F','3F-G','1F-G','경주기록'] bu_col_list=['순위', '마번', 'S1F-1C-2C-3C-4C-G1F', 'S-1F','10-8F', '8-6F', '6-4F', '4-2F', '2F-G', '3F-G','1F-G', '경주기록'] col_rename={"S1F-1C-2C-3C-4C-G1F":"S1F_G1F", "S-1F":"S_1F", '10-8F':"10_8F", '8-6F':'8_6F', '6-4F':'6_4F', '4-2F':'4_2F', '2F-G':'2F_G', '3F-G':'3F_G', '1F-G':'1F_G', 'G-3F': 'G_3F', 'G-1F': 'G_1F', } result = urlopen(url) html = result.read() soup = BeautifulSoup(html, 'html.parser') temp = soup.find_all('div',attrs={'class':'tableType2'}) table = temp[1].find('table') p=parser.make2d(table) record_detail=pd.DataFrame(p[2:],columns=seo_col_list if location==1 else bu_col_list) record_detail=record_detail.rename(columns=col_rename) if len(record_detail)==0: logging.info("{}, {}, {} 데이터는 없습니다.".format(date,location,rc_no)) return pd.DataFrame() if location==1: record_detail["10_8F"]="" record_detail["8_6F"]="" record_detail["6_4F"]="" record_detail["4_2F"]="" record_detail["2F_G"]="" elif location==3: record_detail["1코너"]="" record_detail["2코너"]="" record_detail["3코너"]="" record_detail["G_3F"]="" record_detail["4코너"]="" record_detail["G_1F"]="" record_detail["S1F_G1F"]=record_detail["S1F_G1F"].str.replace("\r\n", "").str.replace(" ", "").str.replace("\t","") # # race infor temp = soup.find_all('div',attrs={'class':'tableType1'}) day = temp[0].find_all('th')[0].text day_th = temp[0].find_all('th')[1].text weather = temp[0].find_all('th')[2].text race_st = temp[0].find_all('th')[3].text race_time = temp[0].find_all('th')[5].text # race infor2 race_infor1 = temp[0].find_all('td')[0].text distance = temp[0].find_all('td')[1].text race_infor2 = temp[0].find_all('td')[2].text race_infor3 = temp[0].find_all('td')[3].text race_infor4 = temp[0].find_all('td')[4].text race_infor5 = temp[0].find_all('td')[5].text # 배당정보 div_info = temp[4] div_info_all = div_info.find_all("td") div = dict() for d in div_info_all: try: d_text = d.text div_temp = {d_text.split(":")[0] : d_text.split(":")[1].split(" ")[2] } div = dict(div, **div_temp) except: pass # 경기결과 temp = soup.find_all('div', attrs={'class': 'tableType2'}) table = temp[0].find('table') p = parser.make2d(table) hr_stat = pd.DataFrame(p[1:], columns=p[0]) hr_1 = pd.merge(hr_stat, record_detail.drop("순위", 1), on="마번") hr_1["day"] = day hr_1["day_th"] = day_th hr_1["weather"] = weather hr_1["race_st"] = race_st hr_1["race_time"] = race_time hr_1["race_infor1"] = race_infor1 hr_1["distance"] = distance hr_1["race_infor2"] = race_infor2 hr_1["race_infor3"] = race_infor3 hr_1["race_infor4"] = race_infor4 hr_1["race_infor5"] = race_infor5 hr_1["복승"]=div.get("복승식","") hr_1["쌍승"]=div.get("쌍승식","") hr_1["복연승"]=div.get("복연승식","") hr_1["삼복승"]=div.get("삼복승식","") hr_1["삼쌍승"]=div.get("삼쌍승식","") return hr_1 except: hr_1=collect_race(location, date, rc_no, try_cnt+1) return hr_1 def insert_table(hr_1, db_conf): hr_2=hr_1[["순위", "마번", "마명", "산지", "성별", "연령", "중량", "레이팅", "기수명", "조교사명", "마주명", "도착차", "마체중", "단승", "연승", "복승", "쌍승", "복연승", "삼복승", "삼쌍승", "장구현황", "S1F_G1F", "S_1F", "1코너", "2코너", "3코너", "G_3F", "4코너", "G_1F", "3F_G", "1F_G", "10_8F", "8_6F", "6_4F", "4_2F", "2F_G", "경주기록", "day", "day_th", "weather", "race_st", "race_time", "race_infor1", "distance", "race_infor2", "race_infor3", "race_infor4", "race_infor5"]] hr_2_list=hr_2.values.tolist() db_conf = { "host": "127.0.0.1", "user": "test", "password": "test11", "database": "horse_racing", } con = pymysql.connect(**db_conf) cur = con.cursor() cur.executemany("""REPLACE INTO hr_result VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s, %s,%s,%s,%s,%s,%s,%s,%s,%s,%s, %s,%s,%s,%s,%s,%s,%s,%s,%s,%s, %s,%s,%s,%s,%s,%s,%s,%s,%s,%s, %s,%s,%s,%s,%s,%s,%s,%s )""", hr_2_list) con.commit() con.close() return 1 return 1 if __name__ == '__main__': logging.basicConfig(level=logging.INFO) create_table(db_conf) date="20200628" # lcoation 1은 서울, 3은 부산 location_list=[1,3] for l in location_list: for rc_no in range(1,20): hr_1=collect_race(l, date, rc_no, 1) logging.warning("{}, {}, {}, {} collected".format(l,date,rc_no,len(hr_1))) if len(hr_1)==0: break else: insert_table(hr_1, db_conf) | cs |
테스트를 많이 해보진 않아서, 값이 정확하지 않거나 다른 날짜에서 에러가 날지는 모르겠다.
오늘은 이렇게 파이썬 웹크롤링, 경마 경기결과 데이터를 수집하는 방법에 대해서 알아보았다. 경마 경기 분석은 재미있게 했었고, 언제가 다시 해 보고 싶은 마음은 있지만 여유가 나지 않는다. 언제 다시 코드를 수정하고, 실행해볼지는 모르겠다.
'Python > 파이썬 경마 분석' 카테고리의 다른 글
렛츠런파크 경마 지점 알아보기 (온라인 입장권 구매) (0) | 2022.09.06 |
---|---|
17년 11월 초고배당 경마 경주 #삼쌍승#이만배#복승식도 구백배 (0) | 2017.12.10 |
데이터의 유형과 분류 (0) | 2017.11.30 |
2017년 11월 1주차 BEST 경마 경기(11월 4일) (0) | 2017.11.07 |
경마분석, 말은 거리에 따라 속력이 다를까 (4) | 2017.10.15 |
#2-3 사람들은 경마 예측을 잘 할까요 (0) | 2017.10.14 |
경마로 코딩 교육 배우기(빅데이터 분석학습하기) (2) | 2017.10.12 |
#2-7 decision tree 알고리즘 사용하기 (0) | 2017.10.10 |