목록으로

프로그래밍 · Python

Python 완전정복 시리즈 #22: 웹 크롤링으로 API 없는 사이트 데이터 수집하기

BeanCon
Python으로 API가 없는 웹사이트의 HTML을 분석해 데이터를 수집하는 웹 크롤링 대표 이미지

Python 완전정복 시리즈 22편입니다. API가 없는 웹사이트에서 데이터를 수집하는 웹 크롤링의 개념, HTML 구조 분석, BeautifulSoup 사용법, find와 CSS 선택자, 링크 추출, User-Agent, robots.txt, 서버 배려, 페이지네이션, 예외 처리, 데이터 정제, JSON/CSV 저장까지 정리했습니다.

목차

🗺️ API의 정문이 닫혔다

지난 시간, 우리는 `requests` 라이브러리를 이용해 외부 API에서 데이터를 가져왔습니다.

API는 깔끔했습니다.

정해진 주소로 요청
JSON 응답 수신
필요한 데이터 추출

마치 정문으로 들어가 안내 데스크에서 필요한 자료를 받는 것과 같았습니다.

그런데 어느 날 새로운 임무가 도착합니다.

[데이터 수집 임무]

웹사이트의 뉴스 제목을 수집하라.
상품명과 가격을 저장하라.
게시글 링크를 정리하라.

당당하게 API 주소를 찾습니다.

API 주소 없음
개발자 문서 없음
JSON 응답 없음

정문이 없습니다.

웹사이트 화면만 덩그러니 보입니다.

그때 선임 개발자가 조용히 말합니다.

“정문이 없으면 건물 구조를 읽어야지.”

웹페이지 내부에는 이미 데이터가 들어 있습니다.

다만 사람이 보는 화면 속에 숨어 있을 뿐입니다.

그 데이터를 Python으로 찾아내는 기술이 바로 웹 크롤링(Web Crawling) 입니다.

1. 웹 크롤링이란 무엇인가?

웹 크롤링은 Python 프로그램이 웹페이지에 접속해 필요한 정보를 자동으로 수집하는 작업입니다.

예를 들어 웹페이지에 다음 정보가 있다고 가정해보겠습니다.

오늘의 뉴스
Python 4.0 개발 소식
AI 시장 동향
클라우드 보안 업데이트

사람은 화면을 보고 제목을 읽습니다.

Python은 HTML 코드를 읽고 제목이 들어 있는 위치를 찾아냅니다.

웹페이지 접속
HTML 수신
HTML 구조 분석
원하는 요소 탐색
텍스트와 링크 추출
파일 또는 DB 저장

크롤링은 무작정 긁어오는 기술이 아닙니다.

웹페이지의 구조를 이해하고, 필요한 데이터만 정확하게 찾아오는 작업입니다.

2. 크롤링과 스크래핑은 같은 말일까?

두 용어는 자주 함께 사용됩니다.

엄밀하게 구분하면 다음과 같습니다.

크롤링
여러 페이지를 자동으로 순회하며 수집하는 과정

스크래핑
특정 페이지에서 필요한 데이터를 추출하는 과정

예를 들어 쇼핑몰 전체 상품 페이지를 돌아다니는 것은 크롤링에 가깝고, 한 상품 페이지에서 상품명과 가격을 꺼내는 것은 스크래핑에 가깝습니다.

실무에서는 두 용어를 넓게 섞어 사용하기도 합니다.

이번 글에서는 이해하기 쉽게 모두 웹 크롤링이라는 표현으로 설명하겠습니다.

3. 디지털 성의 구조를 파악하라

웹사이트를 하나의 거대한 성이라고 생각해보겠습니다.

웹사이트
 ├ 대문
 ├ 복도
 ├ 방
 ├ 서랍
 └ 보물 상자

웹페이지에서는 다음과 같이 대응됩니다.

웹사이트  → 전체 페이지
HTML      → 건물 구조
태그      → 방과 가구
속성      → 방 번호와 이름표
텍스트    → 실제 데이터
링크      → 다른 방으로 가는 문

웹 크롤링의 핵심은 건물 안에서 보물 위치를 찾는 것입니다.

4. HTML은 웹페이지의 설계도다

웹페이지는 HTML이라는 문서 구조로 만들어집니다.

간단한 HTML 예시를 보겠습니다.

