BeautifulSoup

BeautifulSoup은 HTML 파싱 라이브러리로서, 몇 개의 메서드만으로 DOM 추출이 가능합니다. 

기본적으로 UTF-8 인코딩 방식이지만 CP949도 지원하며, HTML뿐만 아니라 XML 파싱도 가능합니다.


저는 IDE로 파이참을 사용하고 있는데, 파이참에서 BeautifulSoup을 사용하기 위해서는 install을 해야 합니다.




이제 BeautifulSoup4을 사용하는 기본적인 방법에 대해 알아보겠습니다.

from bs4 import BeautifulSoup

html = '<td id="td1" class="title">' \
' <div class="tit3">' \
' <a href="/movie/bi/mi/basic.nhn?code=161242" title="범죄도시">범죄도시</a>' \
' </div>' \
'</td>'

# 1. 조회
def ex1():
# 위의 html 문자열에 대해서, html 파싱하겠다.
bs = BeautifulSoup(html, 'html.parser')
print(bs, type(bs))
# <td id="td1" class="title"><div class="tit3"><a href="/movie/bi/mi/basic.nhn?code=161242" title="범죄도시">범죄도시</a></div></td> <class 'bs4.BeautifulSoup'>

# a 태그 출력
tag = bs.a
print(tag, type(tag))
# <a href="/movie/bi/mi/basic.nhn?code=161242" title="범죄도시">범죄도시</a> <class 'bs4.element.Tag'>


# 2. Attribute 값 받아오기
def ex2():
bs = BeautifulSoup(html, 'html.parser')

tag = bs.td
print(tag['class']) # ['title'] => 리스트
print(tag['id']) # td1
print(tag.attrs) # {'id': 'td1', 'class': ['title']} => 딕셔너리

tag = bs.div
print(tag['id']) # id가 없으므로 error


# 3. Attribute 검색
def ex3():
bs = BeautifulSoup(html, 'html.parser')

# div 태그 중, class tit3인 태그를 찾는다.
tag = bs.find('div', attrs={'class': 'tit3'})
print(tag) # <div class="tit3"> <a href="/movie/bi/mi/basic.nhn?code=161242" title="범죄도시">범죄도시</a> </div>

tag = bs.find('div')
print(tag) # <div class="tit3"> <a href="/movie/bi/mi/basic.nhn?code=161242" title="범죄도시">범죄도시</a> </div>

# 없는 태그를 조회할 경우
tag = bs.find('td', attrs={'class': 'not_exist'})
print(tag) # None

# 전체 태그에 대해 title이 범죄도시인 태그를 찾는다.
tag = bs.find(attrs={'title': '범죄도시'})
print(tag) # <a href="/movie/bi/mi/basic.nhn?code=161242" title="범죄도시">범죄도시</a>


# 4. select(), content() 메서드
def ex4():
bs = BeautifulSoup(html, 'html.parser')

# CSS 처럼 셀렉터를 지정할 수 있다.
tag = bs.select("td div a")[0]
print(tag) # <a href="/movie/bi/mi/basic.nhn?code=161242" title="범죄도시">범죄도시</a>

text = tag.contents[0]
print(text) # 범죄도시


# 5. extract() 메서드
def ex5():
bs = BeautifulSoup(html, 'html.parser')
tag = bs.select("td")[0]
print(tag) # <td class="title" id="td1"> <div class="tit3"> <a href="/movie/bi/mi/basic.nhn?code=161242" title="범죄도시">범죄도시</a> </div></td>

# div요소를 제거
div_elements = tag.find_all("div")
for div in div_elements:
div.extract()

print(tag) # <td class="title" id="td1"> </td>

조회하고자 하는 웹 페이지의 DOM이 html 변수의 값처럼 되어 있다고 가정하겠습니다.


BeautifulSoup을 사용하기 위해서는 bs4 모듈에서 BeautifulSoup() 함수를 호출하면 되고,

