[Python] BeautifulSoup 라이브러리
BeautifulSoup는 HTML과 XML file들의 data를 가져오는 python 라이브러리이다. 이는 parser를 이용해 html, xml file들의 정보를 쉽게 추출할 수 있도록 도와준다.
parsing은 구문분석을 하는 것을 의미한다. 예를 들어 html parse는 html 문법 규칙에 따른 문자열을 해당 문법규칙을 바탕으로 단어의 의미나 구조를 분석하는 것이다. html parse를 행하는 프로그램을 일컬어 html parser라고 한다.
1. making the 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')
document를 parse하기 위해서 BeautifulSoup 생성자에 인자를 넘겨줘야 한다. 첫번째 인자에는 분석할 document를 string이나 open filehandle 형식으로 넣고, 두번째 인자에는 html.parser, xml.parser같은 분석할 parser를 넣는다.
2. navigating the Tree
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>
"""
from bs4 import BeautifulSoup
soup = BeautifulSoup(html_doc, 'html.parser')
<going down>
tag는 많은 string들과 또 다른 tag들을 포함한다. 이러한 것들을 tag의 children이라고 하는데 BeautifulSoup는 이를 찾는 다양한 방법을 제공한다.
-using tag names
말그대로 tag 이름을 이용해 해당 태그의 내용을 가져오는 것이다.
soup.head
# <head><title>The Dormouse's story</title></head>
soup.title
# <title>The Dormouse's story</title>
soup.body.b
# <b>The Dormouse's story</b>
soup.a
# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
위에서부터 순서대로 html document의 head태그, title 태그, body태그 안의 b태그, a태그를 가져오는 것을 확인할 수 있다.
-.content and .children
.content를 사용해 태그의 children을 list형태로 가져올 수 있다.
head_tag = soup.head
head_tag
# <head><title>The Dormouse's story</title></head>
head_tag.contents
# [<title>The Dormouse's story</title>]
title_tag = head_tag.contents[0]
title_tag
# <title>The Dormouse's story</title>
title_tag.contents
# ['The Dormouse's story']
len(soup.contents)
# 1
head_tag에는 head태그의 내용이 저장되어 있다. head_tag.contents를 통해 head태그의 children을 list 형태로 가져온 것을 확인할 수 있다. list형태이기 때문에 각 element에 인덱스를 통해 접근할 수 있다. 또 len함수를 통해 해당 list의 길이도 알 수 있다.
-.string
태그가 오직 하나의 child를 가지고 그 child가 navigableString이라면 .string을 통해 string형태로 가져올 수 있다.
title_tag.string
# 'The Dormouse's story'
title태그 안의 내용을 string형태로 가져온 것을 확인할 수 있다.
head_tag.contents
# [<title>The Dormouse's story</title>]
head_tag.string
# 'The Dormouse's story'
만약 위의 상황처럼 tag(예:head)가 child로 오직 한 tag(title)만을 가지고 또 그 tag가 .string을 사용할 수 있다면 굳이 parent 태그(head)에서 .string을 동일하게 사용할 수 있다.
<going up>
going down에서는 children에 대해 접근하는 것이었다면 going up에서는 parent에 접근하는 것이다.
-.parent
.parent를 통해 요소의 parent에 접근할 수 있다.
title_tag = soup.title
title_tag
# <title>The Dormouse's story</title>
title_tag.parent
# <head><title>The Dormouse's story</title></head>
title_tag.parent를 통해서 title태그의 parent인 head태그의 내용을 가져오는 것을 확인할 수 있다.
3. searching the tree
BeautifulSoup에서는 parse tree를 searching하는 다양한 방법을 제공하고 그 중 find(), find_all() 방법이 가장 많이 사용된다.
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>
"""
from bs4 import BeautifulSoup
soup = BeautifulSoup(html_doc, 'html.parser')
<kinds of filters>
find()나 find_all()과 같은 method를 사용할 때 filter를 이용해 원하는 정보만을 추출할 수 있다. tag의 name, tag의 attributes, string의 text와 같은 것을 사용할 수 있다.
-a string
soup.find_all('b')
# [<b>The Dormouse's story</b>]
soup.find_all('b')를 통해 document에서 모든 <b>태그를 찾을 수 있다.
-a regular expression
정규식을 통해서도 찾을 수 있다.
import re
for tag in soup.find_all(re.compile("^b")):
print(tag.name)
# body
# b
위는 태그이름이 'b'로 시작하는 것을 모두 찾아주는 코드로 body와 b태그를 찾아준 것을 확인할 수 있다.
for tag in soup.find_all(re.compile("t")):
print(tag.name)
# html
# title
위는 태그이름에 't'가 포함된 것을 모두 찾아주는 코드로 html과 title태그를 찾아준 것을 확인할 수 있다.
-True
True는 가능한 모든 것을 매치해준다.
for tag in soup.find_all(True):
print(tag.name)
# html
# head
# title
# body
# p
# b
# p
# a
# a
# a
# p
<find_all()>
find_all()은 filter에 맞는 모든 descendants를 찾아서 리스트형태로 반환해준다.
find_all(name, attrs, recursive, string, limit, **kwargs) 이런식으로 사용된다.
-the name argument
soup.find_all("title")
# [<title>The Dormouse's story</title>]
title이라는 태그이름을 가진 것을 모두 찾아 리스트형태로 반환해주는 것을 확인할 수 있다.
-the keyword arguments
soup.find_all(id='link2')
# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
태그의 id가 'link2'인 태그를 모두 찾아 리스트형태로 반환해주는 것을 확인할 수 있다.
soup.find_all(href=re.compile("elsie"))
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
태그의 herf에 "elsie"가 포함된 태그를 모두 찾아 리스트형태로 반환해주는 것을 확인할 수 있다.
soup.find_all(id=True)
# [<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>]
id가 True, 즉 id가 있는 태그를 모두 찾아 리스트형태로 반환해주는 것을 확인할 수 있다.
soup.find_all(href=re.compile("elsie"), id='link1')
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
태그의 href에 "elsie"가 포함되면서 id가 'link1'인 태그를 모두 찾아 리스트형태로 반환해주는 것을 확인할 수 있다.
data_soup = BeautifulSoup('<div data-foo="value">foo!</div>', 'html.parser')
data_soup.find_all(data-foo="value")
# SyntaxError: keyword can't be an expression
data_soup.find_all(attrs={"data-foo": "value"})
# [<div data-foo="value">foo!</div>]
data-*와 같은 형식으로 쓰인 attributes는 dictionary 형태로 전달해줘야한다.
-CSS class
soup.find_all("a", class_="sister")
# [<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>]
태그이름이 'a'이고 class는 'sister'인 태그를 모두 찾아 리스트형태로 반환해주는 것을 확인할 수 있다.
-The limit argument
모든 결과가 필요없고, limit 만큼의 결과만 필요할때 limit을 넘겨주면 된다.
soup.find_all("a", limit=2)
# [<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'인 태그를 2개까지만 찾아 리스트형태로 반환해주는 것을 확인할 수 있다.
-The recursive argument
find_all()을 사용하면 tag의 모든 descendants까지 검사하게 된다. 만약 태그의 직접적인 children까지만 고려하고 싶다면 recursive=False를 넘겨주면 된다.
<html>
<head>
<title>
The Dormouse's story
</title>
</head>
...
soup.html.find_all("title")
# [<title>The Dormouse's story</title>]
soup.html.find_all("title", recursive=False)
# []
위의 예시에서 recursive가 없는 코드에서는 모든 descendants를 검사하기에 html의 children인 head와 head의 children인 title까지 검사하여 title태그를 찾아 반환하게 된다. 그러나 recursive=False를 넘겨준 코드에서는 html의 직접적인 chlidren인 head까지만 검사하기에 빈 리스트를 반환한다.
<find()>
find_all()은 전체 document의 모든 결과를 반환했다면 find()는 한개의 결과만을 반환한다.
soup.find_all('title', limit=1)
# [<title>The Dormouse's story</title>]
soup.find('title')
# <title>The Dormouse's story</title>
첫번째 코드는 한개의 결과가 담긴 리스트를 반환하지만 두번째 코드는 1개의 결과 그 자체를 반환한다.
soup.find("div",{"class":"pagination"}
soup.find("span", class_=False)
soup.find("a")["data-jk"]
이 코드는 위의 예시 document에 대한 내용이 아니라 그냥 공부하다가 찾게 된 것이므로 위 예시 document에서는 결과를 얻을 수 없다.
첫번째 코드는 태그이름이 "div"이고 class이름이 "pagination"인 태그를 반환한다.
두번째 코드는 태그이름이 "span"이고 class가 없는 태그를 반환한다.
밑의 사진과 같은 html 코드 중 <h3>태그에서 첫번째 span태그만을 반환하고 싶을 때 사용할 수 있다.
세번째 코드는 태그이름이 "a"인 태그에서 "data-jk"라는 attribute의 값을 반환한다.
밑의 사진과 같은 html 코드에서 중 <a>태그에서 "data-jk"라는 attribute의 값을 반환하고 싶을 때 사용할 수 있다.
<get_text()>
document나 tag 안의 human-readable text만 추출하길 원한다면 get_text()를 사용하면 된다. get_text()는 documnet나 tag 안의 모든 text를 single Unicode string으로 반환한다.
markup = '<a href="http://example.com/">\nI linked to <i>example.com</i>\n</a>'
soup = BeautifulSoup(markup, 'html.parser')
soup.get_text()
'\nI linked to example.com\n'
soup.i.get_text()
'example.com'
soup.get_text()는 태그 안의 모든 text를 추출하고, soup.i.get_text()는 i태그 안의 모든 text를 추출한다.
# soup.get_text("|", strip=True)
'I linked to|example.com'
또 strip=True를 통해 처음과 끝의 whitespace를 없앨 수도 있다.
<참고사이트>
https://www.crummy.com/software/BeautifulSoup/bs4/doc/
Beautiful Soup Documentation — Beautiful Soup 4.9.0 documentation
Non-pretty printing If you just want a string, with no fancy formatting, you can call str() on a BeautifulSoup object, or on a Tag within it: str(soup) # ' I linked to example.com ' str(soup.a) # ' I linked to example.com ' The str() function returns a str
www.crummy.com