Web Crawling

Ywoosang·2020년 12월 26일

Python

목록 보기
1/1
post-thumbnail

파이썬으로 웹 크롤링할때 쓸만한 테크닉을 정리했습니다.

1. Beautiful Soup

기본 구조는 아래와 같습니다.

requests 는 웹 페이지를 가져오기 위한 라이브러리
bs4(BeautifulSoup) 는 웹 페이지 파싱을 위한 라이브러리입니다.
soup 에 HTML을 파싱한 정보가 들어갑니다.

import requests
from bs4 import BeautifulSoup
res = requests.get('가져올 웹 페이지 주소')
soup = BeautifulSoup(res.content,'html.parser')

☑️ 테스트 코드 작성하는 방법

실제 웹페이지에서 가져오기 전 테스트 코드를 작성하고 싶을때 아래와 같이 사용 합니다.

from bs4 import BeautifulSoup
   
# html 구조 만들기
html="""
<p>요소1</p><p>요소2</p><p>요소3</p>
"""
# html 파싱
soup=BeautifulSoup(html,'html.parser')
    
# 메소드로 원하는 부분 추출
p = soup.find('p') 
print(p)
print(p.next_sibling)
print(p.next_sibling.next_sibling)
  
실행결과 
<p>요소1</p>
<p>요소2</p>
<p>요소3</p>

1.1 find

태그로 요소를 선택합니다.

<h1>제목입니다</h1> 

data = soup.find('h1') 

아이디로 요소를 선택합니다.

<div id="title">제목입니다</div> 

data = soup.find(id='title')

태그와 클래스 이름을 지정해 선택합니다.

<p class="title">제목입니다</p> 

data = soup.find('p', class_='title') 

태그 안 속성으로 선택합니다.

<p  

data = soup.find('p', attrs = {'align': 'center'})

📌 속성 추출

attr

태그 안에 있는 속성들을 전부 key : value 형태의 딕셔너리로 추출합니다.

p class="tag" href="http://example.com" style="text-align:center">요소1</p> 

data = soup.find('p').attrs  

실행결과
{'class': ['tag'], 'href': 'http://example.com', 'style': 'text-align:center'} 

href

태그에서 링크를 추출할때 사용합니다.
이미지에서 링크 추출할 때 많이 씁니다.

<p href="http://example.com">요소</p>

data = soup.find('p')['href']

실행결과
http://example.com 

속성 검증

try except 를 사용하는 대신 아래 속성 검증을 통해
오류를 방지할 수 있습니다.

if 'href' in a.attrs :
     ... 
     추출하는 코드
     ... 

1.2 css select

select_one 은 요소를 반환합니다.
select 는 요소의 리스트를 반환합니다.

soup.select_one('선택자')  
soup.select('선택자') 

선택자

태그명으로 가져옵니다.

'p'

아이디로 가져옵니다.

'#main'

자식들 중 해당 태그를 가져옵니다.

`ul.items li` 

바로 아래에 있는 자식 태그를 가져옵니다

`ul.items > li` 

href의 값이 특정 문자열로 시작하는 태그를 가져옵니다.

`a[href^='http://www.testsite.co.kr/']`

href의 값이 특정 문자열로 끝나는 태그를 가져옵니다. 입니다.

'[href$='contact/']'

href의 값이 특정 문자열을 포함하는 태그를 가져옵니다.

a[href*='news']

class의 값이 특정 문자(열)와 같지 않은 태그를 가져옵니다

`a[class!='문자열']`

📌 속성 추출

문서 전체에서 a 태그 중 속성 값 href를 갖은 태그를 찾습니다.

soup.select('a[href]')

id가 bodyContent인 태그의 자손 태그 중에서 a 태그 속성 값 href를 갖은 태그를 찾습니다.

soup.select('#bodyContent a[href]')

1.3 텍스트 추출

아래와 같이 태그 내부의 글자를 추출할 수 있습니다.

<div>글자</div>

data = soup.find('div')

# 글자 추출
data.get_text()
data.string 

2. Selenium

셀레니움을 이용하면 브라우저를 제어해서 크롤리을 할 수 있습니다.

스크롤, 클릭, 키보드입력 등이 가능하기 때문에 인스타그램, 페이스북 등 로그인이 필요한 사이트들의 소스를 가져올 수 있습니다.

pip install selenium

기본 코드 구조입니다.

from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import time
 
