Beautiful Soup은 HTML과 XML로부터 데이터를 추출하기 위한 파이썬 라이브러리이다. 쉽게 말해 웹페이지의 HTML (또는 XML) 수준에서 필요로 하는 데이터를 뽑아내는 도구이다. 개발자 모드(F12)를 열어 찾을 필요도 없고 무식하게 소스보기를 해서 찾을 필요도 없다. 물론, 페이지의 기본적은 구조는 알아야 하니 처음에는 F12의 도움을 받아야 한다.
이런 것을 사용할 때 먼저 확인해야 하는 것은 버전 정보다. 일단, "Beautiful Soup 3"은 더 이상 지원하지 않은 것 같으니 거르고 문서에 맞는 "4.9.3"버전을 사용한다. (또는 해당 시점은 마지막 버전...) Python 버전은 2.7과 3.8 둘 다 사용 가능하다고 하니 땡큐.
1. 설치하기
"python -V" 명령어를 사용 Python 버전부터 확인, 물론 2.7도 같이 깔려있을 수 있겠지만 편한 것으로 고르자. 참고로 Beautiful Soup은 Python 2 버전으로 만들어졌다고 한다. 이 말은 들으면 Python 2.7로 하는 게 좋을 듯한다. 하지만, Python 3로 설치해도 자등으로 변환되어 상관없다고 하니 그냥 Python 3으로 진행한다. (문제 있을 경우 참조)
나는 apt-get으로 설치하려고 하는데, easy_install, pip, setup.py 등을 지원하니 다른 설치 방식을 원한다면 설치 페이지를 확인하자.
# for Python 2
$ sudo apt-get install python-bs4
# for Python 3
$ sudo apt-get install python3-bs4
2. 파서 설치
HTML & XML parsing을 위해 라이브러리를 설치해야 하는데 각각 장단점이 있어 확인 후 설치하면 된다. 나는 python-lxml 만 설치할 계획이다.
$ sudo apt-get install python-lxml
3. soup 만들어 보기
from bs4 import BeautifulSoup
with open("index.html") as fp:
soup = BeautifulSoup(fp, 'html.parser')
soup = BeautifulSoup("<html>a web page</html>", 'html.parser')
위와 같이 html 파일을 읽어서 하거나 html string을 사용할 수 있다. web page를 읽어 임시파일로 저정했다가 사용하면 전자가 되고 바로 사용한다면 후자가 된다.
예제 1)
from bs4 import BeautifulSoup
soup = BeautifulSoup('<b class="boldest">Extremely bold</b>', 'html.parser')
tag = soup.b
type(tag)
print(f'tag : {tag}')
print(f'type(tag) : {type(tag)}')
print(f'tag.name : {tag.name}')
tag.name = "blockquote"
print(f'tag : {tag}')
간단한 HTML 문을 만들어 tag "b"에 대한 정보를 확인하는 소스코드다. tag "b"는 입력한 값과 동일한 정보일 것이고 tag의 type은 <class 'bs4.element.Tag'> 인 것을 확인할 수 있다.
$ python test1.py
tag : <b class="boldest">Extremely bold</b>
type(tag) : <class 'bs4.element.Tag'>
예제 2)
from bs4 import BeautifulSoup
soup = BeautifulSoup('<b class="boldest">Extremely bold</b>', 'html.parser')
tag = soup.b
type(tag)
print(f'tag : {tag}')
print(f'type(tag) : {type(tag)}')
print(f'tag.name : {tag.name}')
tag.name = "blockquote"
print(f'tag : {tag}')
예제 1에 몇 줄 추가해 보았다. 기존 tag name은 "b"였는데 name을 "blockquote"로 변경 후, 확인해 본 결과 변경된 것을 확인할 수 있다. 이렇게 쉽게 변경 가능하고 참 좋은 라이브러리다.
$ python test2.py
tag : <b class="boldest">Extremely bold</b>
type(tag) : <class 'bs4.element.Tag'>
tag.name : b
tag : <blockquote class="boldest">Extremely bold</blockquote>
예제 3)
from bs4 import BeautifulSoup
tag = BeautifulSoup('<b id="boldest">bold</b>', 'html.parser').b
print(f"tag['id'] : {tag['id']}")
print(f"tag.attrs : {tag.attrs}")
tag 내에는 수많은 속성이 있다. 속성 하나하나도 확인이 필요한데 아래와 같은 방식으로 확인할 수 있다.
$ python test3.py
tag['id'] : boldest
tag.attrs : {'id': 'boldest'}
예제 4)
from bs4 import BeautifulSoup
tag = BeautifulSoup('<b></b>', 'html.parser').b
tag['id'] = 'verybold'
tag['another-attribute'] = 1
print(f'tag : {tag}')
del tag['id']
del tag['another-attribute']
print(f'tag : {tag}')
print(f"tag.get('id') : {tag.get('id')}")
print(f"tag['id'] : {tag['id']}'")
속성은 위의 예제와 같이 변경할 수 있고 또한, 삭제할 수 있다. 실행 결과는 다음과 같다. 변경 및 삭제에 따라 HTML의 내용이 변하며 삭제된 속성을 읽을 경우, 예외가 발생한다. 이런 경우를 방지하기 위해 속성이 있는지 또는 예외 처리 코드를 넣어야 한다.
$ python test4.py
tag : <b another-attribute="1" id="verybold"></b>
tag : <b></b>
tag.get('id') : None
Traceback (most recent call last):
File "test4.py", line 14, in <module>
print(f"tag['id'] : {tag['id']}'")
File "/usr/lib/python3/dist-packages/bs4/element.py", line 1321, in __getitem__
return self.attrs[key]
KeyError: 'id'
예제 5)
# 사용할 html 문
html_doc = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title"><b>The Dormouse's story</b></p>
<p class="story">Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>
<p class="story">...</p>
</body>
"""
from bs4 import BeautifulSoup
# 그냥 사용하니 아래서 확인할 len(soup.contents) 값이 2가 된다.
# html_doc = """<html <-- 이렇게 붙이면 값이 1이 되는 것으로 보아 앞에 뭐가 있는 것으로
# 인식되는 듯 하다.
# 그래서 strip() 시켰다.
#soup = BeautifulSoup(html_doc, 'html.parser')
soup = BeautifulSoup("".join(html_doc.strip()), 'html.parser')
# title 출력
print(f'soup.title : {soup.title}')
# body 출력
print(f'soup.body : {soup.body}')
# body의 tag b 출력
# tag b가 2개 이상 일 수 있는데 가장 처음 tag가 출력된다.
print(f'soup.body.b : {soup.body.b}')
# body의 tag a 출력
# 가장 처음 tag가 출력된다.
print(f'soup.body.a : {soup.body.a}')
# find_all()을 사용하면 해당되는 모든 tag가 리스트 형태로 반환된다.
print(f"soup.body.find_all('a') : {soup.body.find_all('a')}")
head_tag = soup.head
print(f"head_tag : {head_tag}")
# head의 내용 출력
# 2개 이상일 경우, 리스트 형태로 모두 반환된다.
print(f"head_tag.contents : {head_tag.contents}")
title_tag = head_tag.contents[0]
print(f"title_tag : {title_tag}")
# title 내의 내용 출력
print(f"title_tag.contents : {title_tag.contents}")
# 전체(soup) contents 개수 출력
print(f"len(soup.contents) : {len(soup.contents)}")
# 첫 번째 contents 출력
print(f"soup.contents[0] : {soup.contents[0]}")
print(f"soup.contents[0].name : {soup.contents[0].name}")
# 2개 이상일 경우, 두 번째 contents 출력
if len(soup.contents) > 1:
print(f"soup.contents[1] : {soup.contents[1]}")
print(f"soup.contents[1].name : {soup.contents[1].name}")
# title_tag의 하위 내용의 개수 및 내용 출력
print(f"title_tag.children ==> {len(list(title_tag.children))}")
for child in title_tag.children:
print(child)
# children와 달리 children의 children까지 조회 (recursively)
print(f"head_tag.descendants ==> {len(list(head_tag.descendants))}")
for child in head_tag.descendants:
print(child)
# 전체 HTML 내용을 대상으로 개수 확인
print(f"soup.children : {len(list(soup.children))}")
print(f"soup.descendants : {len(list(soup.descendants))}")
이러저런 테스트하려고 코드를 막 집어넣었더니 지저분해졌다. 코드의 주석을 참조하고 실행 결과는 아래와 같다.
$ python test5.py
soup.title : <title>The Dormouse's story</title>
soup.body : <body>
<p class="title"><b>The Dormouse's story</b></p>
<p class="story">Once upon a time there were three little sisters; and their names were
<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> and
<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>
<p class="story">...</p>
</body>
soup.body.b : <b>The Dormouse's story</b>
soup.body.a : <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
soup.body.find_all('a') : [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>, <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
head_tag : <head><title>The Dormouse's story</title></head>
head_tag.contents : [<title>The Dormouse's story</title>]
title_tag : <title>The Dormouse's story</title>
title_tag.contents : ["The Dormouse's story"]
len(soup.contents) : 1
soup.contents[0] : <html><head><title>The Dormouse's story</title></head>
<body>
<p class="title"><b>The Dormouse's story</b></p>
<p class="story">Once upon a time there were three little sisters; and their names were
<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> and
<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>
<p class="story">...</p>
</body></html>
soup.contents[0].name : html
title_tag.children ==> 1
The Dormouse's story
head_tag.descendants ==> 2
<title>The Dormouse's story</title>
The Dormouse's story
soup.children : 1
soup.descendants : 26
모든 예제를 해볼 수 없으니 여기 까지...
촌스럽게 모든 것을 외우지 말자. 그런 시대는 끝났다.
'놀기 > Python' 카테고리의 다른 글
map function 안에서 문자열 strip 및 replace 하기 (0) | 2021.07.26 |
---|---|
Beautiful Soup으로 오늘의 판(네이트) 게시물 목록 긁어오기 (0) | 2021.07.19 |
댓글