직방 데이터 크롤링하기 - Python Selenium
사람이 살아가는 데 필수적인 의식주 중에서 주거는 가장 기본적인 생활 요소입니다.그래서 '크롤링해볼까?' 생각이 들어하게 되었습니다.직방 선정 이유부동산 플랫폼은 네이버부동산, 다방,
written-memories.tistory.com
이전글에서 이어집니다.
df_01 = pd.read_excel("직방_01.xlsx")
df_02 = pd.read_excel("직방_02.xlsx")
df = pd.concat([df_01, df_02], ignore_index=True)
df.info()
두 개의 xlsx 데이터를 합쳐서 사용합니다.
직방 크롤링 데이터 개요
크롤링 결과, 총 1778개의 매물 정보가 수집되었습니다.
데이터는 판다스(DataFrame)로 정리되어 있으며,
각 열(column)의 의미와 데이터 타입(Dtype)은 다음과 같습니다.
0 | 등록번호 | 매물의 고유한 등록번호 | int (정수형) |
1 | 위치 | 매물이 위치한 주소 | object (문자형) |
2 | 월/전세 | 거래 형태 (월세 or 전세) | object |
3 | 금액 | 매물의 보증금 및 월세 금액 | object |
4 | 관리비 | 해당 매물의 관리비 정보 | object |
5 | 설명 | 매물의 기본 설명 및 특징 | object |
6 | 면적 | 매물의 면적 정보 | object |
7 | 상태 | 방 형태 (예: 분리형 원룸) | object |
8 | 주차 | 주차 가능 여부 | object |
9 | 층 | 건물 층수 및 위치한 층 | object |
10 | 입주 여부 | 즉시 입주 가능 여부 등 | object |
직방 크롤링 데이터 전처리 요약
칼럼 | 전처리내용 |
등록번호 | 고유한 등록번호. 별도의 처리 하지 않음 |
위치 | 구체적인 동 단위 주소를 시 단위로 통일 (예: 대구광역시 북구 침산동 → 대구시) |
월/전세 | 월세 매물만 필터링하여 분석 |
금액 | 보증금/월세 형식 데이터를 각각의 숫자로 분리 (예: 1억 7,388/16 → 보증금 17388, 월세 16 단위(만원)) |
관리비 | 관리비 데이터에서 '00만원' 형식을 숫자로 변환 '관리비 없음', '확인 불가'는 0원 처리 |
설명 | 텍스트가 불필요하고 분석에 부적합하여 미사용 |
면적 | 면적 데이터 중 전용면적만 사용, 소수점 이하는 반올림하여 정수화 |
상태 | 방 형태와 욕실 수를 별도 칼럼으로 분리 (예: 오픈형 원룸(욕실 1개) → 오픈형 원룸 / 1) |
주차 | 주차 가능 여부를 **Yes(가능), No(불능)**로 표준화 |
층 | 특수 표기(저층, 중층, 고층, 반지하, 옥탑방 등)를 실제 거주층 수로 변환하여 분석에 용이하게 처리 |
입주 여부 | 입주 가능 날짜를 통일, 즉시 입주 가능 여부를 별도의 Yes/No 칼럼으로 생성하고 날짜 정보는 별도 명시 |
직방 크롤링 데이터 전처리 상세
- 위치
인천시 서구 가좌동을 '인천시'로 바꿉니다.
시로 표기되지 않고 군으로 표기되는 것도 바꾸어 '시'칼럼에 넣습니다.
(ex 칠곡군)
# 1. '시' 추출: 한글로 구성된 단어 중 "시"로 끝나는 경우 추출
df['시'] = df['위치'].str.extract(r'([가-힣]+시)', expand=False)
# 2. '시'가 없는 경우, 한글로 구성된 단어 중 "군"으로 끝나는 경우 추출해서 채워넣기
mask = df['시'].isna()
df.loc[mask, '시'] = df.loc[mask, '위치'].str.extract(r'([가-힣]+군)', expand=False)
광주광역시 서구 쌍촌동 역시 광주시 이므로 바꿔줍니다.
# 변환 매핑 딕셔너리 정의
mapping = {
'인천광역시': '인천시',
'부산광역시': '부산시',
'대구광역시': '대구시',
'광주광역시': '광주시',
'서울특별시': '서울시',
'세종특별자치시': '세종시',
}
# '시' 컬럼에 대해 일괄 치환
df['시'] = df['시'].replace(mapping)
안 바뀐 데이터가 있는지 확인합니다.
missing_rows = df[df['시'].isna()]
print(missing_rows[['위치', '시']])
- 월/전세
df2 = df[df['월/전세'] == '월세'].copy()
월세 데이터만 사용하기로 합니다.
- 금액
'금액' 컬럼을 '/' 기준으로 분리하여 보증금과 월세 칼럼 생성합니다.
ex) 1,000/35 보증금 1,000 월세 35
# '금액' 컬럼을 '/' 기준으로 분리하여 보증금과 월세 컬럼 생성
df2[['보증금', '월세']] = df2['금액'].str.split('/', expand=True)
# 결과 확인 (상위 5개 행 출력)
print(df2[['금액', '보증금', '월세']].head())
억 단위와 쉼표, 공백처리후 int형으로 변환해 줍니다.
def convert_korean_money(s):
# 양쪽 공백 제거 및 쉼표 제거
s = s.strip().replace(',', '')
total = 0
if '억' in s:
# '억'을 기준으로 분리
parts = s.split('억')
try:
# '억' 앞의 숫자를 만원 단위로 변환 (1억 = 10000)
total += float(parts[0]) * 10000
except ValueError:
pass
# '억' 이후의 문자열 처리 (예: " 2000" 또는 "2000만")
remainder = parts[1].strip() if len(parts) > 1 else ''
if remainder:
# 만약 "만" 단위가 명시되어 있으면 제거
if remainder.endswith('만'):
remainder = remainder[:-1]
try:
total += float(remainder)
except ValueError:
pass
else:
# '억'이 없으면 숫자만 있다고 가정 (이미 만원 단위)
try:
total = float(s)
except ValueError:
total = 0
return int(total)
# 보증금과 월세 칼럼에 변환 함수 적용
df2['보증금'] = df2['보증금'].apply(convert_korean_money)
df2['월세'] = df2['월세'].apply(convert_korean_money)
- 관리비
관리비는 관리비 5만원의 형식으로 되어있습니다.
관리비 확인불가와 관리비 없음도 소수 존재하기에, 0원으로 처리하였습니다.
df2['관리비'] = df2['관리비'].str.extract(r'(\d+)', expand=False).fillna(0).astype(int)
- 설명
아래와 같이 집들에 대한 간단한 설명이 나와있습니다.
따로 전처리하지 않고 미사용 했습니다.
보증보험, 안심대출, 풀옵션, 주차 |
저렴한반전세 남향 밝음 탁트인뷰 |
서대전네거리 바로근처, 초역세권 , 주변 인프라 좋은방 |
No.1 빌트인만점 v 즉시입주가능 |
LH가능,중기청80프로,버팀목 |
- 면적
' 면적'은 [계약 45.52m² / 전용 29.98m²]과 [전용 33.06m²] 두 가지 타입으로 존재합니다.
전용 기준으로 통일합니다. 면적 소수점 아래는 반올림합니다.
#'면적' 칼럼에서 "전용" 뒤의 숫자 추출 (예: "전용 19.83m²" → "19.83", "계약 45m² / 전용 32.2m²" → "32.2")
df2['면적_num'] = df2['면적'].str.extract(r'전용\s*([0-9.]+)', expand=False)
- 상태
'상태' 칼럼은 방형과 욕실수로 분리합니다
ex 오픈형원룸 (욕실 1개) -> 오픈형 원룸 / 1
# '상태' 컬럼을 방형과 욕실수로 분리
df2[['방형', '욕실수']] = df2['상태'].str.extract(r'(.+)\s+\(욕실\s+(\d+)개\)', expand=True)
- 주차
'주차' 가능 Yes, 불가능 No로 바꿉니다.
df2['주차'] = df2['주차'].replace({'주차 가능': 'Yes', '주차 불가능': 'No'})
- 층
수집한 데이터에는 숫자로 표현되지 않은 층수 정보(예: 옥탑방, 고층, 반지하 등)가 존재합니다.
이를 분석에 용이하게 처리하기 위해 다음과 같은 기준으로 변환했습니다.
특수층 표기처리 방식 비고
저층 | 전체 층수의 ¼ (반올림) | 예: 전체 8층 → 2 |
중층 | 전체층의 1/2 (반올림) | 예: 전체 10층 → 5 |
고층 | 전체층의 ¾ (반올림) | 예: 전체 12층 → 9 |
옥탑방 | 0층으로 표기 | 예: 옥탑방 -> 0 |
반지하 | -1층으로 처리 | 예 반지하 -> -1 |
거주층(raw)과 전체층(raw)은
특수 케이스(옥탑방, 고층, 반지하 등), 그대로 별도 칼럼에 보관하려고 만들었으나,
후에 사용하지는 않았습니다.
import re
def parse_floor(living_str, total_str):
"""
living_str: 거주층(raw) 문자열 (예: "4층", "옥탑방층", "고층", "반지하", "저층", "중층")
total_str: 전체층(raw) 문자열 (예: "11층")
"""
# 전체층 숫자 추출
total_match = re.search(r'(\d+)', total_str)
total = int(total_match.group(1)) if total_match else None
# 거주층에 숫자가 포함되어 있으면 숫자 추출해서 반환
digit_match = re.search(r'(\d+)', living_str)
if digit_match:
return int(digit_match.group(1))
# 특수 문자열 처리
if '반지하' in living_str:
return -1
elif '옥탑방' in living_str:
return 0
elif '저층' in living_str:
if total is not None:
return int(round(total * 0.25))
elif '중층' in living_str:
if total is not None:
return int(round(total * 0.5))
elif '고층' in living_str:
if total is not None:
return int(round(total * 0.75))
return None
# 원본 '층' 칼럼에서 거주층(raw)와 전체층(raw) 분리
df2[['거주층(raw)', '전체층(raw)']] = df2['층'].str.split('/', expand=True)
# 새로운 '거주층' 칼럼 계산
df2['거주층'] = df2.apply(lambda row: parse_floor(row['거주층(raw)'], row['전체층(raw)']), axis=1)
# 전체층은 이미 숫자만 추출하는 방식으로 변환 (예: "11층" -> 11)
df2['전체층'] = df2['전체층(raw)'].str.extract(r'(\d+)', expand=False).astype(int)
# missing value 개수 확인
missing_count = df2['거주층'].isna().sum()
print("Missing count in 거주층:", missing_count)
# missing value가 없다면 int형으로 변환
if missing_count == 0:
df2['거주층'] = df2['거주층'].astype(int)
- 입주여부
ex 즉시 입주 가능 과 (즉시입주가)를 즉시 입주 가능 통일했습니다.
즉시입주가능여부칼럼 생성하여 Yes or No로 표기했습니다.
'2025. 03. 15 이후 입주 가능'과 같은 경우는 입주가능일 별로 생성해서 2025. 03. 15 넣었습니다.
df2['입주 여부'] = df2['입주 여부'].apply(
lambda x: '즉시 입주 가능' if '즉시 입주 가능' in x else x
)
def extract_date_if_any(s):
"""
'2025. 03. 15 이후 입주 가능' 같은 문자열에서 '2025.03.15' 추출.
추출 후 datetime으로 변환. 없으면 None 반환.
"""
match = re.search(r'(\d{4}\.\s*\d{2}\.\s*\d{2})', s)
if match:
# 공백과 '.' 제거를 정리한 뒤, datetime으로 파싱
date_str = match.group(1).replace(' ', '') # "2025.03.15" 형태로
try:
return datetime.strptime(date_str, '%Y.%m.%d')
except ValueError:
return None
return None
df2['입주가능일'] = df2['입주 여부'].apply(extract_date_if_any)
others = df2[
(df2['입주 여부'] != '즉시 입주 가능') & (df2['입주가능일'].isna())
]
print(others['입주 여부'].value_counts())
df2['즉시입주가능여부'] = df2['입주 여부'].apply(lambda x: 'Yes' if '즉시 입주 가능' in x else 'No')
전처리가 완료되었습니다!
필요한 칼럼만 사용하도록 합시다.
final_columns = ['등록번호', '위치', '시', '관리비', '면적',
'보증금', '월세', '주차','방형', '욕실수', '거주층', '전체층',
'입주가능일', '즉시입주가능여부']
df_final = df2[final_columns].copy()
df_final.to_excel("직방_전처리.xlsx", index=False)