[2주차 - Day3] 웹 브라우저 자동화 - Selenium

2023. 3. 23. 23:44BOOTCAMP/프로그래머스 인공지능 데브코스

동적 웹 페이지와의 만남

▶ HTML 내용이 고정된 정적(static) 웹 사이트, HTML 문서가 완전하게 응답됩니다.

▶ HTML 내용이 변하는 동적(dynamic) 웹 사이트, HTML이 렌더링이 될 때까지 지연시간이 존재합니다. (유튜브)

 

웹 브라우저에선 JavaScript라는 프로그래밍 언어가 동작하며, 비동기 처리를 통해서 필요한 데이터를 채우는 경향이 있습니다.

 

▶동기 처리: 요청에 따른 응답을 기다리며(렌더링을 다 해야만 데이터 처리가 진행), HTML 로딩에 문제가 없습니다.

▶비동기 처리: 요청에 따른 응답을 기다리지 않고(렌더링과 데이터 처리가 동시에 진행), 상황에 따라서 데이터가 완전하지 않은 경우가 발생합니다.

 

스크래퍼의 문제점

▶동적 웹 사이트에 적용이 어려움

렌더링과 데이터 처리가 동시에 진행되는 상황에서 요청을 보내면 불완전한 응답을 받게 됩니다.

 

▶UI 상호작용이 어려움

키보드 입력, 마우스 클릭 등을 requests로는 진행하기 어렵습니다.

 

[해결 방법]

Q. 정보를 추출하는 시간을 임의로 지연시킨다면?

A. 임의로 시간을 지연한 후, 데이터 처리가 끝난 후 정보를 가져오면 됩니다.

 

Q. UI Action을 프로그래밍을 통해 명령을 내린다면?

A. 키보드 입력, 마우스 클릭 등을 프로그래밍할 수 있습니다.

 

웹 브라우저와 파이썬의 만남

from selenium import webdriver

driver = webdriver.Chrome()
driver.implicitly_wait(10)
driver.get("http://www.example.com")

웹 브라우저를 파이썬을 조작할 수 있으며, 자동화하는 라이브러리인 Selenium으로 응답 시간을 지연시킬 있습니다.

from selenium import webdriver

elem = driver.find_element_by_tag_name("hello-input")
elem.send_keys("Hello!")

UI와의 상호작용이 가능합니다.

 

동적 웹사이트는 응답 후 바로 정보를 추출하기 어렵고, 다양한 키보드 입력과 마우스 클릭 등의 상호작용이 존재합니다. 이런 상황을 해결하려면 웹 브라우저를 파이썬으로 조작하는 게 좋습니다.

 

브라우저 자동화하기, Selenium

Selenium 라이브러리

  • selenium은 Python을 이용해서 웹 브라우저를 조작할 수 있는 자동화 프레임워크입니다.

pip install을 통해서 이를 간단하게 설치할 수 있습니다.

# selenium 라이브러리를 설치해줍니다.
%pip install selenium

