끄적끄적
2025.07.07 데이터 크롤링 세션 3 본문
| 튜터 | 김대영 튜터님 |
| 학습 목표 | 1. 웹 페이지의 동적 로딩 방식 (JavaScript, AJAX)을 이해합니다. 2. Selenium 라이브러리를 사용하여 동적으로 로드되는 웹 페이지를 제어하고 크롤링합니다. 3. 웹 개발자 도구의 Network 탭을 활용하여 API 호출을 식별하고 분석합니다. 4. requests 라이브러리를 사용하여 직접 API를 호출하고 JSON 데이터를 파싱합니다. 5. Selenium과 requests를 조합하여 효율적인 크롤링 전략을 수립합니다. |
1️⃣ 동적 웹페이지와 AJAX 통신 이해
이전 1, 2회차에서 다룬 정적 웹페이지는 서버에서 HTML 파일을 통째로 받아와 브라우저가 그대로 표시하는 방식이었다. 그러나 요즘 대부분의 웹사이트는 동적 웹페이지로 작동한다.
- 동적 웹페이지: 웹 페이지의 일부 내용이 사용자의 행동(스크롤, 버튼 클릭 등)이나 시간에 따라 JavaScript를 통해 비동기적으로 로드되거나 변경되는 웹 페이지이다.
- AJAX (Asynchronous JavaScript and XML): 웹 페이지 전체를 다시 로드하지 않고도 백그라운드에서 서버와 데이터를 주고받아 웹 페이지의 특정 부분만 업데이트하는 기술이다. 이 과정에서 서버와 주고받는 데이터는 주로 API (Application Programming Interface)를 통해 이루어진다.
📌 중요할까요?
우리가 requests와 BeautifulSoup만으로는 웹 페이지의 모든 내용을 가져올 수 없는 이유가 여기에 있다. requests는 최초 서버가 보내는 HTML만 가져오므로 JavaScript가 나중에 추가하는 내용은 가져오지 못한다. 이때 Selenium이나 직접 API를 호출하는 방법이 필요하다.
2️⃣ Selenium을 활용한 동적 웹페이지 크롤링
Selenium은 실제 웹 브라우저(Chrome, Firefox 등)를 파이썬 코드로 직접 제어해 웹 페이지를 여는 것은 물론 스크롤, 클릭, 키보드 입력 등 사용자처럼 다양한 상호작용을 수행한다. 이를 통해 JavaScript로 동적으로 로드되는 내용도 가져올 수 있다.
📌 Selenium 설치 및 웹 드라이버 설정
Selenium을 사용하려면 웹 브라우저를 제어할 수 있는 웹 드라이버(WebDriver)가 필요하다. 여기서는 Chrome 브라우저용 ChromeDriver를 사용한다.
- Selenium 라이브러리 설치:
- pip install selenium
- ChromeDriver 다운로드:
- 현재 사용하고 있는 Chrome 브라우저의 버전을 확인합니다. (Chrome 설정 > Chrome 정보)
- ChromeDriver 다운로드 페이지에 접속하여 본인 Chrome 버전과 일치하는 ChromeDriver를 다운로드한다.
- 다운로드한 chromedriver.exe 파일을 Python 스크립트를 실행하는 디렉토리나 시스템 PATH에 추가된 디렉토리에 저장한다.
📌 Selenium 기본 사용법
from selenium import webdriver
from selenium.webdriver.common.by import By # 요소를 찾기 위한 전략 제공
from selenium.webdriver.support.ui import WebDriverWait # 웹 드라이버 대기 기능
from selenium.webdriver.support import expected_conditions as EC # 대기 조건 정의
import time
# 1. WebDriver 객체 생성 (ChromeDriver 경로 지정)
# chromedriver.exe 파일이 현재 스크립트와 같은 폴더에 있거나 PATH에 추가되어 있어야 합니다.
driver = webdriver.Chrome() # 또는 webdriver.Chrome('/path/to/chromedriver')
try:
# 2. 웹 페이지 열기
url = "<https://www.naver.com>"
driver.get(url)
print(f"'{url}' 페이지를 열었습니다.")
time.sleep(2) # 페이지 로딩을 위해 2초 대기 (필요시 더 늘릴 수 있음)
# 3. 요소 찾기 (ID, Class Name, CSS Selector, XPath 등 사용)
# By.ID, By.CLASS_NAME, By.CSS_SELECTOR, By.XPATH 등 다양한 방법이 있습니다.
# 여기서는 검색창을 찾아봅니다.
search_input = driver.find_element(By.ID, 'query') # 네이버 검색창의 ID는 'query'
# 4. 요소와 상호작용하기
search_input.send_keys("파이썬 크롤링") # 텍스트 입력
search_input.submit() # 엔터 키를 누르는 것과 동일
print("검색어를 입력하고 검색했습니다.")
time.sleep(3) # 검색 결과 페이지 로딩 대기
# 5. 동적으로 로드된 내용 가져오기 (예: 검색 결과 중 첫 번째 링크 텍스트)
# 명시적 대기: 특정 요소가 나타날 때까지 기다립니다. (가장 좋은 방법)
# 내가 원하는 요소가 뜰 때까지 10초 기다리겠다. (아래는 li.bx a.info가 DOM에 존재하는지)
first_result_link = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.CSS_SELECTOR, 'li.bx a.info')) # CSS Selector 예시
)
print(f"첫 번째 검색 결과 링크 텍스트: {first_result_link.text}")
# 6. 현재 페이지의 HTML 소스 가져오기
html_source = driver.page_source
# print(html_source[:500]) # HTML 소스의 일부 출력
finally:
# 7. WebDriver 종료 (브라우저 창 닫기)
driver.quit()
print("브라우저를 종료했습니다.")
📌Selenium으로 클릭 및 스크롤 자동화
인스타그램과 같이 스크롤로 데이터가 계속 추가되는 사이트를 크롤링하고 싶을 때 사용 가능하다.
# ... (driver 초기화 코드 동일) ...
driver = webdriver.Chrome()
try:
driver.get("<https://www.daum.net>") # 다음 뉴스 페이지 예시 (실제 동작 확인 필요)
time.sleep(2)
# 1. 버튼 클릭하기 (예: 뉴스 탭 클릭)
# 개발자 도구로 뉴스 탭 버튼의 CSS Selector나 XPath를 찾습니다.
# news_tab = driver.find_element(By.CSS_SELECTOR, '#gnbService > li:nth-child(2) > a')
# news_tab.click()
# time.sleep(3)
# 2. 스크롤 내리기 (동적으로 콘텐츠 로드하는 페이지에 유용)
print("페이지 하단으로 스크롤합니다.")
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
time.sleep(2) # 추가 콘텐츠 로드를 위해 대기
print("페이지 상단으로 스크롤합니다.")
driver.execute_script("window.scrollTo(0, 0);")
time.sleep(2)
finally:
driver.quit()
번개장터 페이지에서 상품 이름과 가격을 크롤링하기(스크롤 5번)
# 번개장터 크롤링 코드
from selenium import webdriver
from selenium.webdriver.common.by import By # 요소를 찾기 위한 전략 제공
from selenium.webdriver.support.ui import WebDriverWait # 웹 드라이버 대기 기능
from selenium.webdriver.support import expected_conditions as EC # 대기 조건 정의
import time
# ... (driver 초기화 코드 동일) ...
driver = webdriver.Chrome()
try:
driver.get("https://m.bunjang.co.kr/") # 번개장터
time.sleep(2)
product_list = [] # 데이터를 저장할 리스트
for i in range(5):
# 방금 찾은 상품 div 출력해보기
product_div = driver.find_elements(By.CLASS_NAME, "styled__ProductWrapper-sc-32dn86-1")
for product in product_div:
product_name = product.find_element(By.CLASS_NAME, "sc-fcdeBU").text
product_price = product.find_element(By.CLASS_NAME, "sc-gmeYpB").text
product_list.append({
"name": product_name,
"price": product_price
})
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
time.sleep(2) # 추가 콘텐츠 로드를 위해 대기
print(product_list)
finally:
driver.quit()

