메뉴 바로가기 검색 및 카테고리 바로가기

한빛미디어

[파이썬으로 웹 크롤러 만들기] 고급 HTML 분석(4/4)

2016-12-27

|

by 한선용

38,821

2.2.3 트리 이동

findAll 함수는 이름과 속성에 따라 태그를 찾습니다. 하지만 문서 안에서의 위치를 기준으로 태그를 찾을 때는 어떻게 해야 할까요? 이럴 때 트리 내비게이션이 필요합니다. 1장에서는 BeautifulSoup 트리를 단방향으로 이동하는 방법을 봤었습니다.

 

bsObj.tag.subTag.anotherSubTag

 

이제 http://www.pythonscraping.com/pages/page3.html 에 있는 온라인 쇼핑 사이트를 스크레이핑 예제 페이지로 써서 HTML 트리를 이동하는 방법을 알아봅시다(그림 2-1).

 

wswp_0201.png

그림 2-1 http://www.pythonscraping.com/pages/page3.html 스크린샷

 

이 페이지의 HTML은 다음과 같은 트리 구조로 나타낼 수 있습니다(간결함을 위해 일부 태그는 생략했습니다).

 

 

 

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

 

다음 몇 섹션에 걸쳐 이 HTML 구조를 예제로 쓰겠습니다

 

 

자식과 자손

컴퓨터 과학과 일부 수학 분야에서는 자식들을 끔찍하게 다루곤 합니다. 이리저리 이동하고, 저장하고, 제거하고, 심지어 죽이기도 하죠. 다행히 BeautifulSoup에서는 자식들을 다른 방법으로 다룹니다.

 

여러 다른 라이브러리와 마찬가지로 BeautifulSoup 라이브러리도 자식children자손descendants을 구별합니다. 사람의 가족과 마찬가지로, 자식은 항상 부모보다 한 태그 아래에 있고, 자손은 조상보다 몇 단계든 아래에 있을 수 있습니다. 예를 들어 tr 태그는 table 태그의 자식이며 tr th, td, img, span은 모두 table 태그의 자손입니다(최소한 우리 예제 페이지에서는 말입니다). 모든 자식은 자손이지만, 모든 자손이 자식인 것은 아닙니다.

 

일반적으로 BeautifulSoup 함수는 항상 현재 선택된 태그의 자손을 다룹니다. 예를 들어 bsObj.body.h1body의 자손인 첫 번째 h1 태그를 선택합니다. body 바깥에 있는 태그에 대해서는 동작하지 않습니다.

마찬가지로 bsObj.div.findAll("img")는 문서의 첫 번째 div 태그를 찾고, 그 div 태그의 자손인 모든 img 태그의 목록을 가져옵니다.

자식만 찾을 때는 .children을 사용합니다.

 

from urllib.request import urlopen

from bs4 import BeautifulSoup

 

html = urlopen("http://www.pythonscraping.com/pages/page3.html")

bsObj = BeautifulSoup(html, "html.parser")

 

for child in bsObj.find("table",{"id":"giftList"}).children:

print(child)

 

이 코드는 giftList 테이블에 들어 있는 제품 행 목록을 출력합니다. children() 대신 descendants() 함수를 썼다면 테이블에 포함된 태그가 20개 이상 출력됐을 테고, 거기에는 img, span, td 태그 등이 모두 포함됐을 겁니다. 자식과 자손의 구별이 중요합니다!

 

 

형제 다루기

BeautifulSoup의 next_siblings() 함수는 테이블에서 데이터를 쉽게 수집할 수 있으며, 특히 테이블에 타이틀 행이 있을 때 유용합니다.

 

from urllib.request import urlopen

from bs4 import BeautifulSoup

html = urlopen("http://www.pythonscraping.com/pages/page3.html")

 

bsObj = BeautifulSoup(html, "html.parser")

for sibling in bsObj.find("table",{"id":"giftList"}).tr.next_siblings:

print(sibling)

 

이 코드의 출력 결과는 제품 테이블에서 첫 번째 타이틀 행을 제외한 모든 제품 행입니다. 타이틀 행은 왜 건너뛰었을까요? 이유는 두 가지입니다. 첫째, 객체는 자기 자신의 형제sibling가 될 수 없습니다. 객체의 형제를 가져올 때, 객체 자체는 항상 그 목록에서 제외됩니다. 둘째, 이 함수는 다음 형제만 가져옵니다. 예를 들어 우리가 목록 중간에 있는 임의의 행을 선택하고 next_siblings을 호출했다면 그다음에 있는 형제들만 반환됩니다. 즉, 타이틀 행을 선택하고 next_siblings을 호출하면 타이틀 행 자체를 제외한 모든 테이블 행을 선택하게 됩니다.

 


[NOTE_ 선택은 명확하게 하십시오]

이전 코드는 bsObj.table.tr, 심지어 bsObj.tr을 써서 테이블의 첫 번째 행을 선택했더라도 마찬가지로 잘 동작했을 겁니다. 하지만 필자는 번거로움을 무릅쓰고 위 코드를 길고 명확하게 작성했습니다.

 

bsObj.find("table",{"id":"giftList"}).tr

 

설령 페이지에 테이블(또는 다른 타겟 태그)이 하나뿐인 것처럼 보일 때에도 실수를 하기 쉽습니다. 또한 페이지 레이아웃은 시시때때로 변합니다. 코드를 작성할 때는 페이지 처음에 있던 테이블이, 어느 날 보니 두 번째 또는 세 번째 테이블이 되어 있을 수도 있는 겁니다. 스크레이퍼를 더 견고하게 만들려면 항상 태그를 가능한한 명확하게 선택하는 것이 최선입니다. 가능하다면 태그 속성을 활용하십시오.

 

 

next_siblings를 보완하는 previous_siblings 함수도 있습니다. 이 함수는 원하는 형제 태그 목록의 마지막에 있는 태그를 쉽게 선택할 수 있을 때 사용합니다.

 

물론 next_siblings, previous_siblings와 거의 같은 next_sibling, previous_sibling 함수도 있습니다. 이들 함수는 리스트가 아니라 태그 하나만 반환한다는 점을 빼면 똑같이 동작합니다.

 

 

부모 다루기

페이지를 스크랩하다 보면, 자식이나 형제가 아니라 아주 가끔은 부모parent를 찾아야 할 때도 있습니다. 일반적으로 HTML 페이지에서 데이터를 수집할 목적으로 살펴볼 때는 보통 맨 위계층에서 시작해 원하는 데이터까지 어떻게 찾아 들어갈지 생각하기 마련입니다. 하지만 가끔 BeautifulSoup의 부모 검색 함수 .parent.parents가 필요할 때도 있습니다.

 

from urllib.request import urlopen

from bs4 import BeautifulSoup

 

html = urlopen("http://www.pythonscraping.com/pages/page3.html")

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> 태그)를 선택합니다.

③ 2에서 선택한 <td>previous_sibling(이 경우 제품 가격이 들어 있는 <td> 태그)을 선택합니다.

④ 태그에 들어 있는 텍스트인 $15.00를 선택합니다.

 

 

댓글 입력
자료실