Gopher artwork by Ashley McNamara

Using ChatGPT and Golang to find the perfect hotel

How I used AI to help my wife plan our summer vacation

David Minkovski
CodeX
Published in
7 min readJun 3, 2023

--

Motivation

Ah, the sweet scent of summer is in the air! It’s that time of the year when we eagerly plan to embark on our well-deserved vacation. But wait! Choosing the perfect hotel has always been a challenge for my wife and me. You see, she’s a meticulous reviewer, meticulously combing through every hotel and its countless feedback to ensure perfection. And then there’s me, the self-proclaimed lazy husband who can’t be bothered to sift through a gazillion reviews. Can you blame me? Of course not.
True laziness is an art, after all. But fear not, for I, the lazy engineer have harnessed the power of programming and AI to solve our hotel woes!

Chapter 1: The Lazy Engineers Dilemma

As we faced the annual hotel hunt, the struggle was real. I mean it was brutal. My wife, the ultimate feedback enthusiast, would meticulously analyze every hotel’s ratings, scrutinizing each review with precision. Meanwhile, there I was, lounging on the couch, contemplating whether to even bother with the never-ending sea of 100,000 reviews. I felt bad and the bitter looks my better half gave me convinced me of what I had known deep down all along. I had to find a smarter way to approach this challenge.

Chapter 2: Being lazy can be a skill

I remembered my programming skills. I know some Golang, with its efficiency and speed, which seemed to me like the perfect weapon to tackle the monstrous task of parsing hotel websites. But I needed something to help me with magically summarizing all those endless reviews in a way that would help to get an overall sense of the hotel quality. Enter ChatGPT the ultimate AI tool for summaries. Together, I believe we could conquer the world of hotel reviews and save my sanity!
Join me on a small adventure as I embarked on a quest to conquer the daunting task of parsing a hotel and travel website using Golang and the magnificent ChatGPT for review summaries. Who knows? Maybe you will find some use for yourself as well!

Chapter 3: Where is the ocean of data?

There are a lot of websites out there, so I picked one with a lot of hotels and reviews on it. It will remained unnamed for obvious reasons.
How was I to get the data out of there? Enter the world of web scraping. From my experience I found two main approaches:

  1. Parse the HTML and write css and id selectors to get the data you need
  2. Understand the API and extract the data from there

Since I did not want to parse the HTML, simply because it seemed always more complicated for me. Been there done that.
I decided to go with option Nr. 2.
Yes — this is a tedious task and might need a couple of hours. Essentially you need to find the requests the website makes, the authentication system they use and then try to get that running locally. But…you get data in a form you can work with and the rest then is truly a piece of cake!

In this particular case — the site needed the following steps:

  1. Send a search request (they have a search form on the website) containing the country id, the start and end date, type of room etc. The values can all be retrieved from the site.
  2. Once that was done, I had to parse the site itself for the searchId — which is hidden inside the HTML as a JS parameter.
  3. The searchID can then be used to make a request get the Hotels for the search as a result.
  4. Finally you can use the hotel ID to get the reviews per hotel.
  5. Store the data in a local json file so the results are “cached”.

For Step 2 I was lucky enough that I could use “regexp” since the searchId was placed next to a very intuitive name of the variable. You will see:

// ...
response, err := http.Get(searchURL)
if err != nil {
log.Fatal(err)
}
defer response.Body.Close()
bodyHTML, err := ioutil.ReadAll(response.Body)
if err != nil {
log.Fatal(err)
}
re := regexp.MustCompile(`"searchId":\d+`)
searchIds := re.FindAllString(string(bodyHTML), -1)
if len(searchIds) > 0 {
firstMatchId := searchIds[0]
rd := regexp.MustCompile(`\d+`)
searchId := rd.FindString(firstMatchId)
return searchId, nil
} else {
return "", errors.New("No search ids found.")
}
// ...

I used go routines and mutex with the default http libraries to have the requests executed in parallel and we have all we need.

// ...
var mutex sync.Mutex
reviewsMap := make(map[int][]models.HotelReview)
for _, hotel := range apiResult.Search.Results {
go func(wg *sync.WaitGroup, hotel models.SearchResult) {
defer wg.Done()
hotelReviews := getReviewsForSingleHotel(hotel)
mutex.Lock()
defer mutex.Unlock()
reviewsMap[hotel.HotelId] = hotelReviews
}(wg, hotel)
}
wg.Wait()
// ...

Chapter 4: AI becomes my friend.

