웹사이트에서 필요한 데이터만 추출하고 싶으신가요? HTML 코드 전체가 아닌 특정 요소의 값만 가져오는 방법이 궁금하신가요? 이 글에서는 BeautifulSoup과 Requests를 활용한 실전 웹 크롤링 기법을 완벽하게 알려드립니다.
웹 크롤링 기본 원리
웹 크롤링은 requests로 HTML을 가져오고, BeautifulSoup으로 파싱한 후 원하는 데이터를 추출하는 3단계 과정입니다. 검색 순위 키워드, 뉴스 제목, 상품 가격 등 모든 웹 데이터를 자동으로 수집할 수 있습니다.
기본 코드 구조
HTML 전체 가져오기
import requests
from bs4 import BeautifulSoup
url = '<https://www.naver.com>'
html = requests.get(url)
soup = BeautifulSoup(html.text, 'html.parser')
print(soup.prettify()) # HTML을 보기 좋게 출력
핵심 요소:
requests.get(url): 웹페이지 요청html.text: HTML 문자열 추출BeautifulSoup(html.text, 'html.parser'): HTML 파싱
CSS 선택자로 요소 찾기
select() – 여러 개 찾기
import requests
from bs4 import BeautifulSoup
url = '<https://example.com>'
html = requests.get(url)
soup = BeautifulSoup(html.text, 'html.parser')
# CSS 선택자로 여러 요소 찾기
elements = soup.select('div.content')
for element in elements:
print(element.text)
CSS 선택자 기본:
div: 모든 div 태그.classname: 특정 class#idname: 특정 iddiv.content: div 태그 중 content 클래스div > p: div의 직계 자식 p 태그div p: div 안의 모든 p 태그
select_one() – 하나만 찾기
# 첫 번째 매칭되는 요소만 가져오기
element = soup.select_one('h1.title')
if element:
print(element.text)
else:
print("요소를 찾을 수 없습니다")
차이점: select()는 리스트 반환, select_one()은 단일 요소 또는 None 반환
태그 검색 메서드
find() vs find_all()
import requests
from bs4 import BeautifulSoup
url = '<https://example.com>'
html = requests.get(url)
soup = BeautifulSoup(html.text, 'html.parser')
# 첫 번째 h1 태그 찾기
first_h1 = soup.find('h1')
print(first_h1.text)
# 모든 h1 태그 찾기
all_h1 = soup.find_all('h1')
for h1 in all_h1:
print(h1.text)
# class로 찾기
content_div = soup.find('div', class_='content')
print(content_div.text)
# id로 찾기
header = soup.find('div', id='header')
print(header.text)
# 여러 조건
result = soup.find('a', class_='link', href=True)
find vs select 비교:
find(): 파이썬 문법, 간단한 검색select(): CSS 선택자, 복잡한 검색
값 추출 방법
텍스트 추출
import requests
from bs4 import BeautifulSoup
url = '<https://example.com>'
html = requests.get(url)
soup = BeautifulSoup(html.text, 'html.parser')
# 텍스트만 추출
element = soup.select_one('h1.title')
text = element.text
print(text)
# 또는
text = element.get_text()
print(text)
# 공백 제거
text = element.text.strip()
print(text)
# 줄바꿈 제거 및 공백 정리
text = ' '.join(element.text.split())
print(text)
text vs get_text():
.text: 속성으로 접근.get_text(): 메서드, 옵션 설정 가능
속성값 추출
# href 속성 가져오기
link = soup.select_one('a.main-link')
href = link['href']
print(href)
# 또는
href = link.get('href')
print(href)
# 이미지 src
img = soup.select_one('img.thumbnail')
src = img['src']
print(src)
# 여러 속성
attrs = link.attrs
print(attrs) # {'href': '...', 'class': ['main-link'], ...}
# 속성 존재 확인
if link.has_attr('href'):
print(link['href'])
안전한 접근: get() 메서드를 사용하면 속성이 없어도 None을 반환합니다.
data 속성 추출
# data-* 속성
element = soup.select_one('div.item')
data_id = element['data-id']
data_price = element['data-price']
print(f"ID: {data_id}, 가격: {data_price}")
실전 크롤링 예제
네이버 실시간 검색어
import requests
from bs4 import BeautifulSoup
def crawl_naver_realtime():
url = '<https://www.naver.com>'
# User-Agent 추가 (차단 방지)
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
html = requests.get(url, headers=headers)
soup = BeautifulSoup(html.text, 'html.parser')
# CSS 선택자로 검색어 추출
keywords = soup.select('.ah_k')
print("🔥 네이버 실시간 검색어")
for i, keyword in enumerate(keywords[:10], 1):
print(f"{i}. {keyword.text.strip()}")
crawl_naver_realtime()
핵심: User-Agent를 추가해 봇 차단을 우회합니다.
뉴스 헤드라인 수집
import requests
from bs4 import BeautifulSoup
def crawl_news_headlines(url):
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
html = requests.get(url, headers=headers)
soup = BeautifulSoup(html.text, 'html.parser')
# 뉴스 제목 찾기 (사이트마다 다름)
headlines = soup.select('a.news_tit')
news_list = []
for headline in headlines:
title = headline.text.strip()
link = headline['href']
news_list.append({
'title': title,
'link': link
})
return news_list
# 사용 예시
url = '<https://news.naver.com/>'
news = crawl_news_headlines(url)
for i, item in enumerate(news[:5], 1):
print(f"{i}. {item['title']}")
print(f" {item['link']}\\\\n")
구조화: 딕셔너리 리스트로 데이터를 정리합니다.
쇼핑몰 상품 정보
import requests
from bs4 import BeautifulSoup
def crawl_product_info(url):
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
html = requests.get(url, headers=headers)
soup = BeautifulSoup(html.text, 'html.parser')
# 상품명
product_name = soup.select_one('h1.product-title')
# 가격
price = soup.select_one('span.price')
# 이미지
image = soup.select_one('img.product-image')
product_info = {
'name': product_name.text.strip() if product_name else 'N/A',
'price': price.text.strip() if price else 'N/A',
'image_url': image['src'] if image else 'N/A'
}
return product_info
# 사용 예시
product = crawl_product_info('<https://example-shop.com/product/123>')
print(f"상품명: {product['name']}")
print(f"가격: {product['price']}")
print(f"이미지: {product['image_url']}")
None 체크: 요소가 없을 때를 대비한 안전한 코드입니다.
테이블 데이터 추출
import requests
from bs4 import BeautifulSoup
import pandas as pd
def crawl_table_data(url):
html = requests.get(url)
soup = BeautifulSoup(html.text, 'html.parser')
# 테이블 찾기
table = soup.select_one('table.data-table')
# 헤더 추출
headers = []
for th in table.select('thead th'):
headers.append(th.text.strip())
# 데이터 추출
rows = []
for tr in table.select('tbody tr'):
row = []
for td in tr.select('td'):
row.append(td.text.strip())
rows.append(row)
# DataFrame 생성
df = pd.DataFrame(rows, columns=headers)
return df
# 사용 예시
df = crawl_table_data('<https://example.com/data-table>')
print(df.head())
df.to_csv('table_data.csv', index=False)
판다스 연동: 테이블 데이터를 DataFrame으로 쉽게 변환합니다.
고급 크롤링 기법
페이지네이션 처리
import requests
from bs4 import BeautifulSoup
import time
def crawl_multiple_pages(base_url, max_pages=5):
all_items = []
for page in range(1, max_pages + 1):
url = f"{base_url}?page={page}"
print(f"🔍 페이지 {page} 크롤링 중...")
html = requests.get(url)
soup = BeautifulSoup(html.text, 'html.parser')
items = soup.select('div.item')
for item in items:
title = item.select_one('h3.title').text.strip()
all_items.append(title)
# 서버 부담 방지
time.sleep(1)
return all_items
# 사용 예시
items = crawl_multiple_pages('<https://example.com/list>', max_pages=3)
print(f"총 {len(items)}개 항목 수집")
예의: 페이지 간 1초 딜레이로 서버 부담을 줄입니다.
동적 검색어 처리
import requests
from bs4 import BeautifulSoup
def search_and_crawl(keyword):
# 검색 URL 생성
search_url = f"<https://example.com/search?q={keyword}>"
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
html = requests.get(search_url, headers=headers)
soup = BeautifulSoup(html.text, 'html.parser')
results = soup.select('div.search-result')
search_results = []
for result in results:
title = result.select_one('h2.title').text.strip()
link = result.select_one('a')['href']
search_results.append({
'title': title,
'link': link
})
return search_results
# 사용 예시
keywords = ['파이썬', '크롤링', '데이터분석']
for keyword in keywords:
print(f"\\\\n🔍 '{keyword}' 검색 결과:")
results = search_and_crawl(keyword)
for result in results[:3]:
print(f"- {result['title']}")
자동화: 여러 키워드를 반복 검색합니다.
중첩 요소 탐색
import requests
from bs4 import BeautifulSoup
url = '<https://example.com>'
html = requests.get(url)
soup = BeautifulSoup(html.text, 'html.parser')
# 부모에서 자식 탐색
parent = soup.select_one('div.parent')
children = parent.select('div.child')
for child in children:
print(child.text)
# 형제 요소 찾기
element = soup.select_one('h2.title')
next_sibling = element.find_next_sibling('p')
print(next_sibling.text)
# 부모 요소 찾기
child = soup.select_one('span.price')
parent_div = child.find_parent('div')
print(parent_div['class'])
# 특정 부모까지 올라가기
section = child.find_parent('section')
계층 탐색: 복잡한 HTML 구조에서 원하는 요소를 정확히 찾습니다.
에러 처리 및 예외 상황
안전한 크롤링 코드
import requests
from bs4 import BeautifulSoup
def safe_crawl(url):
try:
# 타임아웃 설정
html = requests.get(url, timeout=10)
# 상태 코드 확인
html.raise_for_status() # 4xx, 5xx 에러 시 예외 발생
soup = BeautifulSoup(html.text, 'html.parser')
# 요소 안전하게 추출
title = soup.select_one('h1.title')
if title:
print(f"제목: {title.text.strip()}")
else:
print("제목을 찾을 수 없습니다")
return soup
except requests.exceptions.Timeout:
print("❌ 타임아웃: 서버 응답 시간 초과")
return None
except requests.exceptions.ConnectionError:
print("❌ 연결 오류: 네트워크 확인 필요")
return None
except requests.exceptions.HTTPError as e:
print(f"❌ HTTP 오류: {e}")
return None
except Exception as e:
print(f"❌ 예상치 못한 오류: {e}")
return None
# 사용 예시
result = safe_crawl('<https://example.com>')
if result:
print("✅ 크롤링 성공")
견고성: 모든 예외 상황을 처리해 프로그램이 중단되지 않습니다.
인코딩 문제 해결
import requests
from bs4 import BeautifulSoup
url = '<https://example.com>'
html = requests.get(url)
# 자동 인코딩 감지
html.encoding = html.apparent_encoding
soup = BeautifulSoup(html.text, 'html.parser')
# 또는 명시적 인코딩
html.encoding = 'utf-8'
soup = BeautifulSoup(html.text, 'html.parser')
# 한글 깨짐 방지
html = requests.get(url)
soup = BeautifulSoup(html.content, 'html.parser', from_encoding='utf-8')
한글 처리: 인코딩을 올바르게 설정하면 한글이 깨지지 않습니다.
데이터 저장하기
CSV 파일로 저장
import requests
from bs4 import BeautifulSoup
import csv
def crawl_and_save_csv(url, filename='data.csv'):
html = requests.get(url)
soup = BeautifulSoup(html.text, 'html.parser')
items = soup.select('div.item')
with open(filename, 'w', encoding='utf-8-sig', newline='') as f:
writer = csv.writer(f)
writer.writerow(['순위', '제목', '링크']) # 헤더
for i, item in enumerate(items, 1):
title = item.select_one('h3').text.strip()
link = item.select_one('a')['href']
writer.writerow([i, title, link])
print(f"✅ {filename}에 저장 완료")
crawl_and_save_csv('<https://example.com/list>')
엑셀 호환: utf-8-sig 인코딩을 사용하면 엑셀에서 한글이 정상 표시됩니다.
JSON 파일로 저장
import requests
from bs4 import BeautifulSoup
import json
from datetime import datetime
def crawl_and_save_json(url, filename='data.json'):
html = requests.get(url)
soup = BeautifulSoup(html.text, 'html.parser')
items = soup.select('div.item')
data = {
'collected_at': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
'total_count': len(items),
'items': []
}
for item in items:
title = item.select_one('h3').text.strip()
link = item.select_one('a')['href']
data['items'].append({
'title': title,
'link': link
})
with open(filename, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
print(f"✅ {filename}에 저장 완료")
crawl_and_save_json('<https://example.com/list>')
구조화 데이터: JSON은 복잡한 데이터 구조를 저장하기 좋습니다.
데이터베이스 저장
import requests
from bs4 import BeautifulSoup
import pymysql
from datetime import datetime
def crawl_and_save_db(url):
# 크롤링
html = requests.get(url)
soup = BeautifulSoup(html.text, 'html.parser')
items = soup.select('div.item')
# DB 연결
conn = pymysql.connect(
host='localhost',
user='root',
password='password',
database='crawling_db'
)
cursor = conn.cursor()
# 테이블 생성
cursor.execute("""
CREATE TABLE IF NOT EXISTS crawled_data (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255),
link VARCHAR(500),
collected_at DATETIME
)
""")
# 데이터 삽입
sql = "INSERT INTO crawled_data (title, link, collected_at) VALUES (%s, %s, %s)"
for item in items:
title = item.select_one('h3').text.strip()
link = item.select_one('a')['href']
collected_at = datetime.now()
cursor.execute(sql, (title, link, collected_at))
conn.commit()
print(f"✅ {len(items)}개 데이터 DB 저장 완료")
cursor.close()
conn.close()
crawl_and_save_db('<https://example.com/list>')
대용량 처리: 데이터베이스는 대량의 크롤링 데이터를 효율적으로 관리합니다.
성능 최적화
Session 재사용
import requests
from bs4 import BeautifulSoup
# Session 객체 생성 (연결 재사용)
session = requests.Session()
session.headers.update({
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
})
urls = [
'<https://example.com/page1>',
'<https://example.com/page2>',
'<https://example.com/page3>'
]
for url in urls:
html = session.get(url)
soup = BeautifulSoup(html.text, 'html.parser')
# 크롤링 로직
print(f"✅ {url} 완료")
session.close()
성능 향상: Session을 재사용하면 연결 오버헤드가 줄어듭니다.
병렬 처리
import requests
from bs4 import BeautifulSoup
from concurrent.futures import ThreadPoolExecutor
def crawl_single_page(url):
html = requests.get(url)
soup = BeautifulSoup(html.text, 'html.parser')
title = soup.select_one('h1').text.strip()
return {'url': url, 'title': title}
urls = [f'<https://example.com/page{i}>' for i in range(1, 11)]
# 병렬로 크롤링 (최대 5개 동시)
with ThreadPoolExecutor(max_workers=5) as executor:
results = list(executor.map(crawl_single_page, urls))
for result in results:
print(f"{result['title']}: {result['url']}")
속도 향상: 여러 페이지를 동시에 크롤링해 시간을 단축합니다.
마치며
BeautifulSoup과 Requests로 웹 크롤링하는 핵심은 requests.get()으로 HTML을 가져오고, BeautifulSoup으로 파싱한 후, select() 또는 find()로 원하는 요소를 추출하는 것입니다.
CSS 선택자를 마스터하고, 안전한 에러 처리를 추가하며, 적절한 형식으로 데이터를 저장하면 실무급 크롤러를 만들 수 있습니다. User-Agent 설정과 딜레이 추가로 서버에 부담을 주지 않는 것도 잊지 마세요.
이 가이드의 예제를 활용해 원하는 웹 데이터를 자동으로 수집하는 프로젝트를 시작해보세요!
참고 자료
- BeautifulSoup 공식 문서: https://www.crummy.com/software/BeautifulSoup/bs4/doc/
- Requests 공식 문서: https://requests.readthedocs.io/
- CSS 선택자 가이드: https://www.w3schools.com/cssref/css_selectors.php