부동산 - 조인스랜드 데이터 크롤링
화면 하단에 다음과 같은 데이터가 테이블(table) 표시된다. 이 데이터들이 크롤링하고자 하는 데이터
여기에 표시되는 데이터가 어떻게 구성되어 있는지 개발자 도구로 살펴보자
개발자 도구에서 보면, id="sesae_info_gu" 인 div 태그 하위에 table이 있고, 여기에 데이터들이 구성되어 있음을 확인할 수 있다
하지만 pandas.read_html()로 읽었을 때 테이블이 읽히지 않는다.
import pandas as pd
url = 'http://price.joinsland.joins.com/area/index.asp?mcateGroup=A1&mcateCode=A1A3A4&areaCode=1168010600'
dfs = pd.read_html(url, 'lxml')
dfs
"ValueError: No tables found" 에러가 발생한다
페이지 소스 확인¶
메뉴에서 "보기 > 개발자 보기 > 소스보기" 선택하여 페이지 소스를 확인
크롬 개발자 도구로 table과 데이터가 보이지만, 소스 보기를 하면 소스에는 데이터가 보이지 않는다
id="sesae_info_gu" 인 div 태그에 table 도 없고 데이터도 없다
이 처럼 크롬개발자 도구에서는 데이터가 있으나, 페이지 소스에는 데이터가 없는 경우는 다음 두 가지 중의 하나
- Ajax로 데이터를 요청하여 데이터를 표현 → 데이터 요청 URL을 따로 분석
- JavaScript가 실행되어 데이터를 표현 → Selenium 사용
Ajax (Asynchronous JavaScript and XML, 에이잭스)는 자바스크립트로 요청하는 비동기 데이터 요청.
Ajax 데이터 요청 확인¶
- Network 탭을 열고
- 검색조건이 달라질 때('동'을 선택), 어떤 데이터가 오가는지 살펴본다
- 요청/응답 데이터들 중에 데이터기 포함되어 오가는지 살펴본다
joins.com/ajax¶
여기서는 다음 URL로 데이터가 요청/응답 되었음을 확인할 수 있다
다음 URL의 인자로 다음 값들이 전달.
http://price.joinsland.joins.com/ajax/price.info.dongapt.asp
- mcateGroup: A1=아파트, A6=오피스텔
- mcateCode: A1A3A4
- areaCode=1168010600
결과적으로 지역코드(areaCode)만 지정하면 가격 데이터를 가져올 수 있다.
지역이름과 코드¶
(참고) joinsland.joins.com 자체에도 법정동을 조회하는 내용이 있으나 데이터가 온전한 JSON 이 아니어서 가공하기 어렵다
# 구/시/군 (MCODE) 코드 가져오기
import requests
# 서울특별시 하위 '구/시/군' 읽기
area_code = '110000000'
url = 'http://price.joinsland.joins.com/ajax/area_search.asp?div=MCODE&areaCode=' + area_code
r = requests.get(url)
print(r.text)
# JSON처럼 보이지만 JSON 규격을 따르지 않고 있다
법정동 이름과 코드¶
부동산 정보등에는 법정동 코드가 사용된다.
- 법정동 코드(8자리)
- 2(시/도) + 2자리(구/군/구) + 2자리(읍/면/동) + 2자리(리/단지)
법정동 전체 코드는 아래 링크
상세한 내용은 다음 링크에 설명을 참고
import pandas as pd
def get_areacode():
df_areacode = pd.read_csv('https://goo.gl/tM6r3v', sep='\t', dtype={'법정동코드':str, '법정동명':str})
df_areacode = df_areacode[df_areacode['폐지여부'] == '존재']
df_areacode = df_areacode[['법정동코드', '법정동명']]
return df_areacode
def get_province():
df_areacode = get_areacode()
df_province = df_areacode[ df_areacode['법정동코드'].str.contains('\d{2}0{8}|36110{6}')]
return df_province
df_areacode = get_areacode()
df_province = get_province()
df_province
df_areacode.head(10)
df_areacode[ df_areacode['법정동명'].str.contains('서울특별시 강남구') ]
데이터 전처리¶
http://price.joinsland.joins.com/ajax/price.info.dongapt.asp
- mcateGroup A1=아파트, A6=오피스텔
- mcateCode: A1A3A4
- areaCode=1168010600 # 강남구 대치동
import pandas as pd
url = 'http://price.joinsland.joins.com/ajax/price.info.dongapt.asp?mcateGroup=A1&mcateCode=A1A3A4&areaCode=' + '1168010600'
print(url)
dfs = pd.read_html(url)
df = dfs[0]
df.head(10)
import pandas as pd
import requests
from bs4 import BeautifulSoup
url = 'http://price.joinsland.joins.com/ajax/price.info.dongapt.asp?mcateGroup=A1&mcateCode=A1A3A4&areaCode=' + '1168010600'
r = requests.get(url)
soup = BeautifulSoup(r.text, 'lxml')
table = soup.find('table')
trs = table.tbody.find_all('tr')
rowspan_val = ''
for tr in trs[:20]: # 처음 20개 확인
tds = tr.find_all('td')
if tds[0].has_attr('rowspan'):
rowspan_val = tds[0].text
단지명, 면적, 매매가, 매물, 전세가, 전세_매물 = tds[0].text, tds[1].text, tds[2].text, tds[3].text, tds[4].text, tds[5].text
else:
단지명, 면적, 매매가, 매물, 전세가, 전세_매물 = rowspan_val, tds[0].text, tds[1].text, tds[2].text, tds[3].text, tds[4].text
매매가_저 = int(매매가.split(' ~ ')[0].replace(',', ''))
매매가_고 = int(매매가.split(' ~ ')[1].replace(',', ''))
전세가_저 = int(전세가.split(' ~ ')[0].replace(',', ''))
전세가_고 = int(전세가.split(' ~ ')[1].replace(',', ''))
print(단지명, 면적, 매매가_저, 매매가_고, 매물, 전세가_저, 전세가_고, 전세_매물)
# DataFrame으로 만들기
values_list = []
table = soup.find('table', attrs={'class':'tbl_compare'})
trs = table.tbody.find_all('tr')
rowspan_val = ''
for tr in trs:
tds = tr.find_all('td')
if tds[0].has_attr('rowspan'):
rowspan_val = tds[0].text
단지명, 면적, 매매가, 매물, 전세가, 전세_매물 = tds[0].text, tds[1].text, tds[2].text, tds[3].text, tds[4].text, tds[5].text
else:
단지명, 면적, 매매가, 매물, 전세가, 전세_매물 = rowspan_val, tds[0].text, tds[1].text, tds[2].text, tds[3].text, tds[4].text
매매가_저 = int(매매가.split(' ~ ')[0].replace(',', ''))
매매가_고 = int(매매가.split(' ~ ')[1].replace(',', ''))
전세가_저 = int(전세가.split(' ~ ')[0].replace(',', ''))
전세가_고 = int(전세가.split(' ~ ')[1].replace(',', ''))
values_list.append([단지명, 면적, 매매가_저, 매매가_고, 매물, 전세가_저, 전세가_고, 전세_매물])
cols = ['단지명', '면적', '매매가_저', '매매가_고', '매물', '전세가_저', '전세가_고', '전세가_매물']
df = pd.DataFrame(values_list, columns=cols)
print ("건수:", len(df))
df.head(20)
함수로 만들기¶
# 조인스부동산 아파트 조회
import pandas as pd
import requests
from bs4 import BeautifulSoup
def get_areacode():
df_areacode = pd.read_csv('https://goo.gl/tM6r3v', sep='\t', dtype={'법정동코드':str, '법정동명':str})
df_areacode = df_areacode[df_areacode['폐지여부'] == '존재']
df_areacode = df_areacode[['법정동코드', '법정동명']]
return df_areacode
def get_province():
df_areacode = get_areacode()
df_province = df_areacode[ df_areacode['법정동코드'].str.contains('\d{2}0{8}|36110{6}')]
return df_province
def joins_realasset(areacode = ''):
url = 'http://price.joinsland.joins.com/ajax/price.info.dongapt.asp?mcateGroup=A1&mcateCode=A1A3A4&areaCode=' + areacode
r = requests.get(url)
soup = BeautifulSoup(r.text, 'lxml')
values_list = []
table = soup.find('table', attrs={'class':'tbl_compare'})
trs = table.tbody.find_all('tr')
rowspan_val = ''
for tr in trs:
tds = tr.find_all('td')
if tds[0].has_attr('rowspan'):
rowspan_val = tds[0].text
단지명, 면적, 매매가, 매물, 전세가, 전세_매물 = tds[0].text, tds[1].text, tds[2].text, tds[3].text, tds[4].text, tds[5].text
else:
단지명, 면적, 매매가, 매물, 전세가, 전세_매물 = rowspan_val, tds[0].text, tds[1].text, tds[2].text, tds[3].text, tds[4].text
매매가_저 = int(매매가.split(' ~ ')[0].replace(',', ''))
매매가_고 = int(매매가.split(' ~ ')[1].replace(',', ''))
전세가_저 = int(전세가.split(' ~ ')[0].replace(',', ''))
전세가_고 = int(전세가.split(' ~ ')[1].replace(',', ''))
values_list.append([단지명, 면적, 매매가_저, 매매가_고, 매물, 전세가_저, 전세가_고, 전세_매물])
cols = ['단지명', '면적', '매매가_저', '매매가_고', '매물', '전세가_저', '전세가_고', '전세가_매물']
df = pd.DataFrame(values_list, columns=cols)
return df
검색과 활용¶
df_areacode = get_areacode()
area = '신사동'
area_code = df_areacode[ df_areacode['법정동명'].str.contains(area) ]
area_code
area = '강남구 신사동'
area_code = df_areacode[ df_areacode['법정동명'].str.contains(area) ]['법정동코드'].values[0]
area_code
joins_realasset(area_code)
댓글
Comments powered by Disqus