chromedriver = '설치한 chromedriver 경로' 
driver = webdriver.Chrome(chromedriver)
driver.get('가져올 웹 페이지 주소')
# 검증 코드입니다.
assert '웹 페이지 타이틀' in driver.title
...

driver.quit()

2.1 페이지 로드 대기

브라우저에서 해당 웹 페이지의 요소들을 로드하는 데 시간이 걸리기 때문에 대기가 필요합니다.

driver.implicitly_wait(time_to_wait=3) 

찾으려는 element가 로드될 때까지 지정한 시간만큼 대기할 수 있도록 설정합니다.
이는 한 webdriver에 영구적으로 작용합니다.

time.sleep(3)

time.sleep(secs) 함수를 사용하여 무조건 몇 초간 대기하는 방법도 있습니다.

from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

driver = webdriver.Chrome('chromedriver')
driver.get(url='https://www.google.com/')
try:
    element = WebDriverWait(driver, 5).until(
        EC.presence_of_element_located((By.CLASS_NAME , 'gLFyf'))
    )
finally:
    driver.quit()

위의 코드는 웹페이지에서 class가 gLFyf인 어떤 element를 찾을 수 있는지를 최대 5초 동안 매 0.5초마다 시도합니다.
expected_conditions(EC)는 element를 찾았다면 True를, 아니라면 False를 반환합니다.

2.2 키보드 입력

input 태그를 선택하고 키보드 입력을 명령으로 텍스트를 입력할 수 있습니다.

clear() : 기존에 입력값이 있었다면 지웁니다.
send_keys('입력할 값') : 키보드 입력값을 input 태그에 전달합니다.
Keys.RETURN : 엔터키를 누릅니다.
dir(Keys) 로 키에 대응되는 이름을 찾습니다.

elem = driver.find_element_by_name('username')
elem.clear()
elem.send_keys('Ywoosang')
elem.send_keys(Keys.RETURN)

2.3 클릭하기(click)

find_element 함수로 요소를 선택한 다음 click() 함수를 호출합니다.

tag = driver.find_element_by_tag_name('button')
tag.click()

2.4 스크롤

scrollTo 이용

driver.execute_script("window.scrollTo(0, Y)")  

페이지 끝까지 가려면 Y 값을 아래와 같이 설정합니다. (Yheight 을 의미합니다.)

document.body.scrollHeight  

동적 웹페이지에서 스크롤 다운하면서 데이터를 조회할 경우입니다.

SCROLL_PAUSE_SEC = 1

# 스크롤 높이 가져옴
last_height = driver.execute_script("return document.body.scrollHeight")

while True:
    # 끝까지 스크롤 다운
    driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")

	# 1초 대기
    time.sleep(SCROLL_PAUSE_SEC)

    # 스크롤 다운 후 스크롤 높이 다시 가져옴
    new_height = driver.execute_script("return document.body.scrollHeight")
    if new_height == last_height:
    	break 
        #time.sleep(1)
        #if new_height == last_height:
        #	break
    last_height = new_height

간혹 로딩이 걸려 중간에 코드가 멈춘다면 SCROLL_PAUSE_SEC 를 증가시켜봅니다.

ActionChains 의 move_to_element 이용

ActionChains 에 대해 아래에서 자세히 다룹니다.
특정한 element 를 알고 있을 때 그 위치까지 스크롤 할 수 있습니다.

# ActionChains 를 사용하기 위해서.
from selenium.webdriver import ActionChains

# id가 something 인 element 를 찾음
some_tag = driver.find_element_by_id('something')

# somthing element 까지 스크롤
action = ActionChains(driver)
action.move_to_element(some_tag).perform()

특정 시간동안 계속해서 scroll down 하기

아래와 같이 datetime 을 이용해서 정해진 초 동안 1초에 한번씩 스크롤 다운합니다.
동적 웹페이지의 데이터를 다 가져올 수 없을 때 시간을 정하고 그 시간동안 크롤링을 실행합니다.

 import datetime
    
def doScrollDown(whileSeconds):
    start = datetime.datetime.now()
    end = start + datetime.timedelta(seconds=whileSeconds)
    while True:
    	driver.execute_script('window.scrollTo(0, document.body.scrollHeight);')
        time.sleep(1)
        if datetime.datetime.now() > end:
        	break

2.5 JavaScript 코드 실행

driver.execute_script() 함수로 자바스크립트 코드를 실행할 수 있습니다.

아래는 Name이 search_box인 요소의 값을 query의 값으로 변경하는 코드입니다.

