areaCode = 3; 은 대전을 특정한다.
totalCount는 751 였다.
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:dotenv/dotenv.dart' as dotenv;
import 'package:my_place_data_uploader/tag_map.dart';
Future<void> main() async {
final env = dotenv.DotEnv()..load();
final tourApiKey = env['TOUR_API_KEY'];
final projectId = env['FIREBASE_PROJECT_ID'];
final firebaseApiKey = env['FIREBASE_API_KEY'];
final areaCode = 3;
const pageSize = 100;
// ✅ 먼저 totalCount 확인
final initUrl = Uri.parse(
'https://apis.data.go.kr/B551011/KorService1/areaBasedList1'
'?serviceKey=$tourApiKey'
'&numOfRows=1'
'&pageNo=1'
'&MobileOS=ETC'
'&MobileApp=AITrip'
'&_type=json'
'&areaCode=$areaCode',
);
final initResponse = await http.get(initUrl);
if (initResponse.statusCode != 200) {
print('❌ 초기 요청 실패: ${initResponse.statusCode}');
return;
}
final decodedInit = utf8.decode(initResponse.bodyBytes);
final initJson = json.decode(decodedInit);
final totalCount = initJson['response']['body']['totalCount'];
final totalPages = (totalCount / pageSize).ceil();
print('📦 총 장소 수: $totalCount → 총 페이지 수: $totalPages');
for (int page = 1; page <= totalPages; page++) {
final tourUrl = Uri.parse(
'https://apis.data.go.kr/B551011/KorService1/areaBasedList1'
'?serviceKey=$tourApiKey'
'&numOfRows=$pageSize'
'&pageNo=$page'
'&MobileOS=ETC'
'&MobileApp=AITrip'
'&_type=json'
'&areaCode=$areaCode',
);
final response = await http.get(tourUrl);
if (response.statusCode != 200) {
print('❌ 페이지 $page 요청 실패: ${response.statusCode}');
continue;
}
final decoded = utf8.decode(response.bodyBytes);
final jsonData = json.decode(decoded);
final items = jsonData['response']['body']['items']['item'];
final places = items is List ? items : [items];
for (final item in places) {
final title = item['title'] ?? '제목 없음';
final address = item['addr1'] ?? '';
final image = item['firstimage'] ?? '';
final lat = double.tryParse(item['mapy']?.toString() ?? '') ?? 0.0;
final lng = double.tryParse(item['mapx']?.toString() ?? '') ?? 0.0;
final contentTypeId = item['contenttypeid']?.toString();
final cat1 = item['cat1']?.toString();
final cat2 = item['cat2']?.toString();
final cat3 = item['cat3']?.toString();
final tags = <String>{};
if (contentTypeId != null && num_tagmap.containsKey(contentTypeId)) {
tags.addAll(num_tagmap[contentTypeId]!);
}
if (cat1 != null && major_tagmap.containsKey(cat1)) {
tags.addAll(major_tagmap[cat1]!);
}
if (cat2 != null && middle_tagmap.containsKey(cat2)) {
tags.addAll(middle_tagmap[cat2]!);
}
if (cat3 != null && sub_tagmap.containsKey(cat3)) {
tags.addAll(sub_tagmap[cat3]!);
}
// ✅ 중복 체크 (pname + address)
final queryUrl = Uri.parse(
'https://firestore.googleapis.com/v1/projects/$projectId/databases/(default)/documents:runQuery?key=$firebaseApiKey',
);
final queryBody = {
"structuredQuery": {
"from": [{"collectionId": "places"}],
"where": {
"compositeFilter": {
"op": "AND",
"filters": [
{
"fieldFilter": {
"field": {"fieldPath": "pname"},
"op": "EQUAL",
"value": {"stringValue": title}
}
},
{
"fieldFilter": {
"field": {"fieldPath": "address"},
"op": "EQUAL",
"value": {"stringValue": address}
}
}
]
}
},
"limit": 1
}
};
final checkResponse = await http.post(
queryUrl,
headers: {'Content-Type': 'application/json'},
body: jsonEncode(queryBody),
);
final checkData = json.decode(checkResponse.body);
final alreadyExists = checkData.any((doc) => doc['document'] != null);
if (alreadyExists) {
print('⛔ 중복: $title');
continue;
}
// ✅ 저장할 데이터 구조
final firestoreData = {
"fields": {
"pname": {"stringValue": title},
"address": {"stringValue": address},
"place_photo": {"stringValue": image},
"location": {
"mapValue": {
"fields": {
"lat": {"doubleValue": lat},
"lng": {"doubleValue": lng}
}
}
},
"ptags": {
"arrayValue": {
"values": tags.map((tag) => {"stringValue": tag}).toList()
}
},
"created_at": {
"timestampValue": DateTime.now().toUtc().toIso8601String()
}
}
};
final firestoreUrl = Uri.parse(
'https://firestore.googleapis.com/v1/projects/$projectId/databases/(default)/documents/places?key=$firebaseApiKey',
);
final saveResponse = await http.post(
firestoreUrl,
headers: {'Content-Type': 'application/json'},
body: jsonEncode(firestoreData),
);
if (saveResponse.statusCode == 200) {
print('✅ 저장 완료: $title');
} else {
print('❌ 저장 실패 (${saveResponse.statusCode}): $title');
print(saveResponse.body);
}
}
}
print('🎉 전체 저장 완료!');
}
지난번에는 numrows 5개까지만 저장하는 코드였고 이번에는 TourAPI에 있는 대전에 해당하는 모든 장소들의 총 개수와 페이지 수를 불러와서 검색된 전체 장소들을 저장하는 코드이다.
참고로 .env에는 여러 API key들이 모여있다.
보안상 민감한 정보이므로 따로 올리지는 않겠다.
'프로젝트' 카테고리의 다른 글
지도에서 위치 범위 설정, 위치 안에 있는 장소 필터링 (0) | 2025.04.17 |
---|---|
Firebase users 정보 UI로 가져오기 (0) | 2025.04.15 |
TourAPI 매핑 작업 - Firestore에 분류 태그 저장하기 (11) | 2025.04.11 |
여행지 추천 기능 구현 중 (TourAPI 서비스 분류 코드 검색) (3) | 2025.04.09 |
TourAPI 한글 인코딩 오류 (0) | 2025.04.09 |