find()
와 findAll()
은 BeautifulSoup에서 가장 자주 쓰는 함수
예제 사이트 : http://www.pythonscraping.com/pages/warandpeace.html
find(tag, attributes, recursive, text, keywords)
findAll(tag, attributes, recursive, text, limit, keywords)
tag
와 attributes
가 사용되는 경우가 대부분tag
매개변수에는 태그 이름인 문자열을 넘기거나, 태그 이름으로 이루어진 파이썬 리스트를 넘길 수도 있음.findAll({"h1","h2","h3","h4","h5","h6"})
attributes
매개변수는 속성으로 이루어진 파이썬 딕셔너리를 받고, 그중 하나에 일치하는 태그를 찾음.findAll("span", {"class":{"green", "red"}})
recursive
매개변수는 불리언으로 문서에서 얼마나 깊이 찾아 들어가고 싶은지를 지정recursive
가 True
이면 findAll
함수는 매개변수에 일치하는 태그를 찾아 자식, 자식의 자식을 검색false
이면 문서의 최상위 태그만 검색findAll
은 재귀적으로(recursive
가 True
) 동작하며 일반적으로 이 옵션은 그대로 두는 것이 좋음text
매개변수는 태그의 속성이 아니라 텍스트 콘텐츠에 일치한다는 점이 좀 다름nameList = bsObj.findAll(text="the prince")
print(len(nameList))
limit
매개변수는 물론 findAll
에만 쓰임find
는 findAll
을 호출하면서 limit
을 1로 지정한 것과 같음keyword
매개변수는 특정 속성이 포함된 태그를 선택할 때 사용allText = bsObj.findAll(id="text")
print(allText[0].get_text())
Tip!
keyword
매개변수 쓸 때 주의할 점
- keyword 매개변수는 특정 상황에서 매우 유용할 수 있지만 기술적으로는 BeautifulSoup 자체의 기능과 중복되기도 함
- 예를 들어 다음 두 행은 완전히 같음
bsObj.findAll(id="text")
bsObj.findAll("", {"id":"text"})
- 또한
keyword
는 가끔 문제를 일으키는데, 가장 흔한 경우는class
속성으로 요소를 검색할 때 일어나며 이는class
가 파이썬에서 보호된 키워드이기 때문
- 즉,
class
는 파이썬 예약어(keyword
)이므로 변수나 매개변수 이름으로 쓸 수 없음BeautifulSoup.findAll()
의keyword
매개변수와는 상관없음
- 예를 들어 다음 행은 class를 비표준적인 방법으로 사용하므로 문법 에러를 일으킴
bsObj.findAll(class="green")
- 해결책
- 밑줄 추가
bsObj.findAll(class_="green")
class
를 따옴표 안에 쓰는 방법
bsObj.findAll("", {"class":"green"})
.findAll()
에 속성 목록으로 넘기면 or
필터처럼 동작함keyword
매개변수는 and
필터처럼 동작하므로 그런 문제가 없음from urllib.request import urlopen
from bs4 import BeautifulSoup
html= urlopen('***')
bs = BeautifulSoup(html, 'html.parser')
# 녹색(green) 글자를 모두 가져오는 방법
nameList = bs.findAll('span',{'class':'green'}) # or find_all
for i in nameList:
print(i.get_text())
# 결과
Anna
Pavlovna Scherer
Empress Marya
Fedorovna
Prince Vasili Kuragin
Anna Pavlovna
St. Petersburg
the prince
Anna Pavlovna
Anna Pavlovna
the prince
the prince
the prince
Prince Vasili
Anna Pavlovna
...생략
findAll()
함수로 페이지의 모든 해당 태그를 찾고, get_text()
를 이용해 태그를 제외한 텍스트를 추출한 것# 문서의 모든 헤더 태그 리스트 반환
bs.findAll({'h1','h2','h3','h4','h5','h6'})
# 결과
[<h1>War and Peace</h1>, <h2>Chapter 1</h2>]
# 녹색(green)과 빨간색(red) span 태그를 모두 반환
bs.findAll({'span':{'class':{'green','red'}}})
# 결과
<span class="red">Well, Prince, so Genoa and Lucca are now just family estates of the
Buonapartes. But I warn you, if you don't tell me that this means war,
if you still try to defend the infamies and horrors perpetrated by
that Antichrist- I really believe he is Antichrist- I will have
nothing more to do with you and you are no longer my friend, no longer
my 'faithful slave,' as you call yourself! But how do you do? I see
I have frightened you- sit down and tell me all the news.</span>,
<span class="green">Anna
Pavlovna Scherer</span>,
... 생략
reculsive=True
가 default 이며, 재귀적으로 자식의 자식 태그까지 모두 검색하며, False
일 경우 최상위 태그만 검색# text 매개변수, 태그에 둘러싸인 ‘the prince’가 몇 번 나타났는지 검색
nameList = bs.findAll(text='the prince')
print(len(nameList))
# 결과
7
the prince
라는 텍스트만이 태그에 둘러싸여 있어야 검색 가능the
라는 텍스트는 혼자 태그에 둘러싸여 있지 않기 때문에 불가능# keyword 매개변수, 특정 속성이 포함된 태그를 선택
bs.findAll(id='text')
# 결과
[<div id="text">
"<span class="red">Well, Prince, so Genoa and Lucca are now just family estates of the
Buonapartes. But I warn you, if you don't tell me that this means war,
if you still try to defend the infamies and horrors perpetrated by
that Antichrist- I really believe he is Antichrist- I will have
nothing more to do with you and you are no longer my friend, no longer
my 'faithful slave,' as you call yourself! But how do you do? I see
I have frightened you- sit down and tell me all the news.</span>"
<p></p>
It was in July, 1805, and the speaker was the well-known <span class="green">Anna
Pavlovna Scherer</span>, maid of honor and favorite of the <span class="green">Empress Marya
Fedorovna</span>. With these words she greeted <span class="green">Prince Vasili Kuragin</span>, a man
of high rank and importance, who was the first to arrive at her
reception. <span class="green">Anna Pavlovna</span> had had a cough for some days. She was, as
she said, suffering from la grippe; grippe being then a new word in
<span class="green">St. Petersburg</span>, used only by the elite.
...생략
# 녹색 글씨를 찾는 다른 방법
bs.findAll(class_='green')
# 결과
<span class="green">Anna
Pavlovna Scherer</span>, <span class="green">Empress Marya
Fedorovna</span>, <span class="green">Prince Vasili Kuragin</span>, <span class="green">Anna Pavlovna</span>, <span class="green">St. Petersburg</span>,
... 생략
class
는 파이썬 예약어이기 때문에 class_
로 사용해야 함limit
매개변수, 페이지의 처음 부터 검색할 개수find()
함수는 findAll()
함수의 limit
매개변수를 1로 둔 것을 제외하고는 다른 것이 없음findAll
함수를 사용하면 이름과 속성에 따라 태그를 찾을 수 있음html
— body
— div.wrapper
— h1
— div.content
— table #giftList
— tr
— th
— th
— th
— th
— tr.gift #gift1
— td
— td
— span.excitingNote
— td
— td
— img
— ...더 많은 테이블 행...
— div.footer
BeautifulSoup
라이브러리는 자식과 자손을 구별tr
태그는 table
태그의 자식이며 tr
과 th
, td
, img
, span
은 모두 table
태그의 자손BeautifulSoup
함수는 항상 현재 선택된 태그의 자손을 다룸bsObj.body.h1
은 body
의 자손인 첫 번째 h1
태그를 선택body
바깥에 있는 태그에 대해서는 동작하지 않음bsObj.div.findAll("img")
는 문서의 첫 번째 div
태그를 찾고, 그 div
태그의 자손인 모든 img
태그의 목록을 가져옴.children
을 사용from urllib.request import urlopen
from bs4 import BeautifulSoup
html = urlopen("***")
bsObj = BeautifulSoup(html, "html.parser")
for child in bsObj.find("table",{"id":"giftList"}).children:
print(child)
giftList
테이블에 들어 있는 제품 행 목록을 출력children()
대신 descendants()
함수를 썼다면 테이블에 포함된 태그가 20개 이상 출력됐을 테고, 거기에는 img
, span
, td
태그 등이 모두 포함됐을 것from urllib.request import urlopen
from bs4 import BeautifulSoup
html = urlopen('***')
bs = BeautifulSoup(html, 'html.parser')
# giftList가 있는 표(부모)의 자식 태그들을 가져오는 방법
for child in bs.find('table',{'id':'giftList'}).children:
print("="*10)
print(child)
# 결과
==========
==========
<tr><th>
Item Title
</th><th>
Description
</th><th>
Cost
</th><th>
Image
</th></tr>
==========
==========
<tr class="gift" id="gift1"><td>
Vegetable Basket
</td><td>
This vegetable basket is the perfect gift for your health conscious (or overweight) friends!
<span class="excitingNote">Now with super-colorful bell peppers!</span>
</td><td>
$15.00
</td><td>
<img src="../img/gifts/img1.jpg"/>
</td></tr>
==========
==========
<tr class="gift" id="gift2"><td>
Russian Nesting Dolls
</td><td>
Hand-painted by trained monkeys, these exquisite dolls are priceless! And by "priceless," we mean "extremely expensive"! <span class="excitingNote">8 entire dolls per set! Octuple the presents!</span>
</td><td>
$10,000.52
</td><td>
<img src="../img/gifts/img2.jpg"/>
</td></tr>
==========
... 생략
<tr class="gift" id="gift1">
인데, 이 태그는 이 태그만을 의미하는게 아니라 아래의 결과 까지를 의미<tr class="gift" id="gift1">…<tr>
만 자식 태그<tr class="gift" id="gift1"><td>
Vegetable Basket
</td><td>
This vegetable basket is the perfect gift for your health conscious (or overweight) friends!
<span class="excitingNote">Now with super-colorful bell peppers!</span>
</td><td>
$15.00
</td><td>
<img src="../img/gifts/img1.jpg"/>
</td></tr>
descendants
를 사용하여 자손 태그를 사용해야 함# 내부의 하위 태그도 출력해야할 때
for des in bs.find('table',{'id':'giftList'}).descendants:
print("="*10)
print(des)
# 결과
==========
<tr class="gift" id="gift1"><td>
Vegetable Basket
</td><td>
This vegetable basket is the perfect gift for your health conscious (or overweight) friends!
<span class="excitingNote">Now with super-colorful bell peppers!</span>
</td><td>
$15.00
</td><td>
<img src="../img/gifts/img1.jpg"/>
</td></tr>
==========
<td>
Vegetable Basket
</td>
==========
Vegetable Basket
==========
<td>
This vegetable basket is the perfect gift for your health conscious (or overweight) friends!
<span class="excitingNote">Now with super-colorful bell peppers!</span>
</td>
==========
This vegetable basket is the perfect gift for your health conscious (or overweight) friends!
==========
<span class="excitingNote">Now with super-colorful bell peppers!</span>
==========
Now with super-colorful bell peppers!
==========
==========
<td>
$15.00
</td>
==========
$15.00
==========
<td>
<img src="../img/gifts/img1.jpg"/>
</td>
==========
==========
<img src="../img/gifts/img1.jpg"/>
==========
BeautifulSoup
의 next_siblings()
함수는 테이블에서 데이터를 쉽게 수집할 수 있으며, 특히 테이블에 타이틀 행이 있을 때 유용from urllib.request import urlopen
from bs4 import BeautifulSoup
html = urlopen("***")
bsObj = BeautifulSoup(html, "html.parser")
for sibling in bsObj.find("table",{"id":"giftList"}).tr.next_siblings:
print(sibling)
next_siblings
을 호출하면 타이틀 행 자체를 제외한 모든 테이블 행을 선택하게 됨Tip! 선택은 명확하게!
- 이전 코드는
bsObj.table.tr
, 심지어bsObj.tr
을 써서 테이블의 첫 번째 행을 선택했더라도 마찬가지로 잘 동작했을 것
- 하지만 번거로움을 무릅쓰고 위 코드를 길고 명확하게 작성
bsObj.find("table",{"id":"giftList"}).tr
- 설령 페이지에서 테이블(또는 다른 타겟 태그)이 하나뿐인 것처럼 보일 때에도 실수를 하기 쉬움
- 또한 페이지 레이아웃은 시시때때로 변함
- 코드를 작성할 때는 페이지 처음에 있던 테이블이, 어느 날 보니 두 번째 또는 세 번째 테이블이 되어 있을 수도 있는 것
- 스크레이퍼를 더 견고하게 만들려면 항상 태그를 가능한한 명확하게 선택하는 것이 최선이며, 가능하다면 태그 속성을 활용하는게 좋음
next_siblings
를 보완하는 previous_siblings
함수도 있음next_siblings
, previous_siblings
와 거의 같은 next_sibling
, previous_sibling
함수도 있음next_siblings()
함수는 웹페이지의 테이블(표)에서 데이터를 쉽게 수집할 수 있고, 특히 타이틀(헤더)가 있을때 유용# 테이블의 타이틀을 제외하고 원소들만 가져오는 방법
for siblings in bs.find('table',{'id':'giftList'}).tr.next_siblings:
print("="*10)
print(siblings)
# 결과
==========
==========
<tr class="gift" id="gift1"><td>
Vegetable Basket
</td><td>
This vegetable basket is the perfect gift for your health conscious (or overweight) friends!
<span class="excitingNote">Now with super-colorful bell peppers!</span>
</td><td>
$15.00
</td><td>
<img src="../img/gifts/img1.jpg"/>
</td></tr>
==========
==========
<tr class="gift" id="gift2"><td>
Russian Nesting Dolls
</td><td>
Hand-painted by trained monkeys, these exquisite dolls are priceless! And by "priceless," we mean "extremely expensive"! <span class="excitingNote">8 entire dolls per set! Octuple the presents!</span>
</td><td>
$10,000.52
</td><td>
<img src="../img/gifts/img2.jpg"/>
</td></tr>
==========
==========
<tr class="gift" id="gift3"><td>
Fish Painting
</td><td>
If something seems fishy about this painting, it's because it's a fish! <span class="excitingNote">Also hand-painted by trained monkeys!</span>
</td><td>
$10,005.00
</td><td>
<img src="../img/gifts/img3.jpg"/>
</td></tr>
==========
==========
<tr class="gift" id="gift4"><td>
Dead Parrot
</td><td>
This is an ex-parrot! <span class="excitingNote">Or maybe he's only resting?</span>
</td><td>
$0.50
</td><td>
<img src="../img/gifts/img4.jpg"/>
</td></tr>
==========
==========
<tr class="gift" id="gift5"><td>
Mystery Box
</td><td>
If you love suprises, this mystery box is for you! Do not place on light-colored surfaces. May cause oil staining. <span class="excitingNote">Keep your friends guessing!</span>
</td><td>
$1.50
</td><td>
<img src="../img/gifts/img6.jpg"/>
</td></tr>
==========
previous_siblings()
함수는 선택한 태그의 이전에 나오는 형제 태그들을 가져옴s
를 뺀 next_sibling()
, previous_sibling()
함수는 선택 태그 이전과 이후 하나의 형제 태그만을 가져옴Tip!
- 헤더처럼 처음 나오는 태그 식별이 쉬울 때는
next_siblings()
함수- 마지막에 있는 태그 식별이 쉬울 때는
previous_siblings()
함수
BeautifulSoup
의 부모 검색 함수 .parent
와 .parents
가 필요할 때도 있음from urllib.request import urlopen
from bs4 import BeautifulSoup
html = urlopen("***")
bsObj = BeautifulSoup(html, "html.parser")
print(bsObj.find("img",{"src":"../img/gifts/img1.jpg"
}).parent.previous_sibling.get_text())
이 코드는 ../img/gifts/img1.jpg
이미지가 나타내는 객체의 가격(이 경우 $15.00)을 출력
HTML 페이지에서 우리가 살펴볼 부분의 트리 구조를 숫자로 표시한 단계와 함께 나타내면 다음과 같음
<tr>
— <td>
— <td>
— <td> ③
— "$15.00" ④
— s<td> ②
— <img src="../img/gifts/img1.jpg"> ①
src="../img/gifts/img1.jpg"
에 해당하는 이미지를 선택<td>
태그)를 선택<td>
의 previous_sibling
(이 경우 제품 가격이 들어 있는 태그)을 선택# 자식을 통해 부모를 찾는 방법
bs.find('img',{'src':'../img/gifts/img1.jpg'}).parent.previous_sibling.get_text()
# 결과
'\n$15.00\n'
img
태그 선택