// Google Maps search results extractor
() => {
const cards = document.querySelectorAll('div.Nv2PK');
const results = [];
for (let card of cards) {
const mainLink = card.querySelector('a.hfpxzc');
if (!mainLink) continue;
const name = mainLink.getAttribute('aria-label') || '';
if (!name) continue;
// Get all links in this card
const links = Array.from(card.querySelectorAll('a'));
let website = '', phone = '', mapsUrl = '';
for (let link of links) {
const href = link.getAttribute('href') || '';
// Maps URL
if (href.includes('/maps/place/') && !href.startsWith('http')) {
mapsUrl = 'https://www.google.com' + href.split('?')[0];
} else if (href.includes('/maps/place/')) {
mapsUrl = href.split('?')[0];
}
// Phone from tel: link
if (href.startsWith('tel:')) {
phone = href.replace('tel:', '');
}
}
// Website: external link that's not Google Maps, social, or booking
for (let link of links) {
const href = link.getAttribute('href') || '';
const text = (link.textContent || '').trim().toLowerCase();
if (href.startsWith('http') &&
!href.includes('google.com') &&
!href.includes('g.page') &&
!href.includes('fb.com') &&
!href.includes('facebook.com') &&
!href.includes('instagram.com') &&
!href.includes('yelp.com') &&
!href.includes('recreateai.com') &&
!href.includes('servicetitan.com') &&
!text.includes('reviews') &&
!text.includes('directions') &&
!text.includes('book online') &&
!text.includes('save') &&
!text.includes('share')) {
website = href;
break;
}
}
// Phone fallback: regex from card text
if (!phone) {
const phoneMatch = card.textContent.match(/\(?(\d{3})\)?[\s.-](\d{3})[\s.-](\d{4})/);
if (phoneMatch) phone = phoneMatch[0];
}
// Rating: look for span with star emoji or number followed by review count
let rating = '';
let reviews = '';
// Google Maps uses a specific structure: a div containing the rating number
// followed by (N reviews) text
const cardText = card.textContent;
const ratingMatch = cardText.match(/(\d\.\d)\s+\(([\d,]+)\s*review[s]?\)/);
if (ratingMatch) {
rating = ratingMatch[1];
reviews = ratingMatch[2];
} else {
// Try simpler: just find a decimal number that looks like a rating
const simpleRating = cardText.match(/(\d\.\d)\s*\(/);
if (simpleRating) {
rating = simpleRating[1];
}
}
// Address: look for line starting with number and containing street info
let address = '';
const lines = cardText.split('\n').map(l => l.trim()).filter(l => l);
for (let line of lines) {
if (/^\d+\s+[A-Za-z]/.test(line) && line.length > 10 && line.length < 80) {
if (!/\d{3,4}$/.test(line) && !line.includes('(')) {
address = line;
break;
}
}
}
results.push({
name,
phone,
website,
rating,
reviews,
address,
maps_url: mapsUrl,
});
}
return results;
}