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

한빛출판네트워크

IT/모바일

[파이썬으로 웹 크롤러 만들기] 첫 번째 웹 스크레이퍼(2/2)

한빛미디어

|

2016-12-08

|

by 한선용

45,522

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 None

 

    try:
      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 같은 범용 함수를 만들고 여기에 예외 처리를 철저하게 만들어두면 빠르고 믿을 수 있는 웹 스크레이퍼를 쉽게 만들 수 있습니다.

 

33.png

 

 

 

파이썬으로 웹 크롤러 만들기

초간단 나만의 웹 크롤러로 원하는 데이터를 가져오는 방법

bb.png

 

댓글 입력
자료실

최근 본 책0