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

한빛출판네트워크

IT/모바일

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

한빛미디어

|

2016-12-15

|

by 한선용

51,514

미켈란젤로는 다비드상 같은 걸작을 어떻게 만들었는지 질문을 받았을 때 이런 명언을 남겼습니다.

“쉽습니다. 돌에서 다비드처럼 보이지 않는 부분을 깎아내기만 하면 됩니다.”

 

웹 스크레이핑이 조각과 비슷한 부분은 별로 없지만, 복잡한 웹 페이지에서 필요한 정보를 얻어낸다는 점에서는 미켈란젤로와 비슷한 방식을 따라야 합니다. 원하지 않는 콘텐츠를 깎아내서 필요한 정보를 얻는 방법은 여러 가지가 있습니다. 이 장에서는 복잡한 HTML 페이지를 분석해서 원하는 정보만 추출하는 방법을 알아보겠습니다.

 

 

2.1 닭 잡는 데 소 잡는 칼을 쓸 필요는 없습니다

 

복잡한 태그를 만나면 당장 달려들어 여러 줄의 코드를 써서라도 필요한 정보를 추출하고 싶은 생각이 들 겁니다. 하지만 이 장에서 소개하는 테크닉을 부주의하게 사용한다면 코드는 디버그하기 어려워지거나, 취약해지거나, 혹은 둘 다가 될 수도 있습니다. 시작하기 전에, 고급 HTML 분석을 쓰지 않아도 필요한 결과를 얻을 수 있는 방법을 몇 가지 알아봅시다.

당신이 원하는 콘텐츠가 있습니다. 그 콘텐츠는 이름일 수도, 통계 자료일 수도, 텍스트 블록 일 수도 있겠죠. 그리고 그 콘텐츠는 20단계나 되는 HTML 덩어리 속에, 단서가 될 만한 태그나 속성 하나 없이 파묻혀 있을 수도 있습니다. 당장 달려들어, 다음과 비슷한 코드를 짰다고 합시다.

bsObj.findAll("table")[4].findAll("tr")[2].find("td").findAll("div")[1].find("a")

 

별로 좋아 보이지는 않는군요. 간결함이나 우아함은 찾아볼 수 없을뿐더러, 사이트 관리자가 사이트를 조금만 수정하더라도 웹 스크레이퍼의 동작이 멈출 수 있습니다. 그럼 어떻게 해야할까요?

 

  • ‘페이지 인쇄’ 같은 링크를 찾아보거나, 더 나은 HTML 구조를 갖춘 모바일 버전 사이트를 찾아보십시오(모바일 장치에서 접근하는 것처럼 속이거나 모바일 사이트 버전을 가져오는 방법은 12장에서 다룹니다).
  • 자바스크립트 파일에 숨겨진 정보를 찾아보십시오. 물론 이렇게 하려면 자바스크립트 파일을 불러와서 분석해야 할 겁니다. 필자는 사이트에 포함된 구글 지도를 조작하는 자바스크립트를 살펴본 후 위도와 경도가 포함된 거리 주소를, 깔끔하게 배열로 정리된 형태로 수집했던 일이 있습니다.
  • 중요한 정보는 페이지 타이틀에 있을 때가 대부분이지만, 원하는 정보가 페이지 URL에 들어 있을 때도 있습니다.
  • 원하는 정보가 오직 이 웹사이트에만 있다면 할 수 있는 일이 더는 없을 수 있습니다. 그렇지 않다면, 이 정보를 다른 소스에서 가져올 수는 없는지 생각해보십시오. 다른 웹사이트에 같은 데이터가 있지는 않을까요? 이 웹사이트에 있는 데이터가 혹시 다른 웹사이트에서 수집한 것은 아닐까요?

 

데이터가 깊숙이 파묻혀 있거나 정형화되지 않았을수록, 곧바로 코드부터 짜서는 안 됩니다. 심호흡을 하고 대안이 없는지 생각해보십시오. 대안이 없다고 확신한다면, 이 장의 내용이 도움이 될 겁니다

 

 

2.2 다시 BeautifulSoup

 

