JavaScript가 동적으로 추가한 컨텐츠를 스크랩하는 방법
python 프로그래밍 - 크롤링
KeywordKatch의 editor component를 만들던 도중 이 문제를 만났다.
문제
웹 브라우저가 보여주는 HTML에는 분명히 존재하는 태그를 가져올 수 없었다.
from urllib.request import urlopen
from bs4 import BeautifulSoup
html = urlopen('https://www.chosun.com/economy/global-stock/top-gainers-and-losers/2023/03/15/YR3G73BF3JACA3OSJRL7DUBEZU/')
bs = BeautifulSoup(html, 'html.parser')
body = bs.find('section', {'itemprop': 'articleBody'})
print(body)
원인
이 문제의 원인을 파악하기 위해서는 우선 웹 페이지가 사용자의 눈에 보여지게 되는 과정을 알아야 한다.
- 클라이언트가 서버에게 요청(URL)을 보낸다
- 서버는 요청에 대한 resource를 응답으로 전송한다. Resource에는 HTML, CSS, JavaScript, 그리고 이미지 등이 포함될 수 있다.
- 웹 브라우저(클라이언트)는 HTML 문서를 DOM 트리 형태로 변환하고 CSS 파일을 CSSOM으로 변환하고 JavaScript를 실행한다.
- DOM 트리와 CSSOM 트리를 합쳐 렌더 트리를 만든다(파싱). 그리고 레이아웃을 결정(렌더링)한다. 레이아웃은 트리의 각 노드가 브라우저의 어디에, 어떤 크기로 배치될 지를 정하는 것이다.
- 브라우저 화면에 렌더 트리의 각 노드를 그려준다.
위의 코드에서는 BeautifulSoup
객체를 사용해 서버에 요청을 보낸뒤 그 resource를 받아와서 JavaScript 코드를 실행하지는 않는다. 그래서 JavaScript에 의해 클라이언트에서 동적으로 추가된 태그에는 접근하지 못한 것이였다.
해결
Selenium과 WebDriver를 사용하면 된다.
WebDriver가 JavaScript를 실행한 뒤에 Selenium으로 HTML에 접근하면 브라우저에 보여지는 그대로의 컨텐츠에 접근할 수 있게 된다.
Selenium과 WebDriver 설치
$ pip install selenium
WebDriver는 파이썬 라이브러리가 아니기 때문에 pip
같은 패키지 매니저로는 설치할 수 없다.
Chrome 버전 확인 - 주소창에 chrome://version/
입력
ChromeDriver 설치
EC2에 ChromeDriver 설치
다운받은 ChromeDriver 바이너리 파일을 PATH
에 추가하면 파이썬으로 WebDriver를 사용할 때 경로를 명시해주지 않아도 된다.
그냥 바이너리 파일을 /usr/local/bin
에 넣었다.
$ xattr -d com.apple.quarantine chromedriver
코드
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
options = webdriver.ChromeOptions()
options.add_argument('--headless')
driver = webdriver.Chrome(options=options)
driver.get('https://www.chosun.com/sports/baseball/2023/03/15/LXBTLDAGIYK4L44C2ZVSQ57D6U/')
try:
element = WebDriverWait(driver, 5)\
.until(EC.presence_of_all_elements_located((By.CSS_SELECTOR, 'section.article-body')))
finally:
body = driver.find_element(By.CSS_SELECTOR, 'section.article-body').text
driver.close()
print(body)
WebDriverWait(driver, 5)
- driver가 웹 페이지를 만드는 과정을 최대 5초 기다린다.
Comments