kyejin0412 님의 블로그
Week 12-7 크롤링 본문
"크롤링" 이란?
웹에 공개된 데이터를 프로그래밍 방식으로 수집하는 행위
- 웹 동작 방식 :
- 클라이언트(브라우저) -> 서버 : url을 request
- SSR(Server Side Rendering) : 서버 -> 클라이언트(브라우저) : 완성된 html 문서를 response
- CSR(Client Side Rendering) : 서버 -> 클라이언트(브라우저) : 기본 페이지와 html 문서를 만들기 위한 도구를 response
- 파이썬은 requests 라이브러리로 html 문서를 request한다.

웹 동작 방식 
파이썬 요청 방식
BeautifulSoup
requests로 받아온 HTML에서 정보를 뽑을 수 있게 해주는 파이썬 패키지.
- css 선택자 예시
<!-- 선택을 위해 태그에 클래스를 지정 -->
<a class="next-page">다음 페이지</a>
<!-- 클래스는 한 HTML 에 중복해서 존재할 수 있음 -->
<li class="item-li">설탕</li>
<li class="item-li">소금</li>
<li class="item-li">후추</li>
<!-- 한 요소에 여러개의 클래스를 띄어쓰기로 구분해서 적용 가능 -->
<img class="item-image hero full-width"/>
<!-- id 는 클래스랑 비슷하지만 한 HTML 문서에 하나만 있는게 룰. (가끔 아닌 경우도 있음) -->
<div id="main-container">...</a>
- ex) .main-container
- main-container 클래스를 가진 요소 선택
- ex) #main-container
- main-container ID 를 가진 요소 선택
- ex) .main-container > div
- main-container 클래스를 가진 요소 바로 하위(child) 에 있는 div 요소 선택
- ex) .main-container div ul > li
- main-container 클래스를 가진 요소 하위에 있는 div 요소 하위 어딘가에 있는(> 없음) ul 요소 바로 하위(child)에(>) 있는 li 요소 선택
HTTP 메소드 - GET
- 브라우저에 URL 을 입력해서, 특정 주소로 요청을 보내는 경우 get 방식의 요청이 전송됨
- GET 방식은 요청에 body 가 없기 때문에 요청 시 데이터를 url에 붙여서 전달
- 이를 GET parameter 라고 부른다.
http://example.com/home?data1=hello&data2=world&sparta=club
- 첫 param 에는 ? 뒤에 써주고 그 다음 부터는 & 뒤에 이어준다.
- requests 로는 이렇게 사용할 수 있다.
params = {
"data1": "hello",
"data2": "world",
"sparta": "club"
}
requests.get("http://example.com/home", params=params)
# or
requests.get("http://example.com/home?data1=hello&data2=world&sparta=club")
HTTP 메소드 - POST
- 브라우저에서 특정 폼을 제출하거나 할 때 POST 메서드를 사용한다.
- POST 방식은 요청에 body 가 있기 때문에 요청 시 데이터를 body에 담아서 보낸다.
- 이를 POST data , POST body 등으로 부른다.
- header에서의 정의에 따라서 form, json 등 몇몇 형식으로 보내고 받을 수 있다.
- requests 로는 이렇게 사용할 수 있다.
data = {
"data1": "hello",
"data2": "world",
"sparta": "club",
}
requests.post("http://example.com/home", data=data)
https://www.kurly.com/goods/5131915?collectionCode=2601-wonder-home-01
Protocol : https://
Domain : www.kurly.com
Path : /goods/5131915
Param : “collectionCode” : “2601-wonder-home-01”
스파르타 강의 크롤링 전체 코드
import requests
from bs4 import BeautifulSoup as bs
import pandas as pd
url = "https://spartaclub.kr/catalog/scc?category=all&sub-category="
response = requests.get(url) # url을 담아 서버에 requests, 받은 html 문서를 response에 저장
soup = bs(response.text, "html.parser")
raw_lectures = soup.select("section > div > div > div > a") # css 선택
lecture_list = []
for raw_lecture in raw_lectures:
title = raw_lecture.select_one("h2").text # raw_lecutres에서 한번더 선택, text로 받음
desc = raw_lecture.select_one("p").text
img_url = raw_lecture.select_one("img").get("src") # 이미지 소스 링크로 받음
lecture_list.append({"title": title, "desc": desc, "img_url": img_url})
df = pd.DataFrame(lecture_list) # 저장한 강의의 리스트를 DF화
df.head()
df.to_csv("lecture_list.csv") # DF화 해서 csv 파일로 저장하여 데이터화
print("강의 목록 파일 저장 완료")
CSR (Client Side Rendering)
- 위에 잠깐 언급했던 CSR을 설명해보겠다.
- 위처럼 SSR은 완성된 페이지를 전달해서 크롤링할 때 문제가 없지만, CSR은 서버는 클라이언트에 기본적인 문서만을 전달하고, 해당 문서에 포함된 script 에서 또 다른 서버에 요청해서 데이터를 가져온다.
- 따라서 빈 배열만 크롤링되기도 한다.
- 크롤링될 때 빈 배열만 나온다면, css 선택자에서 잘못했거나, CSR 페이지일 경우를 고려해보자.
- CSR일 경우 다음과 같은 방법을 사용한다.
-
- 데이터가 포함된 response 가로채기
- 클라이언트 사이드 렌더링을 대신해줄 브라우저를 띄우고, python 으로 조작해서 데이터 가져오기

