Selenium 및 Python을 사용한 웹 스크랩 핑
이것은 내 첫 번째 프로그램 코드이며 실제로 작동합니다. 내 목표는 웹 사이트에서 정보를 긁어내어 데이터베이스에 저장하는 것입니다. 스포츠 이벤트 및 확률에 대한 기록 데이터가있는 사이트입니다.
https://www.oddsportal.com/hockey/sweden/shl-2019-2020/results/
예를 들어,이 페이지에는 50 개의 일치 항목이 있으며 프로그램은 각 항목을 입력하고이 데이터를 스크랩합니다.
그러나 실제로 실제 코드처럼 보이지는 않습니다. 개선하고 최적화하기 위해 할 수있는 일에 관심이 있습니다. 무엇에 가장주의를 기울이고 어떤 일을 잘못합니까?
from selenium import webdriver
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import NoSuchElementException, TimeoutException
from selenium.webdriver.common.keys import Keys
import sqlite3 as sql
import time
# I disable image display to get up to speed
options = webdriver.ChromeOptions()
prefs = {"profile.managed_default_content_settings.images": 2}
options.add_experimental_option("prefs", prefs)
driver = webdriver.Chrome(options=options)
# Database connection
conn = sql.connect('Hockey_data.db')
c = conn.cursor()
driver.get("https://www.oddsportal.com/hockey/sweden/shl-2019-2020/results/")
driver.maximize_window()
cur_win = driver.current_window_handle
list_of_links = driver.find_elements_by_xpath("//td[2]/a")[0:-2] # because 2 more elements have the same xpath and they are always at the end
for index, link in enumerate(list_of_links):
link.send_keys(Keys.CONTROL + Keys.RETURN)
driver.switch_to.window([win for win in driver.window_handles if win !=cur_win][0])
# Basic game info
league = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.XPATH, "/html/body/div[1]/div/div[2]/div[6]/div[1]/div/div[1]/div[1]/a[4]"))).text
game = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.XPATH, "/html/body/div[1]/div/div[2]/div[6]/div[1]/div/div[1]/div[2]/div[1]/h1"))).text
game_split = game.split('- ')
home_team, away_team = game_split[0], game_split[1]
time = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.XPATH, "/html/body/div[1]/div/div[2]/div[6]/div[1]/div/div[1]/div[2]/div[1]/p[1]"))).text
time_split = time.split(', ')
day_in_week = time_split[0]
date = time_split[1]
day = int(date[0:2])
month = date[3:7]
year = int(date[7:11])
hour = time_split[2]
# Result and goals by periods
try:
result = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, "event-status"))).text
except NoSuchElementException:
result = "0000000000000000000000000000000000000000000000000000000000000000000000"
if len(result) == 32:
home_goals = int(result[13:14])
home_first = int(result[18:19])
home_second = int(result[23:24])
home_third = int(result[28:29])
home_overtime = 0
home_penalties = 0
away_goals = int(result[15:16])
away_first = int(result[20:21])
away_second = int(result[25:26])
away_third = int(result[30:31])
away_overtime = 0
away_penalties = 0
elif len(result) == 33:
home_goals = int(result[13:15])
home_first = int(result[19:20])
home_second = int(result[24:25])
home_third = int(result[29:30])
home_overtime = 0
home_penalties = 0
away_goals = int(result[16:17])
away_first = int(result[21:22])
away_second = int(result[26:27])
away_third = int(result[31:32])
away_overtime = 0
elif len(result) == 40:
home_goals = int(result[13:14])
home_first = int(result[21:22])
home_second = int(result[26:27])
home_third = int(result[31:32])
home_overtime = int(result[36:37])
home_penalties = 0
away_goals = int(result[15:16])
away_first = int(result[23:24])
away_second = int(result[28:29])
away_third = int(result[33:34])
away_overtime = int(result[38:39])
away_penalties = 0
elif len(result) == 52:
home_goals = int(result[13:14])
home_first = int(result[28:29])
home_second = int(result[33:34])
home_third = int(result[38:39])
home_overtime = int(result[43:44])
home_penalties = int(result[48:49])
away_goals = int(result[15:16])
away_first = int(result[30:31])
away_second = int(result[35:36])
away_third = int(result[40:41])
away_overtime = int(result[45:46])
away_penalties = int(result[50:51])
else:
home_goals = 0
home_first = 0
home_second = 0
home_third = 0
home_overtime = 0
home_penalties = 0
away_goals = 0
away_first = 0
away_second = 0
away_third = 0
away_overtime = 0
away_penalties = 0
# 1x2 odds
pinnacle = "Pinnacle"
try:
pinnacle_home = driver.find_element_by_xpath("//div[a[contains(text(), 'Pinnacle')]]/following::td[1]")
hov_pinnacle_home = ActionChains(driver).move_to_element(pinnacle_home)
hov_pinnacle_home.perform()
pinnacle_home_closing = driver.find_element_by_xpath("//*[@id='tooltiptext']/strong[1]").text
try:
pinnacle_home_opening = driver.find_element_by_xpath("//*[@id='tooltiptext']/strong[2]").text
except (NoSuchElementException, TimeoutException):
pinnacle_home_opening = pinnacle_home_closing
except (NoSuchElementException, TimeoutException):
pinnacle_home = "0000000000"
pinnacle_home_closing = 0.00
pinnacle_home_opening = 0.00
try:
pinnacle_draw = driver.find_element_by_xpath("//div[a[contains(text(), 'Pinnacle')]]/following::td[2]")
hov_pinnacle_draw = ActionChains(driver).move_to_element(pinnacle_draw)
hov_pinnacle_draw.perform()
pinnacle_draw_closing = driver.find_element_by_xpath("//*[@id='tooltiptext']/strong[1]").text
try:
pinnacle_draw_opening = driver.find_element_by_xpath("//*[@id='tooltiptext']/strong[2]").text
except (NoSuchElementException, TimeoutException):
pinnacle_draw_opening = pinnacle_draw_closing
except (NoSuchElementException, TimeoutException):
pinnacle_draw = "0000000000"
pinnacle_draw_closing = 0.00
pinnacle_draw_opening = 0.00
try:
pinnacle_away = driver.find_element_by_xpath("//div[a[contains(text(), 'Pinnacle')]]/following::td[3]")
hov_pinnacle_away = ActionChains(driver).move_to_element(pinnacle_away)
hov_pinnacle_away.perform()
pinnacle_away_closing = driver.find_element_by_xpath("//*[@id='tooltiptext']/strong[1]").text
try:
pinnacle_away_opening = driver.find_element_by_xpath("//*[@id='tooltiptext']/strong[2]").text
except (NoSuchElementException, TimeoutException):
pinnacle_away_opening = pinnacle_away_closing
except (NoSuchElementException, TimeoutException):
pinnacle_away = "0000000000"
pinnacle_away_closing = 0.00
pinnacle_away_opening = 0.00
average = "Average"
try:
average_home = driver.find_element_by_xpath("//td[strong[contains(text(), 'Average')]]/following::td[1]").text
except NoSuchElementException:
average_home = 0.00
try:
average_draw = driver.find_element_by_xpath("//td[strong[contains(text(), 'Average')]]/following::td[2]").text
except NoSuchElementException:
average_draw = 0.00
try:
average_away = driver.find_element_by_xpath("//td[strong[contains(text(), 'Average')]]/following::td[3]").text
except NoSuchElementException:
average_away = 0.00
highest = "Highest"
try:
highest_home = driver.find_element_by_xpath("//td[strong[contains(text(), 'Highest')]]/following::td[1]").text
except NoSuchElementException:
highest_home = 0.00
try:
highest_draw = driver.find_element_by_xpath("//td[strong[contains(text(), 'Highest')]]/following::td[2]").text
except NoSuchElementException:
highest_draw = 0.00
try:
highest_away = driver.find_element_by_xpath("//td[strong[contains(text(), 'Highest')]]/following::td[3]").text
except NoSuchElementException:
highest_away = 0.00
# Over-Under tab
OU_link = WebDriverWait(driver, 5).until(EC.presence_of_element_located((By.XPATH, "/html/body/div[1]/div/div[2]/div[6]/div[1]/div/div[1]/div[2]/div[1]/div[5]/div[1]/ul/li[5]/a/span"))).click()
pinnacle_over_under = "Pinnacle 5.5"
# 5.5 over-under tab
try:
OU_55 = WebDriverWait(driver, 5).until(EC.presence_of_element_located((By.XPATH, "//div/div/strong/a[contains(text(), 'Over/Under +5.5')]"))).click()
try:
pinnacle_over = driver.find_element_by_xpath("//div[a[contains(text(), 'Pinnacle')]]/following::td[2]")
hov_pinnacle_over = ActionChains(driver).move_to_element(pinnacle_over)
hov_pinnacle_over.perform()
pinnacle_over_closing = driver.find_element_by_xpath("//*[@id='tooltiptext']/strong[1]").text
try:
pinnacle_over_opening = driver.find_element_by_xpath("//*[@id='tooltiptext']/strong[2]").text
except (NoSuchElementException, TimeoutException):
pinnacle_over_opening = pinnacle_over_closing
except (NoSuchElementException, TimeoutException):
pinnacle_over = "0000000000"
pinnacle_over_opening = 0.00
pinnacle_over_closing = 0.00
try:
pinnacle_under = driver.find_element_by_xpath("//div[a[contains(text(), 'Pinnacle')]]/following::td[3]")
hov_pinnacle_under = ActionChains(driver).move_to_element(pinnacle_under)
hov_pinnacle_under.perform()
pinnacle_under_closing = driver.find_element_by_xpath("//*[@id='tooltiptext']/strong[1]").text
try:
pinnacle_under_opening = driver.find_element_by_xpath("//*[@id='tooltiptext']/strong[2]").text
except (NoSuchElementException, TimeoutException):
pinnacle_under_opening = pinnacle_over_closing
except (NoSuchElementException, TimeoutException):
pinnacle_under = "0000000000"
pinnacle_under_opening = 0.00
pinnacle_under_closing = 0.00
except (NoSuchElementException, TimeoutException):
OU_55 = "0000000000"
pinnacle_under_opening = 0.00
pinnacle_under_closing = 0.00
average_over_under = "Average 5.5"
try:
average_over = driver.find_element_by_xpath("//td[strong[contains(text(), 'Average')]]/following::td[2]").text
except NoSuchElementException:
average_over = 0.00
try:
average_under = driver.find_element_by_xpath("//td[strong[contains(text(), 'Average')]]/following::td[3]").text
except NoSuchElementException:
average_under = 0.00
highest_over_under = "Highest 5.5"
try:
highest_over = driver.find_element_by_xpath("//td[strong[contains(text(), 'Highest')]]/following::td[2]").text
except NoSuchElementException:
highest_over = 0.00
try:
highest_under = driver.find_element_by_xpath("//td[strong[contains(text(), 'Highest')]]/following::td[3]").text
except NoSuchElementException:
highest_under = 0.00
c.execute('insert into Model values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)',
(league, home_team, away_team, day_in_week, day, month, year, hour,
home_goals, away_goals, home_first, away_first, home_second,away_second, home_third, away_third,home_overtime, away_overtime, home_penalties, away_penalties,
pinnacle, pinnacle_home_closing, pinnacle_draw_closing, pinnacle_away_closing, pinnacle_home_opening, pinnacle_draw_opening, pinnacle_away_opening,
average, average_home, average_draw, average_away, highest, highest_home, highest_draw, highest_away,
pinnacle_over_under, pinnacle_over_closing, pinnacle_under_closing, pinnacle_over_opening, pinnacle_under_opening,
average_over_under, average_over, average_under, highest_over_under, highest_over, highest_under,))
conn.commit()
print("Number: ", index)
driver.close()
driver.switch_to.window(cur_win)
답변
반복 1
거의 동일한 XPATH가 여러 개 있습니다. 특히 다음과 같이 공통 접두사를 공유 할 때
"/html/body/div[1]/div/div[2]/div[6]/div[1]/div/div[1]/div[1]/a[4]"
나는 변수를 선언 할 것이다
xpath_base = "/html/body/div[1]/div/div[2]/div[6]/div[1]/div/div[1]"
그런 다음 동일한 기반을 가진 모든 xpath에 대해 재사용하십시오. 이렇게하면 기본이 변경 될 경우 코드를 더 읽기 쉽고 수정하기가 더 쉽습니다.
그러면 첫 번째 xpath가 xpath_base + "/div[1]/a[4]", 두 번째 xpath가 xpath_base + "/div[2]/div[1]/h1"됩니다.
Reptition 2 / 매핑
목표 독서를 위해 좀 더 간결한 구조를 사용하고 싶습니다. 더 읽기 쉬운 지 확실하지 않지만 더 짧습니다.
이와 같이 dict와 배열의 조합에 인덱스를 넣을 수 있습니다.
이 경우 goals, first, second, third4 가지 경우 모두 인덱스와 함께 사용되기 때문에 집과 외출용 인덱스 만 사용하고 있습니다 .
result_mapping = {
32: [13, 14, 18, 19, 23, 24, 28, 29, 15, 16, 20, 21, 25, 26, 30, 31],
33: [13, 15, 19, 20, 24, 25, 29, 39, 16, 17, 21, 22, 26, 27, 31, 32],
40: [...],
52: [...]
}
이 매핑을 사용하면 다음을 수행 할 수 있습니다.
if len(result) in result_mapping.keys():
indexes = result_mapping[len(result)]
home_goals = int(result[indexes[0] : indexes[1]])
home_first = int(result[indexes[2] : indexes[3]])
home_second = int(result[indexes[4] : indexes[5]])
등등. int(result[])각 줄에서 반복되기 때문에 함수를 만드는 것과 같이이 코드를 더 짧게 만들 여지가 더 많습니다 .
또한, 인덱스는 항상 n과 n+1를 제외하고 home_goals = int(result[13:15])(색인에 오류가?이다)를. 13:15오류이고 오류가 발생해야하는 경우 13:14위의 배열에서 모든 끝 인덱스를 제거 할 수 있으므로
32: [13, 14, 18, 19, 23, 24, 28, 29, 15, 16, 20, 21, 25, 26, 30, 31]
된다
32: [13, 18, 23, 28, 15, 20, 25, 30]
+1각 경우에 끝 색인 대신 추가하기 만하면 됩니다.
반복 3 / 별칭
당신이 많이 반복하는 것들은 더 짧은 별칭을 만들고 싶습니다. xpath = driver.find_element_by_xpath코드 초반에 정의한 다음 새 이름을 사용하여 해당 함수를 호출 할 수 있습니다.
반복 4 / 기본 케이스
긴 if-elif-else 부분에서 기본 케이스는 else.
그러나 여기에있는 일부 사례는 if-else의 여러 사례와 공유됩니다. 따라서 if-else 전에 기본값을 먼저 정의하는 것이 더 합리적입니다. 모두 0이므로 이렇게 조금 더 짧게 만들 수도 있습니다.
home_goals = home_first = home_second = home_third = home_overtime = home_penalties = 0
이제 if-else 전에이 작업을 수행하면 이러한 변수 가 4 개 중 3 개 케이스 0와 같은 경우에 다시 언급 할 필요가 없습니다 home_penalties.
최종 코멘트
진짜 코드가 아닌 것 같아요
"실제 코드"처럼 보이지 않는 한 가지 이유는 시도가 너무 많기 때문이라고 생각합니다. 예외는 예외입니다. 그러나 특정 웹 사이트에 대한 스크레이퍼를 작성할 때 피하는 것은 어렵습니다. 해당 웹 사이트의 구조에 대한 사용자 지정 코드를 작성해야하며 일반 프로그램을 작성할 때 일반적으로 할 수있는 많은 리팩토링 / 바로 가기 및 재사용을 수행 할 수 없습니다. 따라서 대부분의 코드가이 사용 사례에 적합하다고 생각합니다.
게시 후 하나 더 추가
다음과 같이 동일한 패턴을 따르는 케이스가 많이 있습니다.
try:
average_under = driver.find_element_by_xpath("//td[strong[contains(text(), 'Average')]]/following::td[3]").text
except NoSuchElementException:
average_under = 0.00
이를 리팩토링하고 함수를 생성하여 반복을 줄일 수 있습니다.
average_under = try_or_default("//td[strong[contains(text(), 'Average')]]/following::td[3]", 0.00)
그리고 당신의 기능은
def try_or_default(xpath, default_value):
try:
return driver.find_element_by_xpath(xpath).text
except NoSuchElementException:
return default_value
(나는 그것을 실행하지 않았으므로 구문 오류가있을 수 있습니다).
default_value의 기본값을 설정하여 더 짧게 만들 수 있습니다.
def try_or_default(xpath, default_value=0.00):
0.00대부분의 경우 함수를 호출 할 때를 생략 할 수 있습니다 .