'크롤링'에 해당되는 글 1건

  1. 2021.01.12 Selenium을 이용한 웹사이트 크롤링
반응형

Selenium을 이용한 웹사이트 크롤링

 

참고

 

설치

 

pip install virtualenv

 

  • 이유를 모르겠지만 Python 3 윈도우 설치 버전에서는 virtualenv.exe가 만들어지지 않아

  • 다음과 같이 스크립트 생성

  • cmd.exe를 관리자 권한으로 실행 (또는 PATH에 걸려 있는 아무 디렉토리에서)

 

cd C:\Program Files\Python37\Scripts
copy con virtualenv.cmd
@echo off
python -m venv %*
^Z     (입력이 아니라 Ctrl+Z를 누르라는 의미)

 

  • virtualenv로 환경을 생성

 

cd selenium
virtualenv .
Scripts\activate.bat

(selenium) C:\Users\javalove93\selenium>



  • Selenium 설치

 

pip install selenium

 

 

테스트

  • 다운 받은 크롬 브라우저 Web Driver를 Python의 Scripts(예: C:\Program Files\Python37\Scripts)로 복사

  • 다음과 같은 테스트 프로그램 실행

 

import selenium
from selenium import webdriver
from selenium.webdriver import ActionChains

from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By

from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import Select
from selenium.webdriver.support.ui import WebDriverWait

URL = 'https://www.naver.com'

driver = webdriver.Chrome(executable_path='chromedriver')
driver.get(url=URL)

 

python selenium_test.py

 

  • 다음과 같이 브라우저가 기동 된다.

 

  • 네이버 로그인이 필요하면 다음과 같이 실행한다.

 

from selenium import webdriver
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys
import pyperclip
import time


#클립보드에 input을 복사한 뒤
#해당 내용을 actionChain을 이용해 로그인 폼에 붙여넣기
def copy_input(xpath, input):
    pyperclip.copy(input)
    driver.find_element_by_xpath(xpath).click()
    ActionChains(driver).key_down(Keys.CONTROL).send_keys('v').key_up(Keys.CONTROL).perform()
    time.sleep(1)


id = '<ID>'
pw = '<PWD>'

driver = webdriver.Chrome(executable_path='chromedriver')
driver.implicitly_wait(3)

driver.get('https://nid.naver.com/nidlogin.login?mode=form&url=https%3A%2F%2Fwww.naver.com')

copy_input('//*[@id="id"]', id)
time.sleep(1)
copy_input('//*[@id="pw"]', pw)
time.sleep(1)
driver.find_element_by_xpath('//*[@id="frmNIDLogin"]/fieldset/input').click()

 

브라우저를 백그라운드로 띄우려면

  • 다음과 같이 headless 옵션을 준다

 

# 옵션 생성
options = webdriver.ChromeOptions()
# 창 숨기는 옵션 추가
options.add_argument("headless")

driver = webdriver.Chrome(executable_path='chromedriver', options = options)



본격적으로 브라우저를 제어해 보자

  • 여기서 시나리오는 네이버 단어장의 모든 단어를 읽어 오는 것이다

  • 다음과 같이 네이버 사전으로 이동한다.

 

time.sleep(2)
driver.get('https://learn.dict.naver.com/wordbook/enkodict/#/my/main')

 

  • 다음과 같은 브라우저 화면이 있다고 가정할 때 ‘전체'에 해당하는 부분을 클릭하고자 한다.

 

  • 브라우저의 개발자 도구를 연다

  • Elements에서 태그들을 확장하다 보면 아래와 같이 해당 부분이 하이라이트 되는 부분을 찾을 수 있다.

  • 오른쪽 버튼을 누른 후 Copy ⇒ Copy Xpath 를 선택한다.

 

 

  • 그리고 해당 부분을 이용해서 다음과 같이 클릭 하도록 한다.

 