1. 데이터가 포함된 Response 가로채기
개발자도구 > Network 탭 > XHR , 새로고침 한 번.
원하는 데이터가 오는 request 를 찾아서
- Copy > Copy as cURL
- 윈도우에서는 curl cmd 와 curl bash 로 두개가 나오는데 curl bash 로 진행

curl 'https://api.kurly.com/collection/v2/home/sites/market/product-collections/market-best-logic/products?sort_type=4&page=1&per_page=96&filters=' \
-H 'accept: application/json, text/plain, */*' \
-H 'accept-language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7' \
-b 'kdi=VHQt_drTQFm1czgYpwE4MQ; afUserId=dfec15e1-45ae-46c3-889b-43187e3db57f-p; AF_SYNC=1767655683420; PHPSESSID=39r8lpl65dkup2kdt5mumrbgt2ijcm1vgbt07lss1qm3esu90va1; _clck=1nbsym0%5E2%5Eg2k%5E0%5E2196; ksi=AUc6XBaNd2hJy6ijdBvop4WGAAABm6GjaKE; krt=AUc6XBaNd2hJy6ijdBvop4WGlr8DmLrjVlTTr9aFSIouuPIAu7Q; cookie_check=0; amplitudeBucket=%7B%22browse_id%22%3A%22PpMzYMUOkrLJBIEL_6fjs%22%2C%22screen_name%22%3A%22popular_product%22%2C%22previous_screen_name%22%3A%22bargain%22%2C%22browse_screen_name%22%3A%22popular_product%22%2C%22browse_tab_name%22%3A%22home%22%2C%22event_name%22%3A%22impression_app_install_banner%22%2C%22browse_site_name%22%3A%22market%22%2C%22browse_event_name%22%3A%22select_subtab%22%2C%22browse_event_info%22%3Anull%2C%22browse_sub_event_name%22%3A%22select_recommendation_random_collection%22%2C%22browse_sub_event_info%22%3A%22content%22%2C%22browse_section_id%22%3Anull%2C%22section_id%22%3A1051%2C%22sign_up_source_screen_name%22%3A%22popular_product%22%2C%22is_release_build%22%3Atrue%2C%22referrer_event%22%3A%22null%22%2C%22webview_referrer_event%22%3Anull%2C%22referrer_event_name%22%3A%22null%22%2C%22selection_type%22%3Anull%7D; _clsk=1s7vknu%5E1767929133533%5E39%5E0%5Ea.clarity.ms%2Fcollect; _dd_s=aid=6920d1ce-be48-4813-88fe-b7023d689cec&rum=0&expire=1767930255616; amp_65bebb=4sa79c6I0Ft8B9J1IEN6rn.bnVsbA==..1jeg91fsd.1jegcq7mh.44.9.4d' \
-H 'origin: https://www.kurly.com' \
-H 'priority: u=1, i' \
-H 'referer: https://www.kurly.com/collection-groups/market-best?site=MARKET&page=1&collection=market-best-logic' \
-H 'sec-ch-ua: "Google Chrome";v="143", "Chromium";v="143", "Not A(Brand";v="24"' \
-H 'sec-ch-ua-mobile: ?0' \
-H 'sec-ch-ua-platform: "macOS"' \
-H 'sec-fetch-dest: empty' \
-H 'sec-fetch-mode: cors' \
-H 'sec-fetch-site: same-site' \
-H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36'
위 링크에서 코드 변환
- 변환된 코드
import requests
cookies = {
'kdi': 'VHQt_drTQFm1czgYpwE4MQ',
'afUserId': 'dfec15e1-45ae-46c3-889b-43187e3db57f-p',
'AF_SYNC': '1767655683420',
'PHPSESSID': '39r8lpl65dkup2kdt5mumrbgt2ijcm1vgbt07lss1qm3esu90va1',
'krt': 'AUfmmjnUVx9JSax6XR4GaP9pRhSdcK-4RjZcNMenRYrSj-Xqw1o',
'_clck': '1nbsym0%5E2%5Eg2j%5E0%5E2196',
'cookie_check': '0',
'ksi': 'AUfDRWC7LYpMkon22JLCDr73AAABm56B00g',
'amplitudeBucket': '%7B%22browse_id%22%3A%22PpMzYMUOkrLJBIEL_6fjs%22%2C%22screen_name%22%3A%22popular_product%22%2C%22previous_screen_name%22%3A%22recommendation%22%2C%22browse_screen_name%22%3A%22popular_product%22%2C%22browse_tab_name%22%3A%22home%22%2C%22event_name%22%3A%22impression_app_install_banner%22%2C%22browse_site_name%22%3A%22market%22%2C%22browse_event_name%22%3A%22select_subtab%22%2C%22browse_event_info%22%3Anull%2C%22browse_section_id%22%3Anull%2C%22sign_up_source_screen_name%22%3A%22popular_product%22%2C%22is_release_build%22%3Atrue%2C%22referrer_event%22%3A%22null%22%2C%22webview_referrer_event%22%3Anull%2C%22referrer_event_name%22%3A%22null%22%2C%22selection_type%22%3Anull%7D',
'_clsk': '1mr5ima%5E1767872868932%5E1%5E0%5Ea.clarity.ms%2Fcollect',
'_dd_s': 'aid=ac10e6f7-7931-4e1c-afd2-42e3dbed5303&rum=0&expire=1767874000284',
'amp_65bebb': '4sa79c6I0Ft8B9J1IEN6rn.bnVsbA==..1jeemuasq.1jeen5ep3.n.5.s',
}
headers = {
'accept': 'application/json, text/plain, */*',
'accept-language': 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7',
'origin': 'https://www.kurly.com',
'priority': 'u=1, i',
'referer': 'https://www.kurly.com/collection-groups/market-best?site=MARKET&page=1&collection=market-best-logic',
'sec-ch-ua': '"Google Chrome";v="143", "Chromium";v="143", "Not A(Brand";v="24"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"macOS"',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'same-site',
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36',
# 'cookie': 'kdi=VHQt_drTQFm1czgYpwE4MQ; afUserId=dfec15e1-45ae-46c3-889b-43187e3db57f-p; AF_SYNC=1767655683420; PHPSESSID=39r8lpl65dkup2kdt5mumrbgt2ijcm1vgbt07lss1qm3esu90va1; krt=AUfmmjnUVx9JSax6XR4GaP9pRhSdcK-4RjZcNMenRYrSj-Xqw1o; _clck=1nbsym0%5E2%5Eg2j%5E0%5E2196; cookie_check=0; ksi=AUfDRWC7LYpMkon22JLCDr73AAABm56B00g; amplitudeBucket=%7B%22browse_id%22%3A%22PpMzYMUOkrLJBIEL_6fjs%22%2C%22screen_name%22%3A%22popular_product%22%2C%22previous_screen_name%22%3A%22recommendation%22%2C%22browse_screen_name%22%3A%22popular_product%22%2C%22browse_tab_name%22%3A%22home%22%2C%22event_name%22%3A%22impression_app_install_banner%22%2C%22browse_site_name%22%3A%22market%22%2C%22browse_event_name%22%3A%22select_subtab%22%2C%22browse_event_info%22%3Anull%2C%22browse_section_id%22%3Anull%2C%22sign_up_source_screen_name%22%3A%22popular_product%22%2C%22is_release_build%22%3Atrue%2C%22referrer_event%22%3A%22null%22%2C%22webview_referrer_event%22%3Anull%2C%22referrer_event_name%22%3A%22null%22%2C%22selection_type%22%3Anull%7D; _clsk=1mr5ima%5E1767872868932%5E1%5E0%5Ea.clarity.ms%2Fcollect; _dd_s=aid=ac10e6f7-7931-4e1c-afd2-42e3dbed5303&rum=0&expire=1767874000284; amp_65bebb=4sa79c6I0Ft8B9J1IEN6rn.bnVsbA==..1jeemuasq.1jeen5ep3.n.5.s',
}
params = {
'sort_type': '4',
'page': '1',
'per_page': '96',
'filters': '',
}
response = requests.get(
'https://api.kurly.com/collection/v2/home/sites/market/product-collections/market-best-logic/products',
params=params,
cookies=cookies,
headers=headers,
)
# JSON 응답은 .text 가 아니라 .json() 으로 받아준다
print(response.json())
- JSON 은 참고로 파이썬 딕셔너리와 유사한 형태의 자료구조.
- response.json() 은 딕셔너리 형태로 반환된다.
2. python 으로 브라우저 조작하기
Selenium 이나 Playwright 와 같은 패키지로 크롬 브라우저를 띄우고, 조작할 수 있다.
이를 동적 크롤링 이라 부르며, 단순히 HTTP Request 를 보내는 정적 크롤링과 비교된다.
동적 크롤링에는 명확한 장단점이 있다.
- 장점
- 무적 크롤러라고 불릴 정도로, 브라우저를 직접 띄우는 동적 크롤링으로 크롤링 하지 못하는 페이지는 거의 없다
- 스크롤, 클릭, 로그인 등 단순한 HTTP Request 로는 접근이 까다로운 경우에도 크롤링을 진행할 수 있다.
- 단점
- 브라우저를 직접 띄우기 때문에 단순히 request 를 보내는 방식보다 리소스가 많이 든다. (CPU, 메모리, 디스크 등)
- 단순 HTTP Request 를 보내는 방식보다 복잡도, 난이도가 높다
- 브라우저를 구동하기 때문에 비교적 속도가 느리다
- 브라우저를 설치하고 구동해야 하기 때문에 환경별로 설치 방법과 코드가 상이해질 수 있다.
크롤링 준수, 윤리, 리스크 관리
1) robots.txt / sitemap.xml 확인
사이트가 자동화 접근에 대해 공개적으로 제시한 크롤러 접근 정책을 확인(기술적·윤리적 기준).
- https://도메인/robots.txt 접속
- 내 크롤러의 User-Agent에 해당하는 규칙 확인
- User-agent: * (전체)
- 특정 봇(예: Googlebot 등)에만 허용/차단 규칙이 있는지 확인
- 핵심 지시어 파악
- Disallow: 금지 경로
- Allow: 허용 경로(우선순위/충돌 시 해석 주의)
- Crawl-delay: 요청 간격 힌트(모든 크롤러가 따르진 않지만 존중 권장)
- Sitemap: 사이트맵 위치(가능하면 사이트맵 우선 활용)
- 정책 충돌 시 보수적으로 해석
- 모호하면 “하지 않는다”가 원칙
2) ToS(이용약관) 확인
어디서 찾나
- 보통 페이지 하단: “Terms / 이용약관 / 서비스 이용약관 / Legal”
- 회원/개발자 포털: “API Terms / Developer Policy”
- 콘텐츠 별도: “콘텐츠 이용 정책”, “저작권 정책”, “데이터 이용 가이드”
무엇을 봐야 하나(체크 포인트)
- 자동화 수집 금지 조항(스크래핑, 크롤링, 로봇, 자동화 도구)
- 상업적 이용 제한, 재배포 금지, 2차 가공/재판매 금지
- 트래픽/부하 유발 행위 금지
- 계정 사용 시: 계정 공유/자동 로그인/우회 금지
- API 제공 시 API 사용 강제 또는 우선 사용 권장 여부
- 위반 시 조치(차단/법적 조치/손해배상)
판단 기준(권장)
- ToS에 “자동화 수집 금지”가 명시되면: 원칙적으로 중단
- 대안: 공식 API, 제휴/허가 요청, 데이터 구매, 공개 데이터셋 활용
3) 트래픽 배려
서비스에 피해 없이 안정적으로 수집 + 차단 리스크 최소화.
서버는 기본적으로, 감당할 수 있는 부하에 한계가 있음.
크롤링 요청이 단시간에 많이 들어가면 서버가 느려지거나, 심한 경우 다운, 차단될 가능성 존재
- 초당 요청 수 낮게 시도해보고 → 점진적 상향
- status code 주의
- 429 Too Many Requests
- 500번대 에러 (서버 다운, 서버측 일시적 오류) 등
- 보통 400 아래 숫자의 코드면 오류가 없는 것임.
- Retry-After 헤더 있으면 우선 준수
- 실패 시 Backoff 규칙 (지수 증가)
- 예시 코드
- import time, requests def get(url, n=3): for i in range(n + 1): try: r = requests.get(url, timeout=10) if r.status_code < 400: return r except requests.RequestException: pass time.sleep(2 ** i) # 1,2,4... raise RuntimeError("fail") print(get("<https://example.com>").status_code)
- 1회 실패: 1~2초 대기
- 2회: 2~4초
- 3회: 4~8초 …


