본문 바로가기

Python/Crawler

Beautiful Soup Documentation

이 문서는 Beautiful Soup 4.4.0 documentation를 참고하였습니다.

두고두고 참고하려고 작성한 용도이며 더 자세한 정보는 원본 문서를 참고해주세요.


...더보기

Table Of Contents


Intro

 Beautiful Soup는 HTML, XML 파일에서 데이터를 뽑아내는 Python library이다. parse tree를 탐색, 검색, 수정하는 방법을 제공하기 위해 parser와 함께 동작한다. 프로그래머들의 시간을 절약해주는 역할을 해준다. documents는 Beautiful Soup4의 모든 기능과 예제를 보여주며 라이브러리의 작동 방법, 사용 방법에 대해 알려준다. Python 3.2와 2.7에서 동일한 방식으로 작동된다. Beautiful Soup3는 더이상 개발되고 있지 않으므로 4를 사용하는 것을 추천한다. 

Quick Start

 아래와 같은 HTML document를 예제로 사용할 것이다. 예제는 Alice in Wonderland의 줄거리 중 한 부분이다.

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>

Beautiful Soup를 통해 "three sisters" 문서를 실행하면 중첩된 데이터 구조로 문서를 나타내는 BeautifulSoup 객체가 생성된다.

from bs4 import BeautifulSoup
soup = BeautifulSoup(html_doc, 'html.parser')

print(soup.prettify())
# <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="link2">
#     Tillie
#    </a>
#    ; and they lived at the bottom of a well.
#   </p>
#   <p class="story">
#    ...
#   </p>
#  </body>
# </html>

데이터 구조를 찾아가는 방법은 아래와 같다.

soup.title
# <title>The Dormouse's story</title>

soup.title.name
# u'title'

soup.title.string
# u'The Dormouse's story'

soup.title.parent.name
# u'head'

soup.p
# <p class="title"><b>The Dormouse's story</b></p>

soup.p['class']
# u'title'

soup.a
# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>

soup.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>]

soup.find(id="link3")
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>

간편한 방법으로 <a> 태그를 가져올 수도 있다.

for link in soup.find_all('a'):
    print(link.get('href'))
# http://example.com/elsie
# http://example.com/lacie
# http://example.com/tillie

페이지 안의 텍스트만을 추출해낼 수도 있다.

print(soup.get_text())
# The Dormouse's story
#
# The Dormouse's story
#
# Once upon a time there were three little sisters; and their names were
# Elsie,
# Lacie and
# Tillie;
# and they lived at the bottom of a well.
#
# ...

Installing Beautiful Soup

 Debian, Ubuntu Linux를 사용하고 있다면 아래의 명령어로 설치할 수 있다.

$apt-get install python-bs4
-- for Python2
$apt-get install python3-bs4
-- for Python3

Beautiful Soup4는 PyPi로 배포되고 있으며 패키지를 설치할 수 없을 경우 easy_install 혹은 pip를 이용하여 설치해야 한다. 패키지의 이름은 beautifulsoup4이며 python2와 3에서 동일하다. 

$ easy_install beautifulsoup4
$ pip install beautifulsoup4

만일 easy_install이나 pip가 설치되어 있지 않다면 이 곳에서 받은 후 setup.py로 설치한다.

$ python setup.py install

모든 시도가 실패했을 경우에는 전체 라이브러리를 응용 프로그램과 함께 설치할 수 있다. tarball을 다운로드 한 후 bs4 디렉토리를 사용하고자 하는 응용프로그램의 코드베이스에 복사하면 된다.

Problems after installation

 Beautiful Soup는 Python 2 code로 패키지되어 있다. python3을 사용해 설치하면 자동으로 python 3 코드로 변환된다. 패키지를 설치하지 않으면 코드가 변환되지 않으며 Windows에서는 설치의 문제가 발생할 수도 있다. 

 ImportError : "No module named HTMLParser" 에러의 경우 Python 2 버전을 Python 3에서 실행하고 있는 것이다.

 ImportError : "No module named html.parser" 에러의 경우 Python 3버전을 Python 2에서 실행하고 있는 것이다.