첫 번째 인자로는 html 문서인 문자열을,

두 번째 인자로는 parsing 방법을 작성합니다.

그러면 BeautifulSoup 객체가 생성되고, beautifulsoup의 메서드를 호출하여 얻고자 하는 정보를 얻을 수 있습니다.


DOM을 추출하는 방식은 BeautifulSoup이라는 좋은 라이브러리가 있기 때문에, 개발자가 해야할 것은 크롤링 하고자 하는 웹 페이지의 DOM을 분석하는 것입니다.





1. 네이버 영화 크롤링 - 기본

첫 번째 예제로 네이버 영화 사이트의 영화 제목을 크롤링 해볼 것입니다.


우선 규칙성을 찾기 위해, 네이버 영화( 링크 )에서 소스보기를 통해 DOM을 살펴보겠습니다.

보시는 바와 같이 영화 제목에는 <div class="tit3"> 태그가 감싸져 있습니다.

따라서 BeautifulSoup으로 class 값이 tit3인 요소를 검색하면 될 것 같습니다.


주의 할 것은 class=tit3가 다른 요소에서도 사용되는지 꼭 확인해봐야 합니다.

안그러면 영화제목 뿐만 아니라 원하지 않는 데이터도 크롤링 될 것이니까요.



이제 코드를 BeautifulSoup을 활용하여 크롤링을 해보도록 하겠습니다.

from urllib.request import Request, urlopen
from bs4 import BeautifulSoup

req = Request('http://movie.naver.com/movie/sdb/rank/rmovie.nhn')
res = urlopen(req)
html = res.read().decode('cp949')

bs = BeautifulSoup(html, 'html.parser')
tags = bs.findAll('div', attrs={'class': 'tit3'})

for tag in tags :
# 검색된 태그에서 a 태그에서 텍스트를 가져옴
print(tag.a.text)

# 인덱스를 주고 싶다면 enumerate를 사용한다.
# for index, tag in enumerate(tags):
# print(str(index) + " : " + tag.a.text)

BeautifulSoup객체에서 findAll() 메서드를 호출하니 끝났습니다.


cp949로 decode한 이유는 네이버 영화의 인코딩 타입이 euc-kr 이기 때문입니다.






2. 굽네치킨 매장 정보 - page 번호가 URL에 표시되지 않음 

이번에는 굽네치킨 매장 정보를 크롤링 해보도록 하겠습니다.


[ 일반적인 paging 처리 방법 ]


굽네치킨 사이트( 링크 )의 경우 매장 리스트가 페이징으로 되어 있는데, 페이지를 클릭하면 위의 URL처럼 query string으로 page 번호가 표시되는 것이 아니라 JS 코드로 실행됩니다.


JS로 실행되는 페이징을 크롤링 하기 위해서는 selenium 라이브러리를 사용하여 파이썬 코드에서 직접 화면의 JS를 실행시켜야 합니다.

selenium은 BeautifulSoup을 설치했을 때와 마찬가지로 다운받으면 됩니다.

$ pip install selenium


selenium은 webdriver 인터페이스와 함께 구동되기 때문에 webdriver를 다운 받아야 합니다. ( 링크 )

webdirver를 다운받은 후, 실행파일을 원하는 경로에 위치시킵니다. ( 예를들면 C:\Users\samsung\python\webdriver )


또한 데이터 분석 라이브러리인 pandas가 있는데, 여기서는 크롤링 결과를 csv 파일로 저장하기 위한 용도로 사용할 것입니다.

pandas 역시 selenium을 다운로드한 것처럼 다운 받으셔야 합니다.

$ pip install pandas



크롤링 코드를 작성하기 전에, DOM이 어떻게 이루어졌는지 확인해보겠습니다.



매장의 이름은 <tbody> 아래에 <tr class="lows">태그 아래에 있으므로, 이를 참고하여 beautifulSoup을 사용하면 될 것 같습니다.



