본문 바로가기
코딩 연습/코딩배우기

파이썬 크롤링(Crawling) 연습 - BeautifulSoup Documentation #1 (html 태그로 접근, 객체 4종류 등)

by good4me 2020. 11. 7.

goodthings4me.tistory.com

 

■ 파이썬 크롤링 BeautifulSoup Documentation 내용 정리 #1

html의 Element 구조 탐색(검색), 객체 4종류 (Tag, NavigableString, BeautifulSoup, Comment)

.contents
.children
.string
.parent
.paretns
.next_sibling
.previous_sibling
.next_element
.previous_element

 

BeautifulSoup은 HTML 및 XML 파일에서 데이터를 추출하는 파이썬 라이브러리이다.
BeautifulSoup version 4.9.2. 기준

html_doc = '''<html><head><title>BeautifulSoup Document 기준</title></head>
<body>
<p class="title"><b>파이썬 크롤링 연습을 위한 포스팅</b></p>

<p class="story">국회 법제사법위원회, 대검찰청 국정감사 유튜브 영상
<a href="https://youtu.be/_pOicsZstGo" class="sister" id="link1">김봉현 2차 폭로에 충격! 대검 침묵</a>, 
<a href="https://youtu.be/AZbCWck7or0" class="sister" id="link2">속속 드러나는 검찰 의혹..김종민 "옵티 계좌추적도 안하고 덮었다.</a> 그리고 
<a href="https://youtu.be/kkfxzYaJhgI" class="sister" id="link3">김종민 가족수사 돌직구에 현타 온 윤석열</a></p>
<p class="story">추가적으로, ...</p>
'''

# 위 html_doc HTML코드와 파서(html.parser)를 인자로 받아 BeautifulSoup 실행하면 BeautifulSoup object를 반환한다.

from bs4 import BeautifulSoup
soup = BeautifulSoup(html_doc, 'html.parser')
print(type(soup))
# <class 'bs4.BeautifulSoup'>

print(soup.prettify())  
# prettify()는 파싱 처리 후 트리 형태로 리턴하는 함수
'''
<html>
 <head>
  <title>
   BeautifulSoup Document 기준
  </title>
 </head>
 <body>
  <p class="title">
   <b>
    파이썬 크롤링 연습을 위한 포스팅
   </b>
  </p>
  <p class="story">
   국회 법제사법위원회, 대검찰청 국정감사 유튜브 영상
   <a class="sister" href="https://youtu.be/_pOicsZstGo" id="link1">
    김봉현 2차 폭로에 충격! 대검 침묵
   </a>
   ,
   <a class="sister" href="https://youtu.be/AZbCWck7or0" id="link2">
    속속 드러나는 검찰 의혹..김종민 "옵티 계좌추적도 안하고 덮었다.
   </a>
   그리고
   <a class="sister" href="https://youtu.be/kkfxzYaJhgI" id="link3">
    김종민 가족수사 돌직구에 현타 온 윤석열
   </a>
  </p>
  <p class="story">
   추가적으로, ...
  </p>
 </body>
</html>
'''

### html_doc의 Element 구조를 탐색(검색)해보기 ###

print(soup.title)  ## title 태그 코드
# <title>BeautifulSoup Document 기준</title>

print(soup.title.name)  ## title 태그명
# title

print(soup.title.string)  ## title 태그의 문자열
# BeautifulSoup Document 기준

print(soup.title.parent.name)  ## title 태그의 바로 위 태그
# head

print(soup.p)  ## p 태그 코드 (첫번째 p 태그)
# <p class="title"><b>파이썬 크롤링 연습을 위한 포스팅</b></p>

print(soup.p['class'])  ## p 태그의 class 속성 값(리스트로 반환)
# ['title']

print(soup.a)  ## a 태그 코드 (첫번째 a 태그)
# <a class="sister" href="https://youtu.be/_pOicsZstGo" id="link1">김봉현 2차 폭로에 충격! 대검 침묵</a>