<html>
  <body>
    <h1>오늘의 뉴스</h1>

    <div class="news">
      <a href="/news/1">Python 업데이트 소식</a>
    </div>

    <div class="news">
      <a href="/news/2">AI 기술 동향</a>
    </div>
  </body>
</html>

사람에게는 뉴스 목록처럼 보이지만 Python에게는 태그 구조로 보입니다.

h1   → 제목
div  → 구역
a    → 링크
class="news" → 뉴스 영역 이름표
href → 이동할 주소

HTML 구조를 이해하면 데이터가 어디에 숨어 있는지 찾을 수 있습니다.

5. 오늘의 크롤링 장비

이번 작전에서는 두 가지 핵심 도구를 사용합니다.

requests
웹페이지의 HTML을 가져오는 역할

BeautifulSoup
HTML 안에서 원하는 데이터를 찾는 역할

쉽게 비유하면 다음과 같습니다.

requests      → 성문을 열고 설계도를 가져오는 정찰병
BeautifulSoup → 설계도에서 보물 위치를 찾는 탐험가

6. 작전 준비: 라이브러리 설치

터미널에서 다음 명령어를 실행합니다.

pip install requests beautifulsoup4

Python 코드에서 불러옵니다.

import requests
from bs4 import BeautifulSoup

장비 점검 완료입니다. 🔍

7. 미션 1: 웹페이지 HTML 가져오기

먼저 웹페이지에 접속해 HTML을 가져옵니다.

import requests

url = "https://example.com"

response = requests.get(
    url,
    timeout=5
)

print(response.status_code)
print(response.text)

`response.text`에는 웹페이지의 HTML 문서가 들어 있습니다.

웹브라우저가 화면을 그리기 전에 받는 원본 설계도라고 생각하면 됩니다.

8. 응답 상태부터 확인하라

웹페이지 요청이 항상 성공하는 것은 아닙니다.

if response.status_code == 200:
    print("페이지 요청 성공")
else:
    print(
        "페이지 요청 실패:",
        response.status_code
    )

더 안전한 방법은 `raise_for_status()`를 사용하는 것입니다.

response.raise_for_status()

404나 500 같은 오류가 발생하면 예외가 발생합니다.

9. 미션 2: HTML을 BeautifulSoup에 전달하기

가져온 HTML을 분석 가능한 구조로 변환합니다.

from bs4 import BeautifulSoup

soup = BeautifulSoup(
    response.text,
    "html.parser"
)

이제 `soup` 객체를 이용해 HTML 태그를 검색할 수 있습니다.

print(soup)

HTML 전체 구조가 출력됩니다.

다만 전체 설계도는 너무 큽니다.

우리는 필요한 방만 찾아야 합니다.

10. 첫 번째 보물: 제목 태그 찾기

HTML

<h1>오늘의 뉴스</h1>

Python

title = soup.find("h1")

print(title)

결과

<h1>오늘의 뉴스</h1>

태그 안의 글자만 가져오려면 `.text` 또는 `.get_text()`를 사용합니다.

print(title.get_text())

결과

오늘의 뉴스

태그 껍데기를 벗기고 데이터만 꺼냈습니다.

첫 번째 보물 획득입니다. 🪙

11. find()와 find_all() 차이

HTML에 뉴스가 여러 개 있다고 가정해보겠습니다.

<div class="news">뉴스 1</div>
<div class="news">뉴스 2</div>
<div class="news">뉴스 3</div>

첫 번째 하나만 찾기

news = soup.find(
    "div",
    class_="news"
)

print(news)

모두 찾기

news_list = soup.find_all(
    "div",
    class_="news"
)

print(news_list)

반복문으로 출력할 수 있습니다.

for news in news_list:
    print(news.get_text(strip=True))

`strip=True`를 사용하면 불필요한 공백과 줄바꿈을 줄일 수 있습니다.

12. class는 방 이름표다

HTML

<div class="product">
  Python 입문서
</div>

Python

product = soup.find(
    "div",
    class_="product"
)

Python에서 `class`는 예약어이기 때문에 BeautifulSoup에서는 `class_`라고 작성합니다.

끝에 밑줄 하나가 붙습니다.

이 작은 밑줄을 놓쳐서 초보 개발자가 20분 동안 모니터와 눈싸움하는 일이 자주 발생합니다. 👀

