Skip to content

Python 웹 스크래핑: Requests, BeautifulSoup, Selenium을 사용한 완전 가이드

Updated on

API로 사용할 수 없는 데이터가 필요합니다 -- 경쟁사 웹사이트의 제품 가격, 학술 포털의 연구 논문, 채용 플랫폼의 구인 목록, 또는 여러 소스의 뉴스 기사. 수동으로 데이터를 복사하는 것은 느리고, 오류가 발생하기 쉬우며, 대규모로는 불가능합니다. 웹 스크래핑은 이 과정을 자동화하지만, 잘못된 도구나 접근 방식을 선택하면 차단된 요청, 깨진 파서, 법적 문제가 발생합니다.

이 가이드는 완전한 Python 웹 스크래핑 스택을 다룹니다: 정적 페이지용 requests + BeautifulSoup, JavaScript 렌더링 콘텐츠용 Selenium, 대규모 크롤링용 Scrapy. 실제 과제를 처리하는 실용적인 기술을 배울 수 있습니다.

📚

빠른 시작: requests + BeautifulSoup

정적 HTML 페이지 스크래핑에 가장 일반적인 조합:

import requests
from bs4 import BeautifulSoup
 
# 페이지 가져오기
url = 'https://books.toscrape.com/'
response = requests.get(url)
response.raise_for_status()  # 잘못된 상태 코드에 대해 오류 발생
 
# HTML 파싱
soup = BeautifulSoup(response.text, 'html.parser')
 
# 책 제목 추출
books = soup.select('article.product_pod h3 a')
for book in books[:5]:
    print(book['title'])

설치

# 필요한 패키지 설치
# pip install requests beautifulsoup4 lxml

BeautifulSoup으로 HTML 파싱

요소 찾기

from bs4 import BeautifulSoup
 
html = """
<div class="products">
    <div class="product" id="p1">
        <h2 class="name">Widget A</h2>
        <span class="price">$9.99</span>
        <p class="desc">A useful widget</p>
    </div>
    <div class="product" id="p2">
        <h2 class="name">Widget B</h2>
        <span class="price">$14.99</span>
        <p class="desc">A better widget</p>
    </div>
</div>
"""
 
soup = BeautifulSoup(html, 'html.parser')
 
# 태그로 찾기
print(soup.find('h2').text)  # "Widget A"
 
# 클래스로 찾기
prices = soup.find_all('span', class_='price')
for p in prices:
    print(p.text)  # "$9.99", "$14.99"
 
# ID로 찾기
product = soup.find(id='p2')
print(product.find('h2').text)  # "Widget B"
 
# CSS 선택자
names = soup.select('.product .name')
for n in names:
    print(n.text)

자주 사용하는 선택자

메서드예시찾는 대상
find('tag')soup.find('h2')첫 번째 h2 요소
find_all('tag')soup.find_all('a')모든 앵커 요소
find(class_='x')soup.find(class_='price')해당 클래스를 가진 첫 번째 요소
find(id='x')soup.find(id='main')해당 ID를 가진 요소
select('css')soup.select('div.product > h2')CSS 선택자 일치
select_one('css')soup.select_one('#header')첫 번째 CSS 일치

데이터 추출

from bs4 import BeautifulSoup
 
html = '<a href="/page/2" class="next" data-page="2">Next Page</a>'
soup = BeautifulSoup(html, 'html.parser')
link = soup.find('a')
 
# 텍스트 콘텐츠
print(link.text)           # "Next Page"
print(link.get_text(strip=True))  # "Next Page" (공백 제거)
 
# 속성
print(link['href'])        # "/page/2"
print(link.get('class'))   # ['next']
print(link['data-page'])   # "2"

헤더와 세션 처리

많은 웹사이트가 적절한 헤더 없는 요청을 차단합니다:

import requests
from bs4 import BeautifulSoup
 
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
    'Accept-Language': 'en-US,en;q=0.9',
}
 
# 세션을 사용하여 쿠키와 영구 헤더 관리
session = requests.Session()
session.headers.update(headers)
 
