Matching the same hotel across OTAs: why name matching fails and what to key on instead
Every multi-OTA price comparison rests on one assumption: that the rows being compared describe the same physical property. That assumption is usually enforced by matching names — and names are the least stable thing about a hotel. When the match is wrong, every number downstream is wrong, and nothing errors.
One property, many identities
A hotel does not have one name. It has the name on its sign, the name in its OTA contracts, the name its chain's CRS uses, and the name each OTA's content team edited for their own listing. All of these drift independently:
Rebrands and flag changes. Properties change operator and brand while the building, address and phone number stay the same. One OTA updates the listing that week; another carries the old name for months.
Chain naming collisions. A large chain can have several properties in one city whose names differ by a single locality word. A fuzzy matcher scoring "brand + city" treats them as near-duplicates of each other.
Transliteration and locale. The same property is listed in different scripts and romanisations depending on the OTA's point of sale — a structural problem for India and APAC inventory, where naming conventions vary between MakeMyTrip, Agoda and the global OTAs.
Listing granularity. One OTA lists the resort; another lists its villas as separate properties; a third lists the aparthotel floors under the operator's brand. There is no string-similarity threshold that resolves a disagreement about what counts as one property.
Legal names and marketing names. "Hotel Sunrise Residency Pvt Ltd" and "Sunrise Residency by XYZ" may be one property — or two properties across the street from each other.
The failure is silent. A wrong match does not throw an exception. It produces a plausible-looking price row attached to the wrong building, and the pipeline keeps running. That is what makes property identity a data-quality problem rather than a scraping problem.
What a false match costs
Rate parity and rate shopping
A parity alarm fires because "your" hotel is undercut on an OTA — except the row belongs to the similarly named property two streets away. Teams burn trust chasing phantom violations, then start ignoring the alarms that are real.
Competitive pricing models
A repricing model that ingests one mismatched competitor learns from a property with different positioning, star rating and demand curve. The error is invisible inside an average until someone audits row by row.
AI booking agents
An agent that resolves "the Taj near the airport" to the wrong listing quotes a real price for the wrong hotel. The user discovers it at the booking page — the most expensive possible place to find a data bug.
Inventory joins
Merging OTA catalogues by name either drops real properties (false negatives) or double-counts them (false positives). Both corrupt market-coverage numbers that business decisions get made on.
The fix is a deterministic key, not a better matcher
Smarter fuzzy matching lowers the error rate; it never removes the failure mode. The structural fix is to stop matching on descriptions and start joining on an identifier that names the physical place itself.
The most practical such key for hotels is the Google Place ID — Google's unique identifier for an establishment in the Places database (Google's documentation). It has three properties that matter for this job:
It names the place, not the listing. A Place ID identifies the establishment regardless of how any OTA spells, brands or structures its listing.
Google has already done the OTA join. A hotel's Google listing aggregates prices from the OTAs that sell it — meaning Google maintains, at scale, exactly the property-to-OTA-listing mapping everyone else tries to rebuild with string matching. Keying on Place ID lets you inherit that work instead of repeating it.
It makes lookups repeatable. The same Place ID returns the same property every time. A name search can return different candidates as listings change; an identifier cannot.
How to build the join in practice
1
Resolve each property to a Place ID once. For a portfolio or comp set this is a one-time mapping exercise, and worth doing with human eyes: search, confirm the address and photos match, store the ID. This is minutes per property, done once — versus a fuzzy match re-gambled on every pipeline run.
2
Treat the mapping table as an asset. Property → Place ID is slowly changing data. Keep it versioned, record who verified each row and when, and re-verify a row when a property rebrands or closes.
3
Use the metasearch view as the pivot. Query the property by Place ID on Google Hotels to get the multi-OTA price comparison already joined to the right building. Per-OTA collection (for room-level detail or member pricing) then keys back to the same Place ID, so every row in your warehouse shares one property key.
4
Flag the fallback rows. Where no Place ID exists (small guesthouses, brand-new openings), fall back to name-plus-location search — and mark those rows as probabilistic matches so they never silently pool with deterministic ones.
Where it still gets hard
Vacation rentals and multi-unit listings. Identity is genuinely ambiguous when one host lists overlapping unit bundles. A property key helps less when the "property" itself is fluid.
Coverage differences. An OTA can sell a hotel that Google's comparison does not display for your market or dates. Absence from the pivot is not evidence the OTA lacks the property — check directly before concluding.
Room-level identity. Two OTAs agreeing on the property can still disagree on rooms. Room matching is its own problem, on top of the tax-basis and occupancy normalisation covered in the member-rates guide.
How ScrapeGuys handles it
Full disclosure, this is our product — the mechanics in one paragraph.
The Google Hotels scraper takes a Place ID directly (Place ID mode, which the docs mark as recommended precisely because it is deterministic — no wrong-hotel matches), returns the multi-OTA comparison for that property, and batches up to 20 hotels per request; name-search mode exists as the fallback for properties you have not mapped yet, with the property resolved server-side in v2. The same Place ID key then lines up with per-OTA collection from Agoda, Booking.com, MakeMyTrip and Expedia, and with Rate Shop for many-hotels-many-OTAs sweeps. Coverage and limits are in the FAQ.
Where we are not the answer: if you need a static, GDS-grade mapping database across supplier codes — the problem dedicated hotel-mapping vendors sell — that is a different product category. Our key is Google's, which is the right tool when your pipeline consumes live OTA prices; it is not a replacement for supplier-code mapping inside a GDS integration.
Questions we get about this
A textual identifier for a place in Google's Places database — a string like ChIJN1t_tDeuEmsRUsoyG83frY4 that uniquely identifies one establishment, per Google's documentation. For a hotel it identifies the property itself, independent of how any OTA spells its name.
Coordinates get you close but cannot separate co-located properties — two hotels in one tower, an aparthotel on three floors, a resort and its villas on one campus — and each OTA geocodes addresses independently, so the coordinates themselves disagree slightly. A proximity join still needs a tie-breaker, and the tie-breaker ends up being the name again.
It happens with small guesthouses, new openings and some vacation rentals. Fall back to name-plus-location search and flag the match as probabilistic, so downstream consumers know the row carries risk instead of pooling it silently with deterministic ones.
No. Two OTAs can agree on the hotel and still describe its rooms differently — names, bed configurations, rate-plan bundling. Compare prices at room level only when the room attributes match, not just the property.