Spaces:
Running on Zero
Running on Zero
File size: 10,004 Bytes
3a8438d 4a37f72 cfeb7c9 8ca7b50 cfeb7c9 8ca7b50 4a37f72 8ca7b50 4a37f72 8ca7b50 3a8438d 8ca7b50 3a8438d a088192 3a8438d a088192 3a8438d cfeb7c9 3a8438d 8ca7b50 3a8438d 8ca7b50 3a8438d cfeb7c9 3a8438d a088192 8ca7b50 3a8438d 8ca7b50 3a8438d a088192 8ca7b50 3a8438d 8ca7b50 3a8438d cfeb7c9 3a8438d 8ca7b50 3a8438d cfeb7c9 3a8438d cfeb7c9 3a8438d 98c2c47 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 | import gradio as gr
import pandas as pd
import asyncio
import re
from playwright.async_api import async_playwright
import requests
from bs4 import BeautifulSoup
import tempfile
from spaces import GPU
import os, subprocess, sys
# ---------- Dummy GPU function (ZeroGPU requirement) ----------
@GPU
def dummy_gpu():
return "GPU OK"
# -----------------------------------------------------------------
# ---------- Ensure Playwright Chromium is installed ----------
chromium_executable = "/home/user/.cache/ms-playwright/chromium_headless_shell-1228/chrome-headless-shell-linux64/chrome-headless-shell"
if not os.path.exists(chromium_executable):
print("Downloading Playwright Chromium...")
subprocess.run([sys.executable, "-m", "playwright", "install", "chromium"], check=True)
print("Chromium download complete.")
# -----------------------------------------------------------
HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
}
# ------------------ Scraping functions ------------------
async def search_google_maps(page, query, max_results):
try:
# Load the page and wait until the main content appears
await page.goto("https://www.google.com/maps", wait_until="load", timeout=40000)
# Wait a moment for dynamic content
await page.wait_for_timeout(2000)
# Accept cookies if the popup appears
try:
await page.click('button[aria-label="Accept all"]', timeout=3000)
except:
pass
# Wait for the search box to be present (longer timeout)
await page.wait_for_selector('input#searchboxinput', state="attached", timeout=30000)
search_box = page.locator('input#searchboxinput')
await search_box.fill(query)
await search_box.press("Enter")
# Wait for the results feed to appear
await page.wait_for_selector('div[role="feed"]', timeout=20000)
feed = page.locator('div[role="feed"]')
prev = 0
while True:
items = await feed.locator('div[role="article"]').all()
if len(items) >= max_results or len(items) == prev:
break
if items:
await items[-1].scroll_into_view_if_needed()
await page.wait_for_timeout(2000)
prev = len(items)
return (await feed.locator('div[role="article"]').all())[:max_results]
except Exception as e:
print(f"Error during search: {e}")
return []
# (extract_details, scrape_website remain unchanged)
async def extract_details(page, article):
await article.click()
await page.wait_for_selector('div[aria-label*="Information for"]', timeout=10000)
await page.wait_for_timeout(2000)
details = {}
try:
el = page.locator('h1.fontHeadlineLarge')
details['name'] = await el.inner_text() if await el.count() > 0 else ''
except:
details['name'] = ''
try:
el = page.locator('button[data-item-id="address"]')
details['address'] = await el.inner_text() if await el.count() > 0 else ''
except:
details['address'] = ''
try:
el = page.locator('button[data-item-id*="phone"]')
details['phone'] = await el.inner_text() if await el.count() > 0 else ''
except:
details['phone'] = ''
try:
el = page.locator('a[data-item-id*="authority"]')
details['website'] = await el.get_attribute('href') if await el.count() > 0 else ''
except:
details['website'] = ''
try:
el = page.locator('div.fontBodyMedium > span[aria-hidden="true"]')
details['rating'] = await el.inner_text() if await el.count() > 0 else ''
except:
details['rating'] = ''
try:
el = page.locator('button[aria-label*="reviews"]')
txt = await el.inner_text() if await el.count() > 0 else ''
details['reviews'] = txt.split('(')[-1].split(')')[0] if '(' in txt else ''
except:
details['reviews'] = ''
try:
el = page.locator('div[aria-label*="Opening hours"]')
details['hours'] = await el.inner_text() if await el.count() > 0 else ''
except:
details['hours'] = ''
try:
el = page.locator('button[jsaction*="pane.rating.category"]')
details['category'] = await el.inner_text() if await el.count() > 0 else ''
except:
details['category'] = ''
details['maps_url'] = page.url
try:
back_btn = page.locator('button[aria-label="Back"]')
if await back_btn.count() > 0:
await back_btn.click()
await page.wait_for_timeout(1000)
except:
pass
return details
def scrape_website(url):
res = {"emails": [], "phones": [], "socials": []}
if not url:
return res
try:
r = requests.get(url, headers=HEADERS, timeout=10)
if r.status_code != 200:
return res
soup = BeautifulSoup(r.text, "html.parser")
text = soup.get_text()
for a in soup.find_all("a", href=True):
h = a["href"]
if h.startswith("mailto:"):
e = h.replace("mailto:", "").split("?")[0].strip()
if e and e not in res["emails"]:
res["emails"].append(e)
emails = re.findall(r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}", text)
for e in emails:
if e not in res["emails"]:
res["emails"].append(e)
phones = re.findall(r"(\+?\d{1,3}[-.\s]?)?\(?\d{1,4}\)?[-.\s]?\d{1,4}[-.\s]?\d{1,9}", text)
res["phones"] = list(set(phones))[:5]
socials = []
for a in soup.find_all("a", href=True):
h = a["href"].lower()
if any(s in h for s in ["facebook.com", "twitter.com", "linkedin.com", "instagram.com"]):
socials.append(a["href"])
res["socials"] = list(set(socials))[:5]
except:
pass
return res
def run_scrape(query, country, city, max_results):
full_query = query
if city:
full_query += f" in {city}"
if country:
full_query += f", {country}"
async def _async():
async with async_playwright() as p:
browser = await p.chromium.launch(
headless=True,
args=[
'--disable-blink-features=AutomationControlled',
'--no-sandbox',
'--disable-dev-shm-usage',
]
)
context = await browser.new_context(
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
viewport={"width": 1280, "height": 800}
)
page = await context.new_page()
articles = await search_google_maps(page, full_query, max_results)
results = []
for art in articles:
try:
d = await extract_details(page, art)
contacts = scrape_website(d.get('website', ''))
results.append({
"Name": d.get('name', ''),
"Address": d.get('address', ''),
"Phone (Maps)": d.get('phone', ''),
"Website": d.get('website', ''),
"Rating": d.get('rating', ''),
"Reviews": d.get('reviews', ''),
"Opening Hours": d.get('hours', ''),
"Category": d.get('category', ''),
"Maps URL": d.get('maps_url', ''),
"Emails (site)": ", ".join(contacts['emails']),
"Extra Phones (site)": ", ".join(contacts['phones']),
"Socials": ", ".join(contacts['socials'])
})
except Exception:
pass
await page.wait_for_timeout(1000)
await context.close()
await browser.close()
return results
data = asyncio.run(_async())
if not data:
return pd.DataFrame(), None, "No results found. Try a different query or fewer results."
df = pd.DataFrame(data)
tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".xlsx")
with pd.ExcelWriter(tmp.name, engine="openpyxl") as writer:
df.to_excel(writer, index=False, sheet_name="Results")
excel_path = tmp.name
tsv = df.to_csv(sep='\t', index=False)
return df, excel_path, tsv
# ------------------ Gradio UI ------------------
with gr.Blocks(title="Google Maps Scraper") as demo:
gr.Markdown("# 🗺️ Google Maps Business Scraper + Contact Finder")
gr.Markdown("Enter a search query. Use small numbers (5‑10) to avoid timeouts.")
with gr.Row():
query = gr.Textbox(label="Search Query", value="parking businesses")
country = gr.Textbox(label="Country (optional)", value="UK")
city = gr.Textbox(label="City (optional)", value="London")
max_results = gr.Slider(minimum=5, maximum=30, value=10, step=1, label="Max Results")
scrape_btn = gr.Button("Start Scraping", variant="primary")
dataframe = gr.Dataframe(label="Results", interactive=False)
download_btn = gr.DownloadButton(label="📥 Download Excel", visible=False)
tsv_output = gr.Textbox(label="📋 TSV (Copy manually)", visible=False, lines=10)
def handle_scrape(query, country, city, max_results):
df, excel_path, tsv = run_scrape(query, country, city, max_results)
show = bool(excel_path)
return (
df,
gr.update(value=excel_path, visible=show),
gr.update(value=tsv, visible=show),
)
scrape_btn.click(
fn=handle_scrape,
inputs=[query, country, city, max_results],
outputs=[dataframe, download_btn, tsv_output]
)
if __name__ == "__main__":
demo.launch(ssr_mode=False) |