카테고리 없음

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

Han_Star 2025. 3. 7. 13:56

사람이 살아가는 데 필수적인 의식주 중에서 주거는 가장 기본적인 생활 요소입니다.

그래서 '크롤링해볼까?' 생각이 들어하게 되었습니다.


  • 직방 선정 이유

부동산 플랫폼은 네이버부동산, 다방, 직방, 부동산플래닛 등 다양하게 있습니다.

 

최초에는 특정 지역에서의 부동산 월세 매물을 가져오려 했으나,

특정 지역 한정해서 클릭과 크롤링을 진행하기란, 생각보다

어려운 일이었습니다.

 

고민을 하더 찰나 찾은 것이

매물번호로 검색되는 직방 사이트

매물번호를 기준으로 크롤링을 하는 것입니다.

https://www.zigbang.com/home/oneroom/items/43932924

 

좌 (직방전체화면) 우(크롤링할 내용)

매물번호로 url에 접속하면 다음과 같은 창이 뜨게 됩니다.

 

우측에 매물에 대한 정보가 존재하는데, 스크롤을 내리면 추가정보를 알 수 있습니다.

 

다행히 스크롤을 하지 않아도 html에 정보가 다 포함되어 있기에,

스크롤을 내리는 행위를 추가하지 않아도 됩니다.


  • 크롤링
driver = uc.Chrome()

크롬창을 엽니다. 

크롬 버전이 다르다면,

version_main=133 (숫자는 버전에 맞게)을 괄호 안에 넣어주면 문제없습니다.

driver.get('https://www.zigbang.com/home/oneroom/items/43862239')

직방의 특정 매물 페이지로 이동합니다.

 

location = driver.find_element(By.CSS_SELECTOR, 'div.css-1563yu1.r-aw03qq.r-1wbh5a2.r-1w6e6rj.r-159m18f.r-1b43r93.r-16dba41.r-rjixqe').text
location

매물이 위치한 주소를 태그를 통해 가져옵니다.

class_name = input()
class_name.replace(' ','.')

이때, 클래스 이름에 공백이 들어간다면, 전부.으로 바꿔줘야 합니다.

 

다른 원하는 정보들도 같은 방식으로 찾으면 됩니다.


  • 예외처리

크롤링을 진행하는 과정에서, 데이터의 형식이 다른 예외가 발생했습니다.

 

예외의 대다수가 같은 태그명을 사용하는데,

특수한 경우에서 같은 태그명에 데이터가 더 들어있는 경우가 있었습니다.

manage_cost_s = driver.find_elements(By.CSS_SELECTOR, 'div.css-1563yu1.r-aw03qq.r-1wbh5a2.r-1w6e6rj.r-159m18f.r-1b43r93.r-16dba41.r-rjixqe.r-fdjqy7.r-13wfysu.r-q42fyq.r-1ad0z5i')
#원래 find_element 로 했으나 지역명이 관리비 앞에 존재하는 특수경우가 30% 발생
for i in manage_cost_s:
    text = i.text
    if text.startswith("관리비"):
        manage_cost = text
manage_cost

첫 번째 예외는 find_element 로했으나,

지역명이 관리비 앞에 한 번 더 출몰하는 경우가 있어

elments로 다 가져와서 관리비만 뽑아내도록 바꾸었습니다.

money = driver.find_element(By.CSS_SELECTOR, 'div.css-1563yu1.r-aw03qq.r-1wbh5a2.r-1w6e6rj.r-159m18f.r-1x35g6.r-b88u0q.r-ueyrd6.r-fdjqy7.r-13wfysu.r-q42fyq.r-1ad0z5i').text.split()
money_type = money[0]
charge = " ".join(money[1:])
# 억 단위가 넘어갈을때, split 사용시 1억 에서 끊기는 8% 예외
print(money_type, charge)

 

2번째 예외는 money가 아래와 같이 존재하는데,

split 사용하여 [1]로 처리했더니,

뒷부분  7,388/16을 가져오지 못하는 경우가 생겨

[1:] 첫 번째 이후 모든 값으로 바꾸어 주었습니다.

월세 1억 7,388/16

3번째 예외는 특정매물에 사람들이 많은 관심이 있었을 때,

매물정보에 "이 집을 관심있게 보는 분들이 급격하게 증가했어요"라는 문구가 추가됩니다.

stats = driver.find_elements(By.CSS_SELECTOR, 'div.css-1dbjc4n.r-1mlwlqe.r-eqz5dr.r-16y2uox.r-1wbh5a2.r-1777fci')

# 원하지 않는 텍스트
unwanted = "이 집을 관심있게 보는 분들이 급격하게 증가했어요"
# '이 집을 관심있게 보는 분들이 급격하게 증가했어요' 라는 문구가 포함된 4% 예외가 발생

# unwanted 텍스트가 포함된 요소를 제외하고 필터링
filtered_stats = [data for data in stats if unwanted not in data.text]

