웹 스크래핑
BeautifulSoup을 사용하여 웹 페이지에서 원하는 데이터를 추출하는 방법을 배워봅시다.
웹 스크래핑이란?
웹 스크래핑은 웹 페이지에서 자동으로 데이터를 수집하는 기술입니다. 뉴스 기사, 상품 가격, 날씨 정보 등 웹에 있는 다양한 데이터를 프로그래밍적으로 추출할 수 있습니다.
주요 용도
- 가격 비교 및 모니터링
- 뉴스 및 콘텐츠 수집
- 데이터 분석 및 연구
- 검색 엔진 인덱싱
- 시장 조사 및 경쟁사 분석
주의사항
- robots.txt 확인: 웹사이트의 크롤링 정책 준수
- 이용 약관 검토: 법적 문제 방지
- 서버 부하 고려: 요청 간격 조절
- 개인정보 보호: 민감한 정보 수집 금지
설치하기
# BeautifulSoup 4
pip install beautifulsoup4
# HTML 파서 (lxml이 더 빠름)
pip install lxml
# 또는 html5lib (더 관대한 파싱)
pip install html5lib
# requests (HTTP 요청용)
pip install requests
기본 사용법
HTML 가져오기와 파싱
import requests
from bs4 import BeautifulSoup
# 웹 페이지 가져오기
url = 'https://example.com'
response = requests.get(url)
html = response.text
# BeautifulSoup 객체 생성
soup = BeautifulSoup(html, 'lxml')
# 전체 HTML 출력 (보기 좋게)
print(soup.prettify())
# 제목 가져오기
title = soup.title
print(title.string) # 제목 텍스트
# 모든 텍스트 가져오기
text = soup.get_text()
print(text)
파서 종류
# lxml (빠르고 유연함, 권장)
soup = BeautifulSoup(html, 'lxml')
# html.parser (내장, 추가 설치 불 필요)
soup = BeautifulSoup(html, 'html.parser')
# html5lib (가장 관대함, 느림)
soup = BeautifulSoup(html, 'html5lib')
# XML 파싱
soup = BeautifulSoup(xml, 'xml')
태그 선택하기
기본 선택 방법
from bs4 import BeautifulSoup
html = '''
<html>
<head><title>My Page</title></head>
<body>
<h1 id="main-title">Welcome</h1>
<div class="content">
<p class="intro">First paragraph</p>
<p class="text">Second paragraph</p>
<a href="/page1">Link 1</a>
<a href="/page2">Link 2</a>
</div>
</body>
</html>
'''
soup = BeautifulSoup(html, 'lxml')
# 첫 번째 태그 찾기
h1 = soup.find('h1')
print(h1.string) # Welcome
# 모든 태그 찾기
paragraphs = soup.find_all('p')
for p in paragraphs:
print(p.string)
# ID로 찾기
main_title = soup.find(id='main-title')
print(main_title.string)
# 클래스로 찾기
intro = soup.find(class_='intro')
print(intro.string)
# 여러 조건
link = soup.find('a', href='/page1')
print(link.string)
CSS 선택자 사용
# CSS 선택자로 찾기 (더 직관적)
h1 = soup.select_one('h1#main-title')
print(h1.string)
# 클래스 선택
intro = soup.select_one('.intro')
paragraphs = soup.select('.content p')
# 자식 선택자
content_links = soup.select('div.content > a')
# 속성 선택자
external_links = soup.select('a[href^="http"]')
# 여러 개 선택
items = soup.select('.item')
for item in items:
print(item.text)
데이터 추출하기
텍스트 추출
# 태그의 텍스트
text = tag.string # 직접 자식 텍스트만
text = tag.get_text() # 모든 하위 텍스트
text = tag.text # get_text()와 동일
# 공백 처리
text = tag.get_text(strip=True) # 앞뒤 공백 제거
# 구분자 지정
text = tag.get_text(separator=' | ') # 태그 사이에 구분자
# 예제
html = '<div> <p>Hello</p> <p>World</p> </div>'
soup = BeautifulSoup(html, 'lxml')
div = soup.find('div')
print(div.get_text()) # " Hello World "
print(div.get_text(strip=True)) # "HelloWorld"
print(div.get_text(separator=' | ', strip=True)) # "Hello | World"
속성 추출
# 속성 가져오기
link = soup.find('a')
# 딕셔너리 방식
href = link['href']
title = link.get('title', 'No title') # 기본값 설정
# 모든 속성
attrs = link.attrs
print(attrs) # {'href': '/page1', 'class': ['link'], 'id': 'first'}
# 여러 값을 가진 속성 (class, rel 등)
classes = link.get('class') # 리스트 반환
print(classes) # ['link', 'external']
# 예제: 모든 이미지 URL 추출
images = soup.find_all('img')
for img in images:
src = img.get('src')
alt = img.get('alt', 'No description')
print(f"Image: {src} - {alt}")
탐색 및 순회
# 부모 탐색
tag = soup.find('p')
parent = tag.parent
print(parent.name)
# 모든 부모
for parent in tag.parents:
print(parent.name)
# 형제 탐색
next_sibling = tag.next_sibling # 다음 형제 (텍스트 포함)
next_tag = tag.find_next_sibling() # 다음 태그
prev_tag = tag.find_previous_sibling() # 이전 태그
# 모든 형제
for sibling in tag.next_siblings:
print(sibling)
# 자식 탐색
children = tag.children # 직접 자식 (이터레이터)
descendants = tag.descendants # 모든 하위 요소
# 예제: 테이블 탐색
table = soup.find('table')
for row in table.find_all('tr'):
cells = row.find_all(['td', 'th'])
data = [cell.get_text(strip=True) for cell in cells]
print(data)
실전 예제
예제 1: 뉴스 헤드라인 수집
import requests
from bs4 import BeautifulSoup
from datetime import datetime
def scrape_news_headlines(url):
"""뉴스 사이트에서 헤드라인을 수집합니다."""
try:
# 헤더 설정 (봇 차단 방지)
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
response = requests.get(url, headers=headers, timeout=10)
response.raise_for_status()
soup = BeautifulSoup(response.text, 'lxml')
# 뉴스 기사 찾기 (사이트마다 다름)
articles = soup.select('.news-item') # CSS 선택자는 실제 사이트에 맞게 수정
news_list = []
for article in articles:
# 제목
title_tag = article.select_one('.title')
title = title_tag.get_text(strip=True) if title_tag else 'No title'
# 링크
link_tag = article.select_one('a')
link = link_tag['href'] if link_tag else ''
# 절대 URL로 변환
if link and not link.startswith('http'):
from urllib.parse import urljoin
link = urljoin(url, link)
# 요약
summary_tag = article.select_one('.summary')
summary = summary_tag.get_text(strip=True) if summary_tag else ''
# 시간
time_tag = article.select_one('.time')
timestamp = time_tag.get_text(strip=True) if time_tag else ''
news_list.append({
'title': title,
'link': link,
'summary': summary,
'timestamp': timestamp
})
return news_list
except Exception as e:
print(f"스크래핑 오류: {e}")
return []
# 사용 예시
url = 'https://news.example.com'
news = scrape_news_headlines(url)
for i, article in enumerate(news[:10], 1):
print(f"\n{i}. {article['title']}")
print(f" {article['link']}")
print(f" {article['summary'][:100]}...")
print(f" 시간: {article['timestamp']}")