Tabductor
Explore/ Google Maps business leads
Public workflow

Google Maps business leads

By Malik Ammar Faisal · used 0 times · updated 2026-10-18

How it runs

Distilled · v1 Includes a distilled script, so your copy runs it from its first run. If a page has changed, it falls back to the AI agent for that run.

Instructions

Open google maps
Get name, phone, website and rating for "dentists in Austin"
Review the distilled script

This Python runs in your browser session when you use the workflow. Review it before running.

import refrom playwright.sync_api import expect, TimeoutError as PlaywrightTimeoutError QUERY = "dentists in Austin"SEARCH_URL = "https://www.google.com/maps/search/dentists+in+Austin"FEED = 'div[role="feed"]'LINKS = 'div[role="feed"] a.hfpxzc' PANEL_JS = """()=>{  const m=document.querySelector('div[role=main][aria-label]');  const r=m||document;  const ph=r.querySelector('button[data-item-id^="phone:tel:"]');  const w=r.querySelector('a[data-item-id="authority"]');  const rt=r.querySelector('div.F7nice span[aria-hidden="true"]');  return {main:m&&m.getAttribute('aria-label'),          phone:ph?(ph.getAttribute('aria-label')||'').replace('Phone: ','').trim():null,          website:w?w.href:null,          rating:rt?rt.textContent:null}}""" READY_JS = """(name)=>{  const m=document.querySelector('div[role=main][aria-label]');  if(!m || m.getAttribute('aria-label')!==name) return false;  const ph=m.querySelector('button[data-item-id^="phone:tel:"]');  const w=m.querySelector('a[data-item-id="authority"]');  return !!(ph||w);}"""  def run(page, context, browser):    with browser.scope("open-maps-search", goal="Open Google Maps search results for the query straight from the URL", returns=None) as s_open:        page.goto(SEARCH_URL, wait_until="domcontentloaded")        page.wait_for_load_state("domcontentloaded")        try:            expect(page.locator(FEED)).to_be_visible(timeout=15000)        except AssertionError:            s_open.deopt(reason="results feed did not appear", evidence={"url": page.url})        s_open.value = None     with browser.scope("load-results-feed", goal="Scroll the results feed to the bottom several times so more listings load, then count the listing links",                       returns={"type": "object", "properties": {"count": {"type": "integer"}}, "required": ["count"]}) as s_load:        feed = page.locator(FEED)        links = page.locator(LINKS)        try:            expect(feed).to_be_visible(timeout=10000)        except AssertionError:            s_load.deopt(reason="results feed not visible", evidence={"url": page.url})        for _ in range(6):            before = links.count()            feed.evaluate("e=>e.scrollTo(0,e.scrollHeight)")            try:                expect(links).not_to_have_count(before, timeout=3000)            except AssertionError:                pass        try:            expect(links).not_to_have_count(0, timeout=10000)        except AssertionError:            s_load.deopt(reason="the results feed has no listing links", evidence={"url": page.url})        s_load.value = {"count": links.count()}     with browser.scope("list-result-links", goal="Read every listing link's name, href and visibility so hidden or duplicate entries can be skipped",                       returns={"type": "object", "properties": {"items": {"type": "array", "items": {                           "type": "object",                           "properties": {"index": {"type": "integer"}, "name": {"type": ["string", "null"]},                                          "href": {"type": ["string", "null"]}, "vis": {"type": "boolean"}},                           "required": ["index", "name", "vis"]}}}, "required": ["items"]}) as s_list:        links = page.locator(LINKS)        raw = links.evaluate_all("els=>els.map(e=>({name:e.getAttribute('aria-label'),href:e.href,vis:e.offsetParent!==null}))")        if not raw:            s_list.deopt(reason="no listing links read from feed", evidence={"url": page.url})        items = [{"index": i, "name": it.get("name"), "href": it.get("href"), "vis": bool(it.get("vis"))} for i, it in enumerate(raw)]        s_list.value = {"items": items}     results = []    seen = set()    for it in s_list.value["items"]:        name = it["name"]        if not it["vis"] or not name or name in seen:            continue        seen.add(name)        with browser.scope("extract-place-details", goal="Click the listing and read phone, website and rating from its detail panel",                           returns={"type": "object", "properties": {                               "name": {"type": "string"}, "phone": {"type": ["string", "null"]},                               "website": {"type": ["string", "null"]}, "rating": {"type": ["string", "null"]}},                               "required": ["name", "phone", "website", "rating"]}) as s_item:            link = page.locator(LINKS).nth(it["index"])            try:                expect(link).to_have_attribute("aria-label", name, timeout=10000)            except AssertionError:                s_item.deopt(reason="listing link at index no longer matches the name read", evidence={"index": it["index"], "name": name})            link.scroll_into_view_if_needed(timeout=5000)            link.click(timeout=5000)            try:                page.wait_for_function(READY_JS, arg=name, timeout=8000)            except PlaywrightTimeoutError:                pass            d = page.evaluate(PANEL_JS)            if d.get("main") != name:                s_item.deopt(reason="detail panel did not show the clicked listing", evidence={"expected": name, "panel": d.get("main")})            s_item.value = {"name": name, "phone": d.get("phone") or None,                            "website": d.get("website") or None, "rating": d.get("rating") or None}        if s_item.value:            results.append(s_item.value)     browser.done(result={"query": QUERY, "source": "Google Maps", "count": len(results), "results": results})