13. CSS 선택자는 보물 좌표다

BeautifulSoup에서는 CSS 선택자도 사용할 수 있습니다.

HTML

<div class="product">
  <span class="name">Python 입문서</span>
  <span class="price">25000원</span>
</div>

상품명 찾기

name = soup.select_one(
    ".product .name"
)

가격 찾기

price = soup.select_one(
    ".product .price"
)

출력

print(name.get_text(strip=True))
print(price.get_text(strip=True))

CSS 선택자는 데이터 위치를 나타내는 좌표와 같습니다.

.product
class가 product인 요소

#main
id가 main인 요소

.product .name
product 내부의 name 요소

ul li
ul 내부의 모든 li 요소

14. select_one()과 select() 차이

첫 번째 요소 하나

item = soup.select_one(
    ".product"
)

모든 요소

items = soup.select(
    ".product"
)

리스트로 반환되므로 반복문과 함께 사용할 수 있습니다.

for item in items:
    print(item.get_text(strip=True))

15. 미션 3: 상품명과 가격 수집하기

다음 HTML이 있다고 가정해보겠습니다.

<div class="product">
  <span class="name">Python 입문서</span>
  <span class="price">25,000원</span>
</div>

<div class="product">
  <span class="name">웹 크롤링 실전</span>
  <span class="price">32,000원</span>
</div>

Python 코드

products = soup.select(
    ".product"
)

for product in products:
    name = product.select_one(
        ".name"
    )

    price = product.select_one(
        ".price"
    )

    print(
        name.get_text(strip=True),
        price.get_text(strip=True)
    )

결과

Python 입문서 25,000원
웹 크롤링 실전 32,000원

웹페이지 속 여러 상품을 한 번에 수집했습니다.

16. 링크 주소 추출하기

HTML 링크 태그는 다음과 같습니다.

<a href="/news/1">
  Python 업데이트 소식
</a>

Python

link = soup.find("a")

print(link.get_text(strip=True))
print(link["href"])

결과

Python 업데이트 소식
/news/1

속성값을 안전하게 가져오려면 `get()`을 사용할 수 있습니다.

href = link.get("href")

`href`가 없더라도 프로그램이 바로 오류를 내지 않습니다.

17. 상대 주소와 절대 주소

링크가 다음처럼 들어 있는 경우가 많습니다.

/news/1

이것은 상대 주소입니다.

브라우저는 현재 도메인과 합쳐서 처리하지만 Python에서는 직접 완성해야 할 수 있습니다.

from urllib.parse import urljoin

base_url = "https://example.com"

full_url = urljoin(
    base_url,
    "/news/1"
)

print(full_url)

결과

https://example.com/news/1

`urljoin()`은 인터넷 길찾기 내비게이션입니다.

18. 실전 미션: 뉴스 제목과 링크 수집하기

import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin

url = "https://example.com/news"

response = requests.get(
    url,
    timeout=5
)

response.raise_for_status()

soup = BeautifulSoup(
    response.text,
    "html.parser"
)

news_links = soup.select(
    ".news-list a"
)

for link in news_links:
    title = link.get_text(strip=True)

    href = link.get("href")

    full_url = urljoin(
        url,
        href
    )

    print(title)
    print(full_url)
    print("-" * 30)

이 코드는 뉴스 목록에서 제목과 링크를 추출합니다.

19. User-Agent: 정체를 밝혀라

일부 웹사이트는 기본 requests 요청을 비정상 요청으로 판단할 수 있습니다.

브라우저처럼 요청 정보를 전달하려면 Header에 User-Agent를 설정할 수 있습니다.

headers = {
    "User-Agent": (
        "Mozilla/5.0 "
        "PythonCrawler/1.0"
    )
}

요청

response = requests.get(
    url,
    headers=headers,
    timeout=5
)

User-Agent는 서버에 전달하는 방문자 명함과 비슷합니다.

다만 브라우저처럼 보이게 만든다고 해서 사이트의 이용 규칙을 무시해도 된다는 뜻은 아닙니다.

정중한 방문객이어야 합니다.

20. 크롤링 전에 반드시 확인할 것

웹에 공개된 페이지라고 해서 모든 데이터를 자유롭게 수집해도 되는 것은 아닙니다.

