Skip to content

Lấy xếp hạng từ khóa qua Places API

Tổng quan

MụcNội dung
Trạng thái🔵 Đề xuất
Issue-
Phụ trách-

Chức năng lấy xếp hạng tìm kiếm cho từng từ khóa bằng Google Places API (Text Search). Chạy song song với hệ thống scraping hiện tại, dữ liệu xử lý được phân biệt bằng data_source trên mappy_gbp_locations (1 = Places API, 2 = Scraping).

MụcNội dung
Phương thứcGoogle Places API (New) — Text Search
Chi phí$0 (Field Mask: places.id → Basic SKU miễn phí)
Đối tượngCả 3 hệ thống (MAPPY / GCOR / PIPIT)
Tần suất1 lần/ngày (cùng thời điểm với scraping)
Thực thiPython script (main.py)

Nội dung đề xuất

Bối cảnh & Vấn đề

  • Hệ thống scraping hiện tại dùng Playwright để mở Google Search → gặp nhiều vấn đề: reCAPTCHA, timeout, anti-detection phức tạp
  • Tốn tài nguyên server để chạy headless browser
  • Kết quả không ổn định (phụ thuộc DOM, layout Google thay đổi thường xuyên)
  • Cần phương pháp bổ sung để đối chiếu và tăng độ tin cậy của ranking

Giải pháp đề xuất

Sử dụng Google Places API (New) — Text Search để lấy ranking thông qua place_id. Chạy song song với scraping, lưu vào cùng bảng mappy_* hiện có.

Đặc điểm chính:

  • Gọi API đơn giản (HTTP POST), không cần browser
  • Chi phí $0 khi chỉ lấy places.id (Basic SKU)
  • So khớp chính xác bằng placeId (đã có sẵn trong raw_json tại metadata.placeId)
  • Không bị ảnh hưởng bởi reCAPTCHA hay thay đổi DOM

Danh sách chức năng

#Chức năngMô tảƯu tiên
1Trích xuất placeIdLấy metadata.placeId từ raw_json của mappy_gbp_locationsCao
2Text Search rankingGọi API, tìm vị trí place_id trong kết quảCao
3Ranking mappingLưu vị trí trực tiếp (1-based), 0 = ngoài phạm vi, 997 = lỗiCao
4Multi API keyNhiều API key, mỗi key có --threads thread riêng. Tổng thread = threads × số keyCao
5Rate limiting--min_interval kiểm soát tần suất gọi API (độc lập mỗi thread)Trung bình
6429 Retry--max_retries tự động retry khi bị rate limitTrung bình
7data_source filterChỉ xử lý location có data_source=1 (API)Cao

Tech Stack

Hạng mụcCông nghệLý do chọn
Ngôn ngữPython 3.10+Cùng ngôn ngữ với scraping hiện tại, requests gọi API đơn giản
HTTPrequestsNhẹ, đồng bộ, tương thích tốt với thread
DBSQLAlchemy + PyMySQLCùng ORM với scraping, hỗ trợ shared DB pool
Log uploadboto3Upload log file lên S3
Môi trườngDocker (Amazon ECS)Cùng infra với scraping, 1 CPU / 2GB RAM
SchedulerAmazon EventBridgeTrigger batch hàng ngày (giống hiện tại)

Cấu hình Docker

dockerfile
FROM python:3.10-slim-bookworm

ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
ENV TZ=Asia/Tokyo

