활용 사례
일괄 내보내기와 리뷰 자동화부터 분석 보고서, 등록 정보 점검, 일괄 게시까지 전체 과정을 다루는 Python 구현 예제 10개를 제공합니다.
예제를 실행하기 전에 API 키를 한 번 설정합니다:
export LISTINGSAPI_KEY="your-api-key"1. 사업장 목록 조회
연결한 후 사업장 목록의 첫 페이지를 출력합니다.
import listingsapi client = listingsapi.ListingsAPI() page = client.locations.list(first=5)for loc in page: print(loc.name, "--", getattr(loc, "city", "N/A"), getattr(loc, "stateIso", "N/A")) print(f"Locations on page: {len(page)}, has_more: {page.has_more}")2. CSV로 일괄 내보내기
auto_paging_iter()를 사용하여 모든 사업장을 CSV로 다운로드합니다.
import csvimport listingsapi client = listingsapi.ListingsAPI() locations = list(client.locations.list(first=100).auto_paging_iter())print(f"Fetched {len(locations)} locations") fields = ["id", "name", "storeId", "street", "city", "stateIso", "postalCode", "countryIso", "phone"] with open("locations_export.csv", "w", newline="") as f: writer = csv.DictWriter(f, fieldnames=fields, extrasaction="ignore") writer.writeheader() for loc in locations: writer.writerow(loc.to_dict()) print("Exported to locations_export.csv")auto_paging_iter()는 커서 기반 페이지를 모두 자동으로 처리합니다. 전체 메서드 참조는 사업장을 참고합니다.
3. 사업장 생성
locations.add()는 모든 필수 필드를 키워드 인자로 받고 클라이언트 측에서 페이로드의 유효성을 검사하므로, 잘못된 요청은 네트워크 호출 전에 명확한 메시지와 함께 즉시 실패합니다. client.subcategories()로 유효한 sub_category_id을 조회합니다(databaseId를 사용합니다).
import listingsapifrom listingsapi import ValidationError client = listingsapi.ListingsAPI() subcategories = client.subcategories()dental = [s for s in subcategories if "dentist" in (s.get("name") or "").lower()]sub_category_id = dental[0].databaseId if dental else 1432 try: result = client.locations.add( name="Acme Dental", description=( "Acme Dental is a family-owned dental practice serving downtown " "New York for over fifteen years. Our team offers preventive care, " "cosmetic dentistry, orthodontics, and emergency appointments, with " "weekend hours and a patient-first approach that keeps every visit " "comfortable and affordable for the whole family." ), sub_category_id=sub_category_id, country_iso="US", city="New York", street="123 Jump Street", state_iso="NY", postal_code="10013", phone="6443859313", store_id="NYC-001", )except ValidationError as e: print(f"Fix the payload: {e}")else: print(f"Created location: {result.location.id}")설명은 200자 이상이어야 하며, 게시 플랫폼은 이를 등록 정보의 주요 소개 문구로 사용합니다. client.countries()는 지원되는 국가 및 주 ISO 코드를 나열하고, client.plan_sites()는 이용 중인 요금제에서 게시할 수 있는 디렉터리를 표시합니다.
4. 리뷰 모니터링
최근 리뷰를 확인하고 답글이 필요한 부정적인 리뷰를 표시합니다.
import listingsapi client = listingsapi.ListingsAPI() page = client.locations.list(first=5) for loc in page: print(f"\n--- {loc.name} ---") reviews = client.reviews.list(loc.id, first=10) if not reviews: print(" No recent reviews") continue for review in reviews: rating = getattr(review, "rating", "N/A") author = getattr(review, "authorName", "Anonymous") site = getattr(review, "siteName", "Unknown") responded = bool(getattr(review, "responses", None)) flag = " ** NEEDS ATTENTION **" if isinstance(rating, (int, float)) and rating <= 2 else "" status = "Responded" if responded else "No reply" print(f" [{rating}] {author} on {site} ({status}){flag}")rating_filters, site_urls, 날짜 범위 등의 필터 옵션은 리뷰를 참고합니다.
5. 리뷰 자동 답글
workflows.auto_reply_to_reviews()는 한 번의 호출로 최근 리뷰를 가져오고, 평점과 답글 상태로 필터링한 후, 템플릿을 사용하여 답글을 게시합니다. 먼저 dry_run=True로 미리 확인합니다. {rating} 자리표시자는 각 리뷰의 별점으로 대체됩니다.
import listingsapi client = listingsapi.ListingsAPI() LOCATION_ID = 16808 # Replace with your location IDTEMPLATE = "Thanks for the {rating}-star review! We appreciate you taking the time." # Preview what would be sentpreview = client.workflows.auto_reply_to_reviews( LOCATION_ID, template=TEMPLATE, min_rating=4, dry_run=True,)for entry in preview: print(f" Would reply to {entry['id']} ({entry['rating']} stars)") # Post the replies for realresults = client.workflows.auto_reply_to_reviews( LOCATION_ID, template=TEMPLATE, min_rating=4,)sent = [r for r in results if r["status"] == "sent"]print(f"Replied to {len(sent)} of {len(results)} matching reviews")기본값인 min_rating=4는 부정적인 리뷰에 개별적인 답글이 필요하므로 의도적으로 해당 리뷰를 건너뜁니다. reviews.respond()로 개별 처리합니다:
import listingsapifrom listingsapi import APIError client = listingsapi.ListingsAPI() negative = client.reviews.list(16808, rating_filters=[1, 2], first=20)for review in negative: if getattr(review, "responses", None): continue try: client.reviews.respond( review.interactionId, "We are sorry to hear about your experience. Please reach out " "directly so we can make it right.", ) print(f"Replied to {getattr(review, 'authorName', 'Anonymous')}") except APIError as e: print(f"Failed: {e}")6. 분석 보고서
모든 사업장의 Google 프로필 분석 데이터와 리뷰 통계를 조회합니다.
import listingsapi client = listingsapi.ListingsAPI() FROM_DATE = "2026-01-01"TO_DATE = "2026-06-30" all_locations = list(client.locations.list(first=100).auto_paging_iter())print(f"Generating report for {len(all_locations)} locations ({FROM_DATE} to {TO_DATE})\n") for loc in all_locations[:10]: print(f"--- {loc.name} ---") google = client.analytics.google(loc.id, from_date=FROM_DATE, to_date=TO_DATE) if google: print(f" Google: {google.to_dict()}") review_stats = client.reviews.analytics.overview(loc.id, start_date=FROM_DATE, end_date=TO_DATE) if review_stats: print(f" Reviews: {review_stats.to_dict()}") sites = client.reviews.analytics.sites_stats(loc.id, start_date=FROM_DATE, end_date=TO_DATE) if sites: print(f" Sites: {sites.to_dict()}") print()bing() 및 facebook() 프로필 지표는 분석을 참고합니다.
7. 주간 평판 보고서
workflows.weekly_reputation_report()는 리뷰, 리뷰 분석 데이터, Google 및 Bing 프로필 분석 데이터, 등록 정보 동기화 상태를 하나의 보고서 객체로 통합합니다.
import listingsapi client = listingsapi.ListingsAPI() report = client.workflows.weekly_reputation_report( 16808, start_date="2026-06-29", end_date="2026-07-05",) summary = report.review_summaryprint(f"Average rating: {summary.get('averageRating')}")print(f"Recent reviews: {len(report.recent_reviews)}") analytics = report.analyticsprint(f"Google: {analytics.get('google')}")print(f"Bing: {analytics.get('bing')}") health = report.listings_healthprint(f"Listings synced: {health.get('synced')}/{health.get('total')} ({health.get('sync_rate')})")8. 등록 정보 상태 점검
workflows.listings_health_audit()는 사업장의 프리미엄 등록 정보, 음성 등록 정보, 중복 항목을 확인하고 0-100 범위의 상태 점수를 계산합니다.
import listingsapi client = listingsapi.ListingsAPI() page = client.locations.list(first=10)print(f"Auditing listings for {len(page)} locations\n") for loc in page: audit = client.workflows.listings_health_audit(loc.id) print(f"--- {loc.name} ---") print(f" Health score: {audit.health_score}%") print(f" Synced: {audit.synced_count}, issues: {audit.issue_count}") print(f" Voice listings: {len(audit.voice)}, duplicates: {len(audit.duplicates)}") for issue in audit.issues: print(f" [{issue.get('syncStatus')}] {issue.get('site')}") print()기반이 되는 premium, voice, duplicates 및 중복 표시 메서드는 등록 정보를 참고합니다.
9. 게시물 일괄 게시
posts.bulk_publish()는 여러 사업장에 하나의 게시물을 게시하며, 기본적으로 Google과 Facebook 모두에 게시합니다. 사업장 ID는 자동으로 인코딩되며, 네트워크 호출 전에 클라이언트 측에서 페이로드의 유효성을 검사합니다.
import listingsapi client = listingsapi.ListingsAPI() result = client.posts.bulk_publish( name="Summer hours 2026", location_ids=[16808, 16809, 16810], message="We are open late all summer! Come see us until 9pm, Monday through Saturday.", media_url="https://cdn.example.com/summer-hours.jpg", cta_type="LEARN_MORE", cta_url="https://www.acmedental.com/hours",) post = result.get("socialPost") or {}print(f"Created bulk post: {post.get('id')}")사이트별로 문구를 지정하려면 message에 dict를 전달합니다:
result = client.posts.bulk_publish( name="July whitening offer", location_ids=[16808], message={ "GOOGLE": "20% off teeth whitening this July. Book online today!", "FACEBOOK": "July special: 20% off teeth whitening. Tap to book your visit!", }, sites=["GOOGLE", "FACEBOOK"],)단일 사이트에 타입이 지정된 게시물을 게시하려면 posts.create_announcement(), posts.create_event() 또는 posts.create_offer()를 사용합니다.
10. Google 연결 절차
OAuth URL을 생성하고, 연결된 계정 목록을 조회한 후, 추천 매칭 결과를 검토합니다.
import time import listingsapi client = listingsapi.ListingsAPI() # Step 1: Generate an OAuth URL (valid 24 hours)result = client.connected_accounts.connect_google( success_url="https://yourapp.com/connect/success", error_url="https://yourapp.com/connect/error",)print(f"Redirect user to: {result.get('url', 'N/A')}") # Step 2: List connected Google accounts. The payload is an offset page:# records[] plus pageInfo, not a bare list.accounts = client.connected_accounts.list(publisher="GoogleAccount", page=1, per_page=20)for acc in accounts.get("records") or []: account_id = acc["connectedAccountId"] print(f"{acc['email']} -- status: {acc['status']}") # Step 3: Kick off the fetch-and-match job for this account. client.connected_accounts.trigger_matches([account_id]) # Step 4: Matching is asynchronous. Poll the account's details until it # reports MATCH_COMPLETED, with a bounded number of attempts so a stuck # job cannot hang the script. status = None for _ in range(20): details = client.connected_accounts.details(account_id) status = (details.get("details") or {}).get("requestMatchesStatus") if status == "MATCH_COMPLETED": break time.sleep(15) if status != "MATCH_COMPLETED": print(f" matching still {status!r}, skipping for now") continue # Step 5: Only now are suggestions populated. Same offset-page shape. suggestions = client.connected_accounts.suggestions(account_id, page=1, per_page=10) records = suggestions.get("records") or [] print(f" Suggestions: {len(records)} matches found")requestMatchesStatus가 MATCH_COMPLETED에 도달하기 전에 추천 결과를 조회하면 빈 records 배열이 반환되며, 이는 "일치하는 항목이 없음"과 구분할 수 없으므로, 폴링을 해야 이 예제가 우연에 의존하지 않고 올바르게 동작합니다. 상태 값은 사업장 매칭 실행을 참고합니다.
각 레코드의 matchedDataDatabaseId과 accountType로 구성한 매칭 레코드 ID를 connected_accounts.confirm_matches()에 전달하여 매칭을 확정하고(연결 매칭 확정 참고), listings.connect()로 특정 등록 정보를 사업장에 연결합니다.