본 글은 개인 프로젝트를 진행하면서 만났던 이슈 혹은 그저 과정이었던 내용들을 끄적일 것입니다.
개인 프로젝트로 CardForyou(가제) 를 시작하였습니다. 개인별 맞춤형 카드 추천 시스템을 만들 것 입니다. (대략 12월 쯤 완성 목표에요)
첫 번째 과정으로 카드 데이터를 모아야했습니다.
어디서 모으느냐!? 처음에는 공공데이터에 있을 줄 알았어요 (실제로 있긴 했지만 정말 간단한 정보만 있었는데 쓸모가 없었죠) 하지만 제가 원하는 데이터셋을 만들기 위한 공공데이터는 없었고 결국 웹페이지 크롤링하기로 했습니다.
https://www.card-gorilla.com/home
카드고를때, 카드고릴라
국내 최대 신용카드전문 플랫폼! 1,400여개의 카드 중 내게 딱 맞는 카드를 만나보세요! 실시간 인기순위와 알짜카드 추천으로 최적의 카드를 찾아드립니다
www.card-gorilla.com
(광고아님)
해당 사이트의 구조를 파악하면서 제가 원하는 데이터셋을 만들어 갔던 과정을 공유해보려 합니다.
1. How to start?
최대한 많은 양의 카드 데이터가 필요했지만 전체 데이터를 조회하는 부분이 해당 사이트에는 존재하지 않았습니다. (내가 못찾은 건가) 따라서 처음에는 월별 TOP100을 모두 가져와서 데이터셋을 만들 심산으로 코드를 작성하였습니다.
main.py
import csv
import time
from app.crawing.get_top_100 import get_top100_cards
from app.crawing.get_detail import get_card_detail
all_data = []
top_cards = get_top100_cards("2025-06-01")
for card in top_cards:
try:
print(f"크롤링 중: {card['name']} ({card['detail_url']})")
detail = get_card_detail(card['detail_url'])
print("상세정보:", detail)
card.update(detail)
all_data.append(card)
time.sleep(1)
except Exception as e:
print(f"[에러] {card['name']} 에서 오류 발생: {e}")
# CSV 저장
with open("cardgorilla_top100_June.csv", "w", newline="", encoding="utf-8-sig") as f:
writer = csv.DictWriter(f, fieldnames=all_data[0].keys())
writer.writeheader()
writer.writerows(all_data)
Top100 페이지에서 카드 이름과 카드사의 정보를 수집할 수 있었고 상세 페이지로 갈 수 있는 링크를 통해 상세페이지로 이동해 카드에 대한 상세정보를 가져올 수 있도록 작성하였습니다.
Selenium과 BeautifulSoup을 기반으로 웹 페이지 크롤링을 진행했고 위 코드에서는 렌더링 대기를 위해 time.sleep 을 사용했지만 실제로 데이터가 렌더링 됐는지 확인하기 위해서는 seleuium에 포함된 WebDriverWait를 사용하는 것이 크롤링 속도도 높일 수 있고 안정적입니다.
get_top_100.py
from selenium import webdriver
from bs4 import BeautifulSoup
import time
def get_top100_cards(date="2025-06-01"):
url = f"https://www.card-gorilla.com/chart/top100?term=monthly&date={date}"
options = webdriver.ChromeOptions()
options.add_argument("--headless")
driver = webdriver.Chrome(options=options)
driver.get(url)
time.sleep(3) # JS 렌더링 대기
soup = BeautifulSoup(driver.page_source, "html.parser")
driver.quit()
card_elements = soup.select("ul.rk_lst > li")
card_data = []
for el in card_elements:
try:
name = el.select_one(".card_name").text.strip()
corp = el.select_one(".corp_name span").text.strip()
detail_href = el.select_one("a[href^='/card/detail/']")["href"]
detail_url = "https://www.card-gorilla.com" + detail_href
card_data.append({
"name": name,
"corp": corp,
"detail_url": detail_url
})
except Exception as e:
print(f"[Error] {e}")
return card_data
get_detail.py
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
import time
def get_card_detail(detail_url):
options = webdriver.ChromeOptions()
options.add_argument("--headless")
driver = webdriver.Chrome(options=options)
driver.get(detail_url)
time.sleep(2) # JS 렌더링 대기
condition = driver.find_element(By.CSS_SELECTOR, 'div.bnf2')
fee = condition.find_element(By.CSS_SELECTOR, 'dd.in_out').text
before_month = condition.find_element(By.CSS_SELECTOR, 'dl:nth-child(2)').text
benefit_array = []
benefit_elements = driver.find_elements(By.CSS_SELECTOR, "div.bene_area dl")
for idx, benefit in enumerate(benefit_elements):
try:
if idx == len(benefit_elements) - 1:
break
if idx != 0:
# 클릭 전에 요소가 보이도록 스크롤
driver.execute_script("arguments[0].scrollIntoView(true);", benefit)
# 요소가 clickable할 때까지 대기
WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.CSS_SELECTOR, "div.bene_area dl")))
benefit.click()
time.sleep(0.5) # 짧은 클릭 후 렌더링 대기
main_title = benefit.find_element(By.CSS_SELECTOR, "dt p").text
sub_title = benefit.find_element(By.CSS_SELECTOR, "dt i").text
in_box = benefit.find_element(By.CSS_SELECTOR, "dd div.in_box")
detail_el = in_box.find_elements(By.CSS_SELECTOR, "p")
detail_content = ''
for element in detail_el:
detail_content += element.text.strip() + '\n'
benefit_array.append({
main_title,
sub_title,
detail_content
})
benefit.click()
time.sleep(0.5)
except Exception as e:
print(f"[오류] 혜택 {idx+1} 클릭 또는 추출 실패: {e}")
driver.quit()
return {
"fee": fee,
"before_month": before_month,
"benefits": benefit_array
}
get_detail.py 코드를 작성하는 과정에서는 많은 오류를 겪곤 했는데요.
카드의 Detail 정보를 모두 가져오기 위해서는 아코디언 컴포넌트를 클릭을 해야만 했습니다. 따라서 클릭을 하는 이벤트를 중간에 삽입하여 모든 데이터 요소를 가져올 수 있도록 하였는데, 중간 중간 아코디언 컴포넌트에서 오류가 발생하곤 했습니다.
이유는 요소를 가져올 시점의 position과 클릭하려는 시점의 position이 달라진다는 점이었는데, 첫 번째 아코디언 박스를 클릭하게 되면 자연스럽게 아래 아코디언 박스의 컴포넌트는 화면의 아래로 더 내려가게 됩니다. 이 시점에서 해당 포지션을 기억해뒀던 웹 드라이버가 해당위치를 클릭하게 되면 원하는 아코디언 박스를 클릭할 수 없게 되죠!
따라서 아코디언 박스를 클릭하여 펼친 후에 데이터를 가져온 뒤에는 다시 아코디언 박스를 클릭하여 원래 위치로 돌려주는 로직을 추가했습니다.
해당 부분을 실행하고 나니 첫 번째 아코디언 요소를 클릭할 때 오류가 발생하곤 했는데요, 왜 인지 원인을 모르겠어서 예외 코드를 추가해서 첫 번째 아코디언 요소를 누를 때의 동작을 달리 실행하였습니다 (아는 사람 댓글로 남겨주세요 ㅠ)
이런 과정을 거쳐서 6월 TOP100의 카드의 정보들을 원하는 만큼 수집해볼 수 있었습니다.
2. 아직 부족한데..
위 과정을 거쳐서 데이터를 수집하긴 했으나 제가 만들고자하는 서비스의 최종적인 데이터셋으로 쓰기에는 부족하다고 생각이 들었습니다. 그 이유는 아래와 같은데,,
1. 체크카드인지 신용카드인지 구분이 안됨. (카드 종류 별로 추천해주고 싶은데 데이터가 이러면 방법이 없었습니다.)
2. 월별로 수집해보니 중복이 많음 + 중복됐을 때 수동으로 걸러내야하는 과정이 귀찮았다.
위 문제를 해결하기 위한 방법을 찾다보니 찾아낼 수 있었고 더 좋은 데이터를 가져올 수 있었습니다.
(소스코드는 공개하지 않을께요 - 좀 길어요)
첫 번째 문제점의 해결방법은 API를 이용하는 것이었습니다. 개발자 도구의 네트워크 탭을 보면 해당 페이지가 렌더링 될 때 통신하는 API를 확인할 수 있는데요.

