셀레니엄(selenium)
웹사이트를 크롤링하는데 이번엔 사정이 좀 복잡해졌다.
기존에는 BeautifulSoup을 이용하여 크롤링 할 수 있었으나, 이번에 작업할 사이트는 호출할 때마다 동적으로 만들어지는 페이지다.
게시판인데 하단에 페이지 네비게이션이 따로 없고 스크롤을 최하단으로 내리면 일부가 동적으로 생기는 구조다.
글의 작성시간이 00분전, 00시간 전, 어제 등으로 나타나는데 매일매일 당일치만 데이터를 크롤링하고 싶었다.
그렇게 하기 위해서는 게시판에서 스크롤을 내려 페이지를 계속 생성해 내야 한다. 작성시간이 '어제'가 나올 때까지.
그러면 그 이후에 다시 글내용을 다시 크롤링한다.
그리고 얻어진 데이터 중에서 다시 작성시간이 '어제'인 것들은 제외한다.
동적인 페이지를 크롤링하기 위해서 selenium 사용하기로 했다. 듣기만 했지 써 본 것은 처음이라 그 과정을 적어본다.
셀레니엄은 실제 브라우저를 실행한 것 처럼 보이게 한 후 크롤링하는 방법이다.
그렇게 하면 로그인이 필요하거나 ajax등 동적으로 생기는 페이지도 읽을 수 있다.
브라우저는 크롬을 활용할 예정이기 때문에 크롬브라우저의 버전을 확인 후 사이트에서 동일한 버전의 드라이버를 찾아 다운로드한다.
https://chromedriver.chromium.org/downloads
그리고 셀레니엄을 설치한다.
pip install selenium
다운받은 셀레니엄 드라이버를 압축 해제 후 소스코드와 동일한 위치에 이동 후 샘플 코드를 실행한다.
from selenium import webdriver
driver = webdriver.Chrome('chromedeiver.exe')
url = 'https://google.com'
driver.get(url)
실행을 하면 브라우저가 나타났다가 자동으로 닫힌다. 그리고 아래와 같은 메세지를 확인했다.
(자동으로 닫히게 하지 않기 위해서는 옵션을 추가하면 된다)
test.py:3: DeprecationWarning: executable_path has been deprecated, please pass in a Service object
driver = webdriver.Chrome('chromedeiver.exe')
DevTools listening on ws://127.0.0.1:60827/devtools/browser/563c0903-4a31-4fc4-8147-b3f160e98d4d
확인해보니 버전이 낮은 api를 사용하고 있으니 다시 잘 하라는 내용이었다.
버전을 확인해보니 4.8.3였다.
pip list
위 문법은 3버전의 문법이었고 4문법으로 다시 해야 한다.
버전이 4.8이면 4버전으로 변경되지 꽤 된 것 같은데 검색해 보면 아직도 3문법인 경우가 대부분이다.
아무튼 4문법으로 다시 코딩해 본다.
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from webdriver_manager.chrome import ChromeDriverManager
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))
url = 'https://google.com'
driver.get(url)
driver.implicitly_wait(3)
기존에는 브라우저의 버전을 확인하여 그에 맞는 셀레니움 드라이버를 설치했는데 이번에는 그것이 사라졌다.
실행 시 브라우저에 맞는 버전을 확인하여 자동으로 다운로드한다. 이제는 기존처럼 셀레니엄 사이트에서 드라이버를 다운로드 받을 필요가 없어졌다.
그리고 다시 패키지 설치가 필요하다.
pip install webdriver_manager
브라우저가 실행된 후 바로 크롤링 하지 않는다. 로딩시 지연이 있을 수 있기 때문이다.
강제로 지연할 수 있는 time.sleep() 함수가 있으나 셀레니엄은 driver.implicitly_wait()을 지원한다.
차이는 time.sleep()는 지정시간만큼 무조건 대기이지만, driver.implicitly_wait()은 지정시간만큼 대기하되 그 전에 로딩이 먼저 끝나면 대기를 멈추는 차이가 있다고 한다.
그런데 그 차이는 잘 못느끼겠다.
드라이버 호출시 몇가지 옵션을 줄 수 있다.
options = Options()
options.add_experimental_option('detach', True) # 브라우저가 바로 닫힘 방지
options.add_experimental_option('excludeSwitches', ['enable-logging']) # 불필요한 에러메세지 삭제
#options.add_argument('headless') # 백그라운드 작업
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options)
서버에서 동작할 경우 UI 없기 때문에 백그라운드에서 작업을 해야 하는데 위 'headless'을 주면 백그라운드로 작업이 가능한다. 백그라운드로 작업할 경우 꼭 하단에 driver.quit()를 넣어 주어야 한다.
그러지 않으면 작업이 종료해도 프로세스를 계속 잡아먹는다.
그리고 서버에서 백그라운드에서 작업시 크롤링하지 못했는데 아래 코드를 추가하니 정상적으로 크롤링이 되었다.
아마 user-agent를 넣어주어야 하는 듯 하다.
options.add_argument('user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0')
max_cnt = 50
cnt = 1
while cnt < max_cnt:
time_tags = [i_tag.text.split("\n")[1] for i_tag in driver.find_elements(by=By.CSS_SELECTOR, value='.past')]
print(time_tags)
if '어제' in time_tags:
break
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
time.sleep(2)
cnt += 1
write_time = [i_tag.text.split("\n")[1] for i_tag in driver.find_elements(by=By.CSS_SELECTOR, value='.past')]
text = [tag.text for tag in driver.find_elements(by=By.CSS_SELECTOR, value='.pre-txt')]
datas = []
for temp in zip(write_time, text):
if temp[0] != '어제':
datas.append(temp)
df = pd.DataFrame(datas, columns=['date', 'text'])
# 파일명 : 'blind_career_{날짜}_{최종페이지번호}.csv'
df.to_csv('blind_carrer_' + fdate + '_' + str(cnt) + '.csv', encoding ="utf-8", index=False)
위 코드는 게시물의 작성일시가 '어제'가 나올 때까지 스크롤을 최하단까지 계속 내린다. 최대 50회까지 실행한다.
그리고 '어제'가 나오면 전체적으로 글을 크롤링한다.
크롤링 후에 다시 '어제'가 포함된 것들은 제거한다.
그리고 pandas를 이용하여 파일로 저장한다.
간단한 문법은 공식사이트에서 확인할 수 있다.
https://chromedriver.chromium.org/getting-started
3과 4버전 모두 제공한다.
셀레니움4 관련된 자료다. 문법도 나와 있다. 여기가 제일 나은 것 같다.
https://wikidocs.net/177133