반드시 다음 사항을 확인해야 합니다.

사이트 이용약관
robots.txt
저작권
개인정보 포함 여부
로그인 필요 여부
상업적 이용 제한
과도한 요청 금지

크롤링 기술은 자물쇠 따기 기술이 아닙니다.

허용된 공간에서 필요한 데이터를 정중하게 수집하는 자동화 기술입니다.

21. robots.txt는 성문 앞 안내판이다

많은 웹사이트는 다음 주소에 크롤링 관련 안내를 둡니다.

https://example.com/robots.txt

예시

User-agent: *
Disallow: /private/
Allow: /news/

의미

/private/ 영역은 접근하지 마세요.
/news/ 영역은 접근 가능합니다.

robots.txt는 법적 허가증 그 자체는 아니지만, 사이트 운영자의 크롤링 정책을 확인하는 중요한 안내문입니다.

안내판에 “출입 금지”라고 적혀 있는데 사다리를 가져오는 것은 좋은 크롤링 습관이 아닙니다.

22. 서버를 괴롭히지 않는 크롤링

반복 요청을 너무 빠르게 보내면 서버에 부담을 줄 수 있습니다.

나쁜 예

for page in range(1, 1000):
    requests.get(
        f"https://example.com/page/{page}"
    )

쉬는 시간을 넣는 것이 좋습니다.

import time

for page in range(1, 11):
    url = (
        "https://example.com/"
        f"page/{page}"
    )

    response = requests.get(
        url,
        timeout=5
    )

    time.sleep(1)

크롤러도 숨을 쉬고 서버도 숨을 쉬어야 합니다.

양쪽 모두 산소가 필요합니다. 🌬️

23. 페이지네이션 수집하기

웹사이트의 데이터는 여러 페이지로 나뉘는 경우가 많습니다.

1페이지
2페이지
3페이지
...

URL이 다음과 같다고 가정해보겠습니다.

https://example.com/news?page=1

Python 코드

import time
import requests
from bs4 import BeautifulSoup

for page in range(1, 6):
    params = {
        "page": page
    }

    response = requests.get(
        "https://example.com/news",
        params=params,
        timeout=5
    )

    response.raise_for_status()

    soup = BeautifulSoup(
        response.text,
        "html.parser"
    )

    titles = soup.select(
        ".news-title"
    )

    for title in titles:
        print(
            title.get_text(strip=True)
        )

    time.sleep(1)

1페이지부터 5페이지까지 순서대로 수집합니다.

24. 빈 페이지를 만나면 탐사를 종료하라

페이지 수를 정확히 모르는 경우도 있습니다.

page = 1

while True:
    params = {
        "page": page
    }

    response = requests.get(
        url,
        params=params,
        timeout=5
    )

    response.raise_for_status()

    soup = BeautifulSoup(
        response.text,
        "html.parser"
    )

    items = soup.select(
        ".product"
    )

    if not items:
        print("마지막 페이지입니다.")
        break

    for item in items:
        print(
            item.get_text(strip=True)
        )

    page += 1

더 이상 데이터가 없으면 반복을 종료합니다.

탐험대가 빈 방을 발견하고 철수하는 장면입니다.

25. 예외 처리 없는 크롤러는 유리 검이다

네트워크 요청에는 언제든 오류가 발생할 수 있습니다.

import requests

try:
    response = requests.get(
        url,
        timeout=5
    )

    response.raise_for_status()

except requests.exceptions.Timeout:
    print("응답 시간이 초과되었습니다.")

except requests.exceptions.ConnectionError:
    print("서버에 연결할 수 없습니다.")

except requests.exceptions.HTTPError as error:
    print("HTTP 오류:", error)

except requests.exceptions.RequestException as error:
    print("요청 오류:", error)

크롤러는 한 번 넘어졌다고 탐사를 포기하면 안 됩니다.

하지만 무한정 재시도해서도 안 됩니다.

26. HTML 구조가 바뀌면 크롤러도 길을 잃는다

오늘의 HTML

<span class="price">
  25000원
</span>

내일의 HTML

<strong class="sale-price">
  25000원
</strong>

기존 코드

soup.select_one(
    ".price"
)

결과

None

사이트 구조가 바뀌면 선택자도 수정해야 합니다.