위 이미지의 API는 카드의 상세 정보를 가져오는 API 였고 POSTMAN은 통해서 동일한 API를 호출 했을 때 막혀있지 않고 수월하게 가져올 수 있다는 사실을 알았습니다. (해당 페이지에서만 가져올 수 있게 막아놓는 API들도 많은데 럭키비키였다.)
따라서 해당 API 호출을 카드 정보를 크롤링할때 API 호출을 삽입하여 체크카드인지 신용카드인지 정보를 가져올 수 있었습니다.
위 문제를 해결하다보니 두 번째 문제를 해결할 수 있었는데,, 바로바로 detail/{card_id} 값이 일정하게 보여진다는 점 이었습니다. 혹시나 해서 {card_id}의 값을 1부터 입력했는데 상세페이지에 접근할 수 있었고 대략 2800의 번호까지 상세페이지가 있다는 것을 알았습니다.
상세 페이지 내에서도 카드명, 카드회사 정보는 당연히 크롤링을 통해 가져올 수 있었고 1 부터 올라가다보면 많은 양의 카드 정보를 가져올 수 있겠다고 파악할 수 있었습니다.
물론 카드 정보가 없어진 경우 main 페이지로 리다이렉션이 되거나, 현재 발급이 중단된 카드의 정보들도 있지만 이는 모두 크롤링 과정에서 예외처리를 할 수 있는 부분이었기 때문에 어렵지 않게 처리할 수 있었습니다.
(코드가 궁금하면 개인적으로 연락주시면 공유해드릴께요)
이제 데이터는 얼추 다 모았고,, 본격적으로 프로젝트를 시작해볼까 합니다. (근데 데이터 이렇게 모아서 활용해도 괜찮으려나? 수익창출 안하니 괜찮겠지,,? 홍보도 해줄께 )
다음 글에는 본격적으로 프로젝트를 진행하면서 겪은 이슈나 기획과정을 정리해볼까 합니다.