response = session.get('https://books.toscrape.com/')
soup = BeautifulSoup(response.text, 'html.parser')
print(f"{len(soup.select('article.product_pod'))}권의 책 발견")

페이지네이션

다음 페이지 링크 따라가기

import requests
from bs4 import BeautifulSoup
 
base_url = 'https://books.toscrape.com/catalogue/'
all_books = []
url = 'https://books.toscrape.com/catalogue/page-1.html'
 
while url:
    response = requests.get(url)
    soup = BeautifulSoup(response.text, 'html.parser')
 
    # 현재 페이지에서 책 추출
    for book in soup.select('article.product_pod'):
        title = book.select_one('h3 a')['title']
        price = book.select_one('.price_color').text
        all_books.append({'title': title, 'price': price})
 
    # 다음 페이지 찾기
    next_btn = soup.select_one('li.next a')
    if next_btn:
        url = base_url + next_btn['href']
    else:
        url = None
 
    print(f"지금까지 {len(all_books)}권 스크래핑...")
 
print(f"총: {len(all_books)}권")

Selenium으로 JavaScript 페이지 스크래핑

페이지가 JavaScript로 콘텐츠를 렌더링할 때, requests는 렌더링 전의 원시 HTML만 가져옵니다. Selenium을 사용하여 실제 브라우저를 제어합니다:

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
 
# 헤드리스 Chrome 설정
options = webdriver.ChromeOptions()
options.add_argument('--headless')
driver = webdriver.Chrome(options=options)
 
try:
    driver.get('https://quotes.toscrape.com/js/')
 
    # 콘텐츠 로딩 대기
    WebDriverWait(driver, 10).until(
        EC.presence_of_element_located((By.CLASS_NAME, 'quote'))
    )
 
    quotes = driver.find_elements(By.CLASS_NAME, 'quote')
    for quote in quotes:
        text = quote.find_element(By.CLASS_NAME, 'text').text
        author = quote.find_element(By.CLASS_NAME, 'author').text
        print(f'"{text}" -- {author}')
finally:
    driver.quit()

도구 비교

도구최적 용도속도JavaScript학습 곡선
requests + BeautifulSoup정적 HTML 페이지빠름미지원쉬움
SeleniumJavaScript 렌더링 페이지느림지원중간
Scrapy대규모 크롤링빠름플러그인 필요가파름
Playwright모던 JS 앱중간지원중간
httpx + selectolax고성능 파싱매우 빠름미지원쉬움

Scrapy로 대규모 스크래핑

수천 페이지를 크롤링할 때, Scrapy는 내장된 동시성, 재시도 로직, 데이터 파이프라인을 제공합니다:

# spider.py
import scrapy
 
class BookSpider(scrapy.Spider):
    name = 'books'
    start_urls = ['https://books.toscrape.com/']
 
    def parse(self, response):
        for book in response.css('article.product_pod'):
            yield {
                'title': book.css('h3 a::attr(title)').get(),
                'price': book.css('.price_color::text').get(),
            }
 
        # 페이지네이션 따라가기
        next_page = response.css('li.next a::attr(href)').get()
        if next_page:
            yield response.follow(next_page, self.parse)
 
# 실행 명령: scrapy runspider spider.py -o books.json

데이터 정리 및 저장

import requests
from bs4 import BeautifulSoup
import pandas as pd
 
# 데이터 스크래핑
url = 'https://books.toscrape.com/'
response = requests.get(url)
soup = BeautifulSoup(response.text, 'html.parser')
 
books = []
for item in soup.select('article.product_pod'):
    title = item.select_one('h3 a')['title']
    price_text = item.select_one('.price_color').text
    price = float(price_text.replace('£', ''))
    rating_class = item.select_one('p.star-rating')['class'][1]
 
    books.append({
        'title': title,
        'price': price,
        'rating': rating_class,
    })
 
# DataFrame으로 변환
df = pd.DataFrame(books)
print(df.head())
 
# CSV로 저장
df.to_csv('books.csv', index=False)

윤리적 스크래핑 실천

