การขูดเว็บโดยใช้ซีลีเนียมและ Python

Sep 12 2020

นี่เป็นรหัสโปรแกรมแรกของฉันและใช้งานได้จริง เป้าหมายของฉันคือการรวบรวมข้อมูลจากเว็บไซต์และเก็บไว้ในฐานข้อมูล เป็นเว็บไซต์ที่มีข้อมูลย้อนหลังเกี่ยวกับการแข่งขันกีฬาและอัตราต่อรอง

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)

คำตอบ

3 user985366 Sep 12 2020 at 23:41

การทำซ้ำ 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_base + "/div[2]/div[1]/h1"นี้ไปเรื่อย ๆ

การทำซ้ำ 2 / การทำแผนที่

ฉันต้องการใช้โครงสร้างที่กะทัดรัดกว่านี้สำหรับการอ่านเป้าหมาย ไม่แน่ใจว่าคุณจะอ่านได้มากกว่านี้ แต่มันสั้นกว่า

คุณสามารถใส่ดัชนีของคุณโดยผสมระหว่างคำสั่งและอาร์เรย์เช่นนี้

ในกรณีนี้ฉันใช้เพียงดัชนีสำหรับบ้านและนอกบ้านgoals, first, second, thirdเนื่องจากใช้กับดัชนีในทั้ง 4 กรณี

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 เนื่องจากพวกมันทั้งหมดเป็นศูนย์คุณสามารถทำให้มันสั้นลงเล็กน้อยเช่นนี้

home_goals = home_first = home_second = home_third = home_overtime = home_penalties = 0

ตอนนี้เมื่อทำสิ่งนี้ก่อน if-else คุณไม่จำเป็นต้องพูดถึงตัวแปรเหล่านี้อีกในกรณีที่ควรจะยังคงเป็น0เช่นhome_penaltiesใน 3 ใน 4 กรณี

ความคิดเห็นสุดท้าย

ฉันไม่คิดว่ามันจะดูเหมือนรหัสจริงๆ

ฉันคิดว่าเหตุผลหนึ่งที่มันดูไม่เหมือน "โค้ดจริง" คือมีให้ลองมากมาย - ยกเว้นและกรณีพิเศษและอื่น ๆ แต่นั่นเป็นเรื่องยากที่จะหลีกเลี่ยงเมื่อเขียนมีดโกนสำหรับเว็บไซต์ใดเว็บไซต์หนึ่ง คุณจำเป็นต้องเขียนโค้ดที่กำหนดเองสำหรับโครงสร้างของเว็บไซต์นั้นและคุณไม่สามารถทำการ refactorings / ทางลัดจำนวนมากและการนำสิ่งต่างๆที่ปกติคุณทำได้มาใช้ซ้ำเมื่อเขียนโปรแกรมปกติ ดังนั้นฉันคิดว่าโค้ดส่วนใหญ่ดูดีสำหรับกรณีการใช้งานนี้

อีกหนึ่งรายการหลังจากโพสต์

คุณมีหลายกรณีที่ทำตามรูปแบบเดียวกันเช่นนี้

try:
    average_under = driver.find_element_by_xpath("//td[strong[contains(text(), 'Average')]]/following::td[3]").text
except NoSuchElementException:
        average_under = 0.00

คุณสามารถ refactor สิ่งนี้และทำให้ซ้ำน้อยลงโดยการสร้างฟังก์ชัน

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เวลาเรียกใช้ฟังก์ชันนี้ในกรณีส่วนใหญ่