현재 코드를 짜서 화면에 구현하는 것까지 완료하였습니다. 코드를 리뷰하면서
지금까지 진행한 걸 다시 적어보면서 코드를 다시 한번 공부해 보는 시간을 가지겠습니다.
(* 크롤링하다가 오류가 발생하여 1달을 주기로 끊어서 진행함)
(* 달이 바뀔 때마다 변경할 사항
: 구현 부분의 첫 번째 줄 숫자, 5번째 push_2024_game에서의 두 번째 인자, json 저장 시 파일 이름)
(* 2023.01 ~ 2024.07까지 5,9,10,11,12월은 경기 없음)
(* 2023년을 진행할 경우 go_to_2024의 7번째 부분에 XPath를 아래 코드로만 변경)
규칙으로 보아 현재 월이 마지막 div가 1이고 연도가 줄어들수록 1이 증가하는 것으로 보임
# 2023년 선택 XPATH
"/html/body/div[1]/div[2]/div/main/div[2]/div/div[2]/div/div/div[1]/div[1]/section/div[2]"
# 2024년 선택 XPATH
"/html/body/div[1]/div[2]/div/main/div[2]/div/div[2]/div/div/div[1]/div[1]/section/div[1]"
크롤링 코드 흐름
1. 사이트 접속 및 찾고자 하는 연, 월 선택 : go_to_2024 함수 → click_month 함수 → select_month 함수
2. 월별 게임 최대 수 검색 : find_month_child 함수
3. 게임 클릭 : push_2024_game 함수
4. 일당 게임 자식요소 찾고 세트 함수 접속 (push_2024_game 함수 안)
: → find_day_child 함수 → 게임 클릭 → push_2024_set 함수
5. 세트 버튼의 자식요소 찾고 for문 돌리기 (push_2024_set 함수 안)
: → find_set_child 함수 → 필요한 데이터 찾기 → 필요한 값을 추가 → 다시 원래 화면으로 돌아가기
* 이유 : 뒤로 가기를 해도 처음 화면으로 돌아가기 때문
→ 세트 수만큼 반복 후 push_2024_set 함수 끝
6. push_2024_set이 끝난 후 잠시 대기 후 다시 원하는 월 선택(push_2024_game 함수 안)
- go_to_2024 함수를 재실행 (처음 페이지로 돌아가기 때문에 하나의 게임을 끝내고 나면 다시 눌러줘야 함)
- 즉 3번만 제외하고 1,2,4,5번을 반복
- 월별 게임 최대수(바깥쪽 for문)와 일당 게임 최대 수(안쪽 for문)의 for문 종료 후 함수 종료
7. 자동으로 브라우저 닫기 및 데이터 확인
8. json 파일로 저장하여 크롤링을 다시 하는 시간을 줄이기
느낀 점
1. 크롤링을 할 경우 시간이 걸리더라도 전체 시간 단축을 위해 예외처리가 필수적
2. 시간이 좀 걸리더라도 페이지 로드가 안 되는 경우가 있어 time.sleep이나 driver.implicitly_wait 걸어주기
3. 자식 요소를 찾는 값을 저장하는 child_count가 0이면 에러가 발생하여 1로 수정함. 왜 해결되었는지는 의문
4. Copy full XPath를 통해 요소를 찾는 것이 제일 좋은 것으로 보임
크롤링 코드
○ Import
import pandas as pd
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver import Keys, ActionChains
import pyperclip
from sqlalchemy import create_engine
import re
from collections import defaultdict
import json
import requests
from lxml import html
from selenium.common.exceptions import NoSuchElementException
○ 사이트 접속 및 변수 선언
driver = webdriver.Chrome()
url = "https://esports.op.gg/schedules/lck"
driver.get(url)
driver.maximize_window()
driver.implicitly_wait(5)
time.sleep(3)
lck_2024_data_2 = list()
child_count = 1
# 연도/시간/경기 스코어 데이터
score_data = '/html/body/div[1]/div[2]/div/main/div[1]/div[1]'
# 긁어올 상세 게임데이터
need_data = '/html/body/div[1]/div[2]/div/main/div[2]/div/div[2]/div[2]/div/div[2]/div[1]/div[2]'
game_time = ''
detail = ''
[score_data & need_data Pull XPATH]
○ 연 블록 & 월 블록 클릭 및 월 선택 함수
* 각 월을 선택할 때 마지막 div만 숫자가 변하는 것을 확인 So, format을 통해 동적으로 넣어줌
● go_to_2024( ) : 연도 블록을 찾아서 클릭 → 2024년의 XPATH를 찾아서 클릭 → 달 블록 함수 → 달 선택 함수
결국 2024년 선택과 월 선택을 다 담당하기에 이동하고자 하는 연/월을 클릭해 주는 함수라 보면 됨
● click_month( ) : PullXPATH를 통해 월 블록 클릭
●select_month( ) : PullXPATH를 통해 월을 동적 선택
def go_to_2024(p_i):
# 연도 클릭
driver.find_element(By.XPATH, "/html/body/div[1]/div[2]/div/main/div[2]/div/div[2]/div/div/div[1]/div[1]/div").click()
driver.implicitly_wait(5)
time.sleep(1.5)
# 2024년으로 이동
driver.find_element(By.XPATH, "/html/body/div[1]/div[2]/div/main/div[2]/div/div[2]/div/div/div[1]/div[1]/section/div[1]").click()
driver.implicitly_wait(5)
time.sleep(1.5)
click_month()
driver.implicitly_wait(5)
time.sleep(1.5)
select_month(p_i)
driver.implicitly_wait(5)
def click_month():
# 월 블록 클릭
driver.find_element(By.XPATH, "/html/body/div[1]/div[2]/div/main/div[2]/div/div[2]/div/div/div[1]/div[2]/div").click()
driver.implicitly_wait(5)
time.sleep(1)
def select_month(i):
driver.find_element(By.XPATH, "/html/body/div[1]/div[2]/div/main/div[2]/div/div[2]/div/div/div[1]/div[2]/section/div[{}]".format(i)).click()
driver.implicitly_wait(5)
time.sleep(1)
○ for 문을 돌릴 횟수를 정해주기 위한 자식 요소 찾기 함수
* 트러블 : find_day_child() 함수에서 no such element 오류가 계속 발생하여 창 새로고침 후 다시 찾는 걸로 해결
● find_month_child( ) : ul의 자식 요소 개수인 li의 개수를 찾아서 달에 존재하는 게임 한 일자 수를 찾기
push_2024_game() 함수의 바깥쪽 for문을 돌릴 횟수를 설정
● find_day_child( ) : find_month_child()의 ul 안에 li의 자식 요소를 찾아 하루에 존재하는 경기 수 찾기
push_2024_game() 함수의 안 쪽 for 문 횟수 설정
● find_set_child( ) : 세트 수의 자식 요소를 찾아 세트 수만큼 for문 돌리기
push_2024_set() 함수의 for문 횟수 설정
보라색 네모가 find_month_child()로 찾는 게임 수로 4월 달 기준 6개
빨간색 네모는 find_day_child()로 찾는 일별 게임 수로 4월 3일 기준 1, 4월 4일 1, 4월 6일 1.... 4월 14일 1이 반환
def find_month_child():
current_url = driver.current_url
# 페이지 소스 가져오기
page_source = driver.page_source
# lxml을 사용하여 파싱
tree = html.fromstring(page_source)
# 특정 태그의 XPath를 통해 자식 요소 찾기
xpath = '/html/body/div[1]/div[2]/div/main/div[2]/div/div[2]/ul'
elements = tree.xpath(xpath)
# if elements:
# 첫 번째 요소의 자식 요소 개수 찾기
child_elements = elements[0].getchildren()
child_count = len(child_elements)
return child_count
def find_day_child(i):
current_url = driver.current_url
# 페이지 소스 가져오기
page_source = driver.page_source
# lxml을 사용하여 파싱
tree = html.fromstring(page_source)
# 특정 태그의 XPath를 통해 자식 요소 찾기
xpath = '/html/body/div[1]/div[2]/div/main/div[2]/div/div[2]/ul/li[{}]/ul'.format(i) # 예: '//div[@id="content"]'
elements = tree.xpath(xpath)
try:
# if elements:
# 첫 번째 요소의 자식 요소 개수 찾기
child_elements = elements[0].getchildren()
child_count = len(child_elements)
except NoSuchElementException:
driver.refresh()
driver.implicitly_wait(5)
time.sleep()
find_day_child(i)
finally:
return child_count
def find_set_child():
current_url = driver.current_url
# 페이지 소스 가져오기
page_source = driver.page_source
# lxml을 사용하여 파싱
tree = html.fromstring(page_source)
##/html/body/div[1]/div[2]/div/main/div[1]/div[2]/ul
# 특정 태그의 XPath를 통해 자식 요소 찾기
xpath = '/html/body/div[1]/div[2]/div/main/div[1]/div[2]/ul'
elements = tree.xpath(xpath)
# if elements:
# 첫 번째 요소의 자식 요소 개수 찾기
child_elements = elements[0].getchildren()
child_count = len(child_elements)
return child_count
○ 게임 누르기 함수 및 Set 번호 누르기 함수
● push_2024_game(find_month_child()에서 반환받은 값, 월)
- 달마다 돌리기 때문에 달의 자식 요소 찾기 블록을 한 번 실행하여 인자로 전달
- 받은 인자를 바깥쪽 for문에 1부터 p_month까지 실행
- 해당 블록 안에서 일마다 게임 횟수를 찾는 find_day_child() 실행
- find_day_child() 함수를 통해 얻은 값으로 else 문 안에 for 문을 실행
- 트러블 : 자식 요소가 1개인 경우 no such element 에러가 발생하여 if문을 통해 1개인 경우 통제하여 해결
하루에 1개의 게임이 존재하는 경우에는 li 뒤에 숫자가 붙지 않고 li만 있었음(9번째 코드)
- 20번째 코드 :'/html/body/div [1]/div [2]/div/main/div [2]/div/div [2]/ul/li[{}]/ul/li[{}]'. format(i, j)
아래의 사진과 같은 형태로 li의 숫자가 변하여 format으로 할당
게임에 접속한 후 push_2024_set을 통해 set번호를 누르면서 필요한 데이터를 긁어오기
● push_2024_set() 세트 번호 누르기 함수
- find_set_child 함수를 통해 Set블록의 자식 요소를 찾아서 반환받은 값 + 1까지 for 문 반복
(= 세트 수만큼 돌리기)
- 에러가 발생하면 refresh 후 다시 찾기 button도 1세트부터 반복
- 데이터를 긁어오는 부분에서 가장 많이 에러가 발생하였다. 기본적으로 이전에 선언한 score_data와 need_data
를 찾고 요소를 찾지 못하면(페이지에서 로드되지 않거나 그냥 못 찾는 경우 빈번)
execpt로 들어가 refresh 한 후 다른 요소를 text로 받아옴
- 마지막으로 리스트에 값을 더해준 후 원래 페이지로 돌아감
# 게임 누르기
def push_2024_game(p_month, p_i):
for i in range(1, p_month +1):
# (일별)ul안의 각 li 안의 ul의 자식요소 개수 파악(각 일별 게임 갯수)
# 일별 게임 최대 수
during_day = find_day_child(i)
time.sleep(2)
if during_day == 1:
driver.find_element(By.XPATH, '/html/body/div[1]/div[2]/div/main/div[2]/div/div[2]/ul/li[{}]/ul/li'.format(i)).click()
driver.implicitly_wait(5)
time.sleep(1.5)
push_2024_set()
driver.implicitly_wait(5)
time.sleep(0.5)
go_to_2024(p_i) # 2023년 1월
else: # (일별)ul안의 각 li 안의 ul의 자식요소 개수 파악(각 일별 게임 갯수)
for j in range(1, during_day + 1):
driver.implicitly_wait(5)
time.sleep(2)
driver.find_element(By.XPATH, '/html/body/div[1]/div[2]/div/main/div[2]/div/div[2]/ul/li[{}]/ul/li[{}]'.format(i, j)).click()
driver.implicitly_wait(5)
time.sleep(1.5)
push_2024_set()
driver.implicitly_wait(5)
time.sleep(0.5)
go_to_2024(p_i) # 2023년 1월
# 게임 내에서 set 번호 누르기
def push_2024_set():
set_num = find_set_child()
driver.implicitly_wait(5)
time.sleep(1.5)
for k in range(1, set_num + 1):
try:
driver.find_element(By.XPATH, '/html/body/div[1]/div[2]/div/main/div[1]/div[2]/ul/button[{}]'.format(k)).click()
driver.implicitly_wait(10)
time.sleep(7)
except NoSuchElementException:
driver.refresh()
driver.find_element(By.XPATH, '/html/body/div[1]/div[2]/div/main/div[1]/div[2]/ul/button[{}]'.format(k)).click()
driver.implicitly_wait(10)
time.sleep(5)
finally:
# 값 추가
try:
game_time = driver.find_element(By.XPATH, score_data).text
detail = driver.find_element(By.XPATH, need_data).text
driver.implicitly_wait(5)
time.sleep(1.5)
except NoSuchElementException:
driver.refresh()
time.sleep(1.5)
game_time = driver.find_element(By.XPATH, score_data).text
detail = driver.find_element(By.XPATH, '/html/body/div[1]/div[2]/div/main/div[2]/div/div[2]/div[1]/div/div[2]/div[1]/div[2]/div/ul').text
driver.implicitly_wait(5)
time.sleep(1.5)
finally:
lck_2024_data_2.append(game_time)
lck_2024_data_2.append(detail)
lck_2024_data_2.append('='*50)
# 끝나면 다시 원래 화면으로 돌아가기
driver.get(url)
driver.implicitly_wait(5)
time.sleep(2)
○ 해당 함수들을 사용하여 크롤링 구현
- 처음 접속 시 웹에서 현재 날짜로 되어 있기에 go_to_2024 (i) 함수를 통해 i월로 이동
- 한 달마다 끊어서 크롤링하여 먼저 월의 자식 요소 개수 찾기(월 블록)
- push_2024_game(월의 자식 요소 개수, 찾고자 하는 월)
- 받아온 데이터 값 형태(구분을 위해 '='*50을 append 해줌)
go_to_2024(2)
# 달별 게임 최대 수
during_month = find_month_child()
# 게임 클릭
push_2024_game(during_month, 2)
driver.implicitly_wait(3)
time.sleep(2)
# 저장될때까지 기다렸다가 창 자동 종료
driver.close()
● Json 파일로 저장
- LCK_연도_data_월. json로 파일을 저장
# json 모듈 로드
import json
# json 파일로 저장
with open('LCK_2024_data_2.json', 'w') as f :
json.dump(lck_2024_data_2, f)
○ 전체 코드
import pandas as pd
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver import Keys, ActionChains
import pyperclip
from sqlalchemy import create_engine
import re
from collections import defaultdict
import json
import requests
from lxml import html
from selenium.common.exceptions import NoSuchElementException
# 드라이브 열기
driver = webdriver.Chrome()
url = "https://esports.op.gg/schedules/lck"
driver.get(url)
driver.maximize_window()
driver.implicitly_wait(5)
time.sleep(3)
# 변수 선언
lck_2024_data_2 = list()
child_count = 1
# 연도/시간/경기 스코어 데이터
score_data = '/html/body/div[1]/div[2]/div/main/div[1]/div[1]'
# 긁어올 상세 게임데이터
need_data = '/html/body/div[1]/div[2]/div/main/div[2]/div/div[2]/div[2]/div/div[2]/div[1]/div[2]'
game_time = ''
detail = ''
# 함수
# 월 블록 클릭 함수
def click_month():
# 월 블록 클릭
driver.find_element(By.XPATH, "/html/body/div[1]/div[2]/div/main/div[2]/div/div[2]/div/div/div[1]/div[2]/div").click()
driver.implicitly_wait(5)
time.sleep(1)
# 월 선택 함수
def select_month(i):
driver.find_element(By.XPATH, "/html/body/div[1]/div[2]/div/main/div[2]/div/div[2]/div/div/div[1]/div[2]/section/div[{}]".format(i)).click()
driver.implicitly_wait(5)
time.sleep(1)
# 자식 요소 찾기 함수
# 월의 자식 요소(월당 게임 수)
def find_month_child():
current_url = driver.current_url
# 페이지 소스 가져오기
page_source = driver.page_source
# lxml을 사용하여 파싱
tree = html.fromstring(page_source)
# 특정 태그의 XPath를 통해 자식 요소 찾기
xpath = '/html/body/div[1]/div[2]/div/main/div[2]/div/div[2]/ul'
elements = tree.xpath(xpath)
# if elements:
# 첫 번째 요소의 자식 요소 개수 찾기
child_elements = elements[0].getchildren()
child_count = len(child_elements)
return child_count
# day의 자식요소 찾기 함수(일당 게임 수)
def find_day_child(i):
current_url = driver.current_url
# 페이지 소스 가져오기
page_source = driver.page_source
# lxml을 사용하여 파싱
tree = html.fromstring(page_source)
# 특정 태그의 XPath를 통해 자식 요소 찾기
xpath = '/html/body/div[1]/div[2]/div/main/div[2]/div/div[2]/ul/li[{}]/ul'.format(i) # 예: '//div[@id="content"]'
elements = tree.xpath(xpath)
try:
# if elements:
# 첫 번째 요소의 자식 요소 개수 찾기
child_elements = elements[0].getchildren()
child_count = len(child_elements)
except NoSuchElementException:
driver.refresh()
driver.implicitly_wait(5)
time.sleep()
find_day_child(i)
finally:
return child_count
# 세트의 자식 요소 찾기 함수(세트 번호 찾기)
def find_set_child():
current_url = driver.current_url
# 페이지 소스 가져오기
page_source = driver.page_source
# lxml을 사용하여 파싱
tree = html.fromstring(page_source)
##/html/body/div[1]/div[2]/div/main/div[1]/div[2]/ul
# 특정 태그의 XPath를 통해 자식 요소 찾기
xpath = '/html/body/div[1]/div[2]/div/main/div[1]/div[2]/ul'
elements = tree.xpath(xpath)
# if elements:
# 첫 번째 요소의 자식 요소 개수 찾기
child_elements = elements[0].getchildren()
child_count = len(child_elements)
return child_count
# 연도 이동 함수(결국 달 블록클릭 및 달 선택 함수가 포함되어 있어 이동하는 함수)
def go_to_2024(p_i):
# 연도 클릭
driver.find_element(By.XPATH, "/html/body/div[1]/div[2]/div/main/div[2]/div/div[2]/div/div/div[1]/div[1]/div").click()
driver.implicitly_wait(5)
time.sleep(1.5)
# 2024년으로 이동
driver.find_element(By.XPATH, "/html/body/div[1]/div[2]/div/main/div[2]/div/div[2]/div/div/div[1]/div[1]/section/div[1]").click()
driver.implicitly_wait(5)
time.sleep(1.5)
click_month()
driver.implicitly_wait(5)
time.sleep(1.5)
select_month(p_i)
driver.implicitly_wait(5)
# 게임 누르기 및 세트만큼 반복하여 데이터 뽑는 함수
# 게임 누르기
def push_2024_game(p_month, p_i):
for i in range(1, p_month +1):
# (일별)ul안의 각 li 안의 ul의 자식요소 개수 파악(각 일별 게임 갯수)
# 일별 게임 최대 수
during_day = find_day_child(i)
time.sleep(2)
if during_day == 1:
driver.find_element(By.XPATH, '/html/body/div[1]/div[2]/div/main/div[2]/div/div[2]/ul/li[{}]/ul/li'.format(i)).click()
driver.implicitly_wait(5)
time.sleep(1.5)
push_2024_set()
driver.implicitly_wait(5)
time.sleep(0.5)
go_to_2024(p_i) # 2023년 1월
else: # (일별)ul안의 각 li 안의 ul의 자식요소 개수 파악(각 일별 게임 갯수)
for j in range(1, during_day + 1):
driver.implicitly_wait(5)
time.sleep(2)
driver.find_element(By.XPATH, '/html/body/div[1]/div[2]/div/main/div[2]/div/div[2]/ul/li[{}]/ul/li[{}]'.format(i, j)).click()
driver.implicitly_wait(5)
time.sleep(1.5)
push_2024_set()
driver.implicitly_wait(5)
time.sleep(0.5)
go_to_2024(p_i) # 2023년 1월
# 게임 내에서 set 번호 누르기
def push_2024_set():
set_num = find_set_child()
driver.implicitly_wait(5)
time.sleep(1.5)
for k in range(1, set_num + 1):
try:
driver.find_element(By.XPATH, '/html/body/div[1]/div[2]/div/main/div[1]/div[2]/ul/button[{}]'.format(k)).click()
driver.implicitly_wait(10)
time.sleep(7)
except NoSuchElementException:
driver.refresh()
driver.find_element(By.XPATH, '/html/body/div[1]/div[2]/div/main/div[1]/div[2]/ul/button[{}]'.format(k)).click()
driver.implicitly_wait(10)
time.sleep(5)
finally:
# 값 추가
try:
game_time = driver.find_element(By.XPATH, score_data).text
detail = driver.find_element(By.XPATH, need_data).text
driver.implicitly_wait(5)
time.sleep(1.5)
except NoSuchElementException:
driver.refresh()
time.sleep(1.5)
game_time = driver.find_element(By.XPATH, score_data).text
detail = driver.find_element(By.XPATH, '/html/body/div[1]/div[2]/div/main/div[2]/div/div[2]/div[1]/div/div[2]/div[1]/div[2]/div/ul').text
driver.implicitly_wait(5)
time.sleep(1.5)
finally:
lck_2024_data_2.append(game_time)
lck_2024_data_2.append(detail)
lck_2024_data_2.append('='*50)
# 끝나면 다시 원래 화면으로 돌아가기
driver.get(url)
driver.implicitly_wait(5)
time.sleep(2)
# 구동 및 저장
go_to_2024(2)
# 달별 게임 최대 수
during_month = find_month_child()
# 게임 클릭
push_2024_game(during_month, 2)
driver.implicitly_wait(3)
# 저장될때까지 기다렸다가 창 자동 종료
time.sleep(2)
driver.close()
# json 모듈 로드
import json
# json 파일로 저장
with open('LCK_2024_data_2.json', 'w') as f :
json.dump(lck_2024_data_2, f)
'Networks > Project' 카테고리의 다른 글
SK networks AI Camp - Toy Project 크롤링 코드(2) 및 전처리 리뷰 (0) | 2024.08.15 |
---|---|
SK networks AI Camp - Toy Project 전처리 코드 리뷰 (0) | 2024.08.11 |
SK networks AI Camp - Toy Project(에자일_1) (0) | 2024.08.10 |
SK networks AI Camp - 야구 데이터 분석하기(2) feat. Lotte giants (0) | 2024.08.05 |
SK networks AI Camp - Toy Project (0) | 2024.08.02 |