Requirement already satisfied: selenium in ./opt/anaconda3/lib/python3.9/site-packages (4.8.2)
Requirement already satisfied: urllib3[socks]~=1.26 in ./opt/anaconda3/lib/python3.9/site-packages (from selenium) (1.26.11)
Requirement already satisfied: trio~=0.17 in ./opt/anaconda3/lib/python3.9/site-packages (from selenium) (0.22.0)
Requirement already satisfied: certifi>=2021.10.8 in ./opt/anaconda3/lib/python3.9/site-packages (from selenium) (2022.9.24)
Requirement already satisfied: trio-websocket~=0.9 in ./opt/anaconda3/lib/python3.9/site-packages (from selenium) (0.10.2)
Requirement already satisfied: sniffio in ./opt/anaconda3/lib/python3.9/site-packages (from trio~=0.17->selenium) (1.2.0)
Requirement already satisfied: attrs>=19.2.0 in ./opt/anaconda3/lib/python3.9/site-packages (from trio~=0.17->selenium) (21.4.0)
Requirement already satisfied: sortedcontainers in ./opt/anaconda3/lib/python3.9/site-packages (from trio~=0.17->selenium) (2.4.0)
Requirement already satisfied: outcome in ./opt/anaconda3/lib/python3.9/site-packages (from trio~=0.17->selenium) (1.2.0)
Requirement already satisfied: idna in ./opt/anaconda3/lib/python3.9/site-packages (from trio~=0.17->selenium) (3.3)
Requirement already satisfied: exceptiongroup>=1.0.0rc9 in ./opt/anaconda3/lib/python3.9/site-packages (from trio~=0.17->selenium) (1.1.1)
Requirement already satisfied: async-generator>=1.9 in ./opt/anaconda3/lib/python3.9/site-packages (from trio~=0.17->selenium) (1.10)
Requirement already satisfied: wsproto>=0.14 in ./opt/anaconda3/lib/python3.9/site-packages (from trio-websocket~=0.9->selenium) (1.2.0)
Requirement already satisfied: PySocks!=1.5.7,<2.0,>=1.5.6 in ./opt/anaconda3/lib/python3.9/site-packages (from urllib3[socks]~=1.26->selenium) (1.7.1)
Requirement already satisfied: h11<1,>=0.9.0 in ./opt/anaconda3/lib/python3.9/site-packages (from wsproto>=0.14->trio-websocket~=0.9->selenium) (0.14.0)
Note: you may need to restart the kernel to use updated packages.

Web Driver

  • 웹 브라우저와 연동을 위해서는 WebDriver가 필요합니다.
  • WebDriver는 웹 브라우저를 제어할 수 있는 자동화 프레임워크입니다.

pip install을 통해 webdriver를 관리하는 라이브러리 webdriver-manager를 설치합니다.

# webdriver-manager를 설치해봅니다.
%pip install webdriver-manager

Requirement already satisfied: webdriver-manager in ./opt/anaconda3/lib/python3.9/site-packages (3.8.5)
Requirement already satisfied: tqdm in ./opt/anaconda3/lib/python3.9/site-packages (from webdriver-manager) (4.64.1)
Requirement already satisfied: packaging in ./opt/anaconda3/lib/python3.9/site-packages (from webdriver-manager) (21.3)
Requirement already satisfied: python-dotenv in ./opt/anaconda3/lib/python3.9/site-packages (from webdriver-manager) (1.0.0)
Requirement already satisfied: requests in ./opt/anaconda3/lib/python3.9/site-packages (from webdriver-manager) (2.28.1)
Requirement already satisfied: pyparsing!=3.0.5,>=2.0.2 in ./opt/anaconda3/lib/python3.9/site-packages (from packaging->webdriver-manager) (3.0.9)
Requirement already satisfied: urllib3<1.27,>=1.21.1 in ./opt/anaconda3/lib/python3.9/site-packages (from requests->webdriver-manager) (1.26.11)
Requirement already satisfied: charset-normalizer<3,>=2 in ./opt/anaconda3/lib/python3.9/site-packages (from requests->webdriver-manager) (2.0.4)
Requirement already satisfied: idna<4,>=2.5 in ./opt/anaconda3/lib/python3.9/site-packages (from requests->webdriver-manager) (3.3)
Requirement already satisfied: certifi>=2017.4.17 in ./opt/anaconda3/lib/python3.9/site-packages (from requests->webdriver-manager) (2022.9.24)
Note: you may need to restart the kernel to use updated packages.

Selenium 시작하기

Selenium을 사용하기 위해 우선 Selenium을 불러옵니다.

# selenium으로부터 webdriver 모듈을 불러옵니다.
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager

불러온 모듈 webdriver에서 Chrome() 객체를 생성합니다.

# http://www.example/com으로 요청을 보내봅니다.
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))
driver.get("http://www.example.com")

