본문 바로가기
IT & 개발공부/파이썬(Python)

셀레니움(selenium)을 이용한 웹 크롤링 실습

by 규딩코딩 2023. 8. 8.

이번 글에서는 Jupyter lab을 이용하여 selenium 크롤링 실습을 해보고자 한다.

 

0. 준비하기

- jupyter alb, selenium, webdriver-manager, pandas, requests를 pip install 해주었다.

- 또한, 다음과 같이 ipnyb 파일, driver 폴더를 생성하여 기본 세팅을 해주었다.

web driver 설치는 추가로 구글링 . . 하기 . .

1. 셀레니움 작동 확인하기(url 열기)

- chrome driver path = 바로 위에 이미지에서 보이는 경로(크롬드라이버 실행파일이 있는 곳)

- 이하는 드라이버 설정인데, 그냥 기본 설정이라고 생각하자.

- driver.get 메서드로 드라이버를 통해 원하는 url로 이동

★ 기타 오류를 방지하기 위해 드라이버 사용 후 꼭  " driver.quit()" 메서드를 통해 드라이버를 종료해주자 ★

 

2. 이미지 검색 부터 다운로드까지

2-1 이미지 검색 검색창에 원하는 검색어 입력하기 (사람의 행동을 COPY)

- 먼저 검색어를 입력하는 곳의 태그는 다음과 같다.

 

- 이 경로를 참고해서 다음과 같이 코드를 작성한다. * keys.RETURN이 Enter 의 기능이라고 보면 된다.

elem = driver.find_element(By.CSS_SELECTOR, "body > div.L3eUgb > div.o3j99.ikrT4e.om7nvf > 
form > div:nth-child(1) > div.A8SBwf > div.RNNXgb > div > div.a4bIc > textarea.gLFyf")
elem.send_keys("보라카이")
elem.send_keys(Keys.RETURN)

 

2-2 최대한 많은 이미지 로드를 위해 스크롤

- 보통 이미지를 검색하면 초기화면보다 스크롤을 더 내렸을 때, 이미지가 더 나오곤 한다. 그렇기 때문에 크롤링에서도 이미지 로드를 위한 스크롤 작업이 필요한 것이다. 다음과 같이 selenium에서 스크롤 기능을 구현할 수 있다.

 

elem = driver.find_element(By. TAG_NAME, "body")
for i in range(60):
    elem.send_keys(Keys.PAGE_DOWN)
    time.sleep(0.1)

elem = driver.find_element(By. TAG_NAME, "body") 은 selenium 을 사용하여 웹페이지에서 <body> 태그를 찾아 해당 요소(element)를 가져오는 코드다. 말 그대로 by tag name으로 body라는 태그를 찾아 해당 요소를 elem 변수에 할당하는 코드이다. 이렇게 변수를 할당하는 이유는 이후 해당 변수를 통해 send_keys메서드와 PAGE_DOWN 메서드를 사용하기 위해서이다.

 

try:
    driver.find_element(By.CSS_SELECTOR, "#islmp > div > div > div > div > div.C5Hr4 > div.K414Oe > div.FAGjZe > input").click()
    for i in range(80):
        elem.send_keys(Keys.PAGE_DOWN)
        time.sleep(0.1)
except:
    pass

- 해당 구문은 스크롤을 내리다보면 마주치는 "결과 더보기" 탭을 클릭하기 위한 것이다. 그래서 .click() 메서드가 있는 것이고 이후로 또 80번의 스크롤을 반복하는 코드이다. 예외처리 = pass

 

links=[] 
images = driver.find_elements(By.CSS_SELECTOR, "#islrg > div.islrc > div > a.wXeWr.islib.nfEiy > div.bRMDJf.islir > img")

for image in images:
    if image.get_attribute('src') is not None:
        links.append(image.get_attribute('src'))

- 값을 반환해줄 빈 리스트를 만들고, css 선택자로 이미지의 경로를 복사해서 붙여넣어주고, img src값을 포함한 elements값을 가져와 images 변수에 할당한다.

- 이후 for 와 if 문을 이용해서 elements에서 src 값이  not None 인 값들만 다시 links 리스트에 추가해준다.

 

links=[] 
images = driver.find_elements(By.CSS_SELECTOR, "#islrg > div.islrc > div > a.wXeWr.islib.nfEiy > div.bRMDJf.islir > img")
images2 = driver.find_elements(By.CSS_SELECTOR, "#islrg > div.islrc > div > div > a.wXeWr.islib.nfEiy > div.bRMDJf.islir > img")

# 결과더보기 이후는 경로가 약간 다름 div와 a태그 사이에 또다른 div 존재

for image in images:
    if image.get_attribute('src') is not None:
        links.append(image.get_attribute('src'))

for image in images2:
    if image.get_attribute('src') is not None:
        links.append(image.get_attribute('src'))

print(' 찾은 이미지 개수:',len(links))

- 여기서 심화로 들어가보자면, 결과더보기 클릭 후 스크롤 이후에도 값은 계속 48개인 것을 확인할 수 있었을 것이다. 결과 더보기 이후로(혹은 특정 구간 이후) images와 images2 처럼 selector 경로가 달라졌었기 때문이다. 해당 문제를 위와 같이 두 가지 경우로 리스트를 추가해주면 되겠지만, 구글 이미지 검색 특성상 "관련컨텐츠"에 있는 이미지도 같이 크롤링 되므로 생각을 해볼 문제이다.

 

- 심화 내용을 제외하면 다음과 같이 전체 코드가 작성된다.

from selenium import webdriver
from selenium.webdriver.chrome.service import Service as ChromeService
from webdriver_manager.chrome import ChromeDriverManager
driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()))
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
import time