RUN apt-get update && \
    apt-get install -y --no-install-recommends tzdata && \
    ln -sf /usr/share/zoneinfo/$TZ /etc/localtime && \
    echo $TZ > /etc/timezone && \
    apt-get clean && rm -rf /var/lib/apt/lists/*

WORKDIR /code
COPY requirements.txt /code/
RUN pip install --upgrade pip && pip install -r requirements.txt
COPY . /code/

CMD ["python", "main.py", "--threads", "4", "--type_keyword", "all", "--min_interval", "500"]
# requirements.txt
PyMySQL==1.1.2
SQLAlchemy==2.0.44
SQLAlchemy-Utils==0.42.0
python-dotenv==1.0.1
requests==2.32.5
cryptography==46.0.3
boto3==1.38.0

Ví dụ chạy:

bash
# Local
docker build -t places-api-ranking .
docker run --env-file .env places-api-ranking

# Override arguments
docker run --env-file .env places-api-ranking \
  python main.py --threads 8 --type_keyword normal

# ECS Task Definition (cùng cấu hình với scraping)
# CPU: 1024 (1 vCPU), Memory: 2048 (2GB)
# Override command: ["python", "main.py", "--threads", "4", "--type_keyword", "all"]

Cấu hình AWS Infrastructure

Sử dụng chung AWS account (881980194724) và region (ap-northeast-1) với hệ thống scraping hiện tại.

ECR (Container Registry)

MụcGiá trị
Repositorypython310 (đã có sẵn)
URI881980194724.dkr.ecr.ap-northeast-1.amazonaws.com/python310
Tag:latest
bash
# Build & Push
aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin 881980194724.dkr.ecr.ap-northeast-1.amazonaws.com
docker build -t places-api-ranking .
docker tag places-api-ranking:latest 881980194724.dkr.ecr.ap-northeast-1.amazonaws.com/python310:latest
docker push 881980194724.dkr.ecr.ap-northeast-1.amazonaws.com/python310:latest

Batch Job Definition

Tạo job definition mới, tham khảo scraper_def hiện tại.

MụcGiá trị
Tênplaces_api_def
Loạicontainer
PlatformFargate / LINUX / X86_64
Image881980194724.dkr.ecr.ap-northeast-1.amazonaws.com/python310:latest
vCPU1.0
Memory2048 MB
Execution RoleecsTaskExecutionRole (dùng chung với scraper)
Timeout21600 giây (6 tiếng)
NetworkassignPublicIp: ENABLED
Command[] (Lambda truyền khi submit)

EventBridge (Scheduler)

MụcGiá trị
Rule nameplaces-api-ranking-daily
ScheduleCùng thời điểm với scraping hiện tại (hàng ngày)
TargetLambda function (tích hợp vào flow sync_database → Batch submit → sync_result)

※ Biến môi trường (PLACES_API_KEYS...) được truyền qua containerOverrides khi Lambda submit Batch job (giống cách scraping hiện tại).

Sơ đồ cấu trúc

Biến môi trường

Tên biếnBắt buộcMô tả
GOOGLE_PLACES_API_KEYSAPI keys Google Places (phân cách bằng dấu phẩy). Mỗi key nên từ GCP project riêng
GOOGLE_PLACES_API_KEYCó*API key đơn lẻ (fallback nếu không set GOOGLE_PLACES_API_KEYS)
PLACES_API_BASE_URLKhôngOverride URL endpoint (mặc định: https://places.googleapis.com)
SCRAPER_MYSQL_HOSTMySQL host
SCRAPER_MYSQL_DATABASEMySQL database name
SCRAPER_MYSQL_USERMySQL user
SCRAPER_MYSQL_PASSWORDMySQL password
SCRAPER_MYSQL_PORTKhôngMySQL port (mặc định: 3306)
SCRAPER_ACCESS_KEY_IDAWS access key cho upload S3
SCRAPER_SECRET_ACCESS_KEYAWS secret key
SCRAPER_SCRAPING_BUCKETS3 bucket cho log

Lưu ý: Mỗi API key nên từ GCP project riêng để có QPM quota độc lập. Các key cùng project chia sẻ cùng 1 QPM limit.

Flow tổng thể

Mapping giá trị ranking

Kết quả từ Text Search API lưu trực tiếp vị trí tìm thấy (1-based).

Kết quảGiá trị rankingÝ nghĩa
Tìm thấy ở vị trí NN (1~60)Thứ hạng thực tế
Không tìm thấy (hết 60 kết quả)0Ngoài phạm vi
Lỗi API (sau khi retry hết)997Lỗi không mong đợi

Khác với scraping: Scraping dùng -11/-12/-13 cho Local Pack top 3. Places API không phân biệt Local Pack, lưu thứ hạng thực tế.

Ranking status

Giá trịHằng sốÝ nghĩa
1STATUS_SUCCESSTìm thấy trong kết quả
2STATUS_OUT_OF_RANGEKhông tìm thấy (ranking = 0)
3STATUS_UNEXPECTED_ERRORLỗi API (ranking = 997)

Thiết kế Database

Chiến lược: Dùng chung bảng mappy_*

Places API và scraping ghi kết quả vào cùng bảng mappy_*. Hai hệ thống không xung đột vì mỗi bên xử lý tập location riêng: cột data_source trên mappy_gbp_locations quyết định location nào thuộc hệ thống nào.

BảngVai tròGhi chú
mappy_search_ranking_update_logsLog thực thi theo ngày1 record/ngày
mappy_search_ranking_processesTheo dõi process (normal/reverse)type: 0=normal, 1=reverse
mappy_temp_search_rankingsKết quả ranking thô (1 record/keyword)Dữ liệu chính
mappy_search_ranking_exportsDữ liệu export tổng hợp (1 record/location)JSON array, tối đa 8 keyword

Cột data_source trên mappy_gbp_locations

Cột data_source dùng để phân chia tập location cho mỗi hệ thống xử lý. Không phải để phân biệt kết quả ranking.

Giá trịHằng sốÝ nghĩa
1DATA_SOURCE_APILocation do Places API xử lý
2DATA_SOURCE_RANK_SEARCHLocation do scraping xử lý
  • Places API chỉ query keyword của location có data_source = 1
  • Scraping chỉ query keyword của location có data_source = 2
  • Kết quả ranking của cả hai hệ thống đều lưu vào cùng bảng mappy_temp_search_rankings, mappy_search_ranking_exports

Trích xuất place_id

Lấy trực tiếp từ mappy_gbp_locations.raw_json tại metadata.placeId. Không cần thêm cột mới.

python
import json

# Ví dụ trích xuất từ raw_json
raw = json.loads(raw_json)
placeId = raw["metadata"]["placeId"]  # "ChIJg9Jc9EiHGGARCDHlvl19Coo"
lat = raw["latlng"]["latitude"]        # 35.6928321
lng = raw["latlng"]["longitude"]       # 139.9285475

Xác định tọa độ tìm kiếm

Ưu tiên lấy tọa độ theo thứ tự:

  1. User settings: mappy_user_settings.settingskeywordSettings.locations[location_id]
  2. raw_json: mappy_gbp_locations.raw_jsonlatlng.latitude/longitude
  3. Default: Tokyo area (35.6441649, 139.347756) — fallback khi cả 2 nguồn trên không có

ER Diagram

Định nghĩa bảng: mappy_temp_search_rankings

NoTên cột (logic)Tên cột (vật lý)Kiểu dữ liệuNULLKeyMô tả
1IDidintNOPKTự tăng
2ID log thực thiupdate_log_idintNOIDXmappy_search_ranking_update_logs.id
3ID processprocess_idintNOIDXmappy_search_ranking_processes.id
4User IDuser_idintNOIDXmappy_users.id
5Location IDgbp_location_idintNOIDXmappy_gbp_locations.id
6Keyword IDkeyword_idintNOIDXmappy_keywords.id
7System IDsystem_idintNO-1:MAPPY / 2:GCOR / 3:PIPIT
8RankingrankingintYES-Vị trí (1-60), 0=ngoài phạm vi, 997=lỗi
9Ranking statusranking_statustinyintYES-1:thành công / 2:ngoài phạm vi / 3:lỗi
10Ngày rankingranking_atdateNO-Ranking của ngày nào
11Thời điểm gọi APIsearch_atdatetimeNO-Thời điểm thực tế gọi API
12Ngày tạocreated_atdatetimeNO-Thời điểm tạo record
13Ngày cập nhậtupdated_atdatetimeNO-Thời điểm cập nhật record

Định nghĩa bảng: mappy_search_ranking_exports

Export data là JSON array tổng hợp cho 1 location, chứa tối đa 8 keyword và thống kê top 3/10/20.

NoTên cột (logic)Tên cột (vật lý)Kiểu dữ liệuNULLKeyMô tả
1IDidintNOPKTự tăng
2ID log thực thiupdate_log_idintNO-mappy_search_ranking_update_logs.id
3ID processprocess_idintNOIDXmappy_search_ranking_processes.id
4User IDuser_idintNOIDXmappy_users.id
5Location IDgbp_location_idintNOIDXmappy_gbp_locations.id
6System IDsystem_idintNO-1:MAPPY / 2:GCOR / 3:PIPIT
7Ngày rankingranking_atdateNO-Ranking của ngày nào
8DatadatalongtextNO-JSON array (xem cấu trúc bên dưới)
9Ngày tạocreated_atdatetimeNO-
10Ngày cập nhậtupdated_atdatetimeNO-

Cấu trúc JSON data:

json
[
  "2026-04-07T10:30:00",   // datetime
  "user_login_id",          // login_id
  "store_001",              // store_code
  123,                      // gbp_location_id
  "1234567890",             // location_name_id
  "Tên cửa hàng",           // display_name
  1,                        // flag
  4,                        // keyword_count
  "keyword1", "keyword2", "keyword3", "keyword4", "", "", "", "",  // 8 keyword slots
  35.6595,                  // lat
  139.7004,                 // lng
  null,                     // reserved
  3, 15, 0, null, null, null, null, null,  // 8 ranking slots
  null,                     // reserved
  1, 2,                     // has_top3, top3_count
  1, 3,                     // has_top10, top10_count
  1, 4,                     // has_top20, top20_count
  1                         // system_id
]

Flow xử lý 1 keyword

CLI Arguments

bash
python main.py [options]
ArgumentKiểuMặc địnhMô tả
--threadsint4Số thread mỗi API key. Tổng thread = threads × số key
--type_keywordstrallall (normal+reverse) / normal / reverse
--min_intervalint500Khoảng cách tối thiểu giữa các API request (ms, mỗi thread)
--max_retriesint3Số lần retry khi gặp 429
--retry_delayint5000Thời gian chờ trước mỗi retry (ms)
--max-groupsint0Giới hạn số group xử lý (0=unlimited, dùng để test)

Quan trọng: --threads là số thread mỗi key, không phải tổng. Ví dụ: 3 key + --threads 4 = 12 thread tổng.

Endpoint

POST {PLACES_API_BASE_URL}/v1/places:searchText

Mặc định PLACES_API_BASE_URL=https://places.googleapis.com. Có thể override bằng env var để test với mock server.

Headers

Content-Type: application/json
X-Goog-Api-Key: {API_KEY}
X-Goog-FieldMask: places.id,nextPageToken

Request Body

json
{
  "textQuery": "渋谷 歯医者",
  "languageCode": "ja",
  "locationBias": {
    "circle": {
      "center": {
        "latitude": 35.6595,
        "longitude": 139.7004
      },
      "radius": 5000.0
    }
  }
}

Response mẫu

json
{
  "places": [
    { "id": "ChIJ..." },
    { "id": "ChIJ..." },
    { "id": "ChIJ..." }
  ],
  "nextPageToken": "..."
}

Logic tính ranking

python
def find_ranking(places, place_id):
    """Tìm vị trí của place_id trong kết quả API.

    Returns:
        Vị trí (1-based) nếu tìm thấy, 0 nếu không.
    """
    if not places or not place_id:
        return 0
    for index, place in enumerate(places):
        if place.get("id") == place_id:
            return index + 1
    return 0

Sequence Diagram

Xử lý lỗi

LỗiHTTP StatusXử lý
Vượt quota429Chờ retry_delay ms rồi retry (tối đa max_retries lần), hết retry → ranking=997 + ERROR log
Lỗi xác thực401PlacesAPIError type=AUTH_ERROR, ranking=997
Bị chặn403PlacesAPIError type=BLOCKED, ranking=997
Server error5xxPlacesAPIError type=UNEXPECTED, ranking=997
Timeout-PlacesAPIError type=TIMEOUT, ranking=997
Lỗi kết nối-PlacesAPIError type=UNEXPECTED, ranking=997

Xử lý lỗi DB: Context manager get_db() tự rollback khi exception và re-raise lên caller.

Multi API Key và xử lý Thread

Thread model

--threads là số thread mỗi key. Tổng thread = --threads × số key.

bash
# 1 key + 4 threads/key = 4 thread tổng
GOOGLE_PLACES_API_KEYS=keyA  --threads 4
  Thread-0 keyA    Thread-1 keyA
  Thread-2 keyA    Thread-3 keyA

# 3 key + 4 threads/key = 12 thread tổng
GOOGLE_PLACES_API_KEYS=keyA,keyB,keyC  --threads 4
  chunk 0→keyA  chunk 1→keyB  chunk 2→keyC  chunk 3→keyA
  chunk 4→keyB  chunk 5→keyC  chunk 6→keyA  chunk 7→keyB
  chunk 8→keyC  chunk 9→keyA  chunk 10→keyB chunk 11→keyC

Cách scale: Thêm key vào env var → tự động tăng thread tổng. Không cần sửa code.

Group và phân phối chunk

Toàn bộ keyword được query 1 lần, group theo user_id × gbp_location_id × system_id, rồi chia thành chunks liên tục (không phải round-robin).

Tổng: 120 groups, 12 threads (3 key × 4 threads/key)
chunk_size = ceil(120/12) = 10

  Thread-0:  group 0-9     (keyA)
  Thread-1:  group 10-19   (keyB)
  Thread-2:  group 20-29   (keyC)
  Thread-3:  group 30-39   (keyA)
  ...
  Thread-11: group 110-119 (keyC)

Ưu điểm:

  • Không chia cắt group (lấy toàn bộ → group → phân phối, đơn giản)
  • Normal và reverse chạy liên tiếp trong cùng group (chia sẻ request_timer)
  • Mỗi thread có rate limiter độc lập
  • 1 DB pool dùng chung (pool_size=15, max_overflow=10, tối đa 25 connections)

Rate Limiting & Quota

Google Places Text Search API mặc định 600 QPM (Queries Per Minute). Thực tế Google áp dụng burst limit ~10 req/giây, không chỉ theo phút.

Dữ liệu thực tế (2026/03/25): 4 batch peak ~395 req/phút (dưới 600) nhưng vẫn bị 429. Nguyên nhân: 4 batch × 3.41 req/giây = 13.65 req/giây → vượt 10 req/giây burst limit.

Cách min_interval hoạt động

Trước mỗi API call (bao gồm cả pagination pages):
  elapsed = now - request_timer
  if elapsed < min_interval:
      sleep(min_interval - elapsed)
  request_timer = now
  • request_timer được chia sẻ giữa normal và reverse trong cùng group
  • Mỗi thread có request_timer độc lập
  • Pagination pages (trang 2, 3) cũng được rate-limit

Ước tính throughput

Số key--threads--min_intervalTổng threadsTổng req/sTổng QPMĐánh giá
14500ms48480Cấu hình ban đầu
24500ms816960Khuyến nghị
34500ms12241440Max ổn định trên 2GB RAM
14300ms4~13~800Gần burst limit

Flow retry 429

Lần 1 → 429 → chờ retry_delay → Lần 2 → 429 → chờ → ... → Lần max_retries+1
  • Mỗi lần retry ghi WARNING log
  • Hết retry vẫn fail → ranking=997, ghi ERROR log
  • Sau sleep(retry_delay) reset request_timer

Giải pháp tăng throughput

Thêm API key (khuyến nghị)

Mỗi Google Cloud project có rate limit riêng (~10 req/giây). Chỉ cần thêm key vào GOOGLE_PLACES_API_KEYS.

bash
# Cấu hình ban đầu (1 key × 4 threads = 4 threads)
GOOGLE_PLACES_API_KEYS=keyA  --threads 4       # ~8 req/s

# Scale up (3 key × 4 threads = 12 threads)
GOOGLE_PLACES_API_KEYS=keyA,keyB,keyC  --threads 4  # ~24 req/s
Số keythreads/keyTổng threadsRate limit tổngThời gian (49,912 req)RAM
14410 req/giây83 phút~160MB
24820 req/giây42 phút~240MB
341230 req/giây28 phút~320MB
441640 req/giây21 phút~400MB

Xin tăng quota

Qua Google Cloud Console → Quotas → Increase Requests. IDs Only SKU (miễn phí) nên khả năng duyệt cao. Review 1~2 ngày.

Kết hợp: 3 key × quota tăng lên 1,200 QPM/key = 60 req/giây → 49,912 req xử lý trong 14 phút.

Log

Log file

  • Thư mục: log/debug_aws/
  • Combined log: {YYYY-MM-DD_HH-MM-SS}_total.log (tất cả thread)
  • Per-key log: {YYYY-MM-DD_HH-MM-SS}_key{suffix}.log (lọc theo API key)
  • Upload S3: s3://{bucket}/log/apigoogle/{filename}

Log format

2026-04-07 10:30:18,015 [INFO][T1][...a1b2] [1/1212] location='渋谷歯科', keywords=8
2026-04-07 10:30:18,378 [DEBUG][T1][...a1b2] [uid=1 sys=1 loc=123]   [N][1/8] '渋谷 歯医者': rank=1, results=60
2026-04-07 10:30:18,900 [WARNING][T2][...c3d4] [uid=2 sys=1 loc=456]   [R][3/8] '歯医者 渋谷': 429 rate limited (attempt 1/4), waiting 5000ms...
2026-04-07 10:30:23,910 [INFO][T2][...c3d4] [uid=2 sys=1 loc=456]   [R][3/8] '歯医者 渋谷': retry succeeded (attempt 2/4)
FieldÝ nghĩa
[T{n}]Thread số n
[...{suffix}]4 ký tự cuối của API key
[uid=X sys=Y loc=Z]Context: user_id, system_id, gbp_location_id
[N]/[R]Normal / Reverse
[i/total]Keyword thứ i trong group
attempt X/YLần thử X trên tổng Y

Giới hạn tài nguyên (1 CPU / 2GB RAM)

GIL và I/O-bound

Workload này là I/O-bound nên thread vẫn hiệu quả dù có GIL:

Mỗi keyword:  ~293ms (thực tế trung bình)  Chờ HTTP response từ Google API
              ~5-10ms     Ghi DB
              < 1ms       Python code (parse JSON, tính rank)

→ CPU idle ~98% thời gian → nhiều thread chạy song song hiệu quả.

Ước tính tài nguyên

Tài nguyênƯớc tính
RAM / thread~15-20MB
RAM base process~80MB
RAM (4 threads, 1 key)~80 + 4×20 = ~160MB
RAM (12 threads, 3 key)~80 + 12×20 = ~320MB
CPU trung bình< 20%
DB connectionspool_size=15, max_overflow=10, tối đa 25

Cấu hình khuyến nghị

Số key--threadsTổng threadsRAMTổng QPMĐánh giá
144~160MB480Cấu hình ban đầu
248~240MB960Khuyến nghị
3412~320MB1440Max ổn định trên 2GB
4416~400MB1920Cần tăng QPM quota

Cấu hình ban đầu: GOOGLE_PLACES_API_KEYS=key1 + --threads 4 --min_interval 500

Cấu trúc source code

places_api/
├── main.py                    # Entry point: CLI args, orchestration, threading
├── services/
│   └── places_api.py          # Google Places API client (search_text, find_ranking)
├── database/
│   ├── database.py            # SQLAlchemy engine, session, get_db() context manager
│   ├── models.py              # ORM models (mappy_* tables)
│   └── repositories.py        # DB queries (CRUD operations)
├── logger.py                  # Structured logging (per-thread, per-key file handlers)
├── migrations/
│   └── gplaces_tables.sql     # DDL cho các bảng mới
├── Dockerfile
├── requirements.txt
└── .env.example

Test cases

#Nội dung testCách kiểm tra
1Trích xuất placeIdLấy đúng giá trị từ raw_json.metadata.placeId
2Ranking tìm thấyGiá trị ranking = vị trí thực tế (1-based)
3Ngoài phạm viKhông tìm thấy trong 60 kết quả → ranking=0, status=2
4Lỗi APISau max_retries → ranking=997, status=3
5PaginationLấy đủ tối đa 3 trang qua nextPageToken
6429 retryResponse 429 → retry đúng max_retries lần với retry_delay
7Multi API keyKey phân phối round-robin cho chunks (chunk_idx mod số key)
8Thread modelTổng thread = --threads × số key
9Rate limitingmin_interval kiểm soát đúng khoảng cách request (cả pagination pages)
10normal/reverseMỗi group chạy normal → reverse liên tiếp, chia sẻ request_timer
11Chunk distributionGroup được chia thành chunks liên tục cho các thread
12data_source filterChỉ xử lý location có data_source=1
13Export dataJSON array đúng format với 8 keyword slots, top 3/10/20 counts
14S3 logCombined + per-key log files upload lên S3
15update_log statusSet RUNNING khi bắt đầu, process set SUCCEEDED/FAILED khi kết thúc
16--max-groupsGiới hạn đúng số group khi test
17get_db() exceptionDB error được rollback và re-raise (không bị nuốt)

Yêu cầu phi chức năng

#Hạng mụcYêu cầu
1Chi phí$0/tháng (Field Mask: places.id → Basic SKU miễn phí)
2Hiệu năngBan đầu: 1 key 4 thread 480 QPM. Thêm key = tăng tuyến tính
3Tài nguyên1 CPU / 2GB RAM — 1 key 4 thread ~160MB, 3 key 12 thread ~320MB
4Khả dụngPhụ thuộc SLA Google Places API (99.9%)
5Lưu trữLịch sử ranking lưu vô thời hạn
6Bảo mậtAPI Key quản lý qua biến môi trường server
7Giám sátLog upload S3, per-key log file để debug
8Khả năng mở rộngThêm key vào env → tự động tăng thread và throughput

Điều kiện tiên quyết & Ràng buộc

Điều kiện tiên quyết

  • Google Cloud Project đã bật Places API (New)
  • API Key đã được tạo và có quyền truy cập Text Search (khuyến nghị nhiều project/key)
  • mappy_gbp_locations.raw_json chứa metadata.placeId hợp lệ
  • mappy_gbp_locations có cột data_source (1=API, 2=scraping)
  • Python 3.10+ với các thư viện trong requirements.txt
  • AWS S3 credentials cho upload log

Ràng buộc

  • Text Search API không thể phát hiện Knowledge Panel (-1 / -2)
  • Kết quả tìm kiếm tối đa 60 kết quả (20 x 3 trang) — vị trí 61+ coi là ngoài phạm vi
  • locationBias radius cố định 5000m
  • Kết quả từ scraping và Places API có thể không trùng khớp hoàn toàn (logic ranking khác nhau)
  • Export data giới hạn tối đa 8 keyword/location (keyword thứ 9+ không có trong export, nhưng vẫn lưu trong mappy_temp_search_rankings)
  • Slack thông báo chưa implement (TODO)

Ước tính công sức

Đội ngũ

Vai tròSố ngườiNội dung phụ trách
Thiết kế1Xác nhận yêu cầu → Tạo thiết kế → Review → Chỉ thị sản xuất
Sản xuất1Dựa trên ISSUE tạo code → Code review → Test → Deploy

Chi tiết công sức

#Hạng mụcCông sức (người-ngày)Phụ trách
1Xác nhận yêu cầu & tạo thiết kế1.0Thiết kế
2Migration DB (thêm data_source, indexes)0.5Sản xuất
3Core logic: Text Search API + ranking1.5Sản xuất
4Multi API key + Thread + CLI + Rate limiting1.0Sản xuất
5Export data + Error handling1.0Sản xuất
6Logging + S3 log upload0.5Sản xuất
7Test tích hợp1.0Sản xuất
8Deploy & xác nhận0.5Sản xuất
Tổng cộng7.0

Schedule

タスク担当日数4/74/84/94/104/114/124/134/144/154/164/17
Xác nhận yêu cầu & thiết kếThiết kế1d
Migration DB (data_source + indexes)Sản xuất1d
Review thiết kế & chỉ thịThiết kế1d
Core logic: API + rankingSản xuất2d
Multi API key + Thread + Rate limitSản xuất1d
Export data + error handlingSản xuất1d
Logging + S3 uploadSản xuất1d
Test tích hợpSản xuất1d
Deploy & xác nhậnSản xuất1d