Agoda_hotel_room avatar

Agoda_hotel_room

Pricing

Pay per usage

Go to Apify Store
Agoda_hotel_room

Agoda_hotel_room

Pricing

Pay per usage

Rating

0.0

(0)

Developer

39_Đinh Nguyễn Nhật Lâm

39_Đinh Nguyễn Nhật Lâm

Maintained by Community

Actor stats

0

Bookmarked

3

Total users

2

Monthly active users

6 days ago

Last modified

Share

Agoda Hotel Scraper

Trích xuất dữ liệu khách sạn có cấu trúc từ Agoda: thông tin khách sạn, điểm review theo hạng mục, loại phòng, tiện nghi, tọa độ và hình ảnh — bằng cách gọi JSON API nội bộ của Agoda thay vì parse HTML, giúp bền vững hơn khi giao diện thay đổi.

Actor này lấy cảm hứng từ bovi/agoda-scraper. Đây là bản tự xây để học/tùy biến.


Kiến trúc & vai trò của Apify

Actor gồm 2 phần tách biệt: hạ tầng (Apify)logic nghiệp vụ (code trong src/). Apify không "biết" gì về Agoda — nó chỉ là nơi chạy code, cấp proxy và lưu kết quả. Toàn bộ việc gọi đúng endpoint, dịch dữ liệu, xử lý lỗi là code tự viết.

flowchart LR
subgraph User["Người dùng"]
U1[Nhập input trên Console/API]
U2[Xem / tải Dataset]
end
subgraph Apify["Apify Platform — hạ tầng"]
direction TB
A1[Console / API]
A2["Build từ GitHub<br/>(Dockerfile + .actor/actor.json)"]
A3[Container chạy Actor]
A4[Proxy Residential]
A5[("Dataset (lưu trữ + export)")]
end
subgraph Actor["Container Actor — code tự viết (src/)"]
direction TB
M["main.py<br/>điều phối input → output"]
AG["agoda.py<br/>logic gọi & parse Agoda"]
end
subgraph AgodaSite["agoda.com — bên thứ 3"]
S[Suggest API]
G["GraphQL<br/>propertyDetailsSearch"]
SD[GetSecondaryData]
end
U1 --> A1 --> A3
A2 -. build image .-> A3
A3 --> M --> AG
AG <-->|qua proxy| A4
A4 <--> S
A4 <--> G
A4 <--> SD
M -->|push_data| A5
A5 --> U2

Bên trong 1 lần lấy khách sạn (fetch_hotel) — gọi song song 2 nguồn rồi gộp lại:

sequenceDiagram
participant Main as main.py
participant Client as AgodaClient (agoda.py)
participant Agoda as agoda.com
Main->>Client: resolve_search("Hilton Saigon")
Client->>Agoda: GET GetUnifiedSuggestResult
Agoda-->>Client: danh sách ứng viên + hotelId
Client-->>Main: hotelId = 47843559
Main->>Client: fetch_hotel(47843559)
par Nội dung chính
Client->>Agoda: POST /graphql/property (propertyDetailsSearch)
Agoda-->>Client: tên, địa chỉ, review, tiện nghi, ảnh...
and Phòng & lân cận
Client->>Agoda: GET GetSecondaryData
Agoda-->>Client: rooms, nearby, highlights, giờ check-in/out
end
Client-->>Main: record đã gộp + chuẩn hoá (40 field)
Main->>Main: push_data(record) → Dataset

Ai làm gì

Thành phầnDo ai cung cấpVai trò
Nơi chạy actor (container, trigger qua Console/API/lịch chạy)ApifyHạ tầng chạy code — không cần tự dựng server
Actor.get_input() / push_data() / proxy xoay IP / logApify SDK (apify package)Cầu nối giữa code Python và nền tảng: đọc input form, ghi Dataset, cấp proxy
Build image từ GitHub, versioningApifyTự động build lại mỗi khi bấm Build (đọc Dockerfile + .actor/actor.json)
Kho lưu kết quả, export JSON/CSV/ExcelApify (Dataset)Không cần tự dựng database
Biết gọi đúng endpoint nào của Agoda, header nào bắt buộcCode tự viết (src/agoda.py)"Não" của actor — Apify hoàn toàn không biết gì về Agoda
Parse, chuẩn hoá, dịch dữ liệu theo ngôn ngữCode tự viết (src/agoda.py)Gộp 3 nguồn Agoda thành 1 record 40 field
Đọc input, điều phối vòng lặp, xử lý lỗi từng khách sạnCode tự viết (src/main.py)Cầu nối giữa input Apify và logic Agoda