웹페이지를 아래로 스크롤할 때마다 새로운 JSON 데이터를 받아오는 서비스는 보통 커서(cursor) 기반 페이지네이션을 쓴다.
한 번 호출할 때 “다음 묶음 데이터를 어디서부터 이어서 달라”는 위치 표시자(cursor)가 응답 JSON에 같이 오고, 다음 요청의 파라미터로 그 cursor 값을 보내야 이어지는 데이터가 내려온다.
이러한 커서 토큰은 암호화가 되어있기에 requests가 아닌 selenium으로 처리하는 것이 필요하다.
3️⃣ Network 탭을 활용한 API 호출 분석 및 requests로 직접 가져오기
많은 웹사이트가 보이는 데이터를 JavaScript로 동적으로 로드할 때 실제로는 백그라운드에서 특정 API를 호출해 JSON 데이터를 받아온다. 이 API를 직접 파악하고 requests로 호출하면 Selenium보다 훨씬 빠르고 효율적인 크롤링을 할 수 있다.
📌 API 요청 형태 두 가지
Network 탭의 Payload 영역에서 요청 데이터가 나타나는 방식은 크게 두 가지다.
구분특징주 사용 메서드requests 호출 예시
| 구분 | 특징 | 주 사용 메서드 | requests 호출 예시 |
| Query String Parameters |
URL 끝에 ?key=value&… 형태로 붙는다. 캐싱·링크 공유에 유리하지만 길이 제한이 있다. |
GET | requests.get(url, params=query) |
| Request Payload |
HTTP 메시지 바디에 JSON·폼 데이터로 담긴다. 대량·민감 데이터 전송에 적합하다. |
POST (PUT·PATCH 등) |
requests.post(url, json=payload) 또는 data=payload |
📌 Network 탭에서 API 호출 식별하기
- Chrome 개발자 도구 열기 (F12)
- Network 탭으로 이동: 새로고침하거나 페이지를 스크롤/클릭하여 동적으로 로드되는 부분을 관찰한다.
- 필터링:
- 'XHR' 필터: 대부분의 AJAX/API 호출은 XHR(XMLHttpRequest) 또는 Fetch 요청으로 나타난다.
- 'Fetch/XHR' 필터 (최신 Chrome): XHR과 Fetch API 요청을 함께 보여준다.
- 'JS', 'Doc', 'Img' 등 다른 필터를 해제하여 API 호출에 집중할 수 있다.
- 요청 분석:
- 의심되는 요청(이름, Type, Size 등 확인)을 클릭한다.
- Headers 탭: 요청 URL, 요청 방식 (GET/POST), 요청 헤더(User-Agent, Referer, Authorization 등), 쿼리 문자열(Query String Parameters) 등을 확인한다. 이 정보들은 requests로 API를 호출할 때 필요하다.
- Payload 탭: POST 요청인 경우 서버로 전송된 데이터(JSON, FormData 등)를 확인한다.
- Preview / Response 탭: 서버로부터 받은 응답 데이터를 확인합니다. 대부분 JSON 형태일 것이다.
📌 requests로 직접 API 호출 및 JSON 데이터 파싱
Network 탭에서 파악한 정보를 바탕으로 requests를 사용하여 API를 직접 호출한다.
실습 예시: 가상의 뉴스 웹사이트에서 API로 기사 목록 가져오기
(실제 웹사이트의 API는 계속 변경될 수 있으므로, 아래는 예시로 작성된 코드이다. 실제 API는 Network 탭 분석을 통해 파악해야 한다.)
import requests
import json # JSON 데이터를 파싱하기 위해 필요
# 1. Network 탭에서 파악한 API 정보 설정
api_url = "<https://api.example.com/news/articles>" # 실제 API URL로 변경!
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Accept": "application/json", # 서버에게 JSON 형식으로 응답을 받고 싶다고 알림
# "Referer": "<https://www.example.com/news>", # 필요하다면 Referer 헤더 추가
# "Authorization": "Bearer YOUR_API_TOKEN" # 인증 토큰이 필요할 경우
}
params = { # GET 요청에 필요한 쿼리 파라미터 (예: 페이지 번호, 카테고리)
"page": 1,
"category": "politics",
"limit": 10
}
# POST 요청인 경우 data 또는 json 설정:
# payload = {"startDate": "2023-01-01", "endDate": "2023-12-31"}
try:
# 2. GET 요청 보내기 (POST 요청인 경우 requests.post() 사용)
response = requests.get(api_url, headers=headers, params=params, timeout=10)
response.raise_for_status() # HTTP 오류 발생 시 예외 발생
# 3. JSON 데이터 파싱
# response.json() 메서드는 응답 본문을 JSON으로 파싱하여 Python 딕셔너리/리스트로 반환
data = response.json()
print(f"API 호출 성공! 응답 데이터 타입: {type(data)}")
# 4. 파싱된 데이터 활용
if isinstance(data, dict) and "articles" in data:
articles = data["articles"]
print(f"총 {len(articles)}개의 기사 목록을 가져왔습니다.")
for i, article in enumerate(articles[:3]): # 첫 3개 기사 정보 출력
print(f"--- 기사 {i+1} ---")
print(f" 제목: {article.get('title', '제목 없음')}")
print(f" 날짜: {article.get('publishedAt', '날짜 없음')}")
print(f" URL: {article.get('url', 'URL 없음')}")
print(f" 요약: {article.get('description', '요약 없음')[:50]}...")
else:
print("API 응답 구조가 예상과 다릅니다.")
print(json.dumps(data, indent=2)) # 전체 응답 데이터 구조 확인
except requests.exceptions.HTTPError as e:
print(f"HTTP 에러 발생: {e.response.status_code} - {e.response.text}")
except requests.exceptions.ConnectionError as e:
print(f"네트워크 연결 오류: {e}")
except requests.exceptions.Timeout as e:
print(f"요청 타임아웃: {e}")
except requests.exceptions.RequestException as e:
print(f"그 외 요청 오류: {e}")
except json.JSONDecodeError as e:
print(f"JSON 파싱 오류: {e}. 응답 텍스트: {response.text[:200]}")
except Exception as e:
print(f"예상치 못한 오류 발생: {e}")
네이버 증권 (삼성전자) 크롤링하기
# 네이버 증권 크롤링 코드 requests 이용
import requests
import json # JSON 데이터를 파싱하기 위해 필요
# 1. Network 탭에서 파악한 API 정보 설정
# type=recent&code=005930&page=1 => parameters
api_url = "https://finance.naver.com/item/item_right_ajax.naver" # 실제 API URL로 변경!
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Accept": "application/json", # 서버에게 JSON 형식으로 응답을 받고 싶다고 알림
"Cookie": "NNB=HB7GTPOU7RWGO; ASID=738a25fa00000194cb92ef240000004a; NFS=2; _fbp=fb.1.1741591077607.217294079627861186; cto_bundle=9K_SuF95T0RPY2poSURlZDJzJTJCZ0pmJTJCQVUzSUFyMk5MNFRwT3RkSEFrYzIyVTBWVWg1JTJCREZENXlHdXZCMWl0UFI2ZGNhbEJSYjYlMkZLdEVWb1VSMnZLNDklMkY0SUJhUUIwalJ2SkRmTUIlMkZyandVZGlHMFp5bW16VHNMaFluam9QcVNjUmt2SUJhMTNPZmp5cTQ1OSUyQjd5OU1kZWQ5OUdLbExkeWl0eUpRU3VGRExuenZhVWFoWFNsM0puQWc5WGZzVFN4TzZUSGZvSHBCUTFCMkhXT2RxWjVkVjhoVnFidW96JTJCU1Nla2dLTmJDQ3VEaUdOb1d1VWRRSVJEb1U2NjZ6V0hTSXNrSU9DJTJGWmNDSkZOSmZHRWx2ayUyQjVRWklubGRuajMzcVBGUHhlV1BnU1pZRzJiVXFyWm15R1JYTkdleDNJdW16RllSY3VjdFFuMnllVVMzVDh5TmdoZW5kTUZKSmp1WFdEYzcxd3lrT041VlZtayUzRA; _tt_enable_cookie=1; _ttp=01JT4THWXQSAMYJ9TJAQMATSRD_.tt.1; _ga=GA1.1.GA1.1.GA1.1.1866807985.1741591078; ttcsid_CRLT6VRC77UC5E4HNKOG=1746066273208::7dZajmfVtFg2ZHJZB_kJ.1.1746066353881; ttcsid=1746066273208::WnPYNOmvtqg-f221WlDX.1.1746066353881; _ga_9JHCQLWL5X=GS1.1.1746066272.1.1.1746066354.0.0.0; _ga_NFRXYYY5S0=GS1.1.1746066272.1.1.1746066354.0.0.0; _ga_Q7G1QTKPGB=GS1.1.1746066272.1.1.1746066354.0.0.0; _ga_6X0XMCB9L6=GS2.1.s1746773487$o1$g0$t1746773489$j0$l0$h0; NAC=4QeeHgBxTTe2C; _gcl_au=1.1.956905134.1749690647; _ga_EEN65PKS7L=GS2.1.s1749690647$o10$g1$t1749690723$j60$l0$h0; naver_stock_codeList=005930%7C; NACT=1; SRT30=1751883918; page_uid=jbXmudqptbNssuUBbDwssssstSG-138087; SRT5=1751885483; BUC=ys9Dyu8w-9YIXLKjMmF6OklneW_HugxDuoW0zQqqUFg=; JSESSIONID=8F7B3A4F990AD704AAC86B6747D86C68",
"Referer": "https://finance.naver.com/item/main.naver?code=005930", # 필요하다면 Referer 헤더 추가
# "Authorization": "Bearer YOUR_API_TOKEN" # 인증 토큰이 필요할 경우
}
params = { # GET 요청에 필요한 쿼리 파라미터 (예: 페이지 번호, 카테고리)
"type": "recent",
"code": "005930",
"page": 1
}
# POST 요청인 경우 data 또는 json 설정:
# payload = {"startDate": "2023-01-01", "endDate": "2023-12-31"}
try:
# 2. GET 요청 보내기 (POST 요청인 경우 requests.post() 사용)
response = requests.get(api_url, headers=headers, params=params, timeout=10)
response.raise_for_status() # HTTP 오류 발생 시 예외 발생
# 3. JSON 데이터 파싱
# response.json() 메서드는 응답 본문을 JSON으로 파싱하여 Python 딕셔너리/리스트로 반환
data = response.json()
item = data["item_list"][0]
my_data = {
"종목명": item["itemname"],
"주가변화율": item["change_rate"],
"주가변화량": item["change_val"],
"종목코드": item["itemcode"],
"현재가": item["now_val"]
}
print(my_data)
except requests.exceptions.HTTPError as e:
print(f"HTTP 에러 발생: {e.response.status_code} - {e.response.text}")
except requests.exceptions.ConnectionError as e:
print(f"네트워크 연결 오류: {e}")
except requests.exceptions.Timeout as e:
print(f"요청 타임아웃: {e}")
except requests.exceptions.RequestException as e:
print(f"그 외 요청 오류: {e}")
except json.JSONDecodeError as e:
print(f"JSON 파싱 오류: {e}. 응답 텍스트: {response.text[:200]}")
except Exception as e:
print(f"예상치 못한 오류 발생: {e}")
4️⃣ Selenium과 requests 조합 전략
어떤 상황에서는 Selenium과 requests를 함께 사용하는 것이 가장 효과적인 방법이 될 수 있다.
- Selenium로 초기 페이지 진입 및 로그인 처리: 복잡한 로그인 과정이나 특정 버튼 클릭을 통해 접근해야 하는 페이지는 Selenium으로 처리한다.
- API 호출 정보 추출: Selenium으로 로그인 후 개발자 도구의 Network 탭에서 동적으로 로드되는 데이터의 API 호출 정보를 파악한다.
- requests로 API 직접 호출: 파악된 API URL, 헤더, 파라미터 등을 이용하여 requests로 직접 대량의 데이터를 가져온다. (로그인 후 얻은 세션 쿠키 등을 requests에 전달할 수도 있다.)
- 데이터 파싱 및 저장: requests로 가져온 JSON 데이터를 파이썬 객체로 변환하고 필요한 정보를 추출하여 저장한다.
이러한 조합은 로그인 등 복잡한 상호작용은 Selenium으로 처리하고, 실제 데이터 추출은 더 빠르고 효율적인 requests로 처리하여 크롤링 효율을 극대화할 수 있다.
2025.07.04 - [[스파르타]내일배움캠프 데이터 분석 트랙] - 38일차 - 데이터 크롤링 1
38일차 - 데이터 크롤링 1
튜터김대영 튜터님학습 목표1. 웹의 동작 방식과 주요 구성 요소에 대해 이해합니다.2. HTML과 CSS의 기본적인 구조 및 역할을 파악합니다.3. 웹 개발자 도구의 핵심 기능을 활용하여 웹 페이지를
kminx.tistory.com
2025.07.04 - [[스파르타]내일배움캠프 데이터 분석 트랙] - 38일차 - 데이터 크롤링 2
38일차 - 데이터 크롤링 2
튜터김대영 튜터님학습 목표1. requests로 가져온 HTML 데이터를 BeautifulSoup 객체로 변환하는 방법을 익힙니다.2. BeautifulSoup의 다양한 메서드 (find(), find_all(), select(), select_one())를 활용하여 HTML 요소에
kminx.tistory.com
'[스파르타]내일배움캠프 데이터 분석 트랙 > Session' 카테고리의 다른 글
| 2025.07.09 Pandas 실무 기초 세션 4 (5) | 2025.07.09 |
|---|---|
| 2025.07.09 머신러닝 세션 4 (3) | 2025.07.09 |
| 2025.07.07 Pandas 실무 기초 세션 3 (0) | 2025.07.09 |
| 2025.07.07 머신러닝 세션 3 (5) | 2025.07.09 |
| 2025.07.04 데이터 크롤링 세션 2 (10) | 2025.07.09 |