웹 크롤링 프로그램은 한 번 만들고 영원히 끝나는 프로그램이 아닙니다.

웹페이지 구조 변화에 맞춰 유지보수가 필요합니다.

웹사이트가 가구 배치를 바꾸면 보물지도도 다시 그려야 합니다.

27. None을 검사하는 습관

다음 코드는 요소가 없으면 오류가 발생할 수 있습니다.

price = soup.select_one(
    ".price"
)

print(
    price.get_text(strip=True)
)

안전한 방법

price = soup.select_one(
    ".price"
)

if price is None:
    print("가격 정보를 찾을 수 없습니다.")
else:
    print(
        price.get_text(strip=True)
    )

크롤링에서는 데이터가 항상 존재한다고 믿으면 안 됩니다.

웹페이지는 생각보다 변덕이 심합니다.

고양이보다 약간 덜 예측 가능합니다. 🐈

28. 데이터 정제하기

가격 데이터가 다음과 같다고 가정해보겠습니다.

25,000원

문자열 그대로는 계산하기 어렵습니다.

정제해보겠습니다.

price_text = "25,000원"

price = price_text.replace(
    ",",
    ""
).replace(
    "원",
    ""
)

price = int(price)

print(price)

결과

25000

수집한 데이터는 보통 바로 사용할 수 없습니다.

불필요한 문자, 공백, 통화 기호를 제거해야 합니다.

이 과정을 데이터 정제라고 합니다.

29. 뉴스 제목 정제하기

원본 데이터

"\n   Python 4.0 출시 소식   \n"

정제

title = raw_title.strip()

결과

Python 4.0 출시 소식

연속된 공백까지 정리하려면 다음과 같이 할 수 있습니다.

title = " ".join(
    raw_title.split()
)

30. 수집 결과를 리스트에 담기

results = []

for item in items:
    name_tag = item.select_one(
        ".name"
    )

    price_tag = item.select_one(
        ".price"
    )

    if (
        name_tag is None
        or price_tag is None
    ):
        continue

    results.append({
        "name": name_tag.get_text(
            strip=True
        ),
        "price": price_tag.get_text(
            strip=True
        )
    })

이제 수집 결과가 딕셔너리 리스트 형태로 저장됩니다.

[
    {
        "name": "Python 입문서",
        "price": "25,000원"
    },
    {
        "name": "크롤링 실전",
        "price": "32,000원"
    }
]

31. JSON 파일로 저장하기

import json

with open(
    "products.json",
    "w",
    encoding="utf-8"
) as file:
    json.dump(
        results,
        file,
        ensure_ascii=False,
        indent=4
    )

결과

[
    {
        "name": "Python 입문서",
        "price": "25,000원"
    },
    {
        "name": "크롤링 실전",
        "price": "32,000원"
    }
]

인터넷에서 발견한 보물을 안전한 창고에 넣었습니다.

32. CSV 파일로 저장하기

import csv

with open(
    "products.csv",
    "w",
    newline="",
    encoding="utf-8-sig"
) as file:

    writer = csv.DictWriter(
        file,
        fieldnames=[
            "name",
            "price"
        ]
    )

    writer.writeheader()
    writer.writerows(results)

CSV는 엑셀이나 스프레드시트에서 확인하기 좋습니다.

33. 실전 프로젝트: 웹페이지 뉴스 수집기

이제 지금까지 배운 내용을 하나로 합쳐보겠습니다.

import csv
import time
import requests

from bs4 import BeautifulSoup
from urllib.parse import urljoin


BASE_URL = "https://example.com"
NEWS_URL = (
    "https://example.com/news"
)

HEADERS = {
    "User-Agent": (
        "PythonNewsCollector/1.0"
    )
}


def fetch_html(url, params=None):
    try:
        response = requests.get(
            url,
            params=params,
            headers=HEADERS,
            timeout=5
        )

        response.raise_for_status()

        return response.text

    except requests.exceptions.Timeout:
        print("요청 시간이 초과되었습니다.")

    except requests.exceptions.HTTPError as error:
        print("HTTP 오류:", error)

    except requests.exceptions.RequestException as error:
        print("요청 실패:", error)

    return None

HTML 분석 함수