두 케이스 모두 최고의 방법은 Beautiful Soup를 삭제한 이후 재설치하는 것이다.

 만약 SyntaxError "Invalid syntax" 에러가 ROOT_TAG_NAME = u'[document]' 에서 났을 경우 Python 2코드를 Python 3으로 변환해야 한다. 다른 패키지를 설치하여 해결할 수 있다.

$ python3 setup.py install

또는 bs4 디렉토리에서 Python의 2to3 스크립트를 수동으로 실행한다.

$ 2to3-3.2 -w bs4

Installing a parser

 Beautiful Soup는 HTML 파서를 지원하지만 다른 파서도 지원하고 있다. 그 중 하나가 lxml 파서이다. 다음 명령어에 따라 lxml을 설치할 수 있다. 

$ apt-get install python-lxml
$ easy_install lxml
$ pip install lxml

 다른 방법은 pure-Python html5lib 파서를 사용하는 것이다. 다음 명령어에 따라 html5lib를 설치할 수 있다.

$ apt-get install python-html5lib
$ easy_install html5lib
$ pip install html5lib

아래 테이블은 각 라이브러리의 장점과 단점이 요약되어 있다.

Parser Typical useage Advantages Disadvantages
Python's html.parser BeautifulSoup(markup,"html.parser")
  • Batteries included
  • Decent speed
  • Lenient (as of Python 2.7.3 and 3.2.)
  • Not very lenient (before Python 2.7.3 or 3.2.2)
lxml's HTML parser BeautifulSoup(markup, "lxml")
  • Very fast
  • Lenient
  • External C dependency
lxml's XML parser

BeautifulSoup(markup, "lxml- xml")

BeautifulSoup(markup, "xml")

  • Very fast
  • The only currently supported XML parser
  • External C dependency
html5lib BeautifulSoup(markup, "html5lib")
  • Extremely lenient
  • Parses pages the same way a web browser does
  • Creates valid HTML5
  • Very slow
  • External Python dependency

Python 3.2.2 이전이나 Python 2.7.3 이전의 버전을 사용하고 있다면 lxml을 사용하는 것이 좋다. Python에 빌트인 되어 있는 HTML 파서는 이전 버전에서 성능이 좋지 않기 때문이다.

Making the soup

 document를 파싱하려면 BeautifulSoup constructor에 전달해주어야 한다. string이나 열려있는 파일을 전달할 수 있다.

from bs4 import BeautifulSoup

with open("index.html") as fp:
    soup = BeautifulSoup(fp)

soup = BeautifulSoup("<html>data</html>")

 document가 유니코드로 변환된 이후 HTML entities가 유니코드 문자로 변환된다.

BeautifulSoup("Sacr&eacute; bleu!")
<html><head></head><body>Sacré bleu!</body></html>

 Beautiful Soup는 가장 효율적인 파서를 사용하여 문서를 구문 분석한다. XML 파서를 사용하도록 지정하지 않으면 HTML 파서를 사용하도록 되어있다.

Kinds of objects

 Beautiful Soup는 복잡한 HTML 문서를 Python 개체 트리로 변환한다. 하지만 Tag, NavigableString, BeautifulSoup, Comment의 객체만 가지면 된다.

Tag

 Tga objec는 XML, HTML 문서의 Tag에 해당한다. Tag에는 많은 속성과 메서드가 있으며 트리 탐색에서 대부분 사용된다. 가장 중요한 기능은 이름과 속성이다. 

soup = BeautifulSoup('<b class="boldest">Extremely bold</b>')
tag = soup.b
type(tag)
# <class 'bs4.element.Tag'>

Name

 모든 태그는 이름을 가지고 있다. .name으로 표현된다.

tag.name
# u'b'

 태그의 이름은 다음과 같이 바꾼다.

tag.name = "blockquote"
tag
# <blockquote class="boldest">Extremely bold</blockquote>