print(soup.find_all('a'))  ## a 태그를 모두 찾아서 리스트로 반환
# [<a class="sister" href="https://youtu.be/_pOicsZstGo" id="link1">김봉현 2차 폭로에 충격! 대검 침묵</a>, 
# <a class="sister" href="https://youtu.be/AZbCWck7or0" id="link2">속속 드러나는 검찰 의혹..김종민 "옵티 계좌추적도 안하고 덮었다.</a>, 
# <a class="sister" href="https://youtu.be/kkfxzYaJhgI" id="link3">김종민 가족수사 돌직구에 현타 온 윤석열</a>]

print(soup.find(id="link3"))  ## id가 link3인 태그 찾아 반환(class='link3'인 경우 find()는 첫번째 하나만 반환함)
# <a class="sister" href="https://youtu.be/kkfxzYaJhgI" id="link3">김종민 가족수사 돌직구에 현타 온 윤석열</a>

for link in soup.find_all('a'):  ## 반환된 리스트 객체에 대해 for문 적용
    print(link.get('href'))  ## 속성 href의 값 추출
# https://youtu.be/_pOicsZstGo
# https://youtu.be/AZbCWck7or0
# https://youtu.be/kkfxzYaJhgI

print(soup.get_text())  ## 모든 문자열 추출
# 파이썬 크롤링 연습을 위한 포스팅
# 국회 법제사법위원회, 대검찰청 국정감사 유튜브 영상
# 김봉현 2차 폭로에 충격! 대검 침묵, 
# 속속 드러나는 검찰 의혹..김종민 "옵티 계좌추적도 안하고 덮었다. 그리고 
# 김종민 가족수사 돌직구에 현타 온 윤석열
# 추가적으로, ...


for tag in soup.find_all('p'):  ## .attrs는 dict로 속성:속성값 반환
    print(tag.attrs)
# {'class': ['title']}
# {'class': ['story']}
#{'class': ['story']}
    
for tag in soup.find_all('a'):
    print(tag.attrs)
# {'href': 'https://youtu.be/_pOicsZstGo', 'class': ['sister'], 'id': 'link1'}
# {'href': 'https://youtu.be/AZbCWck7or0', 'class': ['sister'], 'id': 'link2'}
# {'href': 'https://youtu.be/kkfxzYaJhgI', 'class': ['sister'], 'id': 'link3'}

good4me.co.kr

 

객체 4종류 (Tag, NavigableString, BeautifulSoup, Comment)

# Tag
soup = BeautifulSoup('<b class="boldest">볼드체</b>', 'html.parser')
tag = soup.b
print(tag)
# <b class="boldest">볼드체</b>

print(type(tag))
# <class 'bs4.element.Tag'>

print(tag.name)
# b

tag.name = 'blockquote'  ## 태그명 변경
print(tag)
# <blockquote class="boldest">볼드체</blockquote>

print(tag.attrs)  ## 태그의 속성(attrs)명과 속성값을 dict로 반환
# {'class': ['boldest']}

print(tag['class'])  ## dict 키로 접근  
# ['boldest']

tag = BeautifulSoup('<b id="boldest">BOLD</b>', 'html.parser').b
print(tag['id'])
# boldest

print(tag.attrs)
# {'id': 'boldest'}



### 다수의 속성값이 있는 경우 ###

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

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

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


rel_soup = BeautifulSoup('<p>홈으로 가기[<a rel="index">Home</a>]</p>', 'html.parser')
print(rel_soup.a['rel'])
# ['index']

rel_soup.a['rel'] = ['index', 'contents']
print(rel_soup.p)
# <p>홈으로 가기[<a rel="index contents">Home</a>]</p>

soup = BeautifulSoup('<b class="boldest">Extremely bold</b>', 'html.parser')
tag = soup.b
print(tag.string)
# Extremely bold
print(type(tag.string))
# <class 'bs4.element.NavigableString'>
unicode_string = str(tag.string)
print(unicode_string)
# Extremely bold
print(type(unicode_string))
# <class 'str'>

tag.string.replace_with('No longer bold')  ## replace_tith()
print(tag)


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

 