def parse_news(html):
    soup = BeautifulSoup(
        html,
        "html.parser"
    )

    results = []

    links = soup.select(
        ".news-list a"
    )

    for link in links:
        title = link.get_text(
            strip=True
        )

        href = link.get("href")

        if not title or not href:
            continue

        results.append({
            "title": title,
            "url": urljoin(
                BASE_URL,
                href
            )
        })

    return results

CSV 저장 함수

def save_csv(filename, data):
    with open(
        filename,
        "w",
        newline="",
        encoding="utf-8-sig"
    ) as file:

        writer = csv.DictWriter(
            file,
            fieldnames=[
                "title",
                "url"
            ]
        )

        writer.writeheader()
        writer.writerows(data)

실행 코드

all_news = []

for page in range(1, 4):
    print(
        f"{page}페이지 수집 중..."
    )

    html = fetch_html(
        NEWS_URL,
        params={
            "page": page
        }
    )

    if html is None:
        continue

    news = parse_news(html)

    if not news:
        break

    all_news.extend(news)

    time.sleep(1)

save_csv(
    "news.csv",
    all_news
)

print(
    len(all_news),
    "개의 뉴스를 저장했습니다."
)

웹페이지 요청, HTML 분석, 페이지 반복, 링크 정리, CSV 저장까지 하나의 프로그램으로 연결되었습니다.

34. 정적 페이지와 동적 페이지의 차이

여기서 중요한 문제가 등장합니다.

일부 웹사이트는 `requests`만으로 데이터를 가져올 수 없습니다.

왜냐하면 웹페이지가 JavaScript를 실행한 뒤 데이터를 화면에 표시하기 때문입니다.

이를 동적 페이지라고 합니다.

정적 페이지

서버가 완성된 HTML 전달
requests로 데이터 확인 가능

동적 페이지

기본 HTML 전달
JavaScript 실행
추가 데이터 로딩
화면 완성

requests로 가져온 HTML에 데이터가 없다면 동적 페이지일 가능성이 있습니다.

35. 브라우저에서는 보이는데 Python에는 없다

브라우저 화면

상품 100개 표시

requests 응답

<div id="app"></div>

Python

상품이 어디 갔지?

JavaScript가 실행되기 전 HTML만 가져왔기 때문입니다.

이 경우 다음과 같은 도구가 필요할 수 있습니다.

Selenium
Playwright

이 도구들은 실제 브라우저를 자동으로 조작할 수 있습니다.

하지만 무조건 브라우저 자동화를 사용하는 것은 좋지 않습니다.

먼저 다음 순서로 확인하는 것이 좋습니다.

공식 API가 있는가?
HTML에 데이터가 포함되어 있는가?
페이지 내부의 JSON 요청이 있는가?
requests로 처리할 수 있는가?
정말 브라우저 자동화가 필요한가?

가벼운 방법부터 선택해야 합니다.

대포로 모기를 잡으면 모기는 잡히지만 벽도 사라집니다. 🦟💥

36. API와 크롤링의 차이

구분API웹 크롤링
데이터 구조정형화됨HTML에서 직접 추출
응답 형식주로 JSONHTML
안정성비교적 높음페이지 구조 변경에 민감
속도빠른 편상대적으로 느릴 수 있음
이용 규칙문서로 제공약관과 정책 확인 필요
추천 순서우선 사용API가 없을 때 검토

공식 API가 있다면 일반적으로 API를 우선 사용하는 것이 좋습니다.

크롤링은 데이터 제공 통로가 없을 때 사용하는 보조 수단입니다.

37. 초보자가 빠지는 크롤링 함정 극장

함정 1: 클래스 이름 하나만 보고 선택

HTML

<div class="item">

페이지 곳곳에 `item`이 200개 존재합니다.

Python

soup.select(".item")

결과

원하지 않는 데이터까지 대량 입장

선택자를 더 구체적으로 작성해야 합니다.

soup.select(
    ".product-list .item"
)

함정 2: 첫 번째 요소가 항상 존재한다고 믿기

title = soup.select_one(
    ".title"
).get_text()

요소 없음

AttributeError

안전하게 검사해야 합니다.

함정 3: 요청을 너무 빠르게 보냄

1초에 100번 요청

서버

누구세요?
왜 이렇게 급하세요?

결과

429 Too Many Requests
접속 차단 가능

함정 4: 로그인 페이지를 그대로 크롤링

