{
  "version": "1.0.0",
  "exported_at": "2026-05-31T15:05:00.000Z",
  "project": {
    "name": "Google Maps Reviews Scraper by Reviewer",
    "description": "Scrapes public Google Maps contributor review pages by reviewer profile URL. The workflow loops through supplied reviewer /reviews URLs, opens the Reviews tab, scrolls the Google Maps side panel, collects and deduplicates visible review cards during scrolling into a synthetic export table, and appends reviewer profile details, place details, ratings, dates, review text, owner responses, likes, place URL, and reviewer profile URL to one CSV. Google Maps may redirect to sign-in, show CAPTCHA, throttle, hide reviews, virtualize rows, or change class names; this template is best-effort and should be run with a browser profile that can access public Maps pages.",
    "color": "bg-[#4589ff]",
    "template_id": "ai-generated"
  },
  "blocks": [
    {
      "block_id": "navigate-1",
      "block_type": "process",
      "title": "Navigate",
      "description": "Go to a URL",
      "position_x": 120,
      "position_y": 240,
      "config": {
        "urls": [
          "https://www.google.com/maps/contrib/112277453687791435729/reviews",
          "https://www.google.com/maps/contrib/102621106565974123765/reviews",
          "https://www.google.com/maps/contrib/11110509328741153374/reviews"
        ],
        "color": "bg-[#4589ff]",
        "tags": [
          "google-maps",
          "reviewer-profile",
          "multi-url"
        ]
      }
    },
    {
      "block_id": "wait-for-page-load-1",
      "block_type": "process",
      "title": "Wait for Page Load",
      "description": "Wait for page to finish loading",
      "position_x": 480,
      "position_y": 240,
      "config": {
        "timeout": 45,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "sleep-1",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 840,
      "position_y": 240,
      "config": {
        "duration": 5,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "click-1",
      "block_type": "process",
      "title": "Click",
      "description": "Click on element",
      "position_x": 1200,
      "position_y": 240,
      "config": {
        "selector": "button[role=\"tab\"][data-tab-index=\"0\"]",
        "timeout": 15,
        "color": "bg-[#a56eff]"
      }
    },
    {
      "block_id": "sleep-2",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 1560,
      "position_y": 240,
      "config": {
        "duration": 3,
        "color": "bg-[#a56eff]"
      }
    },
    {
      "block_id": "inject-javascript-1",
      "block_type": "process",
      "title": "Inject JavaScript",
      "description": "Execute custom JavaScript",
      "position_x": 1920,
      "position_y": 240,
      "config": {
        "jsCode": "(async()=>{const sleep=ms=>new Promise(r=>setTimeout(r,ms));const clean=s=>(s||'').replace(/\\s+/g,' ').trim();const textOf=e=>clean(e?.innerText||e?.textContent||e?.getAttribute('aria-label')||'');const old=document.querySelector('#uscraper-gmaps-reviews');if(old)old.remove();const tab=Array.from(document.querySelectorAll('button[role=\"tab\"],button')).find(b=>/^Reviews\\b/i.test(textOf(b)));if(tab){try{tab.click();}catch(e){}await sleep(2500);}const profileText=clean(document.body.innerText);const username=clean(document.querySelector('.geAzIe.fontHeadlineLarge')?.textContent||document.querySelector('button.fontHeadlineLarge')?.textContent||'');const userType=(profileText.match(/Local Guide|Reviewer|Contributor/i)||[''])[0];const level=(profileText.match(/Level\\s*\\d+/i)||[''])[0];const points=((document.querySelector('.VEEl9c')?.textContent||profileText).match(/([\\d,.]+)\\s*points/i)||['',''])[1];const reviewCount=(profileText.match(/Reviews\\s*(?:\\n|\\s)+([\\d,]+)/i)||profileText.match(/([\\d,]+)\\s*reviews/i)||['',''])[1];const ratingCount=(profileText.match(/([\\d,]+)\\s*ratings/i)||profileText.match(/Ratings\\s*(?:\\n|\\s)+([\\d,]+)/i)||['',''])[1];const reviewerUrl=location.href.split('?')[0].replace('/photos/','/reviews/').replace(/\\/photos(\\/|$)/,'/reviews$1');const rows=new Map();const getShop=ROW=>{const sels=['a[href*=\"/maps/place/\"]','.DUwDvf','.qBF1Pd','.P5Bobd','.fontTitleMedium','.fontTitleSmall','.fgD3Vc'];for(const s of sels){const e=ROW.querySelector(s);const t=clean(e?.textContent||e?.getAttribute('aria-label')||'');if(t&&!/review|photo|star|response/i.test(t))return t;}return clean((ROW.getAttribute('aria-label')||'').split('\\n')[0].replace(/\\s+review$/i,''));};const getAddress=(ROW,shop)=>{const texts=Array.from(ROW.querySelectorAll('.b3XaXd,.W4Efsd,.fontBodySmall,span,div')).map(e=>clean(e.textContent)).filter(Boolean);let addr=texts.find(t=>/,|\\d/.test(t)&&!/star|ago|review|Like|Local Guide|points|Response from the owner|Translated by Google/i.test(t)&&t.length>15)||'';if(shop&&addr.startsWith(shop))addr=clean(addr.slice(shop.length));return addr;};const scrapeVisible=()=>{Array.from(document.querySelectorAll('button')).filter(b=>/^(More|See more)$/i.test(textOf(b))||/more/i.test(b.getAttribute('aria-label')||'')).forEach(b=>{try{b.click();}catch(e){}});document.querySelectorAll('div.jftiEf[data-review-id],div.jJc9Ad[data-review-id],div[data-review-id]').forEach(ROW=>{const shop=getShop(ROW);const shopAddress=getAddress(ROW,shop);const ratingEl=ROW.querySelector('[aria-label*=\"star\" i],.kvMYJc');const ratingRaw=ratingEl?.getAttribute('aria-label')||ratingEl?.textContent||'';const reviewRating=(ratingRaw.match(/[\\d.]+\\s*stars?/i)||[clean(ratingRaw)])[0];const time=Array.from(ROW.querySelectorAll('.rsqaWe,.DU9Pgb span,.fontBodySmall,span')).map(e=>clean(e.textContent)).find(t=>/ago|yesterday|today|week|month|year|day/i.test(t))||'';const reviewEl=ROW.querySelector('.wiI7pd,.MyEned,.review-full-text');let content=clean(reviewEl?.textContent||'').replace(/\\s+More$/i,'');if(!content){content=(Array.from(ROW.querySelectorAll('span,div')).map(e=>clean(e.textContent)).filter(t=>t.length>80&&!/Local Guide|points|Reviews|Photos|Response from the owner/i.test(t))[0]||'').replace(/\\s+More$/i,'');}const ownerEl=ROW.querySelector('.CDe7pd .wiI7pd,.CDe7pd,.owner-response');const ownerResponse=clean(ownerEl?.textContent||'');const responseTime=clean(ROW.querySelector('.CDe7pd .rsqaWe,.CDe7pd .DZSIDd')?.textContent||'');const likeEl=Array.from(ROW.querySelectorAll('[aria-label],button,span')).find(e=>/like/i.test(e.getAttribute('aria-label')||e.textContent||''));const likes=((likeEl?(likeEl.getAttribute('aria-label')||likeEl.textContent):'').match(/[\\d,]+/)||[''])[0];const placeA=ROW.querySelector('a[href*=\"/maps/place/\"],a[href*=\"?cid=\"],a[href*=\"/maps?cid=\"]');const placeUrl=placeA?.href||'';const id=ROW.getAttribute('data-review-id')||[shop,time,reviewRating,content.slice(0,120)].join('|');if((shop||content)&&!rows.has(id)){rows.set(id,{username,user_type:userType,level,points,review_count:reviewCount,rating_count:ratingCount,review_shop:shop,shop_address:shopAddress,review_rating:reviewRating,review_time:time,review_content:content,owner_response:ownerResponse,response_time:responseTime,likes,place_url:placeUrl,reviewer_profile_url:reviewerUrl});}});};const containers=Array.from(document.querySelectorAll('div,section')).filter(e=>e.scrollHeight>e.clientHeight+300).sort((a,b)=>b.scrollHeight-a.scrollHeight);const pane=containers.find(e=>/Reviews/i.test(e.getAttribute('aria-label')||''))||containers[0]||document.scrollingElement||document.documentElement;let stable=0,lastCount=0,lastTop=-1;for(let i=0;i<70;i++){scrapeVisible();pane.scrollTop=pane.scrollHeight;pane.dispatchEvent(new Event('scroll',{bubbles:true}));window.scrollTo(0,document.body.scrollHeight);await sleep(850);const count=rows.size;const top=pane.scrollTop;if(count===lastCount&&top===lastTop)stable++;else stable=0;lastCount=count;lastTop=top;if(stable>=8)break;}scrapeVisible();const root=document.createElement('div');root.id='uscraper-gmaps-reviews';root.style.cssText='position:absolute;left:-10000px;top:0;width:1px;height:1px;overflow:visible;';const fields=['username','user_type','level','points','review_count','rating_count','review_shop','shop_address','review_rating','review_time','review_content','owner_response','response_time','likes'];for(const r of rows.values()){const d=document.createElement('div');d.className='uscraper-review-row';for(const f of fields){const s=document.createElement('span');s.className=f;s.textContent=r[f]||'';d.appendChild(s);}const pa=document.createElement('a');pa.className='place_url';pa.href=r.place_url||'';pa.textContent=r.place_url||'';d.appendChild(pa);const ra=document.createElement('a');ra.className='reviewer_profile_url';ra.href=r.reviewer_profile_url||'';ra.textContent=r.reviewer_profile_url||'';d.appendChild(ra);root.appendChild(d);}document.body.appendChild(root);return rows.size;})()",
        "waitForCompletion": true,
        "timeout": 150,
        "color": "bg-[#a56eff]"
      }
    },
    {
      "block_id": "element-exists-1",
      "block_type": "process",
      "title": "Element Exists",
      "description": "Check if element exists",
      "position_x": 2280,
      "position_y": 240,
      "config": {
        "selector": "#uscraper-gmaps-reviews .uscraper-review-row",
        "color": "bg-[#ff832b]"
      }
    },
    {
      "block_id": "structured-export-1",
      "block_type": "process",
      "title": "Structured Export",
      "description": "Export data with custom columns",
      "position_x": 2640,
      "position_y": 240,
      "config": {
        "rowSelector": "#uscraper-gmaps-reviews .uscraper-review-row",
        "fileName": "google-maps-reviews-scraper-by-user-page.csv",
        "saveLocation": "C:\\Users\\theskd\\Documents\\UScraper\\templates",
        "includeHeaders": true,
        "fileMode": "append",
        "color": "bg-[#42be65]",
        "columns": [
          {
            "name": "username",
            "selector": ".username",
            "attribute": "text"
          },
          {
            "name": "user_type",
            "selector": ".user_type",
            "attribute": "text"
          },
          {
            "name": "level",
            "selector": ".level",
            "attribute": "text"
          },
          {
            "name": "points",
            "selector": ".points",
            "attribute": "text"
          },
          {
            "name": "review_count",
            "selector": ".review_count",
            "attribute": "text"
          },
          {
            "name": "rating_count",
            "selector": ".rating_count",
            "attribute": "text"
          },
          {
            "name": "review_shop",
            "selector": ".review_shop",
            "attribute": "text"
          },
          {
            "name": "shop_address",
            "selector": ".shop_address",
            "attribute": "text"
          },
          {
            "name": "review_rating",
            "selector": ".review_rating",
            "attribute": "text"
          },
          {
            "name": "review_time",
            "selector": ".review_time",
            "attribute": "text"
          },
          {
            "name": "review_content",
            "selector": ".review_content",
            "attribute": "text"
          },
          {
            "name": "owner_response",
            "selector": ".owner_response",
            "attribute": "text"
          },
          {
            "name": "response_time",
            "selector": ".response_time",
            "attribute": "text"
          },
          {
            "name": "likes",
            "selector": ".likes",
            "attribute": "text"
          },
          {
            "name": "place_url",
            "selector": ".place_url",
            "attribute": "href"
          },
          {
            "name": "reviewer_profile_url",
            "selector": ".reviewer_profile_url",
            "attribute": "href"
          }
        ]
      }
    },
    {
      "block_id": "loop-continue-1",
      "block_type": "process",
      "title": "Loop Continue",
      "description": "Continue multi-input loop",
      "position_x": 3000,
      "position_y": 560,
      "config": {
        "color": "bg-[#8d8d8d]"
      }
    }
  ],
  "connections": [
    {
      "from_block_id": "navigate-1",
      "from_connector_id": "right",
      "to_block_id": "wait-for-page-load-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "wait-for-page-load-1",
      "from_connector_id": "right",
      "to_block_id": "sleep-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "sleep-1",
      "from_connector_id": "right",
      "to_block_id": "click-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "click-1",
      "from_connector_id": "right",
      "to_block_id": "sleep-2",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "sleep-2",
      "from_connector_id": "right",
      "to_block_id": "inject-javascript-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "inject-javascript-1",
      "from_connector_id": "right",
      "to_block_id": "element-exists-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "element-exists-1",
      "from_connector_id": "true",
      "to_block_id": "structured-export-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "element-exists-1",
      "from_connector_id": "false",
      "to_block_id": "loop-continue-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "structured-export-1",
      "from_connector_id": "right",
      "to_block_id": "loop-continue-1",
      "to_connector_id": "left"
    }
  ],
  "canvas_elements": [
    {
      "id": "group-load",
      "element_type": "group",
      "title": "Page Load",
      "color": "#08bdba",
      "position_x": 48,
      "position_y": 136,
      "width": 1760,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "navigate-1",
          "wait-for-page-load-1",
          "sleep-1",
          "sleep-2"
        ]
      }
    },
    {
      "id": "group-pagination",
      "element_type": "group",
      "title": "Pagination Loop",
      "color": "#ff832b",
      "position_x": 1128,
      "position_y": 136,
      "width": 2120,
      "height": 616,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "click-1",
          "element-exists-1",
          "loop-continue-1"
        ]
      }
    },
    {
      "id": "group-interaction",
      "element_type": "group",
      "title": "Interaction",
      "color": "#a56eff",
      "position_x": 1848,
      "position_y": 136,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "inject-javascript-1"
        ]
      }
    },
    {
      "id": "group-extract",
      "element_type": "group",
      "title": "Data Extraction",
      "color": "#42be65",
      "position_x": 2568,
      "position_y": 136,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "structured-export-1"
        ]
      }
    },
    {
      "id": "note-overview",
      "element_type": "note",
      "title": "Overview",
      "content": "Scrapes public Google Maps contributor review pages by reviewer profile URL. The workflow loops through supplied reviewer /reviews URLs, opens the Reviews tab, scrolls the Google Maps side panel, collects and deduplicates visible review cards during scrolling into a synthetic export table, and appends reviewer profile details, place details, ratings, dates, review text, owner responses, likes, place URL, and reviewer profile URL to one CSV. Google Maps may redirect to sign-in, show CAPTCHA, throttle, hide reviews, virtualize rows, or change class names; this template is best-effort and should be run with a browser profile that can access public Maps pages.",
      "color": "#f1c21b",
      "position_x": 80,
      "position_y": 20,
      "width": 480,
      "height": 160,
      "z_index": 22,
      "data": {}
    },
    {
      "id": "note-block-inject-javascript-1",
      "element_type": "note",
      "title": "Note: Inject JavaScript",
      "content": "Runs custom JavaScript in the page: `(async()=>{const sleep=ms=>new Promise(r=>setTimeout(r,ms));const clean=s=>(s||'').replace(/\\s+/g,' ...` Verify in browser if results are empty.",
      "color": "#ee5396",
      "position_x": 2120,
      "position_y": 220,
      "width": 340,
      "height": 140,
      "z_index": 22,
      "data": {
        "block_id": "inject-javascript-1"
      }
    },
    {
      "id": "note-block-element-exists-1",
      "element_type": "note",
      "title": "Note: Element Exists",
      "content": "Condition block: checks `#uscraper-gmaps-reviews .uscraper-review-row`. True / False branches control which path runs next. Keep enough space between branches so both connector lines are visible.",
      "color": "#ee5396",
      "position_x": 2480,
      "position_y": 220,
      "width": 340,
      "height": 145,
      "z_index": 22,
      "data": {
        "block_id": "element-exists-1"
      }
    },
    {
      "id": "note-block-structured-export-1",
      "element_type": "note",
      "title": "Note: Structured Export",
      "content": "Extracts rows matching `#uscraper-gmaps-reviews .uscraper-review-row`. Confirm row count > 0 before running at scale.",
      "color": "#ee5396",
      "position_x": 2840,
      "position_y": 220,
      "width": 340,
      "height": 119,
      "z_index": 22,
      "data": {
        "block_id": "structured-export-1"
      }
    },
    {
      "id": "note-block-loop-continue-1",
      "element_type": "note",
      "title": "Note: Loop Continue",
      "content": "Loop Continue advances a multi-URL or multi-text loop. Place at the end of the loop body with a clear back-edge to the loop start.",
      "color": "#ee5396",
      "position_x": 3200,
      "position_y": 540,
      "width": 340,
      "height": 123,
      "z_index": 22,
      "data": {
        "block_id": "loop-continue-1"
      }
    }
  ]
}