driver.get("http://www.example.com") 출력

응답을 받은 후, page_source 속성을 통해 Response의 HTML 문서를 확인할 수 있습니다.

# page_source 속성을 확인해봅니다.
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))
driver.get("http://www.example.com")
print(driver.page_source)

<html><head>
    <title>Example Domain</title>

    <meta charset="utf-8">
    <meta http-equiv="Content-type" content="text/html; charset=utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style type="text/css">
    body {
        background-color: #f0f0f2;
        margin: 0;
        padding: 0;
        font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
        
    }
    div {
        width: 600px;
        margin: 5em auto;
        padding: 2em;
        background-color: #fdfdff;
        border-radius: 0.5em;
        box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);
    }
    a:link, a:visited {
        color: #38488f;
        text-decoration: none;
    }
    @media (max-width: 700px) {
        div {
            margin: 0 auto;
            width: auto;
        }
    }
    </style>    
</head>

<body>
<div>
    <h1>Example Domain</h1>
    <p>This domain is for use in illustrative examples in documents. You may use this
    domain in literature without prior coordination or asking for permission.</p>
    <p><a href="https://www.iana.org/domains/example">More information...</a></p>
</div>


</body></html>
# with-as를 사용해서 위 코드를 다시 적어봅니다.
with webdriver.Chrome(service=Service(ChromeDriverManager().install())) as driver:
    driver.get("http://www.example.com")
    print(driver.page_source)
    
<html><head>
    <title>Example Domain</title>

    <meta charset="utf-8">
    <meta http-equiv="Content-type" content="text/html; charset=utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style type="text/css">
    body {
        background-color: #f0f0f2;
        margin: 0;
        padding: 0;
        font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
        
    }
    div {
        width: 600px;
        margin: 5em auto;
        padding: 2em;
        background-color: #fdfdff;
        border-radius: 0.5em;
        box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);
    }
    a:link, a:visited {
        color: #38488f;
        text-decoration: none;
    }
    @media (max-width: 700px) {
        div {
            margin: 0 auto;
            width: auto;
        }
    }
    </style>    
</head>

<body>
<div>
    <h1>Example Domain</h1>
    <p>This domain is for use in illustrative examples in documents. You may use this
    domain in literature without prior coordination or asking for permission.</p>
    <p><a href="https://www.iana.org/domains/example">More information...</a></p>
</div>


</body></html>
# By를 import해봅니다.
from selenium.webdriver.common.by import By

# p 태그를 해당하는 요소 하나를 찾아봅니다.
with webdriver.Chrome(service=Service(ChromeDriverManager().install())) as driver:
    driver.get("http://www.example.com")
    for element in driver.find_elements(By.TAG_NAME, "p"):
        print("Text:", element.text)
        
Text: This domain is for use in illustrative examples in documents. You may use this domain in literature without prior coordination or asking for permission.
Text: More information...

Wait and Call

  • Wait을 이용해서 동적 웹 사이트를 성공적으로 스크래핑합니다.

Implicit/Explicit Wait

  • Implicit Wait: 지정한 시간 동안 기다림
  • Explicit Wait: 특정 요소에 대한 제약을 통한 기다림

class를 지정하면 스크래핑하기 용이해지기에 이를 방지하고자 랜덤화된 grid를 지정하는 경우가 많아졌습니다,

 

Target: IndieStreet 이벤트 스크래핑

사이트에 있는 행사의 이름들을 스크래핑 해봅니다: https://indistreet.com/live?sortOption=startDate%3AASC

# 스크래핑에 필요한 라이브러리를 불러옵니다.

from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from webdriver_manager.chrome import ChromeDriverManager

XPath는 XML, HTML 문서 등의 요소와 위치를 경로로 표현하는 것을 의미합니다.

