카테고리 없음

Python으로 직방 크롤링한 데이터 전처리하기

Han_Star 2025. 3. 7. 15:03

https://written-memories.tistory.com/entry/%EC%A7%81%EB%B0%A9-%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%81%AC%EB%A1%A4%EB%A7%81%ED%95%98%EA%B8%B0-Python-Selenium

 

직방 데이터 크롤링하기 - Python Selenium

사람이 살아가는 데 필수적인 의식주 중에서 주거는 가장 기본적인 생활 요소입니다.그래서 '크롤링해볼까?' 생각이 들어하게 되었습니다.직방 선정 이유부동산 플랫폼은 네이버부동산, 다방,

written-memories.tistory.com

 

이전글에서 이어집니다.


직방_01.xlsx
0.09MB
직방_02.xlsx
0.10MB

 

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)

 

전처리 전
전처리 후
직방_전처리.xlsx
0.11MB