1장에서는 BeautifulSoup를 설치하고 실행하는 방법을 훑어봤고, 한 번에 객체 하나씩 선택하는 방법도 알아봤습니다. 이 섹션에서는 속성을 통해 태그를 검색하는 법, 태그 목록을 다루는 법, 트리 내비게이션을 분석하는 법을 알아보겠습니다.

 

거의 모든 웹사이트에 스타일시트가 존재합니다. 웹사이트의 스타일 계층은 육안으로 해석하는 것을 위해 만들어진 것이라 웹 스크레이핑에는 별 도움이 되지 않는다고 생각할 수 있겠지만, 사실 CSS의 등장은 웹 스크레이퍼에도 큰 도움이 되었습니다. CSS는 HTML 요소를 구분해서 서로 다른 스타일을 적용합니다. 예를 들어 다음과 같은 태그가 있다고 해봅시다.

<span class="green"></span>

 

그리고 다음과 같은 태그도 있다고 합시다.

<span class="red"></span>

 

이 경우 웹 스크레이퍼는 클래스를 이용해 쉽게 이 태그들을 구별할 수 있습니다. 예를 들어 BeautifulSoup는 빨간색 텍스트만 전부 수집하고 녹색 텍스트는 수집하지 않을 수 있습니다. CSS는 이런 속성을 통해 사이트에 스타일을 적용하며, 오늘날의 웹사이트 대부분은 이런 클래스(class)와 ID(id) 속성이 가득합니다.

 

http://www.pythonscraping.com/pages/warandpeace.html 페이지를 스크랩하는 예제 웹스크레이퍼를 만들어봅시다.

이 페이지에서 등장인물이 말하는 대사는 빨간색으로, 등장인물의 이름은 녹색으로 표시되어 있습니다. 다음 소스 코드 샘플을 보면 span 태그에 적절한 CSS 클래스가 붙어 있습니다.

<span class="red">Heavens! what a virulent attack!</span>" replied <span class=green">the prince</span>, not in the least disconcerted by this reception.

 

페이지 전체를 가져온 다음, BeautifulSoup 객체로 1장에서 썼던 것과 비슷한 프로그램을 만들 수 있습니다.

from urllib.request import urlopen

from bs4 import BeautifulSoup

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

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

 

BeautifulSoup 객체에 findAll 함수를 쓰면 <span class="green"></span> 태그에 들어 있는 텍스트만 선택해서 고유명사로 이루어진 파이썬 리스트를 추출할 수 있습니다(findAll은 대단히 유연한 함수이며 이 책 후반에서 매우 자주 사용합니다).

nameList = bsObj.findAll("span", {"class":"green"})

for name in nameList:

    print(name.get_text())

 

이 코드는 『전쟁과 평화』에 등장하는 모든 고유명사를 순서대로 출력합니다. 어떤 원리로 그렇게 동작하는 걸까요? 이전에는 bsObj.tagName을 호출해서 페이지에 처음 나타난 태그를 찾아냈습니다. 이번에는 bsObj.findAll(tagName, tagAttributes)을 호출해서 첫 번째 태그만이 아니라 페이지의 태그 전체를 찾은 겁니다.

 

이름 리스트를 만든 뒤에는 리스트의 모든 이름을 순회하며 name.get_text()를 호출해 태그를 제외하고 콘텐츠만 출력합니다.

 


[NOTE get_text()를 쓸 때와 태그를 보존할 때]

 

.get_text()는 현재 문서에서 모든 태그를 제거하고 텍스트만 들어 있는 문자열을 반환합니다. 예를 들어 하이퍼링크, 문단, 기타 태그가 여럿 들어 있는 텍스트 블록에 사용하면 태그 없는 텍스트만 남습니다.

텍스트 블록보다는 BeautifulSoup 객체에 사용하는 게 원하는 결과를 얻기가 훨씬 쉽습니다. .get_text()는 항상 마지막, 즉 최종 데이터를 출력하거나 저장, 조작하기 직전에만 써야 합니다. 일반적으로는 문서의 태그 구조를 가능한 유지해야 합니다.

 

 

▼ 초간단 나만의 웹 크롤러로

원하는 데이터를 가져오는 방법

댓글 입력
자료실