1.2.3 신뢰할 수 있는 연결
웹은 엉망진창입니다. 데이터 형식은 제대로 지켜지지 않고 웹사이트는 자주 다운되며 닫는 태그도 종종 빠져 있습니다. 웹 스크레이핑에서 가장 좌절스러운 경험은 스크레이퍼를 실행해놓고 모든 데이터가 데이터베이스에 저장되어 있길 꿈꾸며 잠들었는데, 다음 날 일어나보니 자리를 뜨자마자 스크레이퍼가 예기치 못한 데이터 형식에 부딪혀 에러를 일으키곤 멈춰 있을 때입니다. 이런 상황이라면 그 웹사이트(그리고 엉망인 데이터)를 만든 개발자를 저주하고 싶겠지만, 사실 그 상황을 예상하지 못한 당신 자신을 탓해야 합니다.
스크레이퍼의 임포트 문 다음 행을 살펴보고 예외를 어떻게 처리할지 생각해봅시다.
html = urlopen("http://www.pythonscraping.com/pages/page1.html")
이 행에서 문제가 생길 수 있는 부분은 크게 두 가지입니다.
• 페이지를 찾을 수 없거나, URL 해석에서 에러가 생긴 경우
• 서버를 찾을 수 없는 경우
첫 번째 상황에서는 HTTP 에러가 반환될 것입니다. 이 HTTP 에러는 “404 Page NotFound”, “500 Internal Server Error” 등입니다. 이런 모든 경우에 urlopen 함수는 범용 예외인 HTTPError를 일으킵니다. 이 예외는 다음과 같이 처리합니다.
from urllib.request import urlopen
from urllib.request import HTTPError
from bs4 import BeautifulSoup
try:
html = urlopen("http://www.pythonscraping.com/pages/error.html")
except HTTPError as e:
print(e)
# null을 반환하거나, break 문을 실행하거나, 기타 다른 방법을 사용
else:
# 프로그램을 계속 실행합니다. except 절에서 return이나 break를 사용했다면
# 이 else 절은 필요 없습니다.
이제 HTTP 에러 코드가 반환되면 프로그램은 에러를 출력하고 else 문은 실행하지 않습니다.
물론 페이지를 서버에서 성공적으로 가져왔어도 페이지 콘텐츠가 예상과 전혀 다를 수 있습니다. BeautifulSoup 객체에 들어 있는 태그에 접근할 때마다 그 태그가 실제 존재하는지 체크하는 편이 좋습니다. 존재하지 않는 태그에 접근을 시도하면 BeautifulSoup는 None 객체를 반환합니다. 문제는 None 객체 자체에 태그가 있다고 가정하고 그 태그에 접근하려 하면 AttributeError가 일어난다는 겁니다.
다음 코드에서 nonExistentTag는 존재한다고 가정하는 태그이며 실제 BeautifulSoup 함수 이름은 아닙니다.
print(bsObj.nonExistentTag)
위 코드는 None 객체를 반환합니다. None 객체는 처리하거나 체크할 때 아무 문제도 없습니다. 문제는 다음 예제처럼 None이 반환될 수 있음을 무시하고 None 객체에 어떤 함수를 호출하는 경우입니다.
print(bsObj.nonExistentTag.someTag)
위 코드는 다음과 같이 예외를 일으킵니다.
AttributeError: 'NoneType' object has no attribute 'someTag'
그럼 이런 두 가지 상황에 어떻게 대응해야 할까요? 가장 쉬운 방법은 두 상황을 명시적으로 체크하는 겁니다.
try:
badContent = bsObj.nonExistingTag.anotherTag
except AttributeError as e:
print("Tag was not found")
else:
if badContent == None:
print ("Tag was not found")
else:
print(badContent)
이렇게 가능한 에러를 모두 체크하고 처리하는 게 처음에는 지겨워 보일 수 있지만, 코드를 조금만 수정하면 좀 더 쉽게 읽을 수 있게 만들 수 있습니다. 예를 들어 다음 코드는 같은 스크레이퍼를 조금 다르게 쓴 겁니다.
from urllib.request import urlopen
from urllib.error import HTTPError
from bs4 import BeautifulSoup
def getTitle(url):
try:
html = urlopen(url)
except HTTPError as e:
return Nonetry:
bsObj = BeautifulSoup(html.read(), "html.parser")
title = bsObj.body.h1
except AttributeError as e:
return None
return title
title = getTitle("http://www.pythonscraping.com/pages/page1.html")
if title == None:
print("Title could not be found")
else:
print(title)
이 예제에서는 페이지 타이틀을 반환하거나, 어떤 문제가 있으면 None 객체를 반환하는 getTitle 함수를 만듭니다. getTitle 내부에서는 이전 예제와 마찬가지로 HTTPError를 체크하고 BeautifulSoup 행 두 개를 try 문으로 캡슐화합니다. 이 두 행 중 어느 행이라도 AttributeError를 일으킬 수 있습니다. 예를 들어 서버가 존재하지 않으면 html은 None 객체이고 html.read()가 AttributeError를 일으킬 겁니다. try 문 하나에 원하는 만큼 여러 행을 넣을 수도 있고, AttributeError를 일으킬 수 있는 별도의 함수도 어느 시점에서든 호출할 수 있습니다.
스크레이퍼를 만들 때는 코드의 전반적 패턴에 대해 생각해야 예외도 처리하고 읽기도 쉽게 만들 수 있습니다. 코드를 많이 재사용하고 싶을 텐데, getSiteHTML이나 getTitle 같은 범용 함수를 만들고 여기에 예외 처리를 철저하게 만들어두면 빠르고 믿을 수 있는 웹 스크레이퍼를 쉽게 만들 수 있습니다.
파이썬으로 웹 크롤러 만들기
초간단 나만의 웹 크롤러로 원하는 데이터를 가져오는 방법
최신 콘텐츠