크롤러 레벨업 3단계: 정적 크롤링부터 API 가로채기까지
처음 크롤링을 배울 때, 저는 이렇게 생각했어요.
“웹사이트 주소 넣으면 화면에 보이는 거 다 딸려오겠지?”
결론부터 말하면, 완전히 틀렸어요. 그리고 그 착각 덕분에 저는 크롤러의 3단계 레벨업을 전부 몸소 체험하게 됐어요. 실패하면서 배운 거라 더 선명하게 기억나더라고요.
오늘은 그 여정을 솔직하게 풀어볼게요. 비전공자라 더 돌아갔던 길이지만, 그래서 더 도움이 될 수도 있어요.
Lv.1 순진한 시절 — “있는 그대로 주겠지?” (정적 크롤링)
사용 기술: Requests, BeautifulSoup
크롤링 공부를 막 시작했을 때, 유튜브랑 블로그에서 제일 먼저 배우는 게 이 조합이에요. requests로 웹페이지를 불러오고, BeautifulSoup으로 HTML을 파싱해서 원하는 데이터를 꺼내는 방식이에요.
쉽게 말하면 이런 거예요. 도서관에 가서 책을 빌려온 다음, 책에서 원하는 페이지를 찾아 읽는 거랑 비슷해요. 웹사이트 주소(URL)가 책 제목이고, HTML이 책의 내용이에요.
근데 현실은 달랐어요.
실제로 코드를 돌려보니까, 분명히 화면에서는 데이터가 보이는데 긁어온 HTML에는 아무것도 없는 거예요. 텅 빈 껍데기만 잔뜩 가져온 거죠. 처음에는 제가 코드를 잘못 짠 줄 알고 세 시간을 디버깅했어요.
알고 보니 이유가 있었어요. 요즘 웹사이트들은 뼈대(HTML)만 먼저 주고, 알맹이(데이터)는 나중에 자바스크립트로 채워 넣어요. 즉, 진짜 데이터는 페이지가 브라우저에서 실행되고 나서야 나타나는 거예요. requests는 그 “실행 전 상태”만 가져오니까, 당연히 텅 비어 있을 수밖에 없었던 거예요.
Lv.1 핵심 정리: 정적 크롤링은 자바스크립트 없이도 데이터가 HTML에 바로 담겨있는 사이트에서만 통해요.
Lv.2 무식하게 뚫어보기 — “안 주면 내가 직접 브라우저를 열지” (동적 크롤링)
사용 기술: Selenium, Puppeteer
그래서 찾은 해결책이 바로 “그냥 브라우저 자체를 코드로 조종하면 되지 않나?”였어요.
Selenium이나 Puppeteer를 쓰면 파이썬/자바스크립트 코드가 진짜 크롬 브라우저를 직접 열어요. 그리고 사람이 하는 것처럼 클릭도 하고, 스크롤도 내리고, 버튼도 눌러요. 자바스크립트가 다 실행된 상태의 화면을 통째로 긁어올 수 있으니까, Lv.1에서 못 가져왔던 데이터도 다 긁어올 수 있었어요.
“이게 바로 만능이구나!” 싶었죠.
근데 이번엔 다른 문제가 생겼어요. 데이터 1만 개를 긁어오려고 코드를 돌리기 시작했는데, 컴퓨터 팬이 이륙하는 소리를 내기 시작하는 거예요. 속도도 속 터지게 느리고, 메모리는 쭉쭉 올라가고. 브라우저를 실제로 띄우다 보니 리소스를 엄청나게 잡아먹더라고요.
데이터 1만 건 긁는 데 몇 시간이 걸리는 걸 보고 “아, 이건 규모가 커지면 답이 없겠구나” 싶었어요.
여러분도 이런 경험 있으시죠? 작을 때는 괜찮았는데, 스케일이 커지면서 한계가 드러나는 그 순간이요.
Lv.2 핵심 정리: 동적 크롤링은 눈에 보이는 건 다 가져올 수 있지만, 느리고 무거워서 대량 수집엔 한계가 있어요.
Lv.3 해커의 시선 — “뒷문으로 바로 빼온다” (API 가로채기)
사용 기술: 개발자 도구(F12) 네트워크 탭, 숨겨진 API 찾기
여기서부터가 진짜예요. 솔직히 이걸 처음 알았을 때 “왜 아무도 이걸 먼저 알려주지 않았지?” 싶었어요.
생각해보면 간단한 원리예요. 웹사이트가 자바스크립트로 데이터를 채워 넣는다고 했잖아요. 그 데이터는 하늘에서 뚝 떨어지는 게 아니에요. 어딘가에서 요청(Request)을 보내고 응답(Response)을 받아오는 거예요. 그 통로가 바로 API예요.
브라우저가 뒤에서 몰래 API를 호출해서 데이터를 가져오는 거라면, 저도 그 API 주소를 알아내서 직접 요청을 보내면 되는 거 아닐까요?
방법은 생각보다 간단해요. 크롬에서 F12를 누르면 개발자 도구가 열려요. 거기서 네트워크(Network) 탭을 클릭하고, 원하는 페이지를 새로고침하면 브라우저가 뒤에서 주고받는 모든 요청이 다 보여요. 여기서 XHR이나 Fetch 필터를 걸면, 데이터를 주고받는 API 요청만 솎아낼 수 있어요.
그러다가 처음으로 JSON 형태로 데이터가 우수수 쏟아지는 API 주소를 발견했을 때, 그 쾌감은 진짜 잊을 수가 없어요. 화면 뒤에서 이런 비밀 통로가 열려있었다니 싶은 거예요.
이 API 주소를 알아내면, Selenium으로 무거운 브라우저를 띄울 필요가 전혀 없어요. 그냥 Lv.1에서 쓰던 requests로 그 주소에 바로 요청을 보내면, 깔끔한 JSON 데이터가 바로 날아와요. 속도는 비교도 안 되게 빠르고, 컴퓨터도 조용하고요.
저는 이걸 처음 성공했을 때 혼자 “유레카!”를 외쳤어요. 부끄럽지만 진짜예요.
Lv.3 핵심 정리: API를 직접 찾아서 요청을 보내면, 무겁고 느린 브라우저 없이도 빠르고 깔끔하게 데이터를 가져올 수 있어요.
보너스 트랙 — “나 로봇 아니야, 진짜 사람이야” (위장술)
크롤링 기술을 익히는 것만큼 중요한 게 있어요. 바로 안 걸리는 법이에요.
서버 입장에서 생각해보면 이해가 쉬워요. 0.1초 만에 페이지를 100번 요청하는 접속자가 있으면, 서버는 당연히 “이건 사람이 아니야”라고 판단하고 차단해버려요. 저도 처음에 이걸 몰라서 IP가 차단되는 경험을 했어요.
첫 번째 위장술: User-Agent 조작
웹 요청을 보낼 때는 “나 어떤 브라우저야”라고 알려주는 정보가 함께 전달돼요. 이게 User-Agent예요. 파이썬 requests는 기본값으로 “나 파이썬 봇이야”라고 솔직하게 밝히거든요. 이걸 “나 아이폰으로 접속한 사람이야”처럼 바꿔주면, 서버 입장에선 일반 사용자랑 구분이 어려워져요.
headers = {
"User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15"
}
requests.get(url, headers=headers)
두 번째 위장술: time.sleep으로 랜덤 휴식
사람은 페이지를 볼 때 읽는 시간도 있고, 클릭하는 시간도 제각각이에요. 이걸 흉내 내는 게 포인트예요. 매번 정확히 1초 쉬는 것보다, 1초에서 3초 사이에서 랜덤하게 쉬어주는 게 훨씬 사람답게 보여요.
import time
import random
time.sleep(random.uniform(1, 3))
이렇게 불규칙한 딜레이를 넣어주는 것만으로도 차단될 확률이 확 줄어들더라고요.
보너스 핵심 정리: 기술만큼이나 서버 눈치 보기가 중요해요. User-Agent 위장과 랜덤 딜레이는 크롤링의 필수 예절이에요.
마치며
돌아보면 Lv.1의 실패가 없었으면 Lv.3의 쾌감도 없었을 것 같아요.
정적 크롤링으로 텅 빈 HTML을 마주하고, 동적 크롤링으로 컴퓨터가 이륙하는 소리를 듣고, 그러다가 API 뒷문을 발견했을 때의 그 희열. 비전공자라 시간이 더 걸렸지만, 오히려 그게 각 단계를 더 깊이 이해하게 해준 것 같아요.
지금 Lv.1에서 막혀있는 분들, 걱정 마세요. 저도 거기서 세 시간 날렸어요. 그 과정이 다 양분이 되더라고요.