### .contents and .children

markup = '''
<html>
<head>
<title>The Dormouse's story</title>
</head>
'''
soup = BeautifulSoup(markup, 'html.parser')
head_tag = soup.head
print(head_tag)
#<head>
#<title>The Dormouse's story</title>
#</head>

print(head_tag.contents)
# ['\n', <title>The Dormouse's story</title>, '\n']
## 줄바꿈 문자까지 대상으로 포함
print(len(head_tag.contents))
# 3
print(head_tag.contents[0])
# \n
print(head_tag.contents[1])
# <title>The Dormouse's story</title>


markup = '''<html><head><title>The Dormouse's story</title></head>'''
soup = BeautifulSoup(markup, 'html.parser')
head_tag = soup.head
print(head_tag.contents)
# [<title>The Dormouse's story</title>]
print(len(head_tag.contents))
# 1
print(type(head_tag.contents))
# <class 'list'>
print(head_tag.contents)
# [<title>The Dormouse's story</title>]
print(head_tag.contents[0])
# <title>The Dormouse's story</title>
print(head_tag.contents[0].contents)
# ["The Dormouse's story"]

print(soup.contents)
# [<html><head><title>The Dormouse's story</title></head></html>]
print(soup.contents[0])
# <html><head><title>The Dormouse's story</title></head></html>
print(soup.contents[0].name)
# html
print(soup.contents[0].contents)
# [<head><title>The Dormouse's story</title></head>]

#print(soup.contents[0].contents.contents)  ## list에 .contents 불가
# AttributeError: 'list' object has no attribute 'contents'
for h in soup.contents[0].contents:  ## list에 대해 for문으로 처리
    print(h.contents)  ## == head.contents (soup.head.contents)
# [<title>The Dormouse's story</title>]

print(soup.contents[0].contents[0].contents)  ## == head.contents
# [<title>The Dormouse's story</title>]
print('---',soup.contents[0].contents[0].contents[0])  ## == head.contents[0]
# <title>The Dormouse's story</title>
print(soup.contents[0].contents[0].contents[0].contents)  ## == title.contents
# ["The Dormouse's story"]

for child in soup.contents[0].contents[0].contents[0]:
    print(child)  ## == title.contents[0]
# The Dormouse's story

print(soup.contents[0].contents[0].children)  ## .children은 generator
# <list_iterator object at 0x0000016F5351EAC8>

for child in soup.contents[0].children:
    print(child)
# <head><title>The Dormouse's story</title></head>

 

### .string

markup = '''<html><head><title>The Dormouse's story</title></head>'''
soup = BeautifulSoup(markup, 'html.parser')
print(soup.string)
# The Dormouse's story

## 태그 내에 하나 이상의 문자열이 있으면, .string은 참조하는 것이 불분명하여 None을 반환함
markup = '''<html><head><title>The Dormouse's story</title></head><body><p>and ...</p>'''
soup = BeautifulSoup(markup, 'html.parser')
print(soup.string)
# None

## 태그 내에 하나 이상의 문자열이 있으면, .strings 제너레이터를 사용하여 해결함
for string in soup.strings:
    print(repr(string))
#"The Dormouse's story"
#'and ...' 

## .strings는 공백문자들이 포함되는데, 이를 제거하기 위해서는 .stripped_strings를 사용함
markup = '''
<html>
<head>
<title>The Dormouse's story</title>
</head>
<body>
<p>  and ...
 so,    
</p>'''
soup = BeautifulSoup(markup, 'html.parser')
for string in soup.strings:
    print(repr(string))

#"The Dormouse's story"
#'and ...'
#'\n'
#'\n'
#'\n'
#"The Dormouse's story"
#'\n'
#'\n'
#'\n'
#'  and ...\n so,    \n'
    
for string in soup.stripped_strings:
    print(repr(string))

#"The Dormouse's story"
#'and ...\n so,'

 

### .parent and .paretns

markup = '''<html><head><title>The Dormouse's story</title></head>'''
soup = BeautifulSoup(markup, 'html.parser')