실제 응답

로그인이 필요합니다.

Python

상품 데이터가 왜 없지?

로그인이나 인증이 필요한 페이지는 별도의 세션과 권한 검토가 필요합니다.

함정 5: 화면에 보인다고 사용 가능하다고 생각함

공개된 화면이라도 저작권, 개인정보, 이용약관이 적용될 수 있습니다.

기술적으로 가능하다는 것과 허용된다는 것은 다른 문제입니다.

38. 좋은 크롤러의 다섯 가지 습관

공식 API를 먼저 확인한다.
이용약관과 robots.txt를 살펴본다.
필요한 데이터만 수집한다.
요청 사이에 적절한 간격을 둔다.
오류와 구조 변경에 대비한다.

좋은 크롤러는 빠른 크롤러가 아닙니다.

정확하고 안정적이며 예의 바른 크롤러입니다.

39. 웹 크롤링을 배우면 가능한 것

뉴스 제목 자동 수집
상품 가격 모니터링
채용 공고 정리
부동산 매물 분석
공공기관 게시판 수집
연구 자료 목록화
서버 상태 페이지 점검
보안 공지 수집
블로그 글 목록 정리
시장 데이터 관찰

다만 각 사이트의 정책과 관련 법규를 반드시 확인해야 합니다.

40. 지금까지 배운 Python 기술이 총출동한다

웹 크롤링에는 지금까지 배운 개념이 거의 모두 등장합니다.

모듈

import requests

함수

def fetch_html():

조건문

if response.status_code == 200:

반복문

for item in items:

리스트

results = []

딕셔너리

{
    "title": title,
    "url": url
}

예외 처리

try:
    ...
except:
    ...

파일 입출력

with open(...):

클래스와 객체

BeautifulSoup(...)

Python 문법 조각들이 웹 데이터 수집이라는 하나의 탐험 장비로 조립됩니다.

41. 오늘의 보물지도 정리

requests
웹페이지 HTML을 가져온다.

BeautifulSoup
HTML 구조를 분석한다.

find / find_all
태그를 기준으로 찾는다.

select_one / select
CSS 선택자로 찾는다.

get_text
태그 내부 텍스트를 꺼낸다.

get("href")
링크 속성을 가져온다.

urljoin
상대 주소를 절대 주소로 만든다.

time.sleep
요청 간격을 조절한다.

json / csv
수집 결과를 저장한다.

42. 오늘 배운 내용을 한 문장으로 정리하면

웹 크롤링은 Python이 웹페이지의 HTML 구조를 분석하여 제목, 가격, 링크 같은 필요한 데이터를 자동으로 찾아 수집하는 기술입니다.

43. 디지털 보물 사냥 작전 완료

처음 웹페이지를 보았을 때는 하나의 완성된 화면으로만 보였습니다.

하지만 이제 여러분은 화면 뒤편의 HTML 구조를 읽을 수 있습니다.

태그는 방
class는 이름표
CSS 선택자는 좌표
링크는 다음 방의 문
데이터는 숨겨진 보물

API의 정문이 닫혀 있어도, HTML이라는 건물 설계도를 읽으면 필요한 정보를 찾을 수 있습니다.

다만 진짜 실력 있는 탐험가는 들어갈 수 있는 곳과 들어가서는 안 되는 곳을 구분합니다.

기술보다 먼저 지켜야 할 것은 규칙입니다. 🧭

다음 편 예고

다음 시간에는

"[Python 완전정복 시리즈 #23] Selenium과 Playwright 완벽 이해하기 | JavaScript 웹사이트를 자동으로 조작하는 방법"

을 배워보겠습니다.

requests와 BeautifulSoup으로 가져올 수 없는 동적 웹페이지에서는 브라우저 자동화가 필요할 수 있습니다.

다음 편에서는

브라우저 자동 실행
버튼 클릭
검색어 입력
페이지 이동
동적 데이터 대기
스크린샷 저장
Selenium과 Playwright 비교
자동화 사용 시 주의사항

을 하나의 비밀 요원 브라우저 잠입 작전으로 구성해보겠습니다.

이제 HTML 지도를 읽는 탐험가를 넘어, 실제 브라우저를 조종하는 자동화 요원으로 진화할 차례입니다.

댓글

0

댓글을 불러오는 중입니다.