API를 직접 제공하는 사이트는 크롤링할 필요없이 데이터를 쓸 수 있다!
크롤링 실무 마이크로 팁
- 가능하면 공식 API / JSON 엔드포인트(네트워크 탭)부터 확인 → HTML 파싱보다 견고합니다.
- 목표 스키마를 먼저 정의: id(고유키), title, date, author, category, content, attachments, source_url, crawled_at 등.
- 원본 보관: 추출 결과만 저장하지 말고 원본 HTML/JSON도 함께 저장(재파싱/디버깅에 유리).
- URL 파라미터(page=, offset=, cursor=) 패턴을 먼저 찾아 “끝 조건”을 명확히 설정.
- 중복 방지를 위해 유니크 id가 있으면 최고. 없으면 title+date+author 해시로 임시 키 구성
- 리스트-디테일 페이지 구조에서 리스트를 크롤링할 때 디테일 URL 을 수집하고 이후에 디테일만 별도로 다시 크롤링
'내일배움캠프-데이터분석' 카테고리의 다른 글
| Week 13-2 실전 프로젝트 - 스타벅스 한국 매장 지도 크롤링, 맵 시각화 (1) | 2026.01.13 |
|---|---|
| Week 13-1 실전 프로젝트 - 주제 정하기 (1) | 2026.01.12 |
| Week 11-5 태블로 시작 (0) | 2026.01.02 |
| Week 11-1 심화프로젝트 - 발표자료 준비 (0) | 2025.12.29 |
| Week 10-7 심화프로젝트 - 머신러닝 모델링 (0) | 2025.12.28 |