한국관광공사 Tour API 활용
August 2024 (933 Words, 6 Minutes)
강원도 18개 시군구의 모든 관광지 정보를 체계적으로 수집하여 사용자에게 정확한 추천을 제공하기 위해 한국관광공사의 Tour API를 활용했다. 단순히 API를 호출하는 것이 아니라, 15,000여 건의 대용량 데이터를 효율적으로 처리하고 의미있는 카테고리로 분류하는 것이 핵심 과제였다.
핵심 구현: 페이지네이션 기반 대용량 데이터 수집
2단계 API 호출 구조 설계
한국관광공사 API는 지역코드 → 시군구코드 → 관광지 데이터 순으로 호출해야 하는 구조였다. 이를 효율적으로 처리하기 위한 중첩 루프 구조를 설계했다.
async function fetchAndStoreLocations() {
try {
await client.connect();
const database = client.db("actapp");
const collection = database.collection("Vendors");
const areaCode = 32; // 강원도 지역코드
// 1단계: 18개 시군구 코드 목록 조회
const sigunguResponse = await axios.get(
"http://apis.data.go.kr/B551011/KorService1/areaCode1",
{
params: {
serviceKey: serviceKey,
areaCode: areaCode,
numOfRows: 50,
pageNo: 1,
MobileOS: "ETC",
MobileApp: "AppTest",
_type: "json",
},
}
);
if (sigunguResponse.data?.response?.body?.items) {
const sigungus = sigunguResponse.data.response.body.items.item;
// 2단계: 각 시군구별로 관광지 데이터 수집
for (const sigungu of sigungus) {
const sigunguCode = sigungu.code;
const sigunguName = sigungu.name;
console.log(`Fetching locations for ${sigunguName}...`);
let pageNo = 1;
let totalCount = 0;
// 페이지네이션을 통한 전체 데이터 수집
do {
const response = await axios.get(
"http://apis.data.go.kr/B551011/KorService1/areaBasedList1",
{
params: {
serviceKey: serviceKey,
areaCode: areaCode,
sigunguCode: sigunguCode,
numOfRows: 100, // 한 번에 100개씩 처리
pageNo: pageNo,
MobileOS: "ETC",
MobileApp: "AppTest",
_type: "json",
},
}
);
// ... 데이터 처리 로직
pageNo++;
} while ((pageNo - 1) * 100 < totalCount);
}
}
} catch (error) {
console.error("Error fetching or storing data:", error.message);
}
}
가장 까다로운 부분은 관광공사에서 제공하는 복잡한 3단계 카테고리 코드를 실제 외부에서 수집한 데이터의 라벨링된 형태 에 맞추어 category mapping 을 해야 했던 점이다.
라벨링도 거의 1주일 걸렸는데 database 에서 또 key 값 가지고 헤매긴 싫어서 A01:'자연관광지' 형태로 매핑했다
또한 대용량 데이터 처리니까 MongoDB의 upset 기능을 넣어서 중복을 방지했다.
API 를 바로바로 호출하고 싶었는데, 이를 실시간 날씨 추천 알고리즘 + 사용자 선호도 알고리즘 에 적용할 자신이 도저히 없어서 데이터를 일단 수집하기로 했다. 이때 MongoDB 집계 파이프라인을 활용했다.
// services/AccommodationService.js - 숙박시설 통계 조회
async getAccommodationInfoBySigungu(sigungu) {
const pipeline = [
{
$match: {
sigungu: sigungu,
category2: '숙박',
category3: { $in: categories },
},
},
{
$group: {
_id: '$category3',
count: { $sum: 1 },
},
},
];
const results = await Accommodation.aggregate(pipeline);
// 결과를 객체 형태로 변환하여 반환
const categoryCounts = {};
results.forEach((result) => {
if (result.count > 0) {
categoryCounts[result._id] = result.count;
}
});
return categoryCounts;
}
3단계 데이터 품질 관리
관광공사 데이터에는 빈 값이나 잘못된 형식의 데이터가 있었다. 이를 처리하기 위해 유효성 검증 로직을 추가했다. 그 결과 15,000여 건의 강원도 관광지 데이터 수집을 완료 했고, 이를 숙박시설과 우리가 찾는 유효한 액티비티 시설로 직접 라벨링하여 분류했다. 또한 18개 시군구 전체를 분류했다.
아직 데이터베이스를 잘 다룰 줄 몰라 시간이 많이 걸렸는데, “춘천 펜션”을 검색했을 때때 정확히 춘천 지역의 펜션들만 필터링되어 나오는 것을 확인할 수 있었을 때 가장 뿌듯했다.
활용
- VendorService.js에서 저장된 관광지 데이터를 실제로 검색에 활용했다.
- 또한 AccommodationService.js에서는 수집한 데이터를 바탕으로 실시간 통계를 제공했다.
- VendorService.js의 getVendorsByCategoryAndRegion에서는 저장된 데이터와 사용자 선호도를 매칭했다
async getAccommodationInfoBySigungu(sigungu) {
const pipeline = [
{
$match: {
sigungu: sigungu,
category2: '숙박',
category3: { $in: categories },
},
},
{
$group: {
_id: '$category3',
count: { $sum: 1 },
},
},
];
// 춘천 펜션 23개, 모텔 15개 이런 식으로 실시간 집계
}
감정분석과 연계된 추천 시스템에서도 쓰였는데, ( 휴 많이 쓰이기도 했다 )PostService.js에서는 후기 게시글과 관광지 데이터를 연결해서 감정분석을 제공한다.
async analyzeSentiments(locationTag, activityTag, vendorTag) {
const query = {};
if (locationTag) query.locationTag = locationTag;
if (vendorTag) query.vendorTag = vendorTag;
const posts = await Post.find(query).exec();
// 특정 관광지에 대한 모든 후기의 감정을 분석
}
‘정리’
지역별 숙박시설 현황 → 실시간 집계 파이프라인
개인 맞춤 추천 → 사용자 선호도 + 저장된 관광지 카테고리 매칭
감정분석 기반 추천 → 후기 데이터와 관광지 정보 연계