이제 webdriver와 beautifulSoup을 사용하여 JS 코드로 실행되는 페이징 사이트를 크롤링 해보도록 하겠습니다.

import time
from selenium import webdriver
from itertools import count
from bs4 import BeautifulSoup
import pandas as pd # pandas 라이브러리


def crawling_goobne():
url = 'http://www.goobne.co.kr/store/search_store.jsp'

# \U는 유니코드로 인식되기 때문에 \\U와 같이 escape 처리했다.
wd = webdriver.Chrome('C:\\Users\samsung\python\webdriver\chromedriver.exe')
wd.get(url)

RESULT_DIRECTORY = '__results__/crawling'
results = []
for page in count(1):
script = 'store.getList(%d)' % page # 굽네치킨에서 사용하는 페이지를 이동시키는 js 코드
wd.execute_script(script) # js 실행
time.sleep(5)             # 크롤링 로직을 수행하기 위해 5초정도 쉬어준다.

html = wd.page_source
bs = BeautifulSoup(html, 'html.parser')
tag_body = bs.find('tbody', attrs={'id': 'store_list'})
tags_tr = tag_body.findAll('tr')
# print(tags_tr)

if tags_tr[0].get('class') is None: # 맨 마지막 페이지인 102에서는 class='on lows'가 없다. => 종료 조건
break

for tag_tr in tags_tr:
strings = list(tag_tr.strings)
name = strings[1]
address = strings[6]
results.append((name, address))

table = pd.DataFrame(results, columns=['name', 'address'])
table.to_csv('{0}/table_goobne.csv'.format(RESULT_DIRECTORY), encoding='utf-8', mode='w')

1) 반복문 내부에 있는 sleep에 대하여

selenuim은 webdriver를 수행하면서 JS 코드를 실행시킵니다.

그런데 지금 상황과 같이 페이지 이동을 하는 JS 코드가 반복문을 돌 때 마다 실행된다면, 크롤링을 수행하는 로직은 실행할 틈이 없게 됩니다.

따라서 크롤링 로직이 수행될 수 있도록 sleep을 걸어서 JS 코드가 실행되는 것을 멈춥니다.



2) pandas

for문이 끝나면, 매장 정보 데이터가 results 리스트에 담겨있고, 그 결과를 csv 파일로 저장하기 위해 pandas를 사용합니다.

DataFrame() 메서드는 데이터를 테이블 형식으로 포맷해줍니다.

즉, 위 예제에서는 name과 address라는 열이 있고, 매장 정보 리스트인 results 정보가 각 열에 대응되어 table 형태로 저장이 됩니다.


DataFrame()으로 반환된 객체에서 to_csv() 메서드를 호출하면, csv 파일로 저장할 수 있습니다.





참고

1)

만약 page가 URL에 표시되어 있다면 코드를 어떻게 작성할까요?

반복문을 돌면서 URL query string의 page 파라미터를 수정해주면 될 것입니다.

JS코드를 실행할 필요가 없으니 webdriver도 필요 없습니다.

네이버 영화 크롤링하는 것과 유사한데, 단지 페이징 처리 되어 있기 때문에 반복문을 돌면서 url을 수정하기만 하면 됩니다.



2)

HTML이 아닌, XML을 크롤링 하고 싶다면 xml.etree.ElementTree 모듈을 사용하면 됩니다.

import xml.etree.ElementTree as et

root = et.fromstring(xml)


root는 XML 트리에서 최상위를 의미합니다.

xml.etree.ElementTree에 대한 보다 자세한 설명은 공식 문서( 링크 )를 참고해주세요.





이상으로 BeautifulSoup을 이용하여 HTML을 파싱하고, 크롤링 해보았습니다.

BeautifulSoup은 매우 유용하고 유명한 라이브러리이며, 그 만큼 기능도 정말 많습니다.

DOM을 파싱하는 다양한 방법은 공식문서( 링크 )를 참고해주세요 !