driver.execute_script("document.getElementsByName('id')[0].value=\'"+query+"\'")

2.6 태그 가져오기

element 는 최초로 발견한 태그만 가져옵니다.
elements 는 조건에 해당하는 모든 태그를 리스트로 가져옵니다.

아래와 같이 다양한 검색 조건이 존재합니다.
필요에 맞는 것을 가져다 쓰면 됩니다.

find_element_by_tag_name(),
find_element_by_id()
find_element_by_css_selector()
driver.find_element_by_xpath() 
ind_element_by_name() 

아래처럼 사용합니다.

diver.find_element_by_css_selector('div > a[href="http://example.com"]')
driver.find_element_by_css_selector('html head title')
driver.find_element_by_css_selector('h3.tit_view')
driver.find_element_by_css_selector("div#harmonyContainer")

2.7 ActionChains 사용방법

마우스 클릭, Drag & Drop, 키보드 입력 등을 연속적으로 수행할 수 있습니다.

메소드수행 동작
click(on_element=None)인자로 주어진 요소를 왼쪽 클릭한다.
double_click(on_element=None)인자로 주어진 요소를 왼쪽 더블클릭한다.
key_down(value, element=None)value 로 주어진 키를 누르고 떼지 않는다.
key_up(value, element=None)value로 주어진 키를 뗀다.
send_keys(*keys_to_send)키보드 입력을 현재 focused된 요소에 보낸다.
send_keys_to_element(element, *keys_to_send)키보드 입력을 주어진 요소에 보낸다.
perform()이미 쌓여 있는(stored) 모든 행동을 수행한다(chaining).

예를들어 Ctrl + C를 누르고 싶다면 아래와 같이 입력합니다.

ActionChains(driver).key_down(Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform()

참고
on_element인자를 받는 함수는, 해당 인자가 주어지지 않으면 현재 마우스 위치를 기준으로 합니다.
element 인자를 받는 함수는, 해당 인자가 주어지지 않으면 현재 선택이 되어 있는 요소를 기준으로 합니다.
key_down, key_up 함수는 Ctrl 등의 키를 누를 때 사용합니다.

링크를 참조합니다.

2.8 뒤로가기, 앞으로 가기

뒤로가기(back)와 앞으로 가기(forward) 입니다.

driver.forward()
driver.back()

2.9 텍스트 추출

태그에 .text 를 붙여 문자열로 가져옵니다.

data.text

2.10 브라우저 크기 설정

브라우저 크기를 원하는대로 조정합니다.

driver.set_window_size(1920, 1080) 

브라우저 크기를 최대, 최소로 조정할 수 있습니다.

driver.minimize_window()
driver.maximize_window() 

2.11 XPath

XPath 문법입니다.

nodename : nodename을 name으로 갖는 모든 요소 선택
/ : root 요소에서 선택
// :현재 요소의 자손 요소를 선택
. :현재 요소를 선택
.. :현재 요소의 부모 요소를 선택
@ :속성(attibutes)를 선택
* : 모든 요소에 매치됨
@* : 모든 속성 요소에 매치됨
node() : 모든 종류의 모든 요소에 매치됨
| : OR 조건의 기능

아래는 예시입니다.

/div/p[0] : root > div > p 요소 중 첫 번째 p 요소를 선택합니다.

/bookstore/book[price>35.00] : root > bookstore > book 요소 중 price 속성이 35.00 초과인 요소들을 선택합니다.

/div/p[position()<3] : root > div > p 요소 중 첫 두 p 요소를 선택합니다.

//*[@id="tsf"]/div[2]/ : id가 tsf인 모든 요소의 자식 div 요소 중 3번째 요소를 선택합니다.

2.12 Headless Chrome

셀레니움을 이용하면 크롬창이 열립니다.
이를 보고싶지 않다면 Headless Chrome 을 이용합니다.

from selenium import webdriver
options = webdriver.ChromeOptions()
options.add_argument('headless')
options.add_argument('window-size=1920x1080')
options.add_argument("disable-gpu")
agent="User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131"
options.add_argument(agent)
options.add_argument("lang=ko_KR")
chromedriver = '설치한 chromedriver 경로' 
driver = webdriver.Chrome(chromedriver, chrome_options=options)
driver.get('가져올 웹 페이지 주소')
...  
 
driver.quit()
profile
백엔드와 인프라에 관심이 많은 개발자 입니다

0개의 댓글