Big Kudos to OpenAI. It is super easy to setup a ChatGPT account.
You simply leave your Credit Card details and acquire a token.
And that is really it. Honestly. The documentation is straight forward as well but I prefer using an amazing ChatGPT client from this library: “github.com/sashabaranov/go-openai” — Big Shoutout to Sasha Baranov!
Now the key to ChatGPT is having the right prompt. Even though mine might not be optimal it worked out “Summarize these negative Hotel Reviews into 10 most important points:”. I am sure there are better ones out there for this purpose — I’d love a comment if you happen to know one!

import (
"context"
"encoding/json"
"fmt"
"gohotels/models"
"strings"
"sync"

openai "github.com/sashabaranov/go-openai"
)

const (
OPENAI_URL = "https://api.openai.com/v1/chat/completions"
OPENAI_KEY = "YOUR_OPEN_AI_KEY"
)

func CheckReviews(id string, reviewsMap map[int][]models.HotelReview) map[int]string {
wg := new(sync.WaitGroup)

wg.Add(len(reviewsMap))
summaryMap := make(map[int]string)
var mutex sync.Mutex

for id, reviewsArray := range reviewsMap {
go func(wg *sync.WaitGroup, id int, reviewsArray []models.HotelReview) {
defer wg.Done()
summary := GetSummary(reviewsArray)
mutex.Lock()
defer mutex.Unlock()
summaryMap[id] = summary
}(wg, id, reviewsArray)
}
wg.Wait()
return summaryMap
}

func GetSummary(reviewsArray []models.HotelReview) string {
var negativeReviewsInput string
// Getting the negative reviews only
for _, review := range reviewsArray {
for _, text := range review.Texts {
if strings.Contains(text.Sentiment, "neg") {
negativeReviewsInput = fmt.Sprintf("%v %v", negativeReviewsInput, text.Text)
}
}
}
if len(negativeReviewsInput) > 4000 {
negativeReviewsInput = negativeReviewsInput[0:4000]
}

prompt := fmt.Sprintf("Summarize these negative Hotel Reviews into 10 most important points: %v", negativeReviewsInput)

client := openai.NewClient(YOU_OPENAI_KEY)
resp, err := client.CreateChatCompletion(
context.Background(),
openai.ChatCompletionRequest{
Model: openai.GPT3Dot5Turbo,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
Content: prompt,
},
},
},
)

if err != nil {
fmt.Printf("ChatCompletion error: %v\n", err)
return ""
}

return resp.Choices[0].Message.Content
}
// ...

With ChatGPT by my side there was no need to review 1000 ratings. I could simply focus on the summary. And it’s DONE.
One prompt, 1000 reviews as an input and AI does the rest. True magic!
Before going on — I setup my main server using Gin Gonic and
my “API” was born!

package main

import (
// ...
"github.com/gin-gonic/gin"
cors "github.com/rs/cors/wrapper/gin"
)
func DoSearch(c *gin.Context){
wg := new(sync.WaitGroup)
result := apis.GetResults()
hotelsCount := len(result.Search.Results)
wg.Add(hotelsCount)
reviews := apis.GetReviewsPerHotel(result, wg)
aiSummaries := apis.CheckReviews(result.Search.Id, reviews)
c.JSON(http.StatusOK, gin.H{"results": result.Search.Results, "locations": models.Locations, "reviews": reviews, "aisummaries": aiSummaries})
}
func Server() {
router := gin.Default()
router.Use(cors.Default())
router.GET("/search", DoSearch)
router.Run() // listen and serve on 0.0.0.0:8080
}

Chapter 5: The UI — Let’s see

Having a lot of experience in frontend web development, this was honestly by far the easiest part. I quickly set up a Vite project using React and typescript and got going. I did not want to spend too much time on the look and feel and since this was our private tool — I picked a library with everything for a Proof of Concept. My choice was Mantine. Check it out!
It really has a lot of components to offer that are great for quick websites.
I quickly created the overview, a modal for the reviews and was pretty much done.

The overview and the AI reviews of my tool

Summary

In this funny little side project, Golang and ChatGPT proved to be a really great duo for a PoC and can be used for bigger projects without a doubt. From parsing hotel websites to using AI to summarize the reviews, it really was intuitive and fairly easy. And FUN!

So, my fellow vacation planners and tech enthusiasts, fear no more! With Golang and ChatGPT by your side, you too can face the overwhelming world of hotel reviews and extract the essence of thousands of reviews with ease. My wife is happier now but we still have not found a hotel — simply because we are now busy with optimizing the tool to accomodate ALL our (her) preferences. This might have been a mistake after all…see you!

PS: Feel free to ping me if you are interested in getting the source code ;)

Curious about more?

My newsletter is a burst of tech inspiration, problem-solving hacks, and entrepreneurial spirit.
Subscribe for your weekly dose of innovation and mind-freeing insights:
https://davidthetechie.substack.com/

--

--