Step 76. 크롤러와 자동화 — 매일 혼자 일하는 수집기
Level 1 — 프로그래밍과 컴퓨터 내부 | 난이도 ★★★★☆ | 예상 소요 시간 4시간
전제: Step 74~75를 마쳤다. requests로 요청을 보내고 BeautifulSoup으로 데이터를 뽑을 수 있다. CSV 파일을 파이썬으로 읽고 쓸 수 있다(Step 48).
- 준비물: 파이썬, requests와 beautifulsoup4(Step 74에서 설치한 그것), 그리고 반나절의 여유. 오늘은 연습용 웹사이트도 직접 만듭니다 — 별도 설치 없이 파이썬만으로 됩니다.
- 주의: 오늘 실습은 100% 안전합니다. 크롤러가 방문하는 사이트는 여러분의 컴퓨터 안(127.0.0.1)에 여러분이 직접 지은 연습장뿐입니다. 외부 사이트에는 한 번도 접속하지 않습니다.
지금까지 우리의 스크립트는 "켜면 한 번 일하고 꺼지는" 장난감이었습니다. 진짜 도구는 다릅니다. 주인이 자는 동안에도 일하고, 어제 모은 것을 기억해서 같은 것을 두 번 가져오지 않고, 실패하면 조용히 기록을 남깁니다. 오늘의 프로젝트는 장난감을 도구로 진화시키는 일입니다. 페이지 순회, CSV 누적, 중복 제거, 오류 격리, 자동 실행 — 이 다섯 가지는 명언 수집이든 취약점 공지 수집이든 대상만 바꾸면 그대로 쓰이는, 보안 실무의 기본 골격입니다.
1. 학습 목표
이 챕터를 끝내면 다음을 할 수 있습니다:
python -m http.server로 로컬 연습 사이트를 띄울 수 있다- "다음 페이지"를 따라 끝까지 순회하는 크롤러의 while 루프 골격을 쓸 수 있다
- 기존 CSV를 set으로 읽어 새 데이터만 누적 저장할 수 있다
- try/except와 로그 파일로 "죽지 않는" 수집기를 만들 수 있다
- cron 또는 작업 스케줄러에 스크립트를 등록하고, 절대 경로가 왜 생명줄인지 설명할 수 있다
2. 배경 지식 — 오늘의 도구와 개념
오늘의 도구 한눈에 보기
| 구분 | 내용 |
|---|---|
| 언어·환경 | 파이썬 3 + requests, BeautifulSoup4 (Step 74~75에서 설치) |
| 오늘의 도구 | python -m http.server(연습 사이트), while 순회 루프, csv 모듈, set, cron / 작업 스케줄러 |
| 필요한 개념 | 페이지네이션(페이지 나누기), 누적 저장, 중복 제거, 오류 격리, 절대 경로 |
| 오늘의 산출물 | crawler.py + quotes.csv + 자동 실행 등록 — 매일 혼자 일하는 수집기 |
2-1. 페이지 순회 — 끝이 나올 때까지 넘기기
긴 목록은 한 페이지에 다 안 실려 있습니다. "다음 페이지" 링크를 따라가야 합니다. 이 순회의 골격은 언제나 같습니다. 한 페이지를 수집하고 → 다음 링크를 찾고 → 없으면 멈춘다. while 루프 하나면 됩니다.
2-2. 누적과 중복 제거 — set의 활용
매일 실행하면 어제 모은 것을 오늘 또 모읍니다. 막는 법은 단순합니다. 시작할 때 기존 CSV를 읽어 "이미 있는 항목"의 집합(set)을 만들어 두고, 새로 수집한 것이 그 집합에 있으면 건너뜁니다. set은 "있냐 없냐" 질문에 번개같이 답하는 자료구조라 수만 건과 비교해도 빠릅니다.
2-3. 오류 격리 — 죽지 않는 프로그램
네트워크는 언제든 아플 수 있습니다. 자동 실행되는 프로그램이 오류 한 번에 죽으면, 아무도 모르는 채 며칠이 지납니다. 그래서 수집 부분을 try/except로 감싸고, 실패하면 로그 파일에 날짜와 이유를 적은 뒤 빈 결과를 돌려줍니다. 실패해도 죽지 않고, 실패를 기록으로 남기는 것 — 이것이 장난감과 도구를 가르는 선입니다.
2-4. 스케줄링 — 컴퓨터의 알람 시계
정해진 시각에 프로그램을 자동 실행시키는 장치가 운영체제마다 있습니다.
- 리눅스/WSL: cron(크론).
crontab -e로 여는 시간표에0 9 * * * 명령어라고 적으면 "매일 9시 0분에 실행"입니다. 다섯 칸은 분·시·일·월·요일이고,*는 "매번"입니다. - 윈도우: 작업 스케줄러(Task Scheduler). 시작 메뉴에서 "작업 스케줄러"를 찾아 "기본 작업 만들기"로 등록합니다.
어느 쪽이든 핵심 주의는 같습니다. 절대 경로를 써야 합니다. 자동 실행은 여러분이 파일을 더블클릭할 때와 달리 "어느 폴더에서 실행되는지"를 모르는 상태로 시작하기 때문입니다.
3. 따라 하기
3-1. 연습 사이트 짓기 — 내 컴퓨터 안의 미니 웹
외부 사이트 대신, 우리가 직접 지은 사이트를 수집 대상으로 삼습니다. 페이지 세 장짜리 "명언 연습장"입니다. 작업 폴더에 site라는 폴더를 만들고 그 안에 파일 세 개를 만드세요.
입력 (site/index.html)
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>명언 연습장 1페이지</title></head>
<body>
<h1>명언 연습장</h1>
<div class="quote">
<span class="text">"The world is a book, and those who do not travel read only one page."</span>
<small class="author">Saint Augustine</small>
</div>
<div class="quote">
<span class="text">"Life is what happens when you're busy making other plans."</span>
<small class="author">John Lennon</small>
</div>
<div class="quote">
<span class="text">"보안은 제품이 아니라 과정이다."</span>
<small class="author">Bruce Schneier</small>
</div>
<ul class="pager">
<li class="next"><a href="/page2.html">Next →</a></li>
</ul>
</body>
</html>
입력 (site/page2.html)
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>명언 연습장 2페이지</title></head>
<body>
<h1>명언 연습장</h1>
<div class="quote">
<span class="text">"Simplicity is the soul of efficiency."</span>
<small class="author">Austin Freeman</small>
</div>
<div class="quote">
<span class="text">"The only truly secure system is one that is powered off."</span>
<small class="author">Gene Spafford</small>
</div>
<ul class="pager">
<li class="next"><a href="/page3.html">Next →</a></li>
</ul>
</body>
</html>
입력 (site/page3.html) — 마지막 페이지라 "다음" 링크가 없는 것이 포인트입니다.
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>명언 연습장 3페이지</title></head>
<body>
<h1>명언 연습장</h1>
<div class="quote">
<span class="text">"Talk is cheap. Show me the code."</span>
<small class="author">Linus Torvalds</small>
</div>
<div class="quote">
<span class="text">"공격자는 규칙을 모른다. 방어자는 규칙을 지켜야 한다."</span>
<small class="author">익명</small>
</div>
<ul class="pager">
</ul>
</body>
</html>
이제 이 폴더를 웹사이트로 서빙합니다. site 폴더의 바로 위(작업 폴더)에서:
입력 (터미널 1 — 창을 닫지 마세요)
python -m http.server 8800 --directory site
출력 (2026-09-09 실측):
Serving HTTP on 0.0.0.0 port 8800 (http://0.0.0.0:8800/) ...
읽는 법: http.server는 파이썬에 내장된 미니 웹서버입니다. --directory site는 "site 폴더를 사이트 루트로 삼아라"라는 뜻이고, 8800은 포트 번호입니다. 브라우저에서 http://127.0.0.1:8800을 열어 명언이 보이면 성공입니다. python -m http.server를 site 폴더 안에서 실행했다면 --directory 없이도 됩니다.
왜: 크롤러를 배우다 실수로 남의 사이트에 부담을 주는 일을 원천 차단하는 구조입니다. 규칙이 바뀌는 일 없이, 몇 번이든 마음껏 수집해도 되는 우리만의 연습장이 생겼습니다.
3-2. 페이지 순회 크롤러 — 끝까지 넘기기
입력 (crawler.py)
import time
import requests
from bs4 import BeautifulSoup
BASE = "http://127.0.0.1:8800"
def collect_all():
rows = []
url = "/"
while url:
r = requests.get(BASE + url, timeout=5)
soup = BeautifulSoup(r.text, "html.parser")
for q in soup.select(".quote"):
text_el = q.select_one(".text")
author_el = q.select_one(".author")
if text_el is None or author_el is None:
continue
rows.append({"저자": author_el.text.strip(),
"명언": text_el.text.strip()})
nxt = soup.select_one("li.next a")
url = nxt.get("href") if nxt else None
time.sleep(0.2)
return rows
if __name__ == "__main__":
data = collect_all()
print(f"수집 {len(data)}건")
입력 (터미널 2 — 서버는 켜 둔 채)
python crawler.py
출력 (2026-09-09 실측):
수집 7건
읽는 법: while 루프가 "다음 페이지 주소(url)가 있는 동안" 돕니다. 한 페이지를 수집하고 → li.next a에서 다음 주소를 찾고 → 0.2초 쉬고 → 다음 페이지로. 3페이지에는 다음 링크가 없으니 nxt가 None이 되어 반복이 끝납니다. 3 + 2 + 2 = 7건이 모인 것이 맞습니다.
왜: "수집하고, 다음을 찾고, 없으면 멈춘다" — 이 while의 모양은 모든 페이지네이션 크롤러에 그대로 재사용됩니다. 어제의 "한 페이지 수집"이 오늘 "끝까지 순회"가 됐습니다.
3-3. 예측해 보기 — 같은 명언이 두 번 수집될까
예측입니다. 사이트에 같은 명언이 두 페이지에 걸쳐 실려 있다면, data에는 어떻게 들어갈까요? 그리고 우리는 그것을 어떻게 알아챌 수 있을까요?
직접 확인: __main__에 한 줄을 추가해 보세요.
print("중복 제거 확인:", len(data), "->", len(set(r["명언"] for r in data)))
출력 (2026-09-09 실측):
수집 7건
중복 제거 확인: 7 -> 7
읽는 법: 수집 건수와 set으로 중복을 제거한 건수가 같으면 "사이트 안에 중복이 없었다"는 뜻입니다. 다르게 나왔다면 set이 중복을 걸러 준 것이고요. 우리 연습장은 깨끗해서 7 -> 7입니다.
왜: "수집했다"와 "정리됐다"는 다릅니다. 이 한 줄의 검증 습관이 다음 단계의 CSV 누적에서 빛을 발합니다.
3-4. CSV 누적 저장과 중복 제거
조각 ②③입니다. crawler.py에 두 함수를 추가하고 __main__을 고칩니다.
입력 (crawler.py에 추가)
import csv
import os
from datetime import date
CSV_FILE = "quotes.csv"
def load_seen():
seen = set()
if os.path.exists(CSV_FILE):
with open(CSV_FILE, newline="", encoding="utf-8") as fp:
for row in csv.DictReader(fp):
seen.add(row["명언"])
return seen
def save_new(rows):
seen = load_seen()
today = date.today().isoformat()
fresh = [r for r in rows if r["명언"] not in seen]
is_new_file = not os.path.exists(CSV_FILE)
with open(CSV_FILE, "a", newline="", encoding="utf-8") as fp:
writer = csv.writer(fp)
if is_new_file:
writer.writerow(["날짜", "저자", "명언"])
for r in fresh:
writer.writerow([today, r["저자"], r["명언"]])
return len(fresh)
입력 (__main__ 부분을 이렇게)
if __name__ == "__main__":
data = collect_all()
added = save_new(data)
print(f"수집 {len(data)}건, 새로 저장 {added}건")
입력: 두 번 연달아 실행해 보세요.
python crawler.py
python crawler.py
출력 (2026-09-09 실측):
수집 7건, 새로 저장 7건
수집 7건, 새로 저장 0건
읽는 법: 첫 실행은 7건 전부 새 것이라 저장되고, 두 번째는 전부 이미 있는 것이라 0건입니다. load_seen이 기존 CSV를 set으로 읽어 두고, save_new가 그 set에 없는 것만 골라 파일 끝에 덧붙입니다("a"는 append, 이어 쓰기 모드).
왜: "수집 7, 저장 0"이라는 두 번째 출력이야말로 누적 시스템이 살아 있다는 증거입니다. 어제의 실행과 오늘의 실행이 CSV 파일을 통해 대화하고 있습니다. 실제로 quotes.csv를 열어 보면 헤더와 7줄이 들어 있습니다 (2026-09-09 실측):
날짜,저자,명언
2026-09-09,Saint Augustine,"""The world is a book, and those who do not travel read only one page."""
2026-09-09,John Lennon,"""Life is what happens when you're busy making other plans."""
(값 안의 따옴표가 두 번 겹쳐 보이는 것은 CSV의 표기 규칙입니다. 정상입니다.)
3-5. 오류 격리 — 죽지 않는 크롤러
조각 ④, 생존 장치입니다. crawler.py에 함수를 하나 더 추가합니다.
입력 (crawler.py에 추가)
def safe_collect():
try:
return collect_all()
except requests.RequestException as e:
with open("crawler_error.log", "a", encoding="utf-8") as fp:
fp.write(f"{date.today().isoformat()} 수집 실패: {type(e).__name__}: {e}\n")
return []
__main__에서 collect_all() 대신 safe_collect()를 부르도록 바꾸세요. 이제 검증합니다. 터미널 1의 서버를 Ctrl+C로 끄고 실행해 보세요.
출력 (2026-09-09 실측, 서버를 끈 상태):
수집 0건, 새로 저장 0건
프로그램이 죽지 않고 정상 종료했습니다. 그리고 crawler_error.log 파일이 생겨 있습니다 (2026-09-09 실측):
2026-09-09 수집 실패: ConnectionError: HTTPConnectionPool(host='127.0.0.1', port=8800): Max retries exceeded with url: / (Caused by NewConnectionError(... [WinError 10061] 대상 컴퓨터에서 연결을 거부했으므로 연결하지 못했습니다"))
읽는 법: requests가 던진 통신 오류(ConnectionError)를 받아서, 로그에 날짜와 이유를 적고 빈 목록을 돌려줬습니다. 빈 목록이니 save_new는 저장할 것이 없고, 프로그램은 조용히 끝납니다. 한글 윈도우라 오류 메시지의 마지막 부분이 한글로 뜬 것도 눈여겨보세요.
왜: 관리되지 않는 자동화는 언젠가 조용히 멈춰 있고 아무도 모릅니다. 로그는 그 침묵을 깨는 장치입니다. 이 한 함수가 "장난감"과 "도구"를 가릅니다.
3-6. 자동 실행 등록 — 내일 아침의 나를 위해
마지막 조각 ⑤입니다. 운영체제별로 하나만 하면 됩니다. 등록 자체는 여러분의 시계와 계정에 달린 일이라, 아래 출력은 출력 예시입니다.
리눅스(WSL 포함) — 입력
crontab -e
입력 내용 (파일 맨 아래 줄에 추가)
0 9 * * * /usr/bin/python3 /home/사용자명/crawler.py >> /home/사용자명/crawler_run.log 2>&1
읽는 법: "매일 9시 0분에, 절대 경로의 파이썬으로, 절대 경로의 스크립트를 실행하고, 출력과 오류를 실행 로그에 덧붙여라"는 한 줄입니다. 사용자명 부분은 실제 경로로 바꾸세요 — pwd로 확인할 수 있습니다. 스크립트 안의 CSV_FILE, 로그 경로도 절대 경로로 바꿔 두는 것이 정석입니다.
윈도우 — 작업 스케줄러: 시작 메뉴에서 "작업 스케줄러" 검색 → 오른쪽 "기본 작업 만들기" → 이름 "명언수집기" → 트리거 "매일" → 시작 시각 09:00 → 동작 "프로그램 시작" → 프로그램에 python.exe의 전체 경로, 인수에 crawler.py의 전체 경로, 시작 위치에 작업 폴더의 전체 경로를 넣습니다.
왜: 등록하고 나면 내일 아침, 키보드를 만지지 않아도 프로그램이 일합니다. 등록 후에는 시각을 2~3분 뒤로 바꿔 "정말 혼자 도는가"를 테스트하고, 확인 후 원래 시각으로 돌려 두는 것이 정석입니다. 테스트가 끝나면 연습 사이트 서버도 꺼 두세요 — 자동 실행 시점에 서버가 꺼져 있으면 방금 만든 오류 격리와 로그가 대신 일해 줍니다.
4. 미션과 연습문제
미션 — 수집기 완성과 변종 만들기
- 3-1~3-6을 완주해 crawler.py + quotes.csv + 오류 로그 + 자동 실행 등록까지 갖춥니다
- site 폴더에 page4.html을 추가하고(3페이지의 pager에
/page4.html링크 연결), 크롤러를 다시 실행해 "새 페이지의 명언만" CSV에 추가되는지 확인합니다 - quotes.csv를 읽어 "저자별 명언 수"를 출력하는 별도 스크립트 stats.py를 작성합니다
- 실행한 명령과 관찰 결과(몇 건이 새로 저장됐는지)를 README에 기록합니다
연습문제
문제 1. 페이지 순회 while 루프가 멈추는 조건은 무엇이며, 우리 연습 사이트의 어느 부분이 그 조건을 만들어 주나요?
문제 2. 두 번 실행했을 때 "새로 저장 0건"이 나오는 원리를 load_seen과 save_new의 역할로 설명해 보세요.
문제 3. 자동 실행에서 절대 경로를 써야 하는 이유를 "실행 폴더"라는 관점에서 설명해 보세요.
문제 4. 오류 격리를 하지 않은 크롤러를 cron에 등록하면 어떤 일이 벌어지는지, "조용한 실패"라는 표현과 함께 설명해 보세요.
5. 모범 답안과 완료 기준
미션 모범 답안
page4.html 추가와 pager 연결 후 실행하면, 3페이지까지의 7건은 이미 있으니 새 페이지의 명언만 저장됩니다. 4페이지에 명언 2건을 넣었다면 "수집 9건, 새로 저장 2건"이 뜨는 것이 정답입니다 — 수집은 전체를 다시 하되, 저장은 새 것만 한다는 누적의 의미가 정확히 드러납니다.
stats.py의 뼈대:
import csv
from collections import Counter
with open("quotes.csv", newline="", encoding="utf-8") as fp:
rows = list(csv.DictReader(fp))
counts = Counter(row["저자"] for row in rows)
for author, n in counts.most_common():
print(f"{author}: {n}건")
실행하면 Saint Augustine: 1건 같은 식으로 저자별 건수가 나옵니다. 우리 연습장 데이터로는 전원 1건씩입니다.
검증하는 법: ① page4 추가 후 재실행에서 새 건수가 정확한가. ② quotes.csv의 줄 수가 헤더 + 누적 건수와 일치하는가. ③ 서버를 끄고 실행해도 죽지 않고 로그가 쌓이는가. ④ crontab 또는 작업 스케줄러의 등록 내용에 절대 경로만 있는가. 넷이 전부 ‘예’이면 완성입니다.
연습문제 해답
문제 1 해답. "다음 페이지 링크가 없는 것"이 멈춤 조건입니다. soup.select_one("li.next a")가 None을 돌려주면 url이 None이 되어 while이 끝납니다. 우리 연습 사이트에서는 page3.html의 비어 있는 <ul class="pager">가 그 조건을 만들어 줍니다 — 3페이지에는 li.next가 없습니다.
문제 2 해답. load_seen이 기존 quotes.csv를 읽어 "이미 저장된 명언"의 set을 만듭니다. save_new는 새로 수집한 목록에서 그 set에 이미 있는 것을 걸러내고(fresh), 남은 것만 파일에 덧붙입니다. 두 번째 실행에서는 수집된 7건 전부가 set에 있으므로 fresh가 비어 0건이 됩니다.
문제 3 해답. 여러분이 터미널에서 python crawler.py를 실행할 때는 "지금 있는 폴더"가 기준이 되어 quotes.csv 같은 상대 경로가 풀립니다. 그러나 cron이나 작업 스케줄러가 실행할 때는 기준 폴더가 다르거나 정해져 있지 않아서, 상대 경로의 파일을 못 찾거나 엉뚱한 곳에 새 파일을 만듭니다. 절대 경로는 기준 폴더가 무엇이든 같은 파일을 가리키므로 자동 실행의 생명줄입니다.
문제 4 해답. 네트워크 오류로 크롤러가 예외를 내며 죽고, cron은 그것을 내일도 모레도 반복합니다. 출력을 로그로 돌려 두지 않았다면 오류 메시지는 어디에도 남지 않고, 주인은 며칠간 "잘 돌고 있겠지"라고 믿습니다. 이것이 조용한 실패입니다. try/except로 실패를 삼키되 로그로 남기는 오류 격리가 그래서 필수입니다.
완료 기준 체크리스트
- [ ]
python -m http.server로 로컬 연습 사이트를 띄울 수 있다 - [ ] 페이지 순회 while 루프의 골격(수집→다음 찾기→없으면 멈춤)을 설명할 수 있다
- [ ] 두 번 실행해 "새로 저장 0건"을 확인했다
- [ ] 서버를 끈 상태에서 실행해 오류 로그가 남는 것을 확인했다
- [ ] cron 또는 작업 스케줄러에 자동 실행을 등록하고 절대 경로를 썼다
- [ ] 미션: page4 증분 저장과 저자별 집계까지 완료했다
6. 흔한 실수와 해결
벽 1. 크롤러가 ConnectionError로 죽는다
증상 (2026-09-09 실측):
requests.exceptions.ConnectionError: HTTPConnectionPool(host='127.0.0.1', port=8800): Max retries exceeded with url: / ... [WinError 10061] 대상 컴퓨터에서 연결을 거부했으므로 연결하지 못했습니다
원인: 연습 사이트 서버(http.server)가 켜져 있지 않습니다. 수집 대상이 없으니 연결이 거절된 것입니다.
해결: 터미널 1에서 서버를 다시 띄우세요. 그리고 이 오류야말로 3-5의 safe_collect가 받아 내도록 설계한 바로 그 경우입니다 — 격리를 붙인 뒤에는 죽지 않고 로그로 흐릅니다.
벽 2. 서버가 "주소가 이미 사용 중"이라며 안 켜진다
증상 (2026-09-09 실측):
OSError: [WinError 10048] 각 소켓 주소(프로토콜/네트워크 주소/포트)는 하나만 사용할 수 있습니다
원인: 이전에 띄운 http.server가 8800번을 아직 붙들고 있습니다. Ctrl+C로 안 끝낸 창이 어딘가 살아 있는 것입니다.
해결: 서버 창을 찾아 끄거나, 다른 포트(8801 등)로 바꾸세요 — 크롤러의 BASE 포트도 같이 바꾸는 것을 잊지 마세요.
벽 3. CSV를 엑셀에서 열면 한글이 깨진다
증상: 메모장에서는 멀쩡한데 엑셀에서만 깨집니다.
원인: 윈도우 엑셀이 BOM 없는 UTF-8을 알아보지 못하는 고질병입니다 (Step 50의 코드표 문제와 같은 뿌리입니다).
해결: 저장 시 encoding="utf-8-sig"로 바꾸면 파일 맨 앞에 보이지 않는 표시가 붙어 엑셀이 인식합니다. 단, 이 표시를 모르는 다른 프로그램과 주고받을 때는 순수 utf-8이 나을 수 있습니다.
벽 4. 중복이 계속 쌓인다
증상: 두 번 실행했는데도 같은 명언이 또 저장됩니다.
원인: 비교 기준과 저장 값이 미세하게 다릅니다. 수집 때 strip()을 빼먹어 앞뒤 공백이 붙은 채 저장되면, 다음 실행에서 "공백 없는 값"과 비교되어 다른 것 취급됩니다.
해결: 수집 단계의 strip을 확인하고, load_seen이 읽는 컬럼("명언")과 저장하는 컬럼이 같은 기준인지 점검하세요.
벽 5. cron이 조용히 아무것도 안 한다
증상: 등록했는데 시간이 지나도 CSV가 안 바뀝니다.
원인: 대부분 경로 문제입니다. cron은 여러분의 폴더 위치를 모른 채 실행됩니다.
해결: 스크립트 경로뿐 아니라 스크립트 안의 파일 경로(CSV_FILE, 로그)도 전부 절대 경로로 바꾸고, 크론 줄 끝에 >> 로그파일 2>&1를 붙여 조용한 실패에 귀를 달아 두세요. 등록 직후 시각을 2~3분 뒤로 당겨 시험하는 것이 확인의 정석입니다.
7. 정리
오늘의 개념
| 개념 | 한 줄 설명 |
|---|---|
| 페이지네이션 | 긴 목록을 여러 페이지로 나누는 방식 — "다음" 링크를 따라 순회 |
| 누적 저장 | 기존 저장고를 읽어 새 것만 덧붙이기 (set + append) |
| 중복 제거 | set의 "있냐 없냐" 판정으로 이미 모은 것 건너뛰기 |
| 오류 격리 | try/except + 로그 — 실패해도 죽지 않고 기록을 남김 |
| 스케줄링 | cron(리눅스) / 작업 스케줄러(윈도우) — 절대 경로가 생명줄 |
| 조용한 실패 | 로그 없는 자동화가 멈춘 채 아무도 모르는 상태 — 로그로 예방 |
오늘의 명령어와 함수
| 명령/함수 | 하는 일 |
|---|---|
python -m http.server 8800 --directory site |
폴더를 로컬 웹사이트로 서빙 |
soup.select(".quote") / select_one("li.next a") |
명언 블록 / 다음 페이지 링크 찾기 |
csv.DictReader / csv.writer |
CSV 읽기(사전처럼) / 쓰기 |
open(..., "a", ...) |
파일 끝에 이어 쓰기(append) |
date.today().isoformat() |
오늘 날짜를 "2026-09-09" 형태로 |
crontab -e |
리눅스 자동 실행 시간표 편집 |
명령어보다 중요한 감각
오늘 완성한 골격 — 순회, 누적, 중복 제거, 오류 격리, 자동 실행 — 은 대상만 바꾸면 "위협 정보 수집기"가 됩니다. 보안 공지, CVE 갱신, 우리 조직 관련 언급 모니터링이 전부 이 다섯 조각 위에 서 있습니다. 반대로 이 골격이 허락 없이 남의 서비스에 향하면 부담과 침해가 되므로, 수집 대상은 언제나 "수집해도 되는 곳"(오늘처럼 내가 지은 연습장, robots.txt와 이용약관이 허락하는 곳)이어야 합니다.
자동화의 완성은 "돌아가게 만드는 것"이 아니라 "확인하는 습관"까지 포함합니다. 내일 아침 quotes.csv를 열어 줄 수를 보고, crawler_error.log가 비어 있는지 보는 그 10초가 자동화의 주인의 일입니다. 그리고 데이터가 수만 건으로 자라 검색이 필요해지면, 그때가 CSV를 데이터베이스(이 책 뒤쪽의 SQLite)로 갈아탈 시기입니다 — 골격은 같고 저장고만 바뀝니다.
전부 체크되면 Step 76 완료입니다. 사이드바의 체크박스를 눌러 진도를 저장하세요.