# 예시 사이트에 요청을 진행하고, 예시 사이트의 첫 번째 이벤트의 제목을 가져옵니다.
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))
driver.get("https://indistreet.com/live?sortOption=startDate%3AASC")
driver.find_element(By.XPATH, '//*[@id="__next"]/div/main/div[2]/div/div[4]/div[1]/div[2]/div/a/div[2]/p[1]').text

# 10초동안 Implicit Wait을 진행하도록 해서 스크래핑이 잘 이루어지도록 수정해봅니다.
from selenium.webdriver.support.ui import WebDriverWait

with webdriver.Chrome(service=Service(ChromeDriverManager().install())) as driver:
    driver.get("https://indistreet.com/live?sortOption=startDate%3AASC")
    driver.implicitly_wait(10)
    print(driver.find_element(By.XPATH, '//*[@id="__next"]/div/main/div[2]/div/div[4]/div[1]/div[2]/div/a/div[2]/p[1]').text)
Sign up

Explicit Wait

until(): 인자의 조건이 만족될 때까지 until_not() 인자의 조건이 만족되지 않을 때까지

element = WebDriverWait(driver, 10). until(EC.presence_of_element_locater(By.ID, "target")))

from selenium.webdriver.support import expected_conditions as EC
with webdriver.Chrome(service=Service(ChromeDriverManager().install())) as driver:
    driver.get("https://indistreet.com/live?sortOption=startDate%3AASC")
    # explicit wait으로 변경
    element = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.XPATH, '//*[@id="__next"]/div/main/div[2]/div/div[4]/div[1]/div[2]/div/a/div[2]/p[1]')))
    print(element.text)
Sign up
# 10개의 이름을 스크래핑하는 코드 작성
with webdriver.Chrome(service=Service(ChromeDriverManager().install())) as driver:
    driver.get("https://indistreet.com/live?sortOption=startDate%3AASC")
    driver.implicitly_wait(10)
    
    for i in range(1, 11): # 1~10
        element =driver.find_element(By.XPATH, '//*[@id="__next"]/div/main/div[2]/div/div[4]/div[1]/div[2]/div/a/div[2]/p[1]'.format(i))
        print(element)
<selenium.webdriver.remote.webelement.WebElement (session="796171469807ecd178b693fdae38d5a0", element="b530c5c7-fcdd-49e6-a414-545b0cd12eb6")>
<selenium.webdriver.remote.webelement.WebElement (session="796171469807ecd178b693fdae38d5a0", element="b530c5c7-fcdd-49e6-a414-545b0cd12eb6")>
<selenium.webdriver.remote.webelement.WebElement (session="796171469807ecd178b693fdae38d5a0", element="b530c5c7-fcdd-49e6-a414-545b0cd12eb6")>
<selenium.webdriver.remote.webelement.WebElement (session="796171469807ecd178b693fdae38d5a0", element="b530c5c7-fcdd-49e6-a414-545b0cd12eb6")>
<selenium.webdriver.remote.webelement.WebElement (session="796171469807ecd178b693fdae38d5a0", element="b530c5c7-fcdd-49e6-a414-545b0cd12eb6")>
<selenium.webdriver.remote.webelement.WebElement (session="796171469807ecd178b693fdae38d5a0", element="b530c5c7-fcdd-49e6-a414-545b0cd12eb6")>
<selenium.webdriver.remote.webelement.WebElement (session="796171469807ecd178b693fdae38d5a0", element="b530c5c7-fcdd-49e6-a414-545b0cd12eb6")>
<selenium.webdriver.remote.webelement.WebElement (session="796171469807ecd178b693fdae38d5a0", element="b530c5c7-fcdd-49e6-a414-545b0cd12eb6")>
<selenium.webdriver.remote.webelement.WebElement (session="796171469807ecd178b693fdae38d5a0", element="b530c5c7-fcdd-49e6-a414-545b0cd12eb6")>
<selenium.webdriver.remote.webelement.WebElement (session="796171469807ecd178b693fdae38d5a0", element="b530c5c7-fcdd-49e6-a414-545b0cd12eb6")>