URL = 'https://www.google.co.kr/imghp'
driver.get(url=URL)

elem = driver.find_element(By.CSS_SELECTOR, "body > div.L3eUgb > div.o3j99.ikrT4e.om7nvf > form > div:nth-child(1) > div.A8SBwf > div.RNNXgb > div > div.a4bIc > textarea.gLFyf")
elem.send_keys("보라카이")
elem.send_keys(Keys.RETURN)

elem = driver.find_element(By. TAG_NAME, "body")
for i in range(70):
    elem.send_keys(Keys.PAGE_DOWN)
    time.sleep(0.1)

try:
    driver.find_element(By.CSS_SELECTOR, "#islmp > div > div > div > div > div.C5Hr4 > div.K414Oe > div.FAGjZe > input").click()
    for i in range(80):
        elem.send_keys(Keys.PAGE_DOWN)
        time.sleep(0.1)
except:
    pass

links=[] 
images = driver.find_elements(By.CSS_SELECTOR, "#islrg > div.islrc > div > a.wXeWr.islib.nfEiy > div.bRMDJf.islir > img")
# images2 = driver.find_elements(By.CSS_SELECTOR, "#islrg > div.islrc > div > div > a.wXeWr.islib.nfEiy > div.bRMDJf.islir > img")
# 결과더보기 이후는 경로가 약간 다름 div와 a태그 사이에 또다른 div 존재

for image in images:
    if image.get_attribute('src') is not None:
        links.append(image.get_attribute('src'))

# for image in images2:
#     if image.get_attribute('src') is not None:
#         links.append(image.get_attribute('src'))

print(' 찾은 이미지 개수:',len(links))

 

2-3 크롤링된 사진 다운로드하기

import urllib.request

for k, i in enumerate(links):
    url = i
    urllib.request.urlretrieve(url, ".\\사진다운로드\\"+str(k)+".jpg")

print('다운로드 완료하였습니다.')
driver.quit()

- urllib.request.urlretrieve() 는 웹에서 파일을 다운로드하는 데 사용되는 urllib.request 모듈의 함수이다. 이 함수를 사용하면 지정한 URL에서 파일을 다운로드하여 로컬 시스템에 저장할 수 있다. 어려운 문법이 필요한 내용은 아니다.

- links 리스트의 url을 반복하면서 만들어놓은 "사진다운로드(변경 가능)" 폴더에 저장한다.

 

* str(k) : 정수 k를 문자열로 변환하는 Python의 내장 함수.

 

사진은 다음과 같이 저장된다.

3. 실시간 검색어 가져오기

- 이제는 역사속으로 사라진 실시간 검색어를 시그널사이트에서 가져와 보도록 하겠다.

반응형

- 먼저 사이트를 상세 분석해보면 다음과 같다.

 

- 공통부분을 찾고 이외에는 합치는 느낌  

1 위 값 : #app > div > main > div > section > div > section > section:nth-child(2) > div:nth-child(2) > div > div:nth-child(1) > div:nth-child(1) > a > span.rank-text

10위값 : #app > div > main > div > section > div > section > section:nth-child(2) > div:nth-child(2) > div > div:nth-child(2) > div:nth-child(5) > a > span.rank-text

 

- 최종 요청할 selector

"#app > div > main > div > section > div > section > section:nth-child(2) > div:nth-child(2) > div > div > div > a > span.rank-text")

from selenium import webdriver
from selenium.webdriver.chrome.service import Service as ChromeService
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
import time

driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()))


URL = 'https://signal.bz/news'
driver.get(url=URL)
results = driver.find_elements(By.CSS_SELECTOR, "#app > div > main > div > section > div > section > section:nth-child(2) > div:nth-child(2) > div > div > div > a > span.rank-text")
results

keyword_list = []
for keyword in results:
    print(keyword.text)

 

- 코드 실행 결과

 

4. 실시간 검색어 가져오기 2 (새로고침되는 리스트 가져오기)