Nói ngắn gọn: nếu thay Agoda bằng 1 trang web khác, phần Apify không đổi gì cả — chỉ cần viết lại src/agoda.py.


Tính năng

  • Nhận từ khóa tìm kiếm (tên khách sạn cụ thể), URL trang khách sạn, hoặc tên vùng/thành phố (vd "Hồ Chí Minh") làm đầu vào — với vùng, actor tự liệt kê hàng loạt khách sạn trong đó.
  • Trả về metadata khách sạn: tên, địa chỉ, hạng sao, điểm review, số review, tiện nghi, phòng, tọa độ, ảnh.
  • Hỗ trợ proxy (khuyên dùng Apify Residential) và giới hạn tần suất để giảm nguy cơ bị chặn.
  • Bắt lỗi theo từng khách sạn: một lỗi không làm hỏng cả run.
  • warnings để báo chất lượng parse (rỗng nếu bình thường).

Input

FieldKiểuMô tả
searchTermsarray (string)Tên khách sạn hoặc điểm đến. Hiệu quả nhất với tên cụ thể (vd "The Peninsula Bangkok").
propertyUrlsarray (string)URL trang khách sạn Agoda. Actor tự tách hotelId. Nếu URL có sẵn checkIn/checkOut (hoặc los)/adults/rooms, actor tự lấy GIÁ THỰC TẾ cho khách sạn đó — không cần điền checkIn ở dưới.
hotelIdsarray (string/int)ID khách sạn Agoda nếu đã biết (vd 10715).
locationsarray (string)Tên vùng/thành phố (vd "Hồ Chí Minh") hoặc dán trực tiếp link trang tìm kiếm Agoda (vd https://www.agoda.com/vi-vn/search?city=13170&checkIn=2026-08-01&checkOut=2026-08-03&adults=2&rooms=1) — link được ưu tiên xử lý vì đã có sẵn cityId, nhanh hơn. Nếu link có ngày/số khách, TẤT CẢ khách sạn tìm được trong vùng đó dùng chung tiêu chí này để lấy giá thực tế. Giới hạn bởi maxItemsPerLocation.
maxItemsPerLocationintegerSố khách sạn tối đa lấy cho mỗi vùng trong locations. 0 = không giới hạn (có trần an toàn nội bộ ~2000). Mặc định 50; ví dụ dùng thực tế: 200.
checkInstringNgày check-in mặc định (YYYY-MM-DD, chọn qua date-picker trên Console), dùng cho khách sạn không có sẵn ngày từ URL (vd hotelIds, searchTerms, hoặc locations gõ tên thường). Bỏ trống = không lấy giá (nhanh hơn).
checkOutstringNgày check-out (YYYY-MM-DD, date-picker). Nếu bỏ trống, tự tính từ checkIn + lengthOfStay.
lengthOfStayintegerSố đêm ở lại — chỉ dùng khi không cấp checkOut. Mặc định 1.
adults / roomsintegerSố người lớn / số phòng, dùng cùng checkIn. Mặc định 2 / 1.
languagestringNgôn ngữ kết quả: en-us (mặc định), vi-vn, th-th, ko-kr, ja-jp, zh-cn, id-id. Ảnh hưởng tên phòng, tiện nghi, mô tả, địa điểm lân cận, và cả category_scores/score_distribution.
maxItemsintegerSố bản ghi tối đa. 0 = không giới hạn. Mặc định 0.
requestDelayintegerSố giây chờ giữa các request (0–30). Mặc định 2.
proxyConfigurationobjectCấu hình proxy. Mặc định Apify Residential.

⚠️ Giá hiện chỉ hỗ trợ tiền tệ VND. Mỗi khách sạn có checkIn (từ URL hoặc input) sẽ tốn thêm 1 request gọi giá (room-grid), nên chạy chậm hơn khi bật tính năng này ở quy mô lớn.

Ví dụ input — tìm 1 khách sạn cụ thể

{
"searchTerms": ["The Peninsula Bangkok"],
"propertyUrls": [],
"maxItems": 0,
"requestDelay": 2,
"proxyConfiguration": {
"useApifyProxy": true,
"apifyProxyGroups": ["RESIDENTIAL"]
}
}

Ví dụ input — tìm 200 khách sạn theo vùng

Cách 1 — gõ tên vùng:

{
"locations": ["Hồ Chí Minh"],
"maxItemsPerLocation": 200,
"language": "vi-vn",
"requestDelay": 2,
"proxyConfiguration": {
"useApifyProxy": true,
"apifyProxyGroups": ["RESIDENTIAL"]
}
}

Cách 2 — dán link tìm kiếm Agoda (copy từ trình duyệt sau khi tự tìm trên agoda.com, nhanh hơn vì đã có sẵn cityId, không tốn thêm request đổi tên → id):

{
"locations": ["https://www.agoda.com/vi-vn/search?city=13170&locale=vi-vn&textToSearch=H%E1%BB%93+Ch%C3%AD+Minh"],
"maxItemsPerLocation": 200,
"language": "vi-vn"
}

Ví dụ input — lấy kèm giá thực tế (không còn null)

Cách đơn giản nhất: dán URL đã có sẵn ngày/số khách (copy thẳng từ thanh địa chỉ trình duyệt sau khi bạn tự chọn ngày trên agoda.com) — actor tự đọc checkIn/checkOut/adults/rooms từ URL, không cần khai báo lại:

{
"propertyUrls": [
"https://www.agoda.com/hilton-saigon/hotel/ho-chi-minh-city-vn.html?checkIn=2026-09-10&checkOut=2026-09-12&adults=2&rooms=1"
],
"language": "vi-vn"
}

Hoặc dùng checkIn/checkOut (hoặc lengthOfStay)/adults/rooms ở input gốc — áp dụng cho mọi khách sạn không có sẵn ngày riêng (kể cả hotelIds, searchTerms, hoặc locations gõ tên thường):

{
"locations": ["Hồ Chí Minh"],
"maxItemsPerLocation": 20,
"checkIn": "2026-09-10",
"checkOut": "2026-09-12",
"adults": 2,
"rooms": 1,
"language": "vi-vn"
}

Không chọn checkOut? Actor tự tính bằng checkIn + lengthOfStay (mặc định 1 đêm) như trước đây.

⚠️ Mỗi khách sạn tìm theo vùng vẫn lấy đầy đủ 40 field (2–3 request/khách sạn, thêm 1 nếu có lấy giá) nên chạy chậm hơn nhiều so với danh sách rút gọn: 200 khách sạn × ~3 request × requestDelay giây có thể mất hàng chục phút. Muốn nhanh hơn thì giảm maxItemsPerLocation hoặc giảm requestDelay (đánh đổi với rủi ro bị chặn).


Output (mỗi bản ghi vào Dataset)

FieldMô tả
hotel_id / hotel_nameĐịnh danh & tên
accommodation_typeLoại hình (Hotel, Apartment...)
address / city / country / area_nameVị trí
location_highlightNhấn mạnh vị trí (vd "trong nội thành")
star_ratingHạng sao (1–5)
review_score / review_count / review_textĐiểm, số review, nhãn đã dịch theo language (vd "Trên cả tuyệt vời")
category_scoresĐiểm theo hạng mục — nhãn đã dịch theo language (vd {"Độ sạch sẽ": 9.6, "Vị trí": 9.7, ...}), lấy trực tiếp từ Agoda nên đúng cho mọi ngôn ngữ hỗ trợ
score_distributionPhân bố review theo mức — nhãn đã dịch theo language (vd {"9+ Hiếm Có": 380, ...})
awardsGiải thưởng (nếu có)
price / currencyGiá phòng rẻ nhất/đêm toàn khách sạn (hiện chỉ hỗ trợ VND) — chỉ có giá trị khi cấp checkIn (qua URL hoặc input); không thì null.
rooms_availableCòn phòng cho ngày đã chọn hay không (true/false/null).
offer_countTổng số gói giá (offer) tìm được trên mọi loại phòng.
check_in / check_outNgày dùng để tính giá (đã chuẩn hoá YYYY-MM-DD, null nếu không lấy giá).
room_typesDanh sách tên loại phòng
roomsChi tiết từng phòng — xem bảng riêng bên dưới.
amenities / amenity_groupsTiện nghi (phẳng & theo nhóm)
highlightsĐiểm nổi bật của khách sạn
nearby_attractionsĐiểm tham quan gần (kèm khoảng cách) — nguồn không có toạ độ riêng từng điểm
nearby_essentialsSân bay/giao thông/tiện ích: category, name, distance_km, distance_text, coordinates (toạ độ riêng của điểm đó, dạng "lat,lng")
check_in_time / check_in_untilGiờ bắt đầu nhận phòng / giờ nhận phòng muộn nhất (vd 14:00 / 23:59)
check_out_time / reception_open_untilGiờ trả phòng / giờ quầy tiếp tân đóng cửa (vd 12:00 / 05:30) — không phải khách sạn nào cũng có đủ 4 mục này, thiếu thì để null
descriptionMô tả đầy đủ (HTML)
image_url / image_count / all_imagesẢnh
coordinatesToạ độ dạng chuỗi "lat,lng" (vd "10.775087,106.705696") — dán thẳng được vào Google Maps hoặc hầu hết ứng dụng bản đồ
property_urlURL trang khách sạn
warningsCảnh báo parse (rỗng nếu bình thường)
scraped_atThời điểm crawl (ISO-8601 UTC)

Cấu trúc từng phòng trong rooms[]

Có 2 mức chi tiết tuỳ việc actor có gọi được giá hay không (tức có checkIn hay không):

FieldLuôn cóChỉ có khi cấp checkIn (gọi thêm room-grid)
name, room_id, bed, size, view
price_per_night / currency / crossed_out (có giảm giá)
sold_out (còn phòng loại này hay không)✅ — phòng hết chỗ vẫn xuất hiện đầy đủ, chỉ price_per_night: null
max_occupancy (vd "Tối đa 2 người lớn")
review_score / review_text (điểm review riêng của loại phòng, khác điểm chung khách sạn)
amenity_groups (tiện nghi theo nhóm, đầy đủ như trên trang phòng)
images (toàn bộ ảnh, không chỉ 1 ảnh đại diện) / image_count

Không có checkIn → mỗi phòng chỉ có bộ field cơ bản (name, room_id, bed, size, view, features, image_count, thumbnail) lấy từ trang khách sạn thường, không cần gọi thêm request.


Dùng qua API

Python

from apify_client import ApifyClient
client = ApifyClient("<YOUR_API_TOKEN>")
run = client.actor("<username>/agoda-scraper").call(
run_input={"searchTerms": ["The Peninsula Bangkok"]}
)
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
print(item)

JavaScript

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<YOUR_API_TOKEN>' });
const run = await client.actor('<username>/agoda-scraper').call({
searchTerms: ['The Peninsula Bangkok'],
});
console.log(`Dataset: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);

CLI

$echo '{ "searchTerms": ["The Peninsula Bangkok"] }' | apify call <username>/agoda-scraper --silent --output-dataset

Cơ chế hoạt động

Actor gọi trực tiếp các endpoint JSON nội bộ của Agoda (không cần trình duyệt):

  1. Tìm kiếm theo tên khách sạnGET /api/cronos/search/GetUnifiedSuggestResult/...?searchText=... → lọc IsHotel==true để lấy hotelId, tên, thành phố, quốc gia.
  2. Tìm theo vùng/thành phố (locations) — 2 bước:
    • Cùng endpoint suggest ở trên, nhưng lọc entry CityId == ObjectId != 0 (thành phố thật, có khách sạn) để đổi tên vùng → cityId.
    • POST /graphql/search (operation citySearch, query lưu trong src/city_search_query.py) → liệt kê hotelId trong thành phố, phân trang tối đa 100/lần (page.pageSize/page.pageNumber). Header ag-page-type-id: 644.
    • Danh sách hotelId tìm được feed thẳng vào bước 3 (chi tiết đầy đủ) như mọi nguồn khác.
  3. Chi tiết 1 khách sạn — 2 nguồn gộp lại:
    • POST /graphql/property (operation propertyDetailsSearch, query lưu trong src/property_details_query.py) → tên, địa chỉ, review, tiện nghi, mô tả, ảnh, tọa độ. Cần bộ header ag-* bắt buộc (thiếu sẽ bị 400 Missing required headers).
    • GET /api/cronos/property/BelowFoldParams/GetSecondaryData → rooms, room_types, nearby_attractions, nearby_essentials, highlights, location_highlight, category_scores/score_distribution (nhãn đã dịch sẵn theo ngôn ngữ, lấy từ reviews.demographic — không cần tự dịch).
  4. Giá theo ngày check-in (tuỳ chọn, chỉ gọi khi có checkIn) — POST /api/v1/property/room-grid (body template lưu trong src/room_grid_template.py) → giá rẻ nhất/đêm, tình trạng còn phòng, danh sách offer theo từng loại phòng. Cần header ag-initiator-api-key (định danh client cố định, không phải auth cá nhân). Hiện chỉ hỗ trợ tiền tệ VND.

Ngôn ngữ kết quả điều khiển bằng header ag-language-locale (input language) — hỗ trợ tiếng Việt đầy đủ.

Agoda không công bố API nên schema có thể đổi theo thời gian. Nếu một ngày kết quả rỗng/lỗi, mở lại một trang khách sạn Agoda, chụp request graphql/property trong DevTools > Network, rồi cập nhật QUERY/VARIABLES trong property_details_query.py. Nên chạy có proxy và giữ requestDelay hợp lý để tôn trọng giới hạn của site.