이미지 수집 100장 상한 추가
parent
593b6d9922
commit
236c7916a7
|
|
@ -37,10 +37,11 @@ class NvMapScraper:
|
||||||
data_source_identifier = "nv"
|
data_source_identifier = "nv"
|
||||||
SUPPLEMENT_THRESHOLD = 30 # 업체 사진이 이 수 미만일 때 방문자 사진으로 보충
|
SUPPLEMENT_THRESHOLD = 30 # 업체 사진이 이 수 미만일 때 방문자 사진으로 보충
|
||||||
# 방문자 사진 보충 시의 합산 상한 (필터링·정책상 제한).
|
# 방문자 사진 보충 시의 합산 상한 (필터링·정책상 제한).
|
||||||
# 업체 제공 사진은 필터링 면제 대상이므로 이 상한과 무관하게 전량 사용한다.
|
# 업체 제공 사진은 필터링 면제 대상이므로 이 상한과 무관하게 수집분을 전부 사용한다
|
||||||
|
# (수집 자체는 BIZ_MAX_PAGES가 상한 — 초과 시 warning 로그 발생).
|
||||||
MAX_IMAGES = 50
|
MAX_IMAGES = 50
|
||||||
BIZ_PAGE_SIZE = 20 # getPhotoViewerItems 'biz' 커서의 페이지당 사진 수
|
BIZ_PAGE_SIZE = 20 # getPhotoViewerItems 'biz' 커서의 페이지당 사진 수
|
||||||
BIZ_MAX_PAGES = 5 # 업체 사진 페이지네이션 안전 상한 (5×20=100장)
|
BIZ_MAX_PAGES = 5 # 업체 사진 수집 상한 (5×20=100장, 도달 시 warning)
|
||||||
OVERVIEW_QUERY: str = """
|
OVERVIEW_QUERY: str = """
|
||||||
query getAccommodation($id: String!, $deviceType: String) {
|
query getAccommodation($id: String!, $deviceType: String) {
|
||||||
business: placeDetail(input: {id: $id, isNx: true, deviceType: $deviceType}) {
|
business: placeDetail(input: {id: $id, isNx: true, deviceType: $deviceType}) {
|
||||||
|
|
@ -282,7 +283,7 @@ query getVisitorReviewStats($id: String!) {
|
||||||
# (필터링 없는 기본 조립. 마케팅 적합성 필터를 적용하려면 호출측에서
|
# (필터링 없는 기본 조립. 마케팅 적합성 필터를 적용하려면 호출측에서
|
||||||
# owner_images / extra_photo_urls를 직접 사용해 image_filter.assemble_images로 재조립할 것.)
|
# owner_images / extra_photo_urls를 직접 사용해 image_filter.assemble_images로 재조립할 것.)
|
||||||
# MAX_IMAGES 상한은 방문자 사진이 섞이는 보충 경로에만 적용하며(필터링·정책상 제한),
|
# MAX_IMAGES 상한은 방문자 사진이 섞이는 보충 경로에만 적용하며(필터링·정책상 제한),
|
||||||
# 업체 제공 사진만으로 구성되는 경우는 전량 사용한다.
|
# 업체 제공 사진만으로 구성되는 경우는 수집분(최대 BIZ_MAX_PAGES 페이지)을 전부 사용한다.
|
||||||
if len(self.owner_images) < self.SUPPLEMENT_THRESHOLD:
|
if len(self.owner_images) < self.SUPPLEMENT_THRESHOLD:
|
||||||
combined = self._dedup_by_original(self.owner_images + self.extra_photo_urls)
|
combined = self._dedup_by_original(self.owner_images + self.extra_photo_urls)
|
||||||
logger.info(
|
logger.info(
|
||||||
|
|
@ -366,16 +367,16 @@ query getVisitorReviewStats($id: String!) {
|
||||||
},
|
},
|
||||||
"query": self.PHOTO_VIEWER_QUERY,
|
"query": self.PHOTO_VIEWER_QUERY,
|
||||||
}
|
}
|
||||||
# 업체 등록 사진 전체. placeDetail.images는 대표 사진 일부(1~수 장)만 반환하는
|
# 업체 등록 사진. placeDetail.images는 대표 사진 일부(1~수 장)만 반환하는
|
||||||
# 경우가 있어, 사진 탭이 실제 사용하는 'biz' 커서로 전량을 별도 조회해 보강한다.
|
# 경우가 있어, 사진 탭이 실제 사용하는 'biz' 커서로 별도 조회해 보강한다.
|
||||||
# biz 커서는 페이지당 BIZ_PAGE_SIZE(20)장이며 lastCursor가 단순 인덱스 문자열
|
# biz 커서는 페이지당 BIZ_PAGE_SIZE(20)장이며 lastCursor가 단순 인덱스 문자열
|
||||||
# ("20", "40")이라 이전 응답 없이도 다음 페이지를 미리 요청할 수 있고, 범위를
|
# ("20", "40")이라 이전 응답 없이도 모든 페이지를 미리 만들 수 있고, 범위를
|
||||||
# 벗어난 페이지는 빈 목록을 반환하므로 블라인드 요청해도 안전하다.
|
# 벗어난 페이지는 빈 목록을 반환하므로 블라인드 요청해도 안전하다.
|
||||||
# 첫 배치에 3페이지를 실어 보내고, 마지막 페이지가 가득 차 있으면(=더 있을 수
|
# 따라서 상한(BIZ_MAX_PAGES)까지의 전 페이지를 첫 배치에 한꺼번에 실어 보낸다
|
||||||
# 있음) 추가 배치를 반복 요청해 업체 사진을 전량 수집한다.
|
# — 추가 왕복(페이지 재탐색 + WTM 토큰 재캡처)이 없고, 개별 페이지 실패가
|
||||||
BIZ_INITIAL_PAGES = 3
|
# 이후 페이지 수집을 막지 못한다.
|
||||||
biz_payloads = [
|
biz_payloads = [
|
||||||
self._build_biz_payload(place_id, page) for page in range(BIZ_INITIAL_PAGES)
|
self._build_biz_payload(place_id, page) for page in range(self.BIZ_MAX_PAGES)
|
||||||
]
|
]
|
||||||
|
|
||||||
payloads = [overview_payload, stats_payload, interior_payload, exterior_payload, review_payload, *biz_payloads]
|
payloads = [overview_payload, stats_payload, interior_payload, exterior_payload, review_payload, *biz_payloads]
|
||||||
|
|
@ -416,30 +417,21 @@ query getVisitorReviewStats($id: String!) {
|
||||||
exterior_urls = self._extract_photo_viewer_urls(results[3] if len(results) > 3 else None)
|
exterior_urls = self._extract_photo_viewer_urls(results[3] if len(results) > 3 else None)
|
||||||
review_urls = self._extract_photo_viewer_urls(results[4] if len(results) > 4 else None)
|
review_urls = self._extract_photo_viewer_urls(results[4] if len(results) > 4 else None)
|
||||||
|
|
||||||
biz_pages = list(results[5:])
|
biz_pages = results[5:]
|
||||||
biz_urls = [url for page_result in biz_pages for url in self._extract_photo_viewer_urls(page_result)]
|
biz_urls = [url for page_result in biz_pages for url in self._extract_photo_viewer_urls(page_result)]
|
||||||
# 마지막 페이지가 가득 차 있으면 다음 배치를 계속 요청해 업체 사진을 전량 수집한다
|
# 개별 페이지 실패(None)는 해당 20장만 누락되고 나머지 페이지는 영향 없다 (best-effort).
|
||||||
# (업체 사진은 필터링 면제 대상이라 많을수록 영상 소재 다양성이 좋아짐).
|
failed_biz_pages = sum(1 for r in biz_pages if r is None)
|
||||||
# 후속 배치 실패는 warning만 남기고 이미 수집한 분량으로 진행한다 (best-effort).
|
if failed_biz_pages:
|
||||||
next_page = BIZ_INITIAL_PAGES
|
logger.warning(
|
||||||
while (
|
f"[NvMapScraper] 업체 사진 {failed_biz_pages}개 페이지 응답 실패 "
|
||||||
biz_pages
|
f"— 페이지당 최대 {self.BIZ_PAGE_SIZE}장 누락 가능 (수집분으로 진행)"
|
||||||
and self._raw_photo_count(biz_pages[-1]) >= self.BIZ_PAGE_SIZE
|
)
|
||||||
and next_page < self.BIZ_MAX_PAGES
|
# 마지막 페이지까지 가득 차 있으면 상한 밖에 사진이 더 있을 수 있다.
|
||||||
):
|
if biz_pages and self._raw_photo_count(biz_pages[-1]) >= self.BIZ_PAGE_SIZE:
|
||||||
chunk_pages = range(next_page, min(next_page + 3, self.BIZ_MAX_PAGES))
|
logger.warning(
|
||||||
chunk_payloads = [self._build_biz_payload(place_id, p) for p in chunk_pages]
|
f"[NvMapScraper] 업체 사진이 수집 상한(BIZ_MAX_PAGES={self.BIZ_MAX_PAGES}, "
|
||||||
try:
|
f"{self.BIZ_MAX_PAGES * self.BIZ_PAGE_SIZE}장)까지 가득 참 — 초과분은 수집되지 않음"
|
||||||
chunk_results = await NvMapPwScraper.fetch_graphql(place_id, chunk_payloads)
|
)
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"[NvMapScraper] 업체 사진 추가 페이지 수집 실패(수집분으로 진행): {e}")
|
|
||||||
break
|
|
||||||
if not chunk_results:
|
|
||||||
logger.warning("[NvMapScraper] 업체 사진 추가 페이지 응답 없음(수집분으로 진행)")
|
|
||||||
break
|
|
||||||
biz_pages = [r for r in chunk_results if r is not None]
|
|
||||||
biz_urls += [url for page_result in biz_pages for url in self._extract_photo_viewer_urls(page_result)]
|
|
||||||
next_page += len(chunk_payloads)
|
|
||||||
|
|
||||||
extra_photo_urls = self._interleave(interior_urls, exterior_urls) + review_urls
|
extra_photo_urls = self._interleave(interior_urls, exterior_urls) + review_urls
|
||||||
logger.info(
|
logger.info(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue