Check What Google AI Mode Can Quote From a Local Business Website: A Small Python Audit
Google's AI Mode now answers local questions like "electrician near me" with a short list of businesses and links. A study by Cheers of 9,435 AI Mode answers to home-services questions in 100 US and Canadian cities (Sept
Google's AI Mode now answers local questions like "electrician near me" with a short list of businesses and links. A study by Cheers of 9,435 AI Mode answers to home-services questions in 100 US and Canadian cities (September 2026) found that AI Mode cited a business's own website in 92.5% of answers, 31.2% linked to a booking page, and 92.8% of emergency answers mentioned 24/7 availability.
The study's authors are clear that these are patterns, not Google's formula. But they point to a short checklist of things a local business page should state plainly. If you build sites for local businesses, you can check a client's current site against that list in a few seconds.
This post builds a small audit with the Python standard library only: no requests, no BeautifulSoup.
What the audit checks
| Check | Why it matters |
|---|---|
| LocalBusiness-type JSON-LD | Gives search engines the name, phone and areas in a structured form |
| Opening hours in the page text | AI answers often include "open now" or hours |
A phone number or tel: link |
The first thing a caller needs |
| A booking or appointment link | 31.2% of answers in the study linked to one |
| 24/7 or emergency wording | 92.8% of emergency answers mentioned it |
The code
import json
import re
from html.parser import HTMLParser
BOOKING_WORDS = ("book", "booking", "appointment", "reserve", "reservation", "schedule", "order online")
HOURS_RE = re.compile(r"\b(mon|tue|wed|thu|fri|sat|sun)[a-z]*\b.{0,40}?\d{1,2}(:\d{2})?\s*(am|pm)", re.I)
PHONE_RE = re.compile(r"(\+?\d[\d\s-]{8,}\d)")
EMERGENCY_RE = re.compile(r"\b24\s*[/x]\s*7\b|\b24 hours\b|\bemergency\b", re.I)
class PageScan(HTMLParser):
def __init__(self):
super().__init__()
self.text = []
self.links = []
self.jsonld = []
self._in_jsonld = False
self._skip = 0
def handle_starttag(self, tag, attrs):
a = dict(attrs)
if tag == "script" and a.get("type") == "application/ld+json":
self._in_jsonld = True
elif tag in ("script", "style"):
self._skip += 1
elif tag == "a":
self.links.append((a.get("href") or "", ""))
def handle_endtag(self, tag):
if tag == "script" and self._in_jsonld:
self._in_jsonld = False
elif tag in ("script", "style") and self._skip:
self._skip -= 1
def handle_data(self, data):
if self._in_jsonld:
self.jsonld.append(data)
elif not self._skip:
self.text.append(data)
if self.links and self.links[-1][1] == "":
href, _ = self.links[-1]
self.links[-1] = (href, data.strip())
def schema_types(blocks):
types = set()
for raw in blocks:
try:
data = json.loads(raw)
except json.JSONDecodeError:
continue
items = data if isinstance(data, list) else data.get("@graph", [data])
for item in items:
t = item.get("@type")
types.update(t if isinstance(t, list) else [t])
return {t for t in types if t}
def audit(html: str) -> dict:
p = PageScan()
p.feed(html)
text = " ".join(p.text)
types = schema_types(p.jsonld)
booking = [h for h, label in p.links
if any(w in (h + " " + label).lower() for w in BOOKING_WORDS)]
return {
"local_business_schema": any(t.endswith("Business") or t in {"Restaurant", "Electrician", "Plumber"} for t in types),
"hours_on_page": bool(HOURS_RE.search(text)),
"phone_on_page": bool(PHONE_RE.search(text)) or any(h.startswith("tel:") for h, _ in p.links),
"booking_link": bool(booking),
"emergency_or_24x7": bool(EMERGENCY_RE.search(text)),
}
def score(result: dict) -> int:
return sum(result.values())
A few notes on the design:
-
HTMLParseris enough here. We only need visible text, link targets with their labels, and the contents ofapplication/ld+jsonscript tags. -
schema_typeshandles the three common JSON-LD shapes: a single object, a list, and an@graph. - The booking check looks at both the href and the link text, because many sites use
/contactwith the label "Book a visit".
Try it on two pages
thin_site = """<html><body><h1>Sharma Electricals</h1>
<p>Welcome to our website. We are the best.</p></body></html>"""
useful_site = """<html><head>
<script type="application/ld+json">
{"@context": "https://schema.org", "@type": "Electrician", "name": "Sharma Electricals",
"telephone": "+91 98765 43210", "areaServed": ["Vijay Nagar", "Palasia"]}
</script></head><body>
<h1>Electrician in Vijay Nagar and Palasia</h1>
<p>House wiring, MCB and DB repair, inverter points. 24/7 emergency calls.</p>
<p>Open Monday to Saturday, 9 am to 8 pm.</p>
<a href="tel:+919876543210">Call +91 98765 43210</a>
<a href="/book">Book a visit</a>
</body></html>"""
for name, html in (("thin_site", thin_site), ("useful_site", useful_site)):
r = audit(html)
print(name, score(r), "/ 5", r)
Output:
thin_site 0 / 5 {'local_business_schema': False, 'hours_on_page': False, 'phone_on_page': False, 'booking_link': False, 'emergency_or_24x7': False}
useful_site 5 / 5 {'local_business_schema': True, 'hours_on_page': True, 'phone_on_page': True, 'booking_link': True, 'emergency_or_24x7': True}
The thin site is the common case: a name, a welcome line and nothing a search engine can quote. The second page is not fancy. It just says what the business does, where, when, and how to reach it.
Running it on real sites
To audit live URLs, fetch the HTML with urllib.request and pass it to audit(). Send a clear User-Agent, respect robots.txt, and keep the rate low. For a list of client prospects, a CSV with url, score and the failed checks is a ready-made sales note: "your site does not show your hours or a booking link".
Limits
- Regex hours detection misses hours written only in an image or a widget loaded by JavaScript.
- A site can pass every check and still be slow or broken on mobile. Treat the score as a conversation starter, not a ranking prediction.
- The thresholds come from one study of US and Canadian home-services answers. Other categories and countries may differ.
Where the bigger gap is
Many local businesses do not have a site to audit at all. In the Mantis data I work with, 1,207 of 1,790 electricians and plumbers across 18 Indian cities (67.4%) have no website, even with median ratings of 4.8 to 5.0 stars. For restaurants the gap is different: the Hyderabad restaurant list has 238 restaurants and cafes, 119 with a website, and 44 of the 51 sites Mantis could read (86%) show no online booking.
Find local businesses with no website or weak sites
Mantis gives 500 free credits once at signup, no card.
FAQ
Does adding JSON-LD guarantee a business appears in AI Mode?
No. Structured data helps search engines read the page, but ratings, reviews and relevance still decide which businesses are named.
Why not use BeautifulSoup?
You can. The standard library keeps the script dependency-free, which is handy when you run it inside a small cron job or a serverless function.
Can this audit run on JavaScript-rendered sites?
Not as written. For those, render the page with a headless browser first and pass the final HTML to audit().
Disclosure: I work on Mantis, a lead-finding tool for agencies and freelancers. AI Mode figures are from the Cheers study of US and Canadian metros, September 2026.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.