- 이번에는 nate의 실시간 이슈 키워드를 가져와보려고 하는데, 이전과 다르게 1-5위 6-10위가 N 초마다 번갈아가면서 나오는 상황이다.

 

- 한편, 실시간 이슈 키워드에서 불러와야할 값은 안타깝게도(?) 두 가지이다. 실시간 이슈 키워드 순위와 실시간 이슈 키워드를 말한다. 사이트 소스코드를 확인해보면 다음과 같이 구성되어있다.

 

- 이러한 부분들을 고려하여 다음과 같이 코드를 작성하였다.

from selenium import webdriver
from selenium.webdriver.chrome.service import Service as ChromeService
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
import time

driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()))
nate_list_1st = []
nate_list_2nd = []
for i in range(2):
    URL='https://www.nate.com'
    driver.get(url=URL) 
    rank_results = driver.find_elements(By.CSS_SELECTOR,'#olLiveIssueKeyword > li > span.num_rank')
    nate_results = driver.find_elements(By.CSS_SELECTOR,'#olLiveIssueKeyword > li > a > span.txt_rank')
    for rank, keyword in zip(rank_results, nate_results):
        if i == 0: # 첫번째 화면
            nate_list_1st.append(f'{rank.text}_{keyword.text}')
        elif i == 1: # 두번째 화면
            nate_list_2nd.append(f'{rank.text}_{keyword.text}')
    time.sleep(8)
    driver.refresh() # driver 재시동
result = nate_list_1st + nate_list_2nd

print(result)

driver.quit()

- 특별한 것 위주로 코드 해설

- for rank, keyword in zip(rank_results, nate_results) : 두 개의 리스트에서 동시에 요소를 하나씩 가져와서 반복 작업을 수행하는 구문. "병렬(iterable) 언패킹(parallel unpacking)"이라고도 한다. 여기서 rank_results와 nate_results는 두 리스트의 각 요소들이 각각 rank와 keyword 변수에 할당됨. 반복문을 통해 두 리스트의 각 요소를 순차적으로 가져와서 두 변수에 할당하고, 각 요소에 대한 작업을 수행할 수 있게 된다.

 

- 코드 실행 결과

*  enumerate() 와 zip()의 차이

따봉 Chat Gpt야 고마워 ..!

5. 쇼핑 리스트 가져오기

- 다음으로는 뽐뿌의 게시판 제목과 URL들을 가져오는 실습을 해보려고 한다.

 

- 첫번째 게시글의 title selector 값은

#revolution_main_table > tbody > tr:nth-child(9) > td:nth-child(3) > table > tbody > tr > td:nth-child(2) > div > a > font

두번째 게시글의 title selector 값은

#revolution_main_table > tbody > tr:nth-child(11) > td:nth-child(3) > table > tbody > tr > td:nth-child(2) > div > a > font

 

- 첫번째 게시글의 href selector 값은

#revolution_main_table > tbody > tr:nth-child(9) > td:nth-child(3) > table > tbody > tr > td:nth-child(2) > div > a

두번째 게시글의 href selector 값은

#revolution_main_table > tbody > tr:nth-child(13) > td:nth-child(3) > table > tbody > tr > td:nth-child(2) > div > a

 

- 위 내용을 조합하여 다음과 같이 코드를 작성해보자.

from selenium import webdriver
from selenium.webdriver.chrome.service import Service as ChromeService
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
import time

driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()))

URL= 'https://www.ppomppu.co.kr/zboard/zboard.php?id=ppomppu'
driver.get(url=URL)

titles = driver.find_elements(By.CSS_SELECTOR, '#revolution_main_table > tbody > tr > td:nth-child(3) > table > tbody > tr > td:nth-child(2) > div > a > font')
urls = driver.find_elements(By.CSS_SELECTOR, '#revolution_main_table > tbody > tr > td:nth-child(3) > table > tbody > tr > td:nth-child(2) > div > a')

title_lists = []
url_lists = []
for i in range(len(titles)):
    print(titles[i].text)     #0번째 게시글 타이틀 출력
    title_lists.append(titles[i].text)     #0번째 게시글 타이틀 리스트로 추가
    print(urls[i].get_attribute('href'))     #0번째 게시글 URL 출력
    url_lists.append(urls[i].get_attribute('href'))     #0번째 게시글 URL 리스트에 추가

 

- 아마 지금까지 글을 쭉 따라온 사람이라면, 이해가 안되는 내용을 없을 것이다. for 반복문도 기본적인 내용이다.

 

* 막간 상식(.text 하기 전의 모습)

print(titles[0])
<selenium.webdriver.remote.webelement.WebElement (session="b231329af1f08bd9b72a62554308b2c6", element="3C9CC48DF124368DD5237BB4B46F1EA5_element_27")>

print(type(titles[0]))
<class 'selenium.webdriver.remote.webelement.WebElement'>

 

 

 

반응형