끄적끄적
2025.07.04 데이터 크롤링 세션 2 본문
| 튜터 | 김대영 튜터님 |
| 학습 목표 | 1. requests로 가져온 HTML 데이터를 BeautifulSoup 객체로 변환하는 방법을 익힙니다. 2. BeautifulSoup의 다양한 메서드 (find(), find_all(), select(), select_one())를 활용하여 HTML 요소에서 원하는 데이터를 추출합니다. 3. HTML 태그의 id와 class 속성을 이해하고 이를 활용하여 요소를 선택하는 방법을 배웁니다. 4. 크롤링 시 발생할 수 있는 문자 인코딩 문제를 이해하고 해결하는 방법을 알아봅니다. 5. 실제 웹페이지에서 여러 데이터를 수집하는 연습을 합니다. |
1️⃣ BeautifulSoup을 이용한 HTML 파싱 및 데이터 추출
BeautifulSoup은 HTML 또는 XML 문서에서 우리가 원하는 데이터를 쉽게 찾아낼 수 있도록 돕는 파이썬 라이브러리
복잡한 웹 페이지의 HTML 코드 속에서도 필요한 정보만 쏙쏙 뽑아낼 수 있는 강력한 도구
📌 BeautifulSoup 설치
pip install beautifulsoup4
📌 BeautifulSoup 객체 만들기
requests로 웹 페이지의 HTML 내용을 가져온 다음, 이 내용을 BeautifulSoup이 이해할 수 있는 형태로 변환해야 한다.
import requests
from bs4 import BeautifulSoup
url = "<https://www.example.com>" # 실제 크롤링할 웹사이트 URL로 변경하세요.
response = requests.get(url)
# HTML 내용을 'html.parser'를 이용해 BeautifulSoup 객체로 변환합니다.
# 'lxml' 파서가 더 빠르고 강력하지만, 설치가 필요하므로 여기서는 기본 파서를 사용합니다.
soup = BeautifulSoup(response.text, 'html.parser')
print("BeautifulSoup 객체 생성 완료!")
📌 HTML 요소 찾기: find()와 find_all()
- find(태그명, 속성=값): HTML 문서에서 가장 먼저 발견되는 특정 태그 하나를 찾는다.
- find_all(태그명, 속성=값): HTML 문서에서 모든 특정 태그들을 찾아서 리스트 형태로 반환한다.
예시:
html_doc = """
<html>
<body>
<h1 id="main-title">오늘의 뉴스</h1>
<p class="summary">날씨가 맑고 좋네요.</p>
<a href="/news/1">첫 번째 뉴스</a>
<a href="/news/2" class="important">두 번째 뉴스</a>
<img src="sunny.jpg" alt="맑은 날씨">
</body>
</html>
"""
soup = BeautifulSoup(html_doc, 'html.parser')
# <h1> 태그 하나 찾기
title_tag = soup.find('h1')
print(f"찾은 h1 태그: {title_tag}")
print(f"h1 태그의 텍스트: {title_tag.get_text()}") # 태그 안의 텍스트만 가져오기
# 모든 <a> (링크) 태그 찾기
all_links = soup.find_all('a')
print(f"\\n모든 링크 태그: {all_links}")
print(f"총 링크 개수: {len(all_links)}")
# 첫 번째 <a> 태그의 'href' 속성 값 가져오기
if all_links:
first_link = all_links[0]
print(f"첫 번째 링크의 텍스트: {first_link.get_text()}")
print(f"첫 번째 링크의 URL (href 속성): {first_link['href']}") # 속성은 딕셔너리처럼 접근
📌 속성을 이용한 요소 찾기: id와 class 활용
HTML 태그에는 id나 class 같은 중요한 속성들이 있다.
이 속성들을 이용하면 특정 요소를 더 정확하게 찾아낼 수 있다.
- id 속성: 웹 페이지에서 단 하나만 존재해야 하는 고유한 이름입니다. 주로 중요한 영역이나 특정 기능을 하는 요소를 식별할 때 사용한다.
- soup.find('div', id='main-content')
- class 속성: 여러 HTML 요소에 동일한 스타일이나 동작을 적용할 때 사용하는 그룹 이름입니다. 하나의 요소는 여러 클래스를 가질 수 있다.
- soup.find_all('p', class_='article-text')
예시:
# id가 'main-title'인 <h1> 태그 찾기
main_title_by_id = soup.find('h1', id='main-title')
print(f"\\nID로 찾은 제목: {main_title_by_id.get_text()}")
# class가 'summary'인 <p> 태그 찾기
summary_paragraph = soup.find('p', class_='summary')
print(f"Class로 찾은 요약: {summary_paragraph.get_text()}")
# class가 'important'인 모든 <a> 태그 찾기
important_links = soup.find_all('a', class_='important')
for link in important_links:
print(f"중요 링크: {link.get_text()} ({link['href']})")
2️⃣ CSS 선택자를 활용한 간편한 데이터 탐색: select()와 select_one()
CSS 선택자는 웹 디자이너나 개발자들이 특정 HTML 요소를 선택하여 스타일을 적용할 때 사용하는 강력한 문법
BeautifulSoup은 이 CSS 선택자를 그대로 사용하여 요소를 찾을 수 있는 기능을 제공한다.
- select_one(CSS_선택자): CSS 선택자에 해당하는 가장 먼저 발견되는 요소 하나를 찾는다. (find()와 유사)
- select(CSS_선택자): CSS 선택자에 해당하는 모든 요소들을 찾아서 리스트 형태로 반환한다. (find_all()과 유사)
📌 주요 CSS 선택자 문법
| 선택자 | 설명 | 예시 (soup.select()) |
| 태그명 | 특정 태그를 선택합니다. | p (모든 <p> 태그) |
| .클래스명 | 특정 클래스(class)를 가진 모든 요소를 선택합니다. | .summary (class="summary"인 요소) |
| #아이디명 | 특정 ID(id)를 가진 단일 요소를 선택합니다. | #main-title (id="main-title"인 요소) |
| 부모태그 자식태그 | 부모 태그 안에 있는 자식 태그를 선택합니다. | div p (모든 <div> 안에 있는 <p> 태그) |
| 태그명.클래스명 | 특정 태그이면서 특정 클래스를 가진 요소를 선택합니다. | p.summary (<p> 태그이면서 class="summary"인 요소) |
| 태그명#아이디명 | 특정 태그이면서 특정 ID를 가진 요소를 선택합니다. | h1#main-title (<h1> 태그이면서 id="main-title"인 요소) |
예시:
# class가 'summary'인 <p> 태그 찾기 (select_one 사용)
summary_by_css = soup.select_one('p.summary')
print(f"\\nCSS 선택자로 찾은 요약: {summary_by_css.get_text()}")
# ID가 'main-title'인 <h1> 태그 찾기 (select_one 사용)
title_by_css = soup.select_one('#main-title')
print(f"CSS 선택자로 찾은 제목: {title_by_css.get_text()}")
# 모든 <a> 태그 찾기 (select 사용)
all_links_by_css = soup.select('a')
print(f"CSS 선택자로 찾은 모든 링크 개수: {len(all_links_by_css)}")
# class가 'important'인 <a> 태그 모두 찾기
important_links_by_css = soup.select('a.important')
for link in important_links_by_css:
print(f"CSS 선택자로 찾은 중요 링크: {link.get_text()}")
# 'body' 태그 안에 있는 모든 'p' 태그 찾기
body_paragraphs = soup.select('body p')
for p in body_paragraphs:
print(f"Body 안의 단락: {p.get_text()}")
3️⃣ 크롤링 시 발생할 수 있는 문자 인코딩 문제
웹 페이지의 문자가 깨져서 보이거나 이상하게 나올 때가 있다.
이는 대부분 문자 인코딩 문제 때문이다.
웹 페이지는 다양한 언어(한글, 영어, 일본어 등)를 표현하기 위해 고유한 문자 인코딩(예: UTF-8, EUC-KR)을 사용한다.
requests는 대부분 자동으로 인코딩을 감지하지만, 때때로 잘못 감지하는 경우가 있다.
📌 인코딩 문제의 이해와 해결 방법
- 원인: 웹 서버가 HTTP 응답 헤더에 올바른 인코딩 정보를 주지 않거나, HTML 파일 내부에 명시된 인코딩 정보와 실제 인코딩이 다를 때 발생한다. 특히 오래된 한국어 웹사이트에서 EUC-KR 인코딩이 ISO-8859-1 등으로 잘못 감지되는 경우가 흔하다.
- 확인 방법:
- requests.get() 후 response.encoding 값을 출력하여 requests가 어떤 인코딩으로 인식했는지 확인한다.
- 웹 개발자 도구의 'Network' 탭에서 해당 페이지의 응답 헤더를 확인하거나, 'Elements' 탭에서 <head> 부분의 <meta charset="인코딩명"> 태그를 찾아 실제 인코딩을 파악한다.
- 해결 방법:
- response.encoding 직접 설정: 가장 간단한 방법. response.text를 읽기 전에 올바른 인코딩으로 명시적으로 설정해준다.
- response.content와 decode() 사용: response.content는 웹 페이지의 원본 바이트(byte) 데이터를 반환한다. 이 바이트 데이터를 .decode() 메서드를 사용하여 원하는 인코딩으로 직접 디코딩할 수 있다. 이는 response.encoding으로 해결되지 않을 때 유용하다.
import requests
url = "http://www.koreatimes.com/" # 인코딩 문제 발생 가능성 있는 웹사이트 (예시)
response = requests.get(url)
print(f"requests가 감지한 인코딩: {response.encoding}")
# 만약 한글이 깨진다면, 'EUC-KR'이나 'UTF-8' 등으로 직접 설정 시도
response.encoding = 'EUC-KR' # 또는 'UTF-8'
print(f"강제로 설정한 인코딩: {response.encoding}")
print(response.text[:500]) # 다시 출력하여 한글이 제대로 나오는지 확인
import requests
from bs4 import BeautifulSoup
url = "http://www.koreatimes.com/"
response = requests.get(url)
# chardet과 같은 라이브러리로 인코딩을 자동 감지할 수도 있지만 (선택 사항)
# 여기서는 바로 EUC-KR로 디코딩을 시도합니다.
try:
decoded_html = response.content.decode('EUC-KR')
soup = BeautifulSoup(decoded_html, 'html.parser')
print(f"EUC-KR로 디코딩 후 제목: {soup.title.get_text()}")
except UnicodeDecodeError:
print("EUC-KR 디코딩 실패. 다른 인코딩을 시도해 보세요 (예: 'UTF-8').")
# 다른 인코딩으로 재시도
decoded_html = response.content.decode('UTF-8', errors='ignore') # 에러 무시 옵션
soup = BeautifulSoup(decoded_html, 'html.parser')
print(f"UTF-8로 디코딩 후 제목: {soup.title.get_text()}")
4️⃣ 실습: 정적 웹페이지에서 여러 데이터 수집하기
이제 배운 내용을 바탕으로 실제 웹페이지에서 여러 종류의 데이터를 크롤링하고 정리하는 연습을 해보자.
시나리오: 온라인 도서 판매 사이트에서 특정 카테고리의 도서 제목, 저자, 가격을 크롤링하기
준비물:
- 크롬 브라우저와 개발자 도구
- Python 환경 (requests, beautifulsoup4 설치 완료)
진행 과정:
- 대상 웹페이지 선정 및 분석:
- 크롤링할 도서 목록 페이지를 선택한다.
- 개발자 도구 (Elements 탭)를 열어 도서 목록이 어떤 HTML 태그로 구성되어 있는지 확인한다. (예: 각 도서 정보가 div나 li 태그로 묶여 있고, 그 안에 제목, 저자, 가격 태그가 있는지)
- 도서 제목, 저자, 가격 각각이 어떤 태그, class, id를 사용하는지 정확히 파악합니다. 이것이 크롤링의 핵심
- requests로 HTML 내용 가져오기:
- 선정된 URL로 requests.get() 요청을 보낸다.
- User-Agent 헤더를 설정하여 웹 브라우저처럼 보이게 한다.
- 인코딩 문제가 발생할 수 있는지 확인하고, 필요하다면 해결한다.
- BeautifulSoup으로 파싱:
- 가져온 HTML 텍스트를 BeautifulSoup 객체로 만든다.
- 데이터 추출 전략 수립 및 코드 작성:
- 단계 1: 각 도서 정보를 포함하는 큰 블록 찾기. (soup.select('div.book-item') 또는 soup.find_all('li', class_='product') 등)
- 단계 2: 각 블록 안에서 제목, 저자, 가격 등 세부 정보 추출. (각 블록 변수에 대해 find(), select_one() 등을 사용)
- **get_text(strip=True)**를 사용하여 태그 안의 텍스트만 가져오고, 앞뒤 공백을 제거한다.
- 속성 값을 가져올 때는 태그변수['속성명'] 형태로 접근한다.
- 추출된 데이터 확인:
- 추출한 데이터를 리스트 안에 딕셔너리 형태로 저장하여 구조화한다.
- 결과를 간단하게 출력하여 데이터가 제대로 추출되었는지 확인한다. (데이터 저장 및 분석은 다음 회차에서 더 자세히 다룬다.)
코드
import requests
from bs4 import BeautifulSoup
import pandas as pd
def add_book(item):
title_tag = item.select_one('a')
author_pub_tag = item.select_one('.b-author')
price_tag = item.select_one('.b-price strong')
title = title_tag.get_text(strip=True) if title_tag else '제목없음'
price = price_tag.get_text(strip=True) if price_tag else '가격없음'
# 저자 + 출판사
author_pub = author_pub_tag.get_text(strip=True).split('|') if author_pub_tag else False
author = author_pub[0].strip() if author_pub[0] else '저자없음'
publisher = author_pub[1].strip() if author_pub[0] else '출판사없음'
book_info = {'제목' : title}
if author_pub:
book_info['저자'] = author
book_info['출판사'] = publisher
else:
book_info['저자'] = '저자없음'
book_info['출판사'] = '출판사없음'
book_info['가격'] = 'price'
return book_info
# 크롤링할 도서 목록 URL (실제 URL로 변경하세요)
url = "https://www.aladin.co.kr/shop/wbrowse.aspx?CID=1" # 예시 URL, 실제 동작 여부 확인 필요
headers = {
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36"
}
books_data, best_data = [], [] # 추출한 도서 정보를 저장할 리스트
col_name = ['제목', '저자', '출판사', '가격']
try:
response = requests.get(url, headers=headers, timeout=10) # 10초 타임아웃 설정
response.raise_for_status() # HTTP 에러 (4xx, 5xx) 발생 시 예외 발생
soup = BeautifulSoup(response.text, 'html.parser')
best_sellers = soup.select('div.b-bestseller')
newbooks = soup.select('div.b-newbook')
# 베스트셀러
if not best_sellers:
print("경고: 베스트셀러 도서 아이템을 찾을 수 없습니다. CSS 선택자를 다시 확인하세요.")
for best in best_sellers:
books = best.select('.b-text')
for item in books:
best_data.append(add_book(item))
# 일반 도서
if not newbooks:
print("경고: 도서 아이템을 찾을 수 없습니다. CSS 선택자를 다시 확인하세요.")
for newbook in newbooks:
books = newbook.select('.b-text')
for item in books:
books_data.append(add_book(item))
# 추출된 상위 5개 도서 정보 출력
print("\n--- 추출된 베스트셀러 도서 정보 (상위 5개) ---")
for book in best_data[:5]:
print(book)
print(f"\n총 {len(best_data)}개의 베스트셀러 도서 정보 추출 완료.\n")
print("\n--- 추출된 일반 도서 정보 (상위 5개) ---")
for book in books_data[:5]:
print(book)
print(f"\n총 {len(books_data)}개의 일반 도서 정보 추출 완료.")
best_data = pd.DataFrame(best_data, columns=col_name)
books_data = pd.DataFrame(books_data, columns=col_name)
except requests.exceptions.HTTPError as e:
print(f"HTTP 오류 발생: {e}")
print(f"상태 코드: {e.response.status_code}")
except requests.exceptions.ConnectionError as e:
print(f"네트워크 연결 오류: {e}")
except requests.exceptions.Timeout as e:
print(f"요청 타임아웃: {e}")
except requests.exceptions.RequestException as e:
print(f"그 외 요청 오류: {e}")
except Exception as e:
print(f"데이터 추출 중 오류 발생: {e}")
2025.07.04 - [[스파르타]내일배움캠프 데이터 분석 트랙] - 38일차 - 데이터 크롤링 1
38일차 - 데이터 크롤링 1
튜터김대영 튜터님학습 목표1. 웹의 동작 방식과 주요 구성 요소에 대해 이해합니다.2. HTML과 CSS의 기본적인 구조 및 역할을 파악합니다.3. 웹 개발자 도구의 핵심 기능을 활용하여 웹 페이지를
kminx.tistory.com
'[스파르타]내일배움캠프 데이터 분석 트랙 > Session' 카테고리의 다른 글
| 2025.07.07 Pandas 실무 기초 세션 3 (0) | 2025.07.09 |
|---|---|
| 2025.07.07 머신러닝 세션 3 (5) | 2025.07.09 |
| 2025.07.04 Pandas 실무 기초 세션 2 (0) | 2025.07.09 |
| 2025.07.03 통계 세션 5 (1) | 2025.07.09 |
| 2025.07.02 데이터 크롤링 세션 1 (2) | 2025.07.09 |