print(soup.title)
# <title>The Dormouse's story</title>
print(soup.title.parent)
# <head><title>The Dormouse's story</title></head>

print(soup.title.string)
# The Dormouse's story
print(soup.title.string.parent)
# <title>The Dormouse's story</title>

print(soup.html.parent)
# <html><head><title>The Dormouse's story</title></head></html>
print(type(soup.html.parent))
# <class 'bs4.BeautifulSoup'>

print(soup.title)
# <title>The Dormouse's story</title>
print(soup.title.parents)
# <generator object PageElement.parents at 0x0000016F53617318>
for parent in soup.title.parents:
    print(parent)

#<head><title>The Dormouse's story</title></head>
#<html><head><title>The Dormouse's story</title></head></html>
#<html><head><title>The Dormouse's story</title></head></html>
    
for parent in soup.title.parents:
    print(parent.name)   

#head
#html
#[document]

 

### .next_sibling and .previous_sibling

## 트리 구조에서 동일 레벨 상의 요소를 사용할 수 있음
sibling_soup = BeautifulSoup("<a><b>text1</b><c>text2</c></b></a>", 'html.parser')
print(sibling_soup.prettify())
#<a>
# <b>
#  text1
# </b>
# <c>
#  text2
# </c>
#</a>

print(sibling_soup.b.next_sibling)
# <c>text2</c>

print(sibling_soup.c.previous_sibling)
# <b>text1</b>

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

## 문자열(string)은 sibling 안됨
print(sibling_soup.b.string)
# text1
print(sibling_soup.b.string.next_sibling)
# None


## sibling 사용 시, 주의사항
## 태그와 태그 사이의 개행이나 콤마 등도 요소로 취급함에 주의!
markup = '''
<p>Three sisters</p>
<a href="#" class="sister" id="link1">Elsie</a>,
<a href="#" class="sister" id="link2">Lacie</a>,
<a href="#" class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.
'''
soup = BeautifulSoup(markup, 'html.parser')
print(repr(soup.p.next_sibling))
# '\n'
print(soup.p.next_sibling.next_sibling)
# <a class="sister" href="#" id="link1">Elsie</a>

print(repr(soup.a.next_sibling))
# ',\n'
print(repr(soup.a.next_sibling.next_sibling))
# <a class="sister" href="#" id="link2">Lacie</a>


## .next_siblings and previous_siblings
## 태그 sibling 전체 다루기
print(soup.p.next_siblings)
# <generator object PageElement.next_siblings at 0x0000016F53615C78>
for sibling in soup.p.next_siblings:
    print(sibling)

#
#<a class="sister" href="#" id="link1">Elsie</a>
#,
#
#<a class="sister" href="#" id="link2">Lacie</a>
#,
#
#<a class="sister" href="#" id="link3">Tillie</a>
#;
#and they lived at the bottom of a well.
#
#<a class="sister" href="#" id="link3">Tillie</a>
#;
#and they lived at the bottom of a well.
#

 

### .next_element and .previous_element

last_a_tag = soup.find("a", id="link3")
print(last_a_tag)
# <a class="sister" href="#" id="link3">Tillie</a>
print(last_a_tag.next_sibling)
#;
#and they lived at the bottom of a well.
print(last_a_tag.next_element)  ## a 태그의 다음 문장이 아닌 그 자체의 단어임 
# Tillie
## 마크 업에서 <a> 태그, "Tillie", </a> 태그, 세미콜론, 나머지 문장 중에서 
## 세미콜론은 <a> 태그와 같은 수준에 있지만 "Tillie"라는 단어가 먼저 발견되었음


print(repr(last_a_tag.previous_element))
# ',\n'

for element in last_a_tag.next_elements:  ## next_elements
    print(repr(element))
#'Tillie'
#'\nand they lived at the bottom of a well.\n'

 

 

[참고] Beautiful Soup 4.9.0 documentation https://www.crummy.com/software/BeautifulSoup/bs4/doc/

 

댓글