# 필터링한 결과 중 원하는 6개의 요소만 선택
desired_data = filtered_stats[:6]

for data in desired_data:
    print(data.text)

결과는 다음과 같습니다.

영등포시장역 도보 3분 컨디션 좋은 풀옵션 오피스텔
계약 26.4m² / 전용 15.03m²
분리형원룸 (욕실 1개)
주차 가능
10층/11층
즉시 입주 가능

 

마지막 예외는 매물번호로 사이트에 검색했을 때,

존재하지 않는 매물이라고 뜨는 경우가 11% 있었습니다.

try:

except:
	continue

continue로 수집하지 않고, 다음으로 넘어가게 처리했습니다.


  • 결과 및 전체코드

원하는 데이터를 성공적으로 가져왔습니다.

 

다음은 전체 코드입니다.

total = []

데이터를 담을 total을 따로 만들어 주었습니다.

#https://www.zigbang.com/home/oneroom/items/43862098
start_num = 43862098+1000
print(start_num)
for n in tqdm(range(start_num, start_num + 1000)):
    driver.implicitly_wait(10)
    driver.get(f'https://www.zigbang.com/home/oneroom/items/{n}')
    #매물번호로 직방 사이트 방문
    
    try:
        box = [] #정보담는 박스
        # 집 번호
        house_num = driver.find_element(
            By.CSS_SELECTOR,
            "div.css-1563yu1.r-jwli3a.r-1wbh5a2.r-1w6e6rj.r-159m18f.r-1enofrn.r-majxgm.r-1cwl3u0.r-fdjqy7.r-13wfysu.r-q42fyq.r-1ad0z5i"
        ).text.split()[1].strip()
        
        location = driver.find_element(
            By.CSS_SELECTOR, 
            'div.css-1563yu1.r-aw03qq.r-1wbh5a2.r-1w6e6rj.r-159m18f.r-1b43r93.r-16dba41.r-rjixqe'
        ).text
        
        money = driver.find_element(By.CSS_SELECTOR, 'div.css-1563yu1.r-aw03qq.r-1wbh5a2.r-1w6e6rj.r-159m18f.r-1x35g6.r-b88u0q.r-ueyrd6.r-fdjqy7.r-13wfysu.r-q42fyq.r-1ad0z5i').text.split()
        money_type = money[0]
        charge = " ".join(money[1:])
        
        manage_cost_s = driver.find_elements(By.CSS_SELECTOR, 'div.css-1563yu1.r-aw03qq.r-1wbh5a2.r-1w6e6rj.r-159m18f.r-1b43r93.r-16dba41.r-rjixqe.r-fdjqy7.r-13wfysu.r-q42fyq.r-1ad0z5i')
        for i in manage_cost_s:
            text = i.text
            if text.startswith("관리비"):
                manage_cost = text
        # box에 기본 정보 저장 (house_num, location, money 정보 등)
        box = [house_num, location, money_type, charge, manage_cost]
        
        stats = driver.find_elements(By.CSS_SELECTOR, 'div.css-1dbjc4n.r-1mlwlqe.r-eqz5dr.r-16y2uox.r-1wbh5a2.r-1777fci')
        
        # 원하지 않는 텍스트
        unwanted = "이 집을 관심있게 보는 분들이 급격하게 증가했어요"
        
        # unwanted 텍스트가 포함된 요소를 제외하고 필터링
        filtered_stats = [data for data in stats if unwanted not in data.text]
        
        # 필터링한 결과 중 원하는 6개의 요소만 선택
        desired_data = filtered_stats[:6]

        for data in desired_data:
            box.append(data.text)
            
        total.append(box)
        #어차피 고유한 housenum일테니 중복확인을 제거하는게 빠르겠지.
            
    except Exception as e:
        continue

크롤링 코드입니다.

column_names = ['등록번호', '위치', '월/전세', '금액', '관리비', '설명','면적', '상태', '주차', '층', '입주 여부']

df = pd.DataFrame(total, columns=column_names)

df.to_excel('직방_02.xlsx', index=False)

수집된 데이터를 데이터프레임으로 만들어 엑셀로 저장합니다.


  • 마무리

매물번호를 기준으로 중복 여부를 확인하여 중복된 데이터는 넣지 않도록 처리했었는데,
생각해 보니 매물번호 자체가 이미 고유한 값이라 굳이 중복 검사를 하지 않아도 될 것 같습니다.
오히려 중복 처리를 생략하면 데이터를 더욱 빠르게 수집할 수 있는 장점도 있습니다.

 

또 적다 보니 생각난 게, 매물번호로 url에 접근을 하는데

굳이 url에 접근해서 'housenum'변수에 매물번호를 담아 가져올 필요가 없었네요.

 

start_num이 43862098+1000 인 이유는

43862098 매물부터 1000개씩 두 번 나누어 수집했기 때문입니다.

43862098이라는 숫자는 임의로 선택한 숫자입니다.

 

2000번의 결과로 만들어진 엑셀을 첨부하며 마무리하겠습니다.

 

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