time.sleep(1)
driver.find_element_by_xpath('//*[@id="content"]/div[1]/ul/li[1]/span').click()

 

  • 만약 목록(여기서는 ‘단어장 목록'을 제어해야 한다면, 다음과 같이 배열과 for 문을 사용한다.

  • 다음 구문은 단어장에 해당하는 main_folder를 구한 후에 그 안에 있는 <ul>, <li> 태그를 구한다.

  • 실제 Python의 for 문은 0부터 시작하지만, xpath를 통한 태그 indexing은 1부터 시작하므로 i+1을 했다.

  • 여기서는 단어장 클릭을 통해 페이지를 변경했다가, 다시 이전 페이지로 돌아오는 작업을 하기 때문에 for 문 안에서 매번 다시 driver.find_xxx 로 태그를 구하고 있다.

  • find_element_xxx 는 하나의 태그를, find_elements_xxx 는 해당 조건에 해당하는 여러 개의 태그들을 배열로 가져오는 역할을 한다.

 

list = driver.find_element_by_xpath('//*[@id="main_folder"]/ul')
folders = list.find_elements_by_xpath('li')
num_of_folders = len(folders)
for i in range(num_of_folders):
list = driver.find_element_by_xpath('//*[@id="main_folder"]/ul')
folder = list.find_element_by_xpath('li[%d]/a' % (i+1))
title = folder.find_element_by_xpath('div/span[2]').text
print("=============================================================")
print("제목 %s" % (title))
folder.click()
time.sleep(1)

 

찾으려는 태그가 존재하지 않으면

  • 브라우저의 컨텐츠에 따라 원하는 태그가 존재하지 않을 수도 있다.

  • 그러면 에러가 발생하므로, 아래와 같이 존재하는지 체크해서 작업을 하게 할 수도 있다.

 

if len(driver.find_elements_by_xpath('//*[@id="btn_more_folder"]')) > 0:
driver.find_element_by_xpath('//*[@id="btn_more_folder"]').click()
time.sleep(1)

 

부모 엘리먼트 선택

  • 아래에서 span을 기준으로 엘리먼트를 선택한 후에 그것의 부모를 선택

 

# 단어장 이름
list = driver.find_element_by_xpath('//*[@id="main_folder"]/ul')
f = list.find_element_by_xpath('//span[contains(text(), "A01")]')
print(f.text)

# 단어장 클릭
f.find_element_by_xpath('../..').click()
time.sleep(2)




네이버 단어장의 모든 단어를 파일로 저장

  • 궁극적으로 셀레니움을 테스트했던 목적으로 완성된 코드이다.

 

from selenium import webdriver
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys
import pyperclip
import time


#클립보드에 input을 복사한 뒤
#해당 내용을 actionChain을 이용해 로그인 폼에 붙여넣기
def copy_input(xpath, input):
    pyperclip.copy(input)
    driver.find_element_by_xpath(xpath).click()
    ActionChains(driver).key_down(Keys.CONTROL).send_keys('v').key_up(Keys.CONTROL).perform()
    time.sleep(1)


id = '<ID>'
pw = '<PWD>'

driver = webdriver.Chrome(executable_path='chromedriver')
driver.implicitly_wait(3)

driver.get('https://nid.naver.com/nidlogin.login?mode=form&url=https%3A%2F%2Fwww.naver.com')

copy_input('//*[@id="id"]', id)
time.sleep(1)
copy_input('//*[@id="pw"]', pw)
time.sleep(1)
driver.find_element_by_xpath('//*[@id="frmNIDLogin"]/fieldset/input').click()
time.sleep(2)

# 사전으로 이동
driver.get('https://learn.dict.naver.com/wordbook/enkodict/#/my/main')
time.sleep(2)

# 전체 보기 버튼 클릭
if len(driver.find_elements_by_xpath('//*[@id="btn_more_folder"]')) > 0:
driver.find_element_by_xpath('//*[@id="btn_more_folder"]').click()
time.sleep(1)

f = open('naver_dict.txt', 'w', encoding='utf8')

# 단어장 이름 배열 작성
list = driver.find_element_by_xpath('//*[@id="main_folder"]/ul')
folders = list.find_elements_by_xpath('li/a/div/span[2]')
titles = []
for t in folders:
titles.append(t.text)

for i in range(0, len(folders)):
# 단어장 제목 출력
title = titles[i]
print("\n\n\n=============================================================")
f.write("\n\n\n=============================================================\n")
print('%d 번째 단어장' % (i))
f.write('%d 번째 단어장\n' % (i))
print("제목 %s\n\n" % (title))
f.write("제목 %s\n\n\n" % (title))

# 특정 단어장 클릭
list = driver.find_element_by_xpath('//*[@id="main_folder"]/ul')
folder = list.find_element_by_xpath('//span[contains(text(), "%s")]' % (title))
folder.find_element_by_xpath('../..').click()
time.sleep(1)

# 각 단어장에서
while True:
time.sleep(1)
if len(driver.find_elements_by_xpath('//*[@id="page_area"]/div/div/span[1]')) == 0:
break
current_page = driver.find_element_by_xpath('//*[@id="page_area"]/div/div/span[1]')
total_page = driver.find_element_by_xpath('//*[@id="page_area"]/div/div/span[3]')
print('\n%s 페이지' % (current_page.text))
f.write('\n%s 페이지\n' % (current_page.text))
print('**********\n')
f.write('**********\n\n')

words = driver.find_elements_by_class_name('card_word')
for w in words:
word = w.find_element_by_xpath('div[1]/div[1]/div/div/a').text
# word = word.replace('·', '').replace('|', '').replace('\u21c4', '').replace('\u30fb', '').replace('\xe1', 'a').replace('\xe0', 'a').replace('\xe9', 'e')
word = word.replace('·', '').replace('|', '').replace('\u21c4', '').replace('\u30fb', '')
print(word)
f.write(word + '\n')

mean = w.find_elements_by_class_name('list_mean')
for m in mean:
mtext = m.text
print(mtext)
f.write(mtext + '\n')
print('------------------------------------------')
f.write('------------------------------------------\n')

f.flush()

if int(current_page.text) >= int(total_page.text):
break
else:
driver.find_element_by_xpath('//*[@id="page_area"]/div/button[2]').click()

# 전체 단어장으로 돌아가기
driver.find_element_by_xpath('//*[@id="title"]').click()
time.sleep(1)

# 전체 보기 버튼 클릭
if len(driver.find_elements_by_xpath('//*[@id="btn_more_folder"]')) > 0:
driver.find_element_by_xpath('//*[@id="btn_more_folder"]').click()
time.sleep(1)

f.close()
driver.close()

 

결과 및 평가

  • headless는 완벽하게 돌지 않아, 백그라운드 작업이 얼마나 가능할지는 봐야 할 듯

  • 모든 작업이 실제 돌려봐야 알기 때문에, trial & error 디버깅이 쉽지 않아 프로그램 작성 시간이 오래 걸림

  • 그럼에도 웹브라우저 작업을 자동화 할 수 있다는 장점














Posted by Hey Jerry
,