개요
면허는 있지만 차가 없어 고민인 요즘
괜찮은 중고차가 있나 한 번 확인이라도 해보려고 여러 중고차 사이트를 돌아다녀보다 문득 encar API를 분석해서 내가 원하는 차량 정보/보험 이력 등을 조회해 나만의 데이터로 가공하면 어떨까 해서 만들어보게 되었다
API 분석
Fetch/XHR
로 필터링
아무 페이지 클릭
그러면 아래처럼 API로 부터 받은 요청이 여러개 보일텐데 Preview
탭에서 포스팅 Id를 리턴하고 있는 것을 찾아서 클릭
자, 이제 Headers 탭에 Requests URL을 복사하고 Payload 탭에 쿼리 파라메터들이 어떠한 형식으로 들어오는지 확인하면 분석은 끝이난다
데이터 추출
Query Parameter와 Request URL을 활용하여 API 서버로부터 데이터를 받아올거다
쿼리 파라메터 생성
위에서 본 Query Parameter를 페이징 처리하여 리스트로 리턴할거다
그렇게 함으로써 여러 페이지(끝 페이지까지)에 대한 데이터를 가져올 수 있게 되므로 국산 자동차 전체에 대한 데이터를 뽑아올 수도 있다.
하지만 난 1페이지만 테스트 해보도록 할거다
def create_query_format(self) -> Dict[str,str]:
params = [
{
"count": "true",
"q": "(And.Hidden.N._.CarType.Y.)",
"sr": f"|PriceAsc|{(page * 10) * 2}|20"
} for page in range(self.page_count)]
return params
get 요청 보내기
send_request
라는 메소드를 하나 작성하여 get 요청으로 위에서 작성한 쿼리 파라메터를 보낼거다.
def send_request(self,param:Dict[str,str]) -> Dict[str,Union[int,list]]:
response = rq.get(url=self._KCAR,headers={"user-agent":"Mozilla/5.0"},params=param)
return response.json()
response = (params=data)
data 매개변수나 json 매겨변수로 데이터를 넘기면 빈 딕셔너리값이 반환된다
하지만 params 매개변수로 데이터를 넘기면 정상적으로 데이터가 들어옴
요청을 한 번 보내보자
if __name__ == "__main__':
KCAR : str = 'http://api.encar.com/search/car/list/premium'
page_count : int = 1
# Create Encar Instance
encar : Encar = Encar(KCAR=KCAR,page_count=page_count)
encar.send_request()
{'Count': 87832, 'SearchResults': [{'Id': '34903200', 'Separation': ['B'], 'Trust': ['Warranty'], 'Condition': ['Inspection', 'Record'], 'Photo': '/carpicture10/pic3490/34903200_', 'Photos': [{'type': '001', 'location': '/carpicture10/pic3490/34903200_001.jpg', 'updatedDate': '2023-04-24T06:01:57Z', 'ordering': 1.0}, {'type': '003', 'location': '/carpicture10/pic3490/34903200_003.jpg', 'updatedDate': '2023-04-24T06:01:57Z', 'ordering': 3.0}, {'type': '004', 'location': '/carpicture10/pic3490/34903200_004.jpg', 'updatedDate': '2023-04-24T06:01:57Z', 'ordering': 4.0}, {'type': '007', 'location': '/carpicture10/pic3490/34903200_007.jpg', 'updatedDate': '2023-04-24T06:01:57Z', 'ordering': 7.0}], 'Manufacturer': '기아', 'Model': '더 뉴 기아 레이', 'Badge': '시그니처', 'Transmission': '오토', 'FuelType': '가솔린', 'Year': 202210.0, 'FormYear': '2023', 'Mileage': 16613.0, 'ServiceCopyCar': 'ORIGINAL', 'Lease': '1', 'LeaseType': '운용리스', 'MonthLeasePrice': 35.0, 'Deposit': 0.0, 'ResidualValue': 398.0, 'Price': 35.0, 'OfficeCityState': '울산', 'ModifiedDate': '2023-05-06 23:10:10.000 +09'}
... 생략 ...
정상적으로 데이터를 받아볼 수 있었다.
포스팅 Id 추출
get_car_infos 메소드 작성
GET_CAR_ID = List[Dict[str,Union[str,List[str]]]]
def get_car_infos(self,json_data:Dict[str,Union[int,list]]) -> GET_CAR_ID:
""" Car id 데이터 리턴 메소드 """
# 데이터 저장용 리스트 변수
save_list : List[Dict[str,str]] = []
# 사진 저장을 위한 .jpg base url
img_base_url : str = 'http://ci.encar.com/carpicture'
for data in json_data['SearchResults']:
# 데이터 저장용 딕셔너리 변수
save_dict : Dict[str,str] = {}
# 데이터 저장
save_dict['Id'] = data['Id']
save_dict['Photos'] = [img_base_url + location['location'] for location in data['Photos']]
save_dict['Manufacturer'] = data['Manufacturer']
save_dict['Model'] = data['Model']
save_list.append(save_dict)
# 데이터 출력
print(save_dict,'\n')
# 저장한 데이터 리턴
return save_list
데이터를 한번 뽑아보자 ..
{'Id': '34860832', 'Photos': ['http://ci.encar.com/carpicture/carpicture06/pic3486/34860832_001.jpg', 'http://ci.encar.com/carpicture/carpicture06/pic3486/34860832_003.jpg', 'http://ci.encar.com/carpicture/carpicture06/pic3486/34860832_004.jpg', 'http://ci.encar.com/carpicture/carpicture06/pic3486/34860832_007.jpg'], 'Manufacturer': '현대', 'Model': '그랜저 IG'}
... 생략 ...
잘 가져와진다.
보험 이력 스크래핑
위에서 작성한 코드를 기반으로 보험이력 조회하는 메소드를 추가해볼거다.
def get_car_detail_infos(self,car_infos: List[Dict[str,str]]) -> List[Dict[str,str]]:
save_detail_list : List[Dict[str,str]] = []
""" 차량 보험이력 데이터 스크래핑 """
for car_info in car_infos:
kcar_detail_url : str = f'http://www.encar.com/dc/dc_cardetailview.do?method=kidiFirstPop&carid={car_info["Id"]}&wtClick_carview=044'
response : rq.Response = rq.get(url=kcar_detail_url,headers=self.headers)
soup : bs = self.get_soup_object(response=response)
if soup.select_one('div.section.message > big > strong'):
continue
table_list = soup.select('div.smlist tbody > tr')
if len(table_list) > 2:
model = table_list[0].text.strip()
owner_count = table_list[2].text.strip()
accident_history = table_list[3].text.strip()
else:
model = "-"
owner_count = "-"
accident_history = "-"
# 기존 car_infos 딕셔너리에 데이터 추가
car_info['owner_count'] = owner_count
car_info['model'] = model
car_info['accident_history'] = accident_history
save_detail_list.append(car_info)
# 데이터 출력
print(car_info,'\n')
# 최종 데이터 리턴
return save_detail_list
출력 결과
Streamlit
위에서 추출한 데이터를 웹으로 출력해보자
# (... 생략 ...)
html_text : str = ""
for x in results:
for result in x:
html_text += f"""
\n ![car-image]({result['Photos'][0]}) \n
- ID: {result['Id']}
- 모델명: {result['model']}
- 소유자 변경 횟수: {result['owner_count']}
- 사고 기록: {result['accident_history']}
"""
st.markdown(html_text)
배포
도커를 사용하여 위에서 진행한 스크래핑 프로젝트를 배포해보자
- Docker Base Image :
python:3.8-slim-buster
- docker-compose 사용
encar.Dockerfile
FROM python:3.9-slim-buster
WORKDIR /app
COPY ./ ./
RUN pip install --upgrade pip && pip install -r requirements.txt
ARG PORT=8501
EXPOSE ${PORT}
CMD [ "streamlit", "run", "main.py" ]
docker-compose.yml
version: "3"
services:
streamlit:
build:
context: python-app/
dockerfile: encar.Dockerfile
restart: always
ports:
- "8501:8501"
container_name: streamlit