미켈란젤로는 다비드상 같은 걸작을 어떻게 만들었는지 질문을 받았을 때 이런 명언을 남겼습니다.
“쉽습니다. 돌에서 다비드처럼 보이지 않는 부분을 깎아내기만 하면 됩니다.”
웹 스크레이핑이 조각과 비슷한 부분은 별로 없지만, 복잡한 웹 페이지에서 필요한 정보를 얻어낸다는 점에서는 미켈란젤로와 비슷한 방식을 따라야 합니다. 원하지 않는 콘텐츠를 깎아내서 필요한 정보를 얻는 방법은 여러 가지가 있습니다. 이 장에서는 복잡한 HTML 페이지를 분석해서 원하는 정보만 추출하는 방법을 알아보겠습니다.
2.1 닭 잡는 데 소 잡는 칼을 쓸 필요는 없습니다
복잡한 태그를 만나면 당장 달려들어 여러 줄의 코드를 써서라도 필요한 정보를 추출하고 싶은 생각이 들 겁니다. 하지만 이 장에서 소개하는 테크닉을 부주의하게 사용한다면 코드는 디버그하기 어려워지거나, 취약해지거나, 혹은 둘 다가 될 수도 있습니다. 시작하기 전에, 고급 HTML 분석을 쓰지 않아도 필요한 결과를 얻을 수 있는 방법을 몇 가지 알아봅시다.
당신이 원하는 콘텐츠가 있습니다. 그 콘텐츠는 이름일 수도, 통계 자료일 수도, 텍스트 블록 일 수도 있겠죠. 그리고 그 콘텐츠는 20단계나 되는 HTML 덩어리 속에, 단서가 될 만한 태그나 속성 하나 없이 파묻혀 있을 수도 있습니다. 당장 달려들어, 다음과 비슷한 코드를 짰다고 합시다.
bsObj.findAll("table")[4].findAll("tr")[2].find("td").findAll("div")[1].find("a")
별로 좋아 보이지는 않는군요. 간결함이나 우아함은 찾아볼 수 없을뿐더러, 사이트 관리자가 사이트를 조금만 수정하더라도 웹 스크레이퍼의 동작이 멈출 수 있습니다. 그럼 어떻게 해야할까요?
데이터가 깊숙이 파묻혀 있거나 정형화되지 않았을수록, 곧바로 코드부터 짜서는 안 됩니다. 심호흡을 하고 대안이 없는지 생각해보십시오. 대안이 없다고 확신한다면, 이 장의 내용이 도움이 될 겁니다
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()는 항상 마지막, 즉 최종 데이터를 출력하거나 저장, 조작하기 직전에만 써야 합니다. 일반적으로는 문서의 태그 구조를 가능한 유지해야 합니다.
▼ 초간단 나만의 웹 크롤러로
원하는 데이터를 가져오는 방법
최신 콘텐츠