HTML · CSS · JavaScript · HTTP · WAS/DB · 정적/동적
박수현 · aSSIST University · 2026
🎨 이미지 프롬프트(배경): "Abstract layered architecture — HTML, CSS, JS layers floating in 3D space connected by glowing teal and purple light streams over a soft light gradient, editorial illustration, no text, 16:9"
📖 도서 매핑: Ch.3 전체
🟦 클라이언트 (1~2) → 🟨 서버 (3~4) → 🟪 브라우저 처리 (5~7) → 🟩 사용자 화면 (8)
이 8단계를 다음 슬라이드들에서 차례대로 자세히 살펴봅니다.
"예쁘게 꾸미는 게 아니라, 데이터를 어디에 두는가"
키워드: 태그 · 속성 · 트리 · 시맨틱
📖 도서 참조: Ch.3 §1
텍스트에 태그를 씌워서 의미·구조를 부여하는 언어.
거의 모든 데이터는 HTML 태그 안.
어느 태그에 있는지만 알면 추출 끝.

HTML5 최소 골격
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>첫 페이지</title>
</head>
<body>
<h1>안녕하세요</h1>
<p>이건 본문입니다.</p>
</body>
</html>
<!DOCTYPE html>: HTML5 라고 알림<html>: 최상위 컨테이너 (모든 것의 부모)<head>: 메타정보 (제목, 인코딩, CSS 링크)<body>: 사용자에게 보이는 실제 내용h1 ~ h6
<h1>1단계 제목 (가장 큰)</h1>
<h2>2단계 제목</h2>
<h3>3단계 제목</h3>
<h4>4단계</h4>
<h5>5단계</h5>
<h6>6단계 (가장 작은)</h6>
🔑 스크래퍼 시점
기사 페이지면
h1은 보통 기사 제목, 쇼핑몰이면 상품명이 자주h1또는h2/h3안에. SEO상h1은 페이지당 하나가 일반적.
문단과 구분
<p>이건 첫 번째 문단입니다.
줄을 바꿔도 한 문단 안.</p>
<p>이건<br>br 태그로<br>강제 줄바꿈</p>
<hr>
<p>가로선 아래의 새 문단.</p>
<p>: paragraph — 한 문단 단위<br>: line break — 강제 줄바꿈 (닫는 태그 없음)<hr>: horizontal rule — 구분선강조의 종류
<p>일반 텍스트, <strong>굵게 강조</strong>,
<em>기울임 강조</em>, <mark>형광펜</mark>,
<u>밑줄</u>, <s>취소선</s>,
<small>작은 글씨</small>입니다.</p>
<strong>: 의미적으로 중요한 강조 (굵게)<em>: 강조 (기울임)<mark>: 형광펜 표시<b> <i>: 단순 시각적 굵게/기울임 (의미 없음)💡 스크래퍼는
<strong>안의 텍스트가 핵심 키워드일 가능성이 높다고 가정.
리스트 두 종류
<h3>비순서 목록 (ul)</h3>
<ul>
<li>사과</li>
<li>바나나</li>
<li>체리</li>
</ul>
<h3>순서 목록 (ol)</h3>
<ol>
<li>물 끓이기</li>
<li>면 넣기</li>
<li>스프 넣기</li>
</ol>
📌 반복 데이터의 첫 번째 후보가 바로
<ul><ol>. 스크래퍼는<li>단위로 순회.
링크 4종
<a href="https://www.naver.com">네이버로 (현재 창)</a><br>
<a href="https://google.com" target="_blank">구글로 (새 창)</a><br>
<a href="/about">상대 경로 — 같은 사이트 내</a><br>
<a href="mailto:hi@x.com">메일 보내기</a><br>
<a href="#section1">페이지 안 앵커</a>
| 속성 | 의미 |
|---|---|
href |
이동할 주소 (스크래퍼의 주요 데이터) |
target="_blank" |
새 창 |
rel="noopener" |
보안 권고 |
🔑 스크래퍼 관점
상세 페이지 URL을 모으려면
<a>태그의href속성을 추출.card.select_one("a.link")["href"]
이미지 속성
<img src="https://placehold.co/200x120/00b894/fff?text=img"
alt="플레이스홀더" width="200">
<br>
<img src="https://placehold.co/120x120/6c5ce7/fff?text=2"
alt="다른 이미지" width="120">
| 속성 | 의미 | 스크래퍼에게 |
|---|---|---|
src |
이미지 URL | ⭐ 추출 대상 |
alt |
대체 텍스트 (접근성) | 의미 단서 |
width/height |
크기 | — |
loading="lazy" |
지연 로딩 | 동적 로딩 단서 |
⚠️
data-srcdata-original같은 속성은 lazy loading 단서. 실제 URL은 거기에 들어 있을 수도.
간단한 표
<table border="1" cellpadding="8">
<thead>
<tr><th>이름</th><th>가격</th><th>재고</th></tr>
</thead>
<tbody>
<tr><td>사과</td><td>1,200</td><td>30</td></tr>
<tr><td>배</td><td>2,500</td><td>15</td></tr>
<tr><td>바나나</td><td>800</td><td>50</td></tr>
</tbody>
</table>
🎯 pandas 와의 만남
pd.read_html(url)한 줄로 페이지의 모든<table>을 DataFrame 리스트로! (회차 4에서 다룸)
div 와 span
<style>
.box { background: #e8fff5; padding: 10px; margin: 4px 0; }
.hi { background: #fff8e1; padding: 2px 6px; border-radius: 4px; }
</style>
<div class="box">div 1 — 블록 (한 줄 통째)</div>
<div class="box">div 2 — 또 블록</div>
<p>본문에서 <span class="hi">span은 인라인</span>이라 줄 안에.</p>
<div>: block-level 컨테이너 — 줄 통째<span>: inline 컨테이너 — 줄 안의 일부분🔑 스크래퍼 시점
가격이 한 줄 안 일부에 들어 있다면
<span class="price">, 카드 한 개를 묶는 박스라면<div class="card">. 둘의 구분이 곧 반복 단위 vs 추출 필드.
간단한 폼
<form>
<label>이름: <input type="text" placeholder="홍길동"></label><br><br>
<label>비밀번호: <input type="password"></label><br><br>
<label>이메일: <input type="email"></label><br><br>
<label><input type="checkbox" checked> 약관 동의</label><br><br>
<button type="submit">제출</button>
</form>
🔑 회차 6 미리보기
로그인 자동화는 이
<form>+<input>을 Selenium으로 채우고<button>클릭하는 방식.
시맨틱 레이아웃
<style>
body{font-family:sans-serif;margin:0}
header,nav,main,aside,footer{padding:8px;border:1px dashed #aaa;margin:4px}
header{background:#e8fff5} nav{background:#e8f0ff}
main{background:#fff} aside{background:#fff8e1} footer{background:#f0eaff}
</style>
<header>header — 사이트 헤더</header>
<nav>nav — 메뉴</nav>
<main>main — 본문</main>
<aside>aside — 사이드바</aside>
<footer>footer — 푸터</footer>
📌 시맨틱 태그가 있는 사이트는 데이터 위치가 예측 가능. 본문은
<main>또는<article>안에 있을 확률 높음.
tag · class · id · data-*
<style>
.product { border: 1px solid #ccc; padding: 10px; margin: 4px 0; border-radius: 6px; }
#featured { background: #fff8e1; border-color: #fdcb6e; }
</style>
<div class="product" id="featured" data-price="51770" data-stock="3">
<h3>이어폰 Pro</h3>
<p>가격: 51,770원 · 재고: 3개</p>
</div>
<div class="product" data-price="22000">
<h3>마우스</h3>
</div>
| 항목 | 특성 | 스크래퍼 시점 |
|---|---|---|
태그명 (div, h3, p) |
모든 요소가 가진 본질 | 가장 기본 매칭 — soup.select("h3") |
class |
여러 요소 공유 가능 | 반복 단위 묶기 — .product |
id |
페이지에 유일 | 한 곳을 정확히 짚을 때 — #featured |
data-* |
사용자 정의 데이터 | 숨겨진 데이터 보고 — data-price 등 |
| 태그 | 자주 담긴 것 | 추출 메서드 |
|---|---|---|
<h1>~<h6> |
기사 제목, 상품명 | .text |
<p> |
본문 | .text |
<span> |
가격, 별점, 라벨 | .text |
<div> |
카드/섹션 컨테이너 | 반복 단위 |
<a> |
상세 링크 | ["href"] |
<img> |
썸네일 | ["src"] ["alt"] |
<table> <ul> <li> |
반복 행 데이터 | for 루프 단위 |
🎯 회차 3 미리보기
도서 카드 1개의
<article class="product_pod">안에h3 a(제목),.price_color(가격),.star-rating(별점) — 이게 회차 3 BS4 실습의 시작.
"도서 카드"를 흉내내는 HTML 한 조각을 작성해 봅니다.
제목 + 가격 + 별점 + 상세 링크가 모두 포함되도록 구성합니다.
예시 답안
<style>
.book-card { border: 1px solid #ddd; padding: 16px; border-radius: 8px;
max-width: 300px; font-family: sans-serif; }
.book-title { font-size: 18px; font-weight: bold; }
.book-price { color: #00b894; font-weight: 600; }
.book-rating { color: #fdcb6e; font-size: 14px; }
</style>
<article class="book-card">
<h3 class="book-title">파이썬 데이터 분석</h3>
<p class="book-price">₩28,000</p>
<p class="book-rating">★★★★☆ (4.0)</p>
<a href="/book/123">상세 보기 →</a>
</article>
디자인이 아니라 "클래스 이름이 곧 데이터의 주소"
키워드: 선택자 · 박스 모델 · flex · 스크래퍼의 주소 표기법
📖 도서 참조: Ch.3 §2
3가지 적용법
<!-- 1. inline (HTML 속성) -->
<p style="color: #00b894">inline — 권장 안 함</p>
<!-- 2. internal (head 안 style) -->
<style>
.me { color: #6c5ce7; font-weight: bold; }
</style>
<p class="me">internal style</p>
<!-- 3. external (별도 파일)
<link rel="stylesheet" href="style.css"> -->
🔑 스크래퍼 시점
스크래퍼는 CSS 값 자체에는 관심 없음.
단, 클래스명(.me,.product-card)은 가장 중요한 단서.
5가지 선택자
<style>
h3 { color: #00b894; } /* 1. 태그명 */
.title { font-style: italic; } /* 2. 클래스 (점) */
#header { background: #fff8e1; } /* 3. id (샵) */
div.card { border: 1px solid #ccc; padding: 8px; } /* 4. 태그+클래스 */
div.card > h3 { margin: 0; } /* 5. 계층 (>는 직계 자식) */
</style>
<div id="header">id로 잡힘</div>
<div class="card">
<h3 class="title">제목 (h3.title 모두 해당)</h3>
</div>
| 패턴 | 의미 |
|---|---|
h3 |
모든 <h3> |
.title |
클래스 title 인 모든 요소 |
#header |
id가 header (페이지 유일) |
div.card |
div면서 클래스 card |
div.card > h3 |
div.card의 직계 자식 h3 |
| 선택자 | 의미 | 왜 쓰나 | 예 |
|---|---|---|---|
a[href] |
href 속성이 있는 a | 빈 링크 제외, 추출 가능한 링크만 | 모든 링크 |
a[href^="/product/"] |
href 가 /product/ 로 시작 |
상품 페이지 URL 만 골라내기 | 상품 링크만 |
img[src$=".jpg"] |
src 가 .jpg 로 끝남 |
특정 확장자 이미지만 다운로드 | JPG만 |
img[src*="naver"] |
src 안에 naver 포함 |
CDN/도메인으로 출처 필터 | 도메인 필터 |
.box .price |
.box 자손 중 .price |
카드 단위 컨테이너 안의 값만 | 가격 추출 |
ul > li:first-child |
ul 의 첫 자식 li | 헤더 행, 첫 결과만 따로 | 첫 항목 |
div.card:nth-child(3) |
3번째 div.card | 광고 위치, n번째 항목 지정 | 특정 위치 |
🔑
^=$=*=— 속성 값을 부분 매칭하는 3 연산자시작·끝·포함 매칭 → URL/이미지 필터링 빈출. 회차 3 BS4 실습 에서 이 3개로 거의 다 됩니다.
margin
border
padding
content
(실제 내용)
| 층 | 역할 | 색 (위 그림) |
|---|---|---|
| margin | 외부 여백 (다른 요소와 거리) | 🟡 노랑 (외곽 점선) |
| border | 테두리 | ⬜ 검정 (실선) |
| padding | 내부 여백 (border ↔ content) | 🟢 연두 |
| content | 실제 텍스트·이미지 | 🟣 보라 (중앙) |
⚠️
box-sizing: border-box면 width 가 border 까지 포함 (기본값content-box는 content 만 = 실제 차지 폭 더 큼)
display 비교
<style>
.b { display: block; background: #e8fff5; padding: 4px; }
.i { display: inline; background: #fff8e1; padding: 4px; }
.ib { display: inline-block; background: #f0eaff; padding: 4px; width: 80px; }
.none { display: none; }
</style>
<div class="b">block — 줄을 통째</div>
<div class="b">block — 또 줄 통째</div>
<span class="i">inline 1</span>
<span class="i">inline 2</span>
<br>
<span class="ib">ib 1</span>
<span class="ib">ib 2</span>
<span class="none">사용자 눈엔 안 보임</span>
⚠️
display: none은 렌더 안 됨 + DOM에는 존재. 스크래퍼는 잡히지만 화면엔 없을 수도.
flexbox
<style>
.row { display: flex; gap: 8px; margin: 4px 0; }
.row.center { justify-content: center; }
.row.between { justify-content: space-between; }
.item { background: #00b894; color: #fff;
padding: 12px 20px; border-radius: 6px; }
</style>
<div class="row"><div class="item">A</div><div class="item">B</div><div class="item">C</div></div>
<div class="row center"><div class="item">중앙</div></div>
<div class="row between"><div class="item">왼쪽</div><div class="item">오른쪽</div></div>
요즘 사이트의 카드 그리드는 거의 flex 또는 grid. 클래스명에 flex row col 보이면 단서.
기본 표기
<style>
.c1 { color: red; } /* 색 이름 */
.c2 { color: #00b894; } /* HEX */
.c3 { color: rgb(108, 92, 231); } /* RGB */
.c4 { color: rgba(231,112,85,0.6); } /* RGBA(투명) */
.px { font-size: 24px; } /* 절대 단위 */
.em { font-size: 1.5em; } /* 부모 기준 */
.rem{ font-size: 1.5rem; } /* 루트(html) 기준 */
</style>
<p class="c1">red</p>
<p class="c2">#00b894 (teal)</p>
<p class="c3 px">rgb 24px</p>
<p class="c4 em">rgba 1.5em</p>
📌 스크래퍼는 색상 값에 거의 관심 없음. 단, 별점이 색으로만 표현될 때(노란 별 vs 회색)는 클래스명 또는 인라인 style 봐야.
실제 사이트의 패턴
<style>
.product-card { border:1px solid #ddd; padding:12px; border-radius:8px; }
.product-card .product-title { font-weight:bold; }
.product-card .price-area .sale { color:#e17055; font-weight:bold; }
.product-card .price-area .original { text-decoration: line-through; color:#aaa; }
</style>
<div class="product-card">
<div class="product-title">무선 이어폰 Pro</div>
<div class="price-area">
<span class="original">128,000원</span>
<span class="sale">89,000원</span>
</div>
</div>
🔑 패턴 인식
.product-card .price-area .sale→ 할인가만 추출하는 정확한 주소.
회차 3에서 BeautifulSoupselect_one(".price-area .sale")으로 그대로 사용.
페이지는 살아있다 — JS가 DOM을 나중에 바꾼다
키워드: 이벤트 · DOM 조작 · fetch · 동적 페이지

브라우저 안에서 돌아가는 프로그래밍 언어. DOM (Document Object Model — 브라우저가 HTML 을 트리 형태로 표현한 객체) 을 조작 · 이벤트 반응 · 서버 통신.
requests.get() 받은 HTML은 JS 실행 전 상태script 사용법
<h1 id="hello">로딩 중...</h1>
<button id="btn">눌러봐</button>
<script>
// 1. DOM 요소 찾기
const h = document.getElementById("hello");
h.innerText = "안녕하세요!";
// 2. 이벤트 등록
document.getElementById("btn").addEventListener("click", () => {
h.innerText = "버튼 눌림! " + new Date().toLocaleTimeString();
});
</script>
외부 파일:
<script src="/app.js"></script>—asyncdefer옵션으로 로딩 순서 제어.
DOM 조작 기본
<div id="list">초기 상태</div>
<script>
const el = document.getElementById("list");
// 1. 텍스트 변경
el.innerText = "텍스트 바뀜";
// 2. HTML 자체 교체
setTimeout(() => {
el.innerHTML = "<ul><li>항목 A</li><li>항목 B</li></ul>";
}, 800);
// 3. 클래스 추가/제거
setTimeout(() => el.classList.add("highlight"), 1500);
// 4. 새 요소 추가
setTimeout(() => {
const p = document.createElement("p");
p.textContent = "👆 1.5초 후 새 줄";
el.appendChild(p);
}, 2200);
</script>
<style>.highlight { background:#fff8e1; padding:8px; }</style>
| 이벤트 | 발생 시점 | 스크래퍼에 어떻게 쓰이나 |
|---|---|---|
click |
사용자가 클릭한 순간 | 「더 보기」 버튼을 코드로 누르기 (Selenium) |
scroll |
페이지를 스크롤할 때 | 무한 스크롤 페이지 — 스크롤 트리거로 새 데이터 로드 |
load |
페이지/이미지 로딩이 끝난 순간 | "이 이벤트가 발생할 때까지 기다려라" — Selenium 의 wait 조건 |
submit |
form 이 제출될 때 | 로그인 버튼 클릭, 검색어 자동 입력 |
input / change |
입력 필드 값이 바뀔 때 | 검색창에 키워드 자동 타이핑 |
mouseover |
마우스가 위에 올라갔을 때 | 드롭다운 메뉴를 펼쳐 그 안의 데이터 노출 |
🔑 회차 6 (Selenium)
Selenium은 이 이벤트들을 사람처럼 발생시켜 페이지를 조작.
driver.find_element(...).click()
AJAX (Asynchronous JavaScript And XML) — 페이지를 새로고침하지 않고 서버와 데이터를 주고받는 기법. 원래 XML 을 썼지만 지금은 JSON 이 표준.
fetch 데모
<button onclick="load()">사용자 받기</button>
<pre id="out" style="background:#f6f8fa;padding:8px;border-radius:6px;font-size:12px"></pre>
<script>
async function load() {
const r = await fetch("https://jsonplaceholder.typicode.com/users/1");
const data = await r.json();
document.getElementById("out").textContent =
JSON.stringify(data, null, 2);
}
</script>
🔑 동적 페이지의 정체
사이트가 페이지를 로드한 후 fetch로 JSON을 받아 화면을 채우는 경우, 스크래퍼는 그 JSON API를 직접 호출하는 것이 훨씬 안정적.
회차 5에서 추가 설명할 예정입니다.
모든 웹은 이 한 장면의 반복
키워드: 요청 · 응답 · 메서드 · 상태코드 · URL
🧑 클라이언트
브라우저 · 앱 · 스크래퍼
1) GET /page →
← 2) 200 OK + HTML
🖥️ 서버
Web Server (nginx · Apache)
+ WAS (Tomcat · Node)
requests, curl, 모바일 앱)🔑 스크래퍼는 브라우저를 흉내내는 클라이언트일 뿐.

| 메서드 | 의미 | 비유 | 예 |
|---|---|---|---|
| GET | 조회 | 도서관에서 빌리기 | 페이지 받기, 검색 |
| POST | 생성/제출 | 우체통에 편지 넣기 | 로그인, 글쓰기 |
| PUT | 전체 수정 | 서류를 갈아끼우기 | 회원정보 |
| PATCH | 부분 수정 | 한 줄만 고쳐쓰기 | 별점만 변경 |
| DELETE | 삭제 | 버리기 | 글 삭제 |
🎯 스크래핑에서 99%는 GET
회차 3에서 쓸
requests.get(url)가 바로 이것.
로그인이 필요한 페이지는 회차 6에서 POST + 세션을 사용.

2xx
성공
200 OK
201 Created
3xx
리다이렉트
301 Moved
302 Found
4xx
클라이언트 오류
404 Not Found
403 Forbidden
429 Too Many Requests
5xx
서버 오류
500 Internal
503 Service Unavailable
🎯 스크래퍼가 자주 만나는 코드
403 (Forbidden — User-Agent 미설정 또는 권한 차단) · 429 (Too Many Requests — 요청 과다) · 503 (Service Unavailable — 서버 일시 불가, 점검·과부하)

https://books.toscrape.com/catalogue/page-2.html?sort=price&order=asc
└─┬─┘ └────────┬────────┘└─────┬─────┘ └────────┬────────┘
스킴 호스트(도메인) 경로 쿼리스트링
| 부분 | 역할 | 스크래퍼 시점 |
|---|---|---|
https |
프로토콜 | http vs https 둘 다 |
books.toscrape.com |
어느 서버? | robots.txt가 여기에 |
/catalogue/page-2.html |
어느 자원? | 페이지 번호 = 반복 핵심 |
?sort=price&order=asc |
쿼리 | 검색·정렬·필터 |
🔑 회차 3 미리보기
python for page in range(1, 51): url = f".../page-{page}.html"페이지 번호만 바꾸는 for문 = 스크래핑의 절반.
브라우저가 보내는 대표 헤더
<pre style="font-size:13px;background:#f6f8fa;padding:12px;border-radius:6px">
GET /catalogue/page-1.html HTTP/1.1
Host: books.toscrape.com
User-Agent: Mozilla/5.0 (Windows NT 10.0...) Chrome/...
Accept: text/html,application/xhtml+xml...
Accept-Language: ko-KR,ko;q=0.9
Cookie: session_id=abc123; theme=dark
Referer: https://www.google.com/
</pre>
| 헤더 | 의미 | 스크래퍼 |
|---|---|---|
User-Agent |
어떤 브라우저/봇이 요청했는지 식별 문자열 | 기본값(python-requests/...)이 차단되어 403 자주 — Chrome UA 로 위장 |
Cookie |
서버가 발급한 세션 ID·로그인 토큰 | 로그인 상태 유지 (회차 6) |
Referer |
직전에 보던 페이지 URL | 핫링크 차단·CSRF 방어용으로 검사하는 사이트 있음 |
Accept-Language |
클라이언트가 원하는 응답 언어 | ko-KR 보내면 한국어 응답 (가격·날짜 형식까지) |
데이터는 DB → WAS → Web Server → 브라우저 순으로 흐른다
키워드: 3-Tier · Web Server · WAS · Database
👤 사용자 브라우저
W
Web Server
nginx · Apache
정적 파일
A
WAS
Tomcat · Django
로직 실행
D
DB
MySQL · Postgres
데이터 저장
| 계층 | 비유 | 역할 |
|---|---|---|
| Web | 음식점 카운터 | 주문 받고 음식 전달 |
| WAS | 주방 | 요리 (로직) 수행 |
| DB | 냉장고/창고 | 재료 (데이터) 보관 |
GET /style.css → 200 OK + 파일
GET /user/me → DB 조회 → JSON 응답
# 1) 클라이언트 요청
GET /hello/박수현 HTTP/1.1
# 2) WAS의 코드 (Flask)
@app.route("/hello/<name>")
def hello(name):
return f"<h1>안녕 {name}님</h1>"
# 3) 실행 후 응답
HTTP/1.1 200 OK
Content-Type: text/html
<h1>안녕 박수현님</h1>
🔑 요청마다 다른 HTML 을 생성 = 동적 페이지의 본질.
정적 페이지는 디스크의.html을 그대로 송출, 동적은 매 요청마다 코드 실행.
requests.get() 한 줄로 OKhtml = requests.get(url).text
# 본문이 그대로 들어 있음 ✅
requests 만으로는 빈 껍데기<div id="product-list"></div> ← 비어 있음!
<script src="/app.js"></script>
| 패턴 | 동작 | 스크래퍼 난이도 |
|---|---|---|
| SSR (Server-Side Rendering) | 사용자가 페이지 요청할 때마다 서버가 데이터 + HTML 을 합쳐서 보냄 | 쉬움 — requests.get() 으로 받은 HTML 에 데이터가 다 들어 있음 |
| SSG (Static Site Generation) | 사이트 빌드 시점에 미리 모든 HTML 을 생성해 둠 (정적 파일) | 간단함 |
| CSR (Client-Side Rendering) | 서버는 빈 HTML + JS 만 보냄 → 브라우저(JS) 가 데이터 받아 화면 그림 | 어려움 — requests 로는 빈 페이지. Selenium 또는 API 직접 호출 |
| SPA (Single Page Application) | 한 HTML 로 시작해 페이지 전환마다 JS 가 화면만 갈아끼움 (Gmail · 인스타) | 어려움 — CSR 의 일종 |
🔍 대표 사례
SSR: 네이버 뉴스 · 정부 공지 · 블로그
CSR/SPA: 카카오 맵 · 네이버 지도 · 인스타그램 · 디스코드 웹
1
Ctrl+U 소스에 원하는 데이터가 이미 있나?
✅ "있다" → requests + BS4 정적 페이지 — 가장 빠름·안정적
2
Network 탭에 JSON 요청이 있나?
✅ "있다" → JSON API 직접 호출 동적이지만 효율적 — Copy as cURL
3
위 둘 다 "없다" — 마지막 카드
⚠️ Selenium / Playwright 브라우저 자동화 — 회차 6 주제
🔑 우선순위
JSON API > requests > Selenium — 위에서부터 시도, 안 되면 한 단계 내려가기.
URL 입력에서 픽셀이 보이기까지 8단계
키워드: Critical Rendering Path · DOM · CSSOM · Layout · Paint
CSSOM (CSS Object Model) — CSS 규칙을 트리 형태로 표현한 객체. DOM 의 CSS 버전. DOM + CSSOM 이 합쳐져 렌더 트리가 됨.
🔑 스크래퍼의 주요 관찰
requests.get()은 HTML만 가져옴 (1단계).
JS가 DOM을 수정하기 전 상태 → 동적 페이지에선 빈 껍데기.
페이지를 계층 구조로 본다 — 부모·자식·형제 관계의 트리
키워드: 트리 · 가지 · 선택자
📖 도서 참조: Ch.3 §1, §5
<div class="product-card">
<img class="thumb" src="img1.jpg">
<h3 class="title">이어폰 Pro</h3>
<div class="price-area">
<span class="original">128,000원</span>
<span class="sale">89,000원</span>
</div>
<a class="link" href="/p/12345">
자세히 보기
</a>
</div>
div .product-card
img .thumb
h3 .title
div .price-area
span .original
span .sale
a .link
🔑 스크래핑의 본질 = 이 트리에서 필요한 가지만 뽑아내는 작업
| 디자이너 | 스크래퍼 | |
|---|---|---|
| 1️⃣ | "이 카드의 간격은?" | "카드의 반복 단위가 뭐?" |
| 2️⃣ | "글꼴이 예쁜가?" | "텍스트가 어느 태그에?" |
| 3️⃣ | "색이 브랜드와 맞나?" | "클래스명에 의미가?" |
| 4️⃣ | "신상품 강조 효과는?" | "선택자가 깨질 가능성은?" |
🔑 4번째가 중요
사이트 리뉴얼·A/B 테스트로 클래스명이 바뀌면 코드 전체가 멈춤. "오늘의
.product-card h3.title이 다음 주에 깨질 수 있다." → 선택자를 코드 상단 상수로 분리, 실패 시 명확한 에러, 주기적 모니터링.
https://books.toscrape.com/
// 1. 책 카드 개수
document.querySelectorAll("article.product_pod").length
// → 20
// 2. 첫 책 제목
document.querySelector("article.product_pod h3 a").innerText
// → "A Light in the ..."
// 3. 모든 가격
Array.from(document.querySelectorAll(".price_color"))
.map(el => el.innerText)
// → ["£51.77", "£53.74", ...]
// 4. 모든 책 제목
Array.from(document.querySelectorAll("article.product_pod h3 a"))
.map(el => el.title)
books.toscrape.com 첫 페이지에서 다음 5개 선택자를 직접 찾아오세요.
| 항목 | 선택자 | 비고 |
|---|---|---|
| 책 카드 반복 단위 | ? |
한 페이지 20개 |
| 책 제목 | ? |
title 속성 사용 |
| 책 가격 | ? |
£ 기호 포함 |
| 별점 | ? |
클래스명 안에 별 개수 |
| 다음 페이지 링크 | ? |
href 속성 |
🔑 답안은 다음 슬라이드 — 먼저 5분 동안 직접 콘솔에서 시도.
| 항목 | 선택자 | 추출 방법 |
|---|---|---|
| 책 카드 반복 단위 | article.product_pod |
for card in soup.select(...) |
| 책 제목 | article.product_pod h3 a |
.text 또는 ["title"] |
| 책 가격 | article.product_pod .price_color |
.text (£ 포함) |
| 별점 | article.product_pod .star-rating |
["class"][1] (One/Two/...) |
| 다음 페이지 | li.next a |
["href"] |
// 콘솔에서 검증
document.querySelectorAll("article.product_pod").length // → 20
document.querySelector("li.next a").href // → page-2.html
🎯 회차 3에서
이 지도가 그대로 Python BeautifulSoup 코드로 변환됩니다 —
select()select_one().
오늘 학습한 HTML, CSS, JS, HTTP, DOM은 이후 회차에서 반복적으로 활용됩니다.
1
웹 기초 + 구조
오늘 ✓
2
DevTools
F12 학습
3
정적 스크래핑
BS4 실습
4
전처리·시각화·윤리
pandas+판례
5
동적 페이지
AJAX·API
6
Selenium
로그인 자동화
7
프라이버시
GDPR·KISA
8
파이프라인+Project
논문 Figure
., #, , >, [attr=])requests / Selenium / JSON API)"오늘 배운 어휘 없이 스크래핑은 불가능하다."