{
  "version": "1.0.0",
  "exported_at": "2026-05-31T13:50:00.000Z",
  "project": {
    "name": "Airbnb Room Details Scraper",
    "description": "Equivalent UScraper template for the Octoparse Airbnb Room Details Scraper. Scrapes Airbnb room/detail pages, one CSV row per successfully loaded room URL, extracting page URL, title, location, guests, bedrooms, beds, baths, price, rating, reviews, amenities, sleeping arrangements, host, response rate if visible, first image URLs, and current scrape time. Navigation strategy: multi-URL detail-page loop using navigate.urls[] + loop-continue, so every supplied Airbnb room URL is visited. Error/placeholder Airbnb pages such as “Oops!” or “Stay tuned” are skipped to avoid blank garbage rows. Airbnb may use anti-bot controls, dynamic rendering, noindex pages, stale room URLs, or locale/layout changes; this template uses semantic headings, meta tags, body text regex, and JS columns as a best-effort approach.",
    "color": "bg-[#ff5a5f]",
    "template_id": "ai-generated"
  },
  "blocks": [
    {
      "block_id": "navigate-1",
      "block_type": "process",
      "title": "Navigate",
      "description": "Go to a URL",
      "position_x": 120,
      "position_y": 220,
      "config": {
        "urls": [
          "https://www.airbnb.com/rooms/28896024?adults=1&category_tag=Tag%3A789&children=0&enable_m3_private_room=true&infants=0&pets=0&photo_id=725911449&search_mode=flex_destinations_search&check_in=2024-04-24&check_out=2024-04-29&source_impression_id=p3_1711682257_bj1No%2Bv3z3Qa%2Fsuy&previous_page_section_name=1000",
          "https://www.airbnb.com/rooms/36637084?adults=1&category_tag=Tag%3A789&children=0&enable_m3_private_room=true&infants=0&pets=0&photo_id=1048367453&search_mode=flex_destinations_search&check_in=2024-04-06&check_out=2024-04-11&source_impression_id=p3_1711682257_GKu2jnfxkvo0rgjr&previous_page_section_name=1000",
          "https://www.airbnb.com/rooms/898106044708374586?adults=1&category_tag=Tag%3A789&children=0&enable_m3_private_room=true&infants=0&pets=0&photo_id=1666806813&search_mode=flex_destinations_search&check_in=2024-04-01&check_out=2024-04-06&source_impression_id=p3_1711682257_nkuga8BHAVqrOPgs&previous_page_section_name=1000",
          "https://www.airbnb.com/rooms/49887963?adults=1&category_tag=Tag%3A789&children=0&enable_m3_private_room=true&infants=0&pets=0&photo_id=1181787847&search_mode=flex_destinations_search&check_in=2024-05-18&check_out=2024-05-23&source_impression_id=p3_1711682257_Oo2IkgAWxxkCGkG0&previous_page_section_name=1000",
          "https://www.airbnb.com/rooms/53233621?adults=1&category_tag=Tag%3A789&children=0&enable_m3_private_room=true&infants=0&pets=0&photo_id=1282236148&search_mode=flex_destinations_search&check_in=2024-10-20&check_out=2024-10-25&source_impression_id=p3_1711682257_7%2Bng4gI6MzwM1Rl9&previous_page_section_name=1000"
        ],
        "color": "bg-[#ff5a5f]"
      }
    },
    {
      "block_id": "wait-for-page-load-1",
      "block_type": "process",
      "title": "Wait for Page Load",
      "description": "Wait for page to finish loading",
      "position_x": 456,
      "position_y": 220,
      "config": {
        "timeout": 45
      }
    },
    {
      "block_id": "wait-for-element-1",
      "block_type": "process",
      "title": "Wait for Element",
      "description": "Wait until element appears",
      "position_x": 792,
      "position_y": 220,
      "config": {
        "selector": "h1",
        "timeout": 45,
        "visible": true
      }
    },
    {
      "block_id": "sleep-1",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 1128,
      "position_y": 220,
      "config": {
        "duration": 3
      }
    },
    {
      "block_id": "text-contains-1",
      "block_type": "process",
      "title": "Text Contains",
      "description": "Check if text exists on page",
      "position_x": 1464,
      "position_y": 220,
      "config": {
        "selector": "h1",
        "text": "Oops",
        "caseSensitive": false,
        "timeout": 1
      }
    },
    {
      "block_id": "text-contains-2",
      "block_type": "process",
      "title": "Text Contains",
      "description": "Check if text exists on page",
      "position_x": 1464,
      "position_y": 520,
      "config": {
        "selector": "h1",
        "text": "Stay tuned",
        "caseSensitive": false,
        "timeout": 1
      }
    },
    {
      "block_id": "structured-export-1",
      "block_type": "process",
      "title": "Structured Export",
      "description": "Export data with custom columns",
      "position_x": 1968,
      "position_y": 800,
      "config": {
        "rowSelector": "body",
        "fileName": "airbnb-room-details-scraper.csv",
        "saveLocation": "C:\\Users\\theskd\\Documents\\UScraper\\templates",
        "includeHeaders": true,
        "fileMode": "append",
        "columns": [
          {
            "name": "page_url",
            "selector": "window.location.href",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "title",
            "selector": "document.querySelector('h1')?.innerText?.trim() || document.querySelector('meta[property=\"og:description\"]')?.content || document.title.replace(/ - .*$/, '')",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "location",
            "selector": "Array.from(document.querySelectorAll('h2')).map(h => h.innerText.trim()).find(t => /\\b(entire|private|shared|room|home|unit|house|apartment|condo|villa|cabin|cottage|loft|guest suite)\\b/i.test(t) && / in /i.test(t)) || ''",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "number_of_guests",
            "selector": "(() => { const text = document.body.innerText; const m = text.match(/(\\d+)\\s+guests?\\s*[·•]/i) || text.match(/(\\d+)\\s+guests?/i); return m ? m[1] : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "number_of_bedrooms",
            "selector": "(() => { const og = document.querySelector('meta[property=\"og:title\"]')?.content || ''; let m = og.match(/(\\d+)\\s+bedrooms?/i); if (m) return m[1]; const text = document.body.innerText; m = text.match(/\\d+\\s+guests?\\s*[·•]\\s*(\\d+)\\s+bedrooms?/i) || text.match(/(\\d+)\\s+bedrooms?/i); return m ? m[1] : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "number_of_beds",
            "selector": "(() => { const og = document.querySelector('meta[property=\"og:title\"]')?.content || ''; let m = og.match(/(\\d+)\\s+beds?/i); if (m) return m[1]; const text = document.body.innerText; m = text.match(/\\d+\\s+guests?\\s*[·•]\\s*\\d+\\s+bedrooms?\\s*[·•]\\s*(\\d+)\\s+beds?/i) || text.match(/(\\d+)\\s+beds?/i); return m ? m[1] : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "number_of_bath",
            "selector": "(() => { const og = document.querySelector('meta[property=\"og:title\"]')?.content || ''; let m = og.match(/(\\d+(?:\\.\\d+)?)\\s+(?:private\\s+|shared\\s+)?baths?/i); if (m) return m[1]; const text = document.body.innerText; m = text.match(/\\d+\\s+guests?\\s*[·•]\\s*\\d+\\s+bedrooms?\\s*[·•]\\s*\\d+\\s+beds?\\s*[·•]\\s*(\\d+(?:\\.\\d+)?)\\s+(?:private\\s+|shared\\s+)?baths?/i) || text.match(/(\\d+(?:\\.\\d+)?)\\s+(?:private\\s+|shared\\s+)?baths?/i); return m ? m[1] : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "price",
            "selector": "(() => { const visible = document.body.innerText.replace(/\\s+/g, ' '); const scripts = Array.from(document.scripts).map(s => s.textContent || '').join(' ').slice(0, 200000); const text = visible + ' ' + scripts; const m = text.match(/\\$[\\d,]+\\s*(?:per\\s+night|night)(?:,?\\s*originally\\s*\\$[\\d,]+)?/i) || text.match(/\\$[\\d,]+\\s*per\\s+night,?\\s*originally\\s*\\$[\\d,]+/i); return m ? m[0].trim() : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "rating",
            "selector": "(() => { const og = document.querySelector('meta[property=\"og:title\"]')?.content || ''; const body = document.body.innerText; const m = og.match(/★\\s*([\\d.]+)/) || body.match(/Rated\\s+([\\d.]+)\\s+out of 5/i) || body.match(/([\\d.]+)\\s+out of 5 stars/i) || body.match(/\\b([345]\\.[0-9]{1,2})\\s*[·•]\\s*\\d+\\s+reviews?/i); return m ? m[1] : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "number_of_reviews",
            "selector": "(() => { const body = document.body.innerText; const m = body.match(/from\\s+([\\d,]+)\\s+reviews?/i) || body.match(/[·•]\\s*([\\d,]+)\\s+reviews?/i) || body.match(/\\b([\\d,]+)\\s+reviews?\\b/i); return m ? m[1].replace(/,/g, '') : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "amenities",
            "selector": "(() => { const text = document.body.innerText.replace(/\\n+/g, '\\n'); const start = text.indexOf('What this place offers'); if (start < 0) return ''; let end = text.indexOf('Show all', start); if (end < 0) end = text.indexOf('5 nights', start); if (end < 0) end = Math.min(text.length, start + 1000); return text.slice(start + 'What this place offers'.length, end).split('\\n').map(s => s.trim()).filter(Boolean).join(' '); })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "sleeping_arrangements",
            "selector": "(() => { const text = document.body.innerText.replace(/\\n+/g, '\\n'); const start = text.indexOf('Where you’ll sleep') >= 0 ? text.indexOf('Where you’ll sleep') : text.indexOf(\"Where you'll sleep\"); if (start < 0) return ''; let end = text.indexOf('What this place offers', start); if (end < 0) end = Math.min(text.length, start + 500); return text.slice(start, end).replace(/Where you.?ll sleep/i, '').split('\\n').map(s => s.trim()).filter(Boolean).join(' '); })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "hosted_by",
            "selector": "(() => { const text = document.body.innerText; const m = text.match(/Hosted by\\s+([^\\n]+?)(?:\\s+Superhost|\\s+\\d+\\s+years hosting|\\n|$)/i); return m ? m[1].trim() : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "response_rate",
            "selector": "(() => { const text = document.body.innerText; const m = text.match(/(\\d+%)\\s+response rate/i) || text.match(/Response rate\\s*(\\d+%)/i); return m ? m[1] : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "image_1",
            "selector": "(() => { const meta = document.querySelector('meta[property=\"og:image\"]')?.content; const imgs = Array.from(document.images).map(i => i.currentSrc || i.src).filter(src => /muscache\\.com\\/im\\/pictures/i.test(src)); return meta || imgs[0] || ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "image_2",
            "selector": "(() => { const imgs = Array.from(new Set(Array.from(document.images).map(i => i.currentSrc || i.src).filter(src => /muscache\\.com\\/im\\/pictures/i.test(src)))); return imgs[1] || imgs[0] || document.querySelector('meta[property=\"og:image\"]')?.content || ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "image_3",
            "selector": "(() => { const imgs = Array.from(new Set(Array.from(document.images).map(i => i.currentSrc || i.src).filter(src => /muscache\\.com\\/im\\/pictures/i.test(src)))); return imgs[2] || imgs[1] || imgs[0] || document.querySelector('meta[property=\"og:image\"]')?.content || ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "current_time",
            "selector": "new Date().toISOString()",
            "attribute": "text",
            "isJs": true
          }
        ]
      }
    },
    {
      "block_id": "loop-continue-1",
      "block_type": "process",
      "title": "Loop Continue",
      "description": "Continue multi-input loop",
      "position_x": 1800,
      "position_y": 520,
      "config": {}
    }
  ],
  "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": "wait-for-element-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "wait-for-element-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": "text-contains-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "text-contains-1",
      "from_connector_id": "true",
      "to_block_id": "loop-continue-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "text-contains-1",
      "from_connector_id": "false",
      "to_block_id": "text-contains-2",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "text-contains-2",
      "from_connector_id": "true",
      "to_block_id": "loop-continue-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "text-contains-2",
      "from_connector_id": "false",
      "to_block_id": "structured-export-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": 116,
      "width": 1328,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "navigate-1",
          "wait-for-page-load-1",
          "wait-for-element-1",
          "sleep-1"
        ]
      }
    },
    {
      "id": "group-pagination",
      "element_type": "group",
      "title": "Pagination Loop",
      "color": "#ff832b",
      "position_x": 1392,
      "position_y": 116,
      "width": 656,
      "height": 596,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "text-contains-1",
          "text-contains-2",
          "loop-continue-1"
        ]
      }
    },
    {
      "id": "group-extract",
      "element_type": "group",
      "title": "Data Extraction",
      "color": "#42be65",
      "position_x": 1896,
      "position_y": 696,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "structured-export-1"
        ]
      }
    },
    {
      "id": "note-overview",
      "element_type": "note",
      "title": "Overview",
      "content": "Equivalent UScraper template for the Octoparse Airbnb Room Details Scraper. Scrapes Airbnb room/detail pages, one CSV row per successfully loaded room URL, extracting page URL, title, location, guests, bedrooms, beds, baths, price, rating, reviews, amenities, sleeping arrangements, host, response rate if visible, first image URLs, and current scrape time. Navigation strategy: multi-URL detail-page loop using navigate.urls[] + loop-continue, so every supplied Airbnb room URL is visited. Error/placeholder Airbnb pages such as “Oops!” or “Stay tuned” are skipped to avoid blank garbage rows. Airbnb may use anti-bot controls, dynamic rendering, noindex pages, stale room URLs, or locale/layout changes; this template uses semantic headings, meta tags, body text regex, and JS columns as a best-effort approach.",
      "color": "#f1c21b",
      "position_x": 80,
      "position_y": 20,
      "width": 480,
      "height": 160,
      "z_index": 22,
      "data": {}
    },
    {
      "id": "note-block-text-contains-1",
      "element_type": "note",
      "title": "Note: Text Contains",
      "content": "Condition block: checks `h1`. True / False branches control which path runs next. Keep enough space between branches so both connector lines are visible.",
      "color": "#ee5396",
      "position_x": 1664,
      "position_y": 200,
      "width": 340,
      "height": 131,
      "z_index": 22,
      "data": {
        "block_id": "text-contains-1"
      }
    },
    {
      "id": "note-block-text-contains-2",
      "element_type": "note",
      "title": "Note: Text Contains",
      "content": "Condition block: checks `h1`. True / False branches control which path runs next. Keep enough space between branches so both connector lines are visible.",
      "color": "#ee5396",
      "position_x": 1664,
      "position_y": 500,
      "width": 340,
      "height": 131,
      "z_index": 22,
      "data": {
        "block_id": "text-contains-2"
      }
    },
    {
      "id": "note-block-structured-export-1",
      "element_type": "note",
      "title": "Note: Structured Export",
      "content": "Structured export with JS columns (page_url, title, location, number_of_guests, number_of_bedrooms). These selectors are fragile — update if the site layout changes.",
      "color": "#ee5396",
      "position_x": 2168,
      "position_y": 780,
      "width": 340,
      "height": 135,
      "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": 2000,
      "position_y": 500,
      "width": 340,
      "height": 123,
      "z_index": 22,
      "data": {
        "block_id": "loop-continue-1"
      }
    }
  ]
}