본문 바로가기
카테고리 없음

파이썬 크롤링 넌타입 오류 해결

by heeday4753 2026. 3. 26.

크롤링을 하다 보면 분명 개발할 때는 잘 될 것 같았는데, 막상 .text를 붙이는 순간 NoneType 오류로 코드가 멈추는 일이 자주 생깁니다. 저도 처음에는 단순 문법 실수라고 생각했지만, 실제 원인은 HTML 구조를 잘못 이해했거나 없는 태그를 당연히 있다고 가정한 데 있었습니다. 이번 글에서는 제가 직접 겪은 흐름대로, 왜 이런 오류가 생겼는지 확인하고 어떻게 중단 없는 수집 코드로 바꿨는지 정리하겠습니다.

 

 

HTML 구조를 잘못 이해했을 때 NoneType 오류가 났습니다

 

처음 문제는 아주 단순했습니다. 특정 상품명이나 제목을 가져오려고 soup.find(...).text처럼 작성했는데, 실행하자마자 AttributeError: 'NoneType' object has no attribute 'text'가 발생했습니다. 이 오류는 대부분 find() 결과가 태그가 아니라 None일 때 생깁니다. 즉, .text가 문제인 것이 아니라 그 앞 단계에서 이미 태그를 찾지 못한 것입니다.

 

실제로 제 경우도 대충 기억해서 적은 것이 원인이었습니다. 브라우저 화면에는 분명 제목이 보였기 때문에 당연히 div.title 정도면 잡힐 것이라고 생각했지만, 개발자도구로 열어 보니 실제 구조는 strong 태그 안에 있었고, 클래스명도 제가 생각한 것과 달랐습니다. 또 어떤 페이지는 같은 사이트라도 광고 영역, 품절 영역, 추천 카드 영역의 HTML 구조가 달라서 같은 선택자로 전부 처리할 수 없었습니다. 그래서 어떤 항목은 정상 수집되고, 어떤 항목은 None이 되어 바로 멈췄습니다. Chrome DevTools에서는 요소를 우클릭해 Inspect로 열면 실제 DOM 트리에서 어떤 태그와 속성이 붙어 있는지 바로 확인할 수 있습니다. 이 과정을 거치지 않으면 “보이는 화면”“파싱되는 HTML”을 혼동하기 쉽습니다.

 

여기서 같이 자주 만난 오류가 list index out of range였습니다. 예를 들어 find_all()로 여러 개를 가져온 뒤 items[0].text처럼 첫 번째 값을 당연히 있다고 가정하면, 결과가 비어 있을 때 바로 이 오류가 납니다. 파이썬 공식 문서에서도 IndexError는 시퀀스 인덱스가 범위를 벗어났을 때 발생한다고 설명합니다. 결국 NoneType 오류와 list index out of range는 겉모습은 달라도 본질은 같습니다. HTML을 잘못 읽었거나, 결과가 없을 수 있다는 가능성을 코드에 반영하지 않은 것입니다.

 

개발자도구로 구조를 다시 보고 예외 처리까지 넣었습니다

 

문제를 풀기 위해 가장 먼저 한 일은 코드를 고치는 것이 아니라 HTML을 다시 보는 것이었습니다. 브라우저에서 수집하려는 영역을 우클릭한 뒤 Inspect를 열고, 실제 태그명과 클래스명, 부모-자식 관계를 다시 확인했습니다. 화면에 보인다고 해서 모두 같은 구조는 아니었고, 어떤 값은 서버에서 늦게 들어오거나 아예 다른 태그로 렌더링되는 경우도 있었습니다. 그래서 선택자를 한 줄로 바로 쓰지 않고, 먼저 find() 결과를 변수에 담아 중간값을 확인하는 방식으로 바꿨습니다. 이 습관 하나만 들여도 어디서 None이 생겼는지 찾기 쉬워집니다. Chrome DevToolsInspect 모드는 요소를 선택하면 DOM 위치를 바로 보여주기 때문에, 크롤링 선택자를 검증할 때 가장 빠른 출발점이 됩니다.

 

제가 실제로 정리한 코드는 아래와 같은 방식입니다.

from bs4 import BeautifulSoup
import requests

url = "https://example.com"
headers = {"User-Agent": "Mozilla/5.0"}

response = requests.get(url, headers=headers)
soup = BeautifulSoup(response.text, "html.parser")

title_tag = soup.find("strong", class_="item_title")

if title_tag:
    title = title_tag.text.strip()
else:
    title = "제목 없음"

print(title)

 

핵심은 세 가지입니다. 첫째, find() 결과를 바로 .text로 이어 붙이지 않고 변수에 담습니다. 둘째, if title_tag:처럼 존재 여부를 먼저 확인합니다. 셋째, 값이 없을 때는 기본값을 넣어 전체 수집 흐름이 끊기지 않게 합니다. Beautiful Soup 문서 기준으로 find()는 못 찾으면 None, find_all()은 못 찾으면 빈 리스트를 돌려주므로, 이 둘은 반드시 “없을 수도 있다”는 전제로 다뤄야 안전합니다.

find_all() 쪽도 같은 방식으로 바꿨습니다.

price_tags = soup.find_all("span", class_="price")

if price_tags:
    first_price = price_tags[0].text.strip()
else:
    first_price = "가격 없음"

print(first_price)

 

이 코드는 단순해 보이지만 실전에서는 꽤 중요합니다. 수집 대상 페이지가 100개일 때 한 페이지의 태그 구조만 달라도 전체 프로그램이 멈출 수 있기 때문입니다. 특히 쇼핑몰, 뉴스, 게시판처럼 레이아웃이 섞여 있는 페이지에서는 “없는 값은 건너뛴다”는 전략이 훨씬 안정적입니다. 추가로 디버깅 중에는 print(title_tag)print(len(price_tags))를 넣어서 선택자가 실제로 무엇을 반환하는지 먼저 확인하면 시간을 크게 줄일 수 있습니다.

 

 

중단 없이 수집하려면 선택자보다 예외를 먼저 생각해야 합니다

 

이번 문제를 겪고 나서 가장 크게 바뀐 점은, 이제는 크롤링 코드를 작성할 때 “어떻게 가져올까”보다 “못 가져오면 어떻게 처리할까”를 먼저 생각하게 되었다는 점입니다. 전에는 선택자만 정확하면 끝난다고 생각했지만, 실제 수집 환경에서는 페이지 구조가 바뀌거나 일부 항목이 비어 있거나, 내가 본 화면과 파싱 대상 HTML이 다를 수 있습니다. 그래서 .text 오류는 단순한 실수가 아니라, HTML 구조 확인과 예외 처리가 빠졌다는 신호에 가깝습니다. 

 

정리하면 이번 문제는 “.text에서 오류가 났다”가 핵심이 아니었습니다. 진짜 문제는 없는 태그를 있다고 가정한 채 접근한 것이었습니다. 해결 방법도 복잡하지 않았습니다. 개발자도구로 실제 구조를 확인하고, if문으로 존재 여부를 체크하고, 필요한 곳에는 try-except를 더해 수집이 중단되지 않도록 바꾸면 됩니다. 파이썬에서 인덱스 범위를 벗어나면 IndexError가 발생하듯, 크롤링도 결과가 없을 수 있다는 전제를 코드에 반영해야 합니다. 그렇게 바꾸고 나니 일부 값이 비어 있어도 프로그램 전체는 계속 돌았고, 나중에 누락 데이터만 따로 점검할 수 있게 되었습니다.


소개 및 문의 · 개인정보처리방침 · 면책조항

© 2026 블로그 이름