Attributes

 태그는 여러가지의 속성을 가질 수 있다. <b id="boldest">는 값이 "boldest"인 속성 "id"를 가지고 있다. 다음과 같이 태그를 처리하면 태그의 속성에 접근할 수 있다.

tag['id']
# u'boldest'

직접 접근도 가능하다.

tag.attrs
# {u'id': 'boldest'}

태그의 속성을 추가, 삭제, 수정이 가능하다. 이 모든 일은 태그를 사전으로 취급하기 때문에 가능한 일이다.

tag['id'] = 'verybold'
tag['another-attribute'] = 1
tag
# <b another-attribute="1" id="verybold"></b>

del tag['id']
del tag['another-attribute']
tag
# <b></b>

tag['id']
# KeyError: 'id'
print(tag.get('id'))
# None

Multi-valued attributes

 HTML 4는 여러 값을 가질 수 있는 몇 가지 속성을 정의한다. HTML 5는 몇 가지를 더 정의한다. 가장 대표적인 것은 class이다. rel, rev, accept-charset, headers, accesskey는 다중 값을 가질 수 있다.

css_soup = BeautifulSoup('<p class="body"></p>')
css_soup.p['class']
# ["body"]

css_soup = BeautifulSoup('<p class="body strikeout"></p>')
css_soup.p['class']
# ["body", "strikeout"]

 속성은 한 개 이상의 값을 가지고 있는 것처럼 보인다. 하지만 HTML 모든 버전에서 통일된 다중 값 속성이 아니기 때문에 Beautiful Soup은 속성을 그대로 표현해준다.

id_soup = BeautifulSoup('<p id="my id"></p>')
id_soup.p['id']
# 'my id'

 태그를 다시 문자열로 변환하면 여러 속성값이 통합되는 것을 볼 수 있다.

rel_soup = BeautifulSoup('<p>Back to the <a rel="index">homepage</a></p>')
rel_soup.a['rel']
# ['index']
rel_soup.a['rel'] = ['index', 'contents']
print(rel_soup.p)
# <p>Back to the <a rel="index contents">homepage</a></p>

'get_attribute_list'를 사용하면 값의 개수 여부와 상관없이 항상 가져올 수 있다. XML 파서를 사용할 경우에는 다중 값이 없는 것을 알 수 있다.

xml_soup = BeautifulSoup('<p class="body strikeout"></p>', 'xml')
xml_soup.p['class']
# u'body strikeout'

NavigableString

 문자열은 태그 내의 텍스트 일부에 해당한다. Beautiful Soup는 NavigableString 클래스를 사용하여 텍스트를 포함한다.

tag.string
# u'Extremely bold'
type(tag.string)
# <class 'bs4.element.NavigableString'>

NavigableString은 Python의 Unicode string와 비슷해보이지만 트리 탐색에서 지원하는 기능이 다르다. unicode()를 사용하여 NavigableString을 유니코드로 변환할 수 있다.

unicode_string = unicode(tag.string)
unicode_string
# u'Extremely bold'
type(unicode_string)
# <type 'unicode'>

string을 바로 수정할 수 없지만 replace_with()을 통해 수정할 수 있다.

tag.string.replace_with("No longer bold")
tag
# <blockquote>No longer bold</blockquote>

 NavigableString은 기능의 대부분을 지원하지만 문자열에는 아무것도 포함할 수 없기 때문에 .contents, .string, find()를 지원하지 않는다. BeautifulSoup 외부에서 사용하려면 unicode()를 통해 문자열로 변환해야 한다.

BeautifulSoup

 BeautifulSoup 객체 자체는 문서 전체를 나타낸다. 실제 HTML, XML과 일치하지 않으므로 이름과 속성이 없다. 때로 .name을 사용하는 것이 유용할 때가 있기 때문에 가끔 사용한다.

soup.name
# u'[document]'

Comments and other special strings

 Tag, NavigableString, BeautifulSoup는 HTML, XML의 거의 모든 것을 확인할 수 있지만 몇 가지는 확인할 수 없다. 주석이 하나의 예이다.