실천설명
robots.txt 확인사이트의 크롤링 규칙 준수
지연 추가요청 간 time.sleep() 사용 (1-3초)
신원 표시설명적인 User-Agent 문자열 설정
응답 캐시이미 가져온 페이지를 다시 스크래핑하지 않기
API 확인많은 사이트에 더 빠르고 허용된 공개 API가 있음
이용약관 읽기일부 사이트는 스크래핑을 명시적으로 금지
속도 제한동시 요청으로 서버에 과부하를 주지 않기
import time
import requests
 
def polite_scrape(urls, delay=2):
    """지연을 두고 예의 바르게 스크래핑합니다."""
    results = []
    for url in urls:
        response = requests.get(url)
        results.append(response.text)
        time.sleep(delay)  # 예의 바르게
    return results

스크래핑한 데이터 분석

웹 스크래핑으로 데이터를 수집한 후, PyGWalker (opens in a new tab)를 사용하면 Jupyter에서 스크래핑한 데이터셋을 인터랙티브하게 탐색하고 시각화할 수 있습니다:

import pandas as pd
import pygwalker as pyg
 
df = pd.read_csv('scraped_data.csv')
walker = pyg.walk(df)

Jupyter에서 AI 지원을 받으며 스크래핑 스크립트를 반복적으로 실행하려면, RunCell (opens in a new tab)이 AI 기반 환경을 제공하여 선택자 디버깅과 파싱 로직 테스트를 인터랙티브하게 수행할 수 있습니다.

자주 묻는 질문

Python 웹 스크래핑에 가장 좋은 라이브러리는?

정적 페이지에는 requests + BeautifulSoup이 가장 간단하고 인기 있는 선택입니다. JavaScript 렌더링 페이지에는 Selenium 또는 Playwright를 사용합니다. 수천 페이지의 대규모 크롤링에는 Scrapy가 내장된 동시성과 파이프라인 관리를 제공합니다.

JavaScript를 사용하는 웹사이트를 스크래핑하려면?

Selenium 또는 Playwright를 사용하여 JavaScript를 실행하는 헤드리스 브라우저를 제어합니다. 또는 브라우저의 Network 탭에서 JSON 데이터를 반환하는 API 엔드포인트를 확인하세요 -- API를 직접 스크래핑하는 것이 브라우저 자동화보다 빠르고 안정적입니다.

웹 스크래핑은 합법인가요?

웹 스크래핑의 합법성은 관할 구역, 웹사이트의 이용약관, 데이터 사용 방법에 따라 다릅니다. 공개적으로 이용 가능한 데이터의 스크래핑은 많은 관할 구역에서 일반적으로 합법이지만, 항상 사이트의 robots.txt와 이용약관을 확인하세요. 개인 데이터나 저작권이 있는 콘텐츠의 스크래핑은 피하세요.

스크래핑 중 차단되는 것을 방지하려면?

요청 간 지연(1-3초)을 사용하고, User-Agent 문자열을 순환하고, robots.txt를 준수하고, 쿠키에 세션을 사용하고, 너무 많은 동시 요청을 피하세요. 지속적으로 차단된다면 해당 사이트에 공개 API가 있는지 확인하세요.

웹 스크래핑에서 페이지네이션을 처리하려면?

HTML에서 "다음 페이지" 링크나 버튼을 찾고, URL을 추출하여 더 이상 페이지가 없을 때까지 루프에서 따라갑니다. 또는 페이지가 쿼리 매개변수(예: ?page=2)를 사용하는 경우 페이지 번호를 직접 반복합니다.

결론

Python의 웹 스크래핑 생태계는 모든 시나리오를 다룹니다: 빠른 정적 페이지 스크래핑에는 requests + BeautifulSoup, JavaScript 집약적 사이트에는 Selenium, 프로덕션 규모의 크롤링에는 Scrapy. 작동하는 가장 간단한 도구부터 시작하고, 필요한 경우에만 복잡성을 추가하며, 항상 윤리적으로 스크래핑하세요 -- robots.txt를 준수하고, 지연을 추가하고, 먼저 API를 확인하세요. 스크래핑한 데이터는 CSV나 DataFrame과 같은 구조화된 형식으로 저장하여 즉시 분석할 수 있도록 하세요.

📚