Skip to content
AvanZ

Restaurant Review Summarizer

A tool that collects Google Maps reviews for a restaurant and returns a sentiment breakdown and summary, so an owner can read the gist of hundreds of reviews instead of scrolling them.

Role
Full-stack Web Developer
Timeline
2 weeks (May 2025)
Team
Five people. Teammates wrote the scraper and the summarisation model, two researched. I built the web application and the API, and integrated their work.
Year
2025

Stack

  • FastAPI
  • Python
  • JavaScript
  • Vite
  • REST APIs

Outcomes

  • Turned two independent Python components into one service a non-technical user could operate
  • Layered FastAPI backend keeping scraping, ML and API concerns separate
  • Never deployed publicly: the scraping approach is fragile, and I would rebuild it on an official API

Note on scope. This project is not live. The scraper depends on Google Maps page structure, which changes without warning, so a public deployment would break unpredictably. I have kept it here because the integration work and the reasons it is not deployed are both worth explaining.

Context

A restaurant with a few hundred Google Maps reviews has a real problem: the information is there, but nobody reads it. An owner sees a star rating and the five most recent comments, and everything else is buried.

This was a five-person college project. Teammates wrote the scraper and the summarisation model, and two handled research. I built the web application and the API layer, and did the integration work that turned their separate pieces into something a person could actually use.

The problem

A star rating compresses everything into one number. A 4.2 tells you almost nothing actionable: it does not distinguish a restaurant with consistently good food and slow service from one that is excellent at lunch and poor at dinner.

The detail lives in the text, and text does not aggregate by itself. The team wanted to answer a specific question: what do people repeatedly complain about, and what do they repeatedly praise?

My role

I was the full-stack web developer, which on this project meant being the integration layer.

My teammates delivered working Python: a scraper that returns review data, and a model that summarises text and produces a word cloud. Both ran as scripts, on their machines, invoked by hand.

What I owned:

  • The FastAPI service exposing scraping and summarisation as HTTP endpoints
  • The front end (JavaScript, Vite) for submitting a restaurant and viewing results
  • The project structure that let several people work without colliding
  • Debugging and adapting the scraper so its output was usable by the API
  • Getting the model artifact out of the repository once it started bloating clones

I want to be precise, since it is easy to overclaim on group work. The summarisation model is not mine, and the scraper was written by teammates; my commits against it are fixes and integration, not the original implementation. I made 30 of the 42 commits on the backend, and they are overwhelmingly API and integration work.

Technical decisions

Layering the backend by concern

Rather than one script, I structured the backend so each responsibility is isolated:

app/
├── api/endpoints/    reviews.py, scraping.py
├── core/             configuration
├── ml/               summarizer.py, wordcloud_generator.py
└── scrapers/         gmaps_scraper.py

This was as much a teamwork decision as an architectural one. With three people committing to one backend in two weeks, clear boundaries meant the scraper authors and the model author could work in their own directories and merge without conflicts. The API layer became the contract between us.

It also isolates the fragile part. The scraper is the piece most likely to break; the ML and API layers are stable. Keeping them apart means the scraper can be swapped for an official API without touching anything else, which is exactly the change I would make if I returned to this.

Getting the model out of the repository

The trained sentiment model is a large binary. I first committed it, then tried Git LFS, and finally removed it entirely (Delete ml_models/model.pth is in the history) in favour of a download link in the README.

That sequence is visible in the commits and I would not repeat it. Model artifacts belong in something like DVC or a release asset from the start. The current setup means the project cannot be reproduced if that link ever dies.

Scraping, and why it was the wrong foundation

Google Maps has no free public reviews API at any useful volume, so the team scraped. It worked, and the whole project depends on that choice in a way we underestimated.

A scraper is coupled to a page structure that a company you do not control can change at any time. That is fine for a college project with a fixed deadline and fatal for anything that has to keep running. This is the reason the project is not deployed.

The hardest problem

Integrating two components written by two different people, neither of them me, in a two-week window.

The scraper returned whatever Google Maps happened to give it: inconsistent field names, missing ratings, reviews with no text at all. The model expected clean input. Wiring them directly meant the API crashed on the first restaurant with an empty review.

The fix was to stop treating the API as a passthrough. I defined a schema at the boundary and normalised the scraper’s output into it before the model ever saw it, so malformed reviews were dropped or filled with defaults rather than propagating. That gave each teammate a contract instead of a moving target, and it moved every parsing failure into one place I could debug.

The lesson I took: when several people build parts of one system, the integration layer is where the real work is, and it is worth designing rather than improvising.

Outcome

  • A working pipeline from a restaurant name to a sentiment breakdown and word cloud
  • Three people’s separate scripts running as one service behind a web UI
  • A backend structure clean enough that the fragile part is genuinely replaceable
  • A clear understanding of where the approach breaks

What I would do differently

Do not build on a scraper. The dependency on someone else’s page structure is the single reason this is not live. Google Places API has real costs and real quotas, but it has a contract, and a project that only runs on our machines is not finished.

Define the data contract on day one. We integrated at the end, which is when we discovered the mismatch. Agreeing the schema between scraper and model upfront would have saved most of the debugging.

Version the model artifact properly. Committing it, moving to Git LFS, then deleting it in favour of a Drive link is not reproducibility. DVC or a tagged release would make the project clonable years from now.