markup = "<b><!--Hey, buddy. Want to buy a used parser?--></b>"
soup = BeautifulSoup(markup)
comment = soup.b.string
type(comment)
# <class 'bs4.element.Comment'>

Comment 객체는 특별한 유형의 NavigableString이다.

comment
# u'Hey, buddy. Want to buy a used parser'

그러나 HTML 문서의 일부로 나타나는 경우 특수 형식으로 설명이 표시된다.

print(soup.b.prettify())
# <b>
#  <!--Hey, buddy. Want to buy a used parser?-->
# </b>

Beautiful Soup는 XML에 나타날 수 있는 클래스(Cdata, ProcessingInstruction, Declaration, Doctype)를 정의한다. Comment와 마찬가지로 문자열에 무언가를 추가하는 NaviagableString의 하위 클래스이며 아래 예제는 주석을 CDATA로 대체하는 예제이다.

from bs4 import CData
cdata = CData("A CDATA block")
comment.replace_with(cdata)

print(soup.b.prettify())
# <b>
#  <![CDATA[A CDATA block]]>
# </b>

Navigating the tree

 'Three sister' HTML doucment를 다시 보자.

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

 태그는 strings 혹은 다른 태그를 포함하고 있다. 이러한 요소들을 태그들의 자식(Tag's children)이라고 부른다. Beautiful Soup는 태그의 하위 요소를 탐색하고 반복할 수 있는 다양한 특성을 제공한다. Beautiful Soup 문자열은 이러한 특성을 지원하지 않는다. 문자열은 자식을 가지지 못하기 때문이다.

Navigating using tag names

 parse tree에 접근하는 방법은 tag의 이름을 호출하는 것이다. 예를 들어 head에 접근해보자.

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>

만일 모든 <a> 태그를 가지고 오고 싶다면 find_all()을 사용해야 한다.

soup.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>]
.contents and .children

 태그의 자식은 .contents:을 통해 존재하는지 알 수 있다.

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
# [u'The Dormouse's story']

BeautifulSoup의 오브젝트는 스스로가 자식이다. BeautifulSoup의 객체에서 <html> 태그가 자식이다.

len(soup.contents)
# 1
soup.contents[0].name
# u'html'

string은 .contents를 가지고 있지 않은데 아무것도 자식으로 가질 수 없기 때문이다.

text = title_tag.contents[0]
text.contents
# AttributeError: 'NavigableString' object has no attribute 'contents'

태그를 목록으로 가져오는 대신 .children을 통해 태그의 자식을 반복할 수 있다.

for child in title_tag.children:
    print(child)
# The Dormouse's story
.descendants

 .contents나 .children은 태그와 직접적으로 연결되어 있다. 예를 들어 <head> 태그는 직접적인 <title> 태그를 가지고 있다.

head_tag.contents
# [<title>The Dormouse's story</title>]

 하지만 title 태그는 자식을 가지고 있다(The Dormouse's stroy).  이 string은 head의 자식이기도 하다. .descendants 속성을 사용하여 태그의 자식을 반복할 수 있다. head tag의 자녀 - title - string 식으로 나타나는 것을 볼 수 있다.

for child in head_tag.descendants:
    print(child)
# <title>The Dormouse's story</title>
# The Dormouse's story

head 태그는 오직 하나의 자식을 가지고 있지만 <title> 태그와 <title> 태그의 자식까지 2개의 descendants를 가지고 있다. BeautifulSoup object는 하나의 직접적인 자식만 이어준다. 하지만 다른 자식이 많다는 것을 알고 있어야 한다.

len(list(soup.children))
# 1
len(list(soup.descendants))
# 25
.string

 태그에 하나의 자식만 있고 그 자식이 NavigableString이라면 자식은 .string이다.

title_tag.string
# u'The Dormouse's story'

 태그의 유일한 자식이 다른 태그이고 자식이 .string이라면 부모 태그는 같은 .string(value)를 가지고 있다고 생각한다.

head_tag.contents
# [<title>The Dormouse's story</title>]

head_tag.string
# u'The Dormouse's story'

만약 태그가 한 개 이상을 가지고 있다면 .string의 영역이 명확하지 않으므로 .string은 None 값이다.

print(soup.html.string)
# None
.strings and stripped_strings

 태그 안에 두 개 이상의 내용이 있는 경우에도 .strings를 사용하여 string만 볼 수 있다. 

for string in soup.strings:
    print(repr(string))
# u"The Dormouse's story"
# u'\n\n'
# u"The Dormouse's story"
# u'\n\n'
# u'Once upon a time there were three little sisters; and their names were\n'
# u'Elsie'
# u',\n'
# u'Lacie'
# u' and\n'
# u'Tillie'
# u';\nand they lived at the bottom of a well.'
# u'\n\n'
# u'...'
# u'\n'

string은 whitespace를 가지고 있다면 이 것을 .stripped_strings 옵션을 통해 지울 수 있다.

for string in soup.stripped_strings:
    print(repr(string))
# u"The Dormouse's story"
# u"The Dormouse's story"
# u'Once upon a time there were three little sisters; and their names were'
# u'Elsie'
# u','
# u'Lacie'
# u'and'
# u'Tillie'
# u';\nand they lived at the bottom of a well.'
# u'...'

공백 문자로만 구성된 문자열은 무시되고, 문자열의 처음과 끝에 있었던 공백이 사라졌다.

Going up

 tree를 살펴보면 모든 태그와 모든 문자열에게는 부모가 있으며 이것을 접근해본다.

.parent

 요소의 부모는 .parents를 사용하여 반복적으로 접근할 수 있다. <a> 태그의 부모를 따라 올라가본다.

link = soup.a
link
# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
for parent in link.parents:
    if parent is None:
        print(parent)
    else:
        print(parent.name)
# p
# body
# html
# [document]
# None

Going sideways

 여기 간단한 문서가 있다.

sibling_soup = BeautifulSoup("<a><b>text1</b><c>text2</c></b></a>")
print(sibling_soup.prettify())
# <html>
#  <body>
#   <a>
#    <b>
#     text1
#    </b>
#    <c>
#     text2
#    </c>
#   </a>
#  </body>
# </html>

<b> 태그와 <c> 태그는 같은 레벨에 있는 것을 알 수 있다. <b>와 <c>는 <a>의 직접적인 자식들이다. 이런 형제들은 document가 예쁘게 정렬되었을 때 동일한 들여쓰기로 표시된다. 이 관계를 코드에서 사용할 수 있다.

.next_sibling and .previous_sibling

 .next_sibling와 .previous_sibling를 사용하면 같은 레벨 안의 요소에 접근할 수 있다.

sibling_soup.b.next_sibling
# <c>text2</c>

sibling_soup.c.previous_sibling
# <b>text1</b>

<b> 태그는 .nex_sibling를 사용할 수 있지만 .previous_sibling를 사용할 수 없다. 이전에는 형제가 없고 이후에만 있기 때문이다. 같은 이유로 <c> 태그는 .previous_sibling를 사용할 수 있지만 .next_sibling는 사용할 수 없다.

print(sibling_soup.b.previous_sibling)
# None
print(sibling_soup.c.next_sibling)
# None

"text1"과 "text2"는 sibling가 아니다. 이들은 같은 부모를 가지고 있지 않기 때문이다.

sibling_soup.b.string
# u'text1'

print(sibling_soup.b.string.next_sibling)
# None

문서에서 .next_sibling나 .previous_sibling는 공백을 포함하는 문자열로 나타난다.

<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a>
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>

첫 번째 <a> 태그의 .next_sibling이 두 번째 <a> 태그처럼 생각되겠지만 문자열이기 때문에 첫번째 <a> 태그와 두번쨰 <a> 태그를 구분하는 쉼표와 개행문자이다.

link = soup.a
link
# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>

link.next_sibling
# u',\n'

두번째 <a> 태그는 쉼표의 .next_sibling이다.

link.next_sibling.next_sibling
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
.next_siblings and .previous_siblings

 .next_siblings 또는 .previous_siblings를 사용하여 반복 호출 할 수 있다.

for sibling in soup.a.next_siblings:
    print(repr(sibling))
# u',\n'
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
# u' and\n'
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
# u'; and they lived at the bottom of a well.'
# None

for sibling in soup.find(id="link3").previous_siblings:
    print(repr(sibling))
# ' and\n'
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
# u',\n'
# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
# u'Once upon a time there were three little sisters; and their names were\n'
# None

Going back and forth

 "three sister"의 시작점을 보자.

<html><head><title>The Dormouse's story</title></head>
<p class="title"><b>The Dormouse's story</b></p>

HTML 파서는 문자열을 받아 이벤트로 변환한다. html 태그를 열고, head 태그를 열고, title 태그를 연 이후 스트링을 추가한다. 이후 비슷한 과정이다. Beautiful Soup는 문서의 초기 구문 분석을 재구성하는 도구를 제공한다.

.next_element and .previous_element

 문자열이나 태그의 .next_element 속성은 그 후의 속성을 가리킨다. .next_sibling과 같을 수 있지만 대개 다른 값을 가진다. 여기 "three sisters"의 마지막 <a>태그가 있다. 이 것의 .next_sibling는 string이다. 

last_a_tag = soup.find("a", id="link3")
last_a_tag
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>

last_a_tag.next_sibling
# '; and they lived at the bottom of a well.'

 하지만 <a>의 .next_element는 문장의 나머지 부분이 아닌 Tillie라는 단어이다.

last_a_tag.next_element
# u'Tillie'

 이유는 markup에서 Tillie라는 단어가 세미콜론 앞에 나타났기 때문이다. 파서는 <a> 태그를 발견한 다음 Tillie라는 단어를 발견하였고 이후 </a> 태그, 세미콜론 및 나머지 문장을 발견하였다. 세미콜론은 <a>태그와 동일한 레벨이지만 Tillie가 먼저 발생되었다. 

 .previous_element 속성은 .next_element와는 정반대의 요소이다.

last_a_tag.previous_element
# u' and\n'
last_a_tag.previous_element.next_element
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
.next_elements and .previous_elements

 반복을 사용하여 elements들을 호출할 수 있다. 

for element in last_a_tag.next_elements:
    print(repr(element))
# u'Tillie'
# u';\nand they lived at the bottom of a well.'
# u'\n\n'
# <p class="story">...</p>
# u'...'
# u'\n'
# None

Searching the tree

 Beautiful Soup는 parse tree에서 서칭을 위한 많은 메서드를 제공하고 있지만 모두 매우 유사하다. 가장 많이 사용되는 find, find_all 위주로 설명하며 이는 다른 메서드들은 거의 비슷하게 사용되기 때문이다.

 다시 한 번 "three sisters" 문서를 예시로 활용한다.

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')

find_all()과 같은 메서드에 filter를 넣으면 원하는 요소를 찾아낼 수 있다.

Kinds of filters

 find_all()이나 비슷한 메서드에 대해 이야기 하기 전에 다른 필터를 사용하는 예제를 본다. 이 필터들은 검색 전반에 사용되며 이름, 속성, 스트링, 이들의 조합에 따라 달라진다.

A string

 간단한 필터는 string이 있다. string을 검색하면 Beautiful Soup는 검색어와일치하는 문자열을 찾아낸다.

soup.find_all('b')
# [<b>The Dormouse's story</b>]
A regular expression

 정규 표현식을 사용해서 검색할 수 있다. 아래 코드는 b로 시작하는 모든 태그를 검색해준다.

import re
for tag in soup.find_all(re.compile("^b")):
    print(tag.name)
# body
# b

이 코드는 t를 포함하는 모든 코드를 검색해준다.

for tag in soup.find_all(re.compile("t")):
    print(tag.name)
# html
# title

A list

 ㅇㅅㅇ