I wanted a link to each hotel’s own website in an itinerary. Simple enough. Search for the name, take the result that loads and mentions the hotel, link it.
It worked for weeks. Then a hotel in York came back pointing at a site that wasn’t the hotel.
A very convincing impostor
The link went to a subdomain on a hotel directory: the hotel’s name on the left, the directory’s brand on the right. Something like thehotel.city-hotels.example. The page loaded. The page title was the hotel’s name in capitals, plus “4-star accommodation”. My check that the page mentions the hotel passed with flying colours.
And the directory ranked above the official site.
That’s the trap. A check that asks “does this page talk about the hotel?” is a check any directory passes. They’re built to. The page check was fine for what it was for, catching dead links and parked domains. It had nothing to say about who was talking.
Look at the domain, not the page
The thing that separates the real site from a listing is usually whose domain it is. thehotelyork.example is the hotel. thehotel.city-hotels.example is somebody else’s domain with the hotel as a prefix.
So I wrote a function that finds the label that actually names a site, the bit just before the public ending, and handles the two-part endings like .co.uk and .com.au where a naive split goes wrong:
function siteLabel(string $host): string
{
$parts = explode('.', strtolower(trim($host)));
$n = count($parts);
if ($n < 2) {
return $host;
}
$twoPart = $n >= 3
&& strlen($parts[$n - 1]) === 2
&& in_array($parts[$n - 2], ['co', 'com', 'org', 'net', 'gov', 'edu', 'ac'], true);
return $parts[$n - ($twoPart ? 3 : 2)];
}
www.thehotelyork.example gives thehotelyork. thehotel.city-hotels.example gives city-hotels. Then the question becomes whether that label carries the hotel’s distinctive words, meaning the ones left after you strip “hotel”, “boutique”, the city name and anything under three letters.
Picking a winner
Selection now goes in three steps, and the third is the one I’m proudest of:
- The best-ranked confirmed site whose own domain carries the hotel name wins, wherever it ranked.
- Otherwise accept only the very top search result, and not if its domain looks like a directory (words like hotels, booking, deals, guide, or the city name itself).
- Otherwise nothing. No link beats a wrong one.
Step two exists because plenty of small hotels use a reservation system on its own domain and that really is their site. If it’s the top result I’ll take it. If it’s the fourth, I won’t.
Then the booking engines turned up
A couple of days later, a hotel in Rome came back pointing at a page on a third-party reservation platform. It’s a per-hotel page, hosted by the platform, with the hotel’s name in the subdomain. Loads fine. Names the hotel. Is not the hotel’s website.
I added a list of booking-engine host fragments to the same refusal list as the big online travel agencies. Seeing the list is mildly depressing: there are a lot of them, and I only know the ones I’ve tripped over.
And then I was too strict
Some hotel sites sit behind a bot check, so the page itself can’t be read. For those I’d required the domain to carry the name. Fine for most. Not fine for a hotel that belongs to a chain, where the address is chain.example/rome/hotel-alba: the chain’s name in the domain and the hotel’s name in the path.
The fix was to accept the lot of it together, domain and path, as long as every distinctive word of the name turns up somewhere in the address and there’s an accommodation word in there too. A different hotel from the same chain still doesn’t pass, because its path has the wrong name in it. The tests cover both directions, plus a gardening site that happened to have “eden” in its domain.
The command that found it
None of this came from reading code. It came from a small diagnostic command that takes a hotel and a city, runs the search, shows every result, then shows what the checker did with each and which rule picked the winner. I added it after the first bad link, and it paid for itself the same afternoon.
If a lookup is going to take a decision on your behalf, give yourself a way to ask it why.
