{
  "version": "1.0.0",
  "exported_at": "2026-06-01T18:25:00.000Z",
  "project": {
    "name": "Houzz URL List Scraper",
    "description": "Best-effort equivalent of the Octoparse Houzz URL List Scraper. Extracts Houzz product search result fields: Original_URL, product_title, price, rating, reviews, img, and Url. The analyzed Houzz product search pages currently show a retired/no-longer-available page and Shop Houzz product URLs redirect to a password/closed-store page. This template therefore checks for product cards first; if products are present it extracts and paginates through enabled Next controls, and if no products are present it writes a fallback row for the input URL with the Houzz unavailable message so the run produces an auditable CSV instead of silently returning zero rows. Multiple input search URLs are processed with navigate.urls[] and loop-continue.",
    "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": 260,
      "config": {
        "urls": [
          "https://www.houzz.com/products/query/bathroom?opc=products",
          "https://www.houzz.com/products/query/Mirrors/nqrwns"
        ],
        "color": "bg-[#4589ff]",
        "tags": [
          "houzz",
          "input-urls"
        ]
      }
    },
    {
      "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": 260,
      "config": {
        "timeout": 30,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "sleep-1",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 840,
      "position_y": 260,
      "config": {
        "duration": 2,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "element-exists-1",
      "block_type": "process",
      "title": "Element Exists",
      "description": "Check if element exists",
      "position_x": 1200,
      "position_y": 260,
      "config": {
        "selector": "[data-component=\"Product Card\"], [data-testid=\"product-card\"], li:has(a[href*=\"/products/\"][href*=\"-prvw-vr-\"]), div:has(> a[href*=\"/products/\"][href*=\"-prvw-vr-\"])",
        "color": "bg-[#ff832b]"
      }
    },
    {
      "block_id": "structured-export-1",
      "block_type": "process",
      "title": "Structured Export",
      "description": "Export data with custom columns",
      "position_x": 1560,
      "position_y": 260,
      "config": {
        "rowSelector": "[data-component=\"Product Card\"], [data-testid=\"product-card\"], li:has(a[href*=\"/products/\"][href*=\"-prvw-vr-\"]), div:has(> a[href*=\"/products/\"][href*=\"-prvw-vr-\"])",
        "fileName": "houzz-url-list-scraper.csv",
        "saveLocation": "C:\\Users\\theskd\\Documents\\UScraper\\templates",
        "includeHeaders": true,
        "fileMode": "append",
        "color": "bg-[#42be65]",
        "columns": [
          {
            "name": "Original_URL",
            "selector": "window.location.href",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "product_title",
            "selector": "(() => { const img = ROW.querySelector('img[alt]'); if (img && img.getAttribute('alt')) return img.getAttribute('alt').trim(); const el = ROW.querySelector('[data-testid*=\"title\"], [class*=\"title\"], h2, h3, a[href*=\"/products/\"], a[href*=\"shophouzz.com/products/\"]'); return el ? el.textContent.trim().replace(/\\s+/g, ' ') : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "price",
            "selector": "(() => { const txt = ROW.innerText || ROW.textContent || ''; const m = txt.match(/\\$\\s?[\\d,]+(?:\\.\\d{2})?/); return m ? m[0].replace(/\\s+/g, '') : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "rating",
            "selector": "(() => { const label = Array.from(ROW.querySelectorAll('[aria-label], [title]')).map(e => e.getAttribute('aria-label') || e.getAttribute('title') || '').find(t => /out of 5|stars?/i.test(t)); const txt = (label || ROW.innerText || ROW.textContent || ''); const m = txt.match(/\\d(?:\\.\\d)?\\s*out\\s*(?:of)?\\s*5\\s*stars?/i) || txt.match(/\\d(?:\\.\\d)?\\s*stars?/i); return m ? m[0].replace(/\\s+/g, ' ').trim() : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "reviews",
            "selector": "(() => { const txt = ROW.innerText || ROW.textContent || ''; const m = txt.match(/(\\d[\\d,]*)\\s*(?:reviews?|ratings?)/i) || txt.match(/\\((\\d[\\d,]*)\\)/); return m ? m[1] : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "img",
            "selector": "(() => { const img = ROW.querySelector('img'); return img ? (img.currentSrc || img.src || img.getAttribute('data-src') || '') : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Url",
            "selector": "(() => { const a = ROW.querySelector('a[href*=\"shophouzz.com/products/\"], a[href*=\"/products/\"][href*=\"-prvw-vr-\"], a[href*=\"/products/\"]'); return a ? a.href : ''; })()",
            "attribute": "text",
            "isJs": true
          }
        ]
      }
    },
    {
      "block_id": "structured-export-2",
      "block_type": "process",
      "title": "Structured Export",
      "description": "Export data with custom columns",
      "position_x": 1560,
      "position_y": 620,
      "config": {
        "rowSelector": "body",
        "fileName": "houzz-url-list-scraper.csv",
        "saveLocation": "C:\\Users\\theskd\\Documents\\UScraper\\templates",
        "includeHeaders": true,
        "fileMode": "append",
        "color": "bg-[#f1c21b]",
        "columns": [
          {
            "name": "Original_URL",
            "selector": "window.location.href",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "product_title",
            "selector": "(() => { const h1 = document.querySelector('h1'); const msg = h1 ? h1.textContent.trim().replace(/\\s+/g, ' ') : (document.body.innerText || '').split('\\n').map(s => s.trim()).filter(Boolean)[0] || 'No product listings found'; return msg; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "price",
            "selector": "''",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "rating",
            "selector": "''",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "reviews",
            "selector": "''",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "img",
            "selector": "''",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Url",
            "selector": "''",
            "attribute": "text",
            "isJs": true
          }
        ]
      }
    },
    {
      "block_id": "element-exists-2",
      "block_type": "process",
      "title": "Element Exists",
      "description": "Check if element exists",
      "position_x": 1920,
      "position_y": 260,
      "config": {
        "selector": "a[rel=\"next\"], a[aria-label*=\"Next\"]:not([aria-disabled=\"true\"]):not(.disabled), button[aria-label*=\"Next\"]:not([disabled]), .pagination a.next:not(.disabled), a.hz-pagination-link--next:not(.disabled)",
        "color": "bg-[#ff832b]"
      }
    },
    {
      "block_id": "click-1",
      "block_type": "process",
      "title": "Click",
      "description": "Click on element",
      "position_x": 2280,
      "position_y": 620,
      "config": {
        "selector": "a[rel=\"next\"], a[aria-label*=\"Next\"]:not([aria-disabled=\"true\"]):not(.disabled), button[aria-label*=\"Next\"]:not([disabled]), .pagination a.next:not(.disabled), a.hz-pagination-link--next:not(.disabled)",
        "timeout": 10,
        "color": "bg-[#a56eff]"
      }
    },
    {
      "block_id": "wait-for-page-load-2",
      "block_type": "process",
      "title": "Wait for Page Load",
      "description": "Wait for page to finish loading",
      "position_x": 2640,
      "position_y": 620,
      "config": {
        "timeout": 30,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "sleep-2",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 3000,
      "position_y": 620,
      "config": {
        "duration": 2,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "loop-continue-1",
      "block_type": "process",
      "title": "Loop Continue",
      "description": "Continue multi-input loop",
      "position_x": 1920,
      "position_y": 940,
      "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": "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": "structured-export-2",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "structured-export-1",
      "from_connector_id": "right",
      "to_block_id": "element-exists-2",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "structured-export-2",
      "from_connector_id": "right",
      "to_block_id": "loop-continue-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "element-exists-2",
      "from_connector_id": "true",
      "to_block_id": "click-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "element-exists-2",
      "from_connector_id": "false",
      "to_block_id": "loop-continue-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "click-1",
      "from_connector_id": "right",
      "to_block_id": "wait-for-page-load-2",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "wait-for-page-load-2",
      "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": "element-exists-1",
      "to_connector_id": "left"
    }
  ],
  "canvas_elements": [
    {
      "id": "group-load",
      "element_type": "group",
      "title": "Page Load",
      "color": "#08bdba",
      "position_x": 48,
      "position_y": 156,
      "width": 3200,
      "height": 656,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "navigate-1",
          "wait-for-page-load-1",
          "sleep-1",
          "wait-for-page-load-2",
          "sleep-2"
        ]
      }
    },
    {
      "id": "group-pagination",
      "element_type": "group",
      "title": "Pagination Loop",
      "color": "#ff832b",
      "position_x": 1128,
      "position_y": 156,
      "width": 1400,
      "height": 976,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "element-exists-1",
          "element-exists-2",
          "click-1",
          "loop-continue-1"
        ]
      }
    },
    {
      "id": "group-extract",
      "element_type": "group",
      "title": "Data Extraction",
      "color": "#42be65",
      "position_x": 1488,
      "position_y": 156,
      "width": 380,
      "height": 656,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "structured-export-1",
          "structured-export-2"
        ]
      }
    },
    {
      "id": "note-overview",
      "element_type": "note",
      "title": "Overview",
      "content": "Best-effort equivalent of the Octoparse Houzz URL List Scraper. Extracts Houzz product search result fields: Original_URL, product_title, price, rating, reviews, img, and Url. The analyzed Houzz product search pages currently show a retired/no-longer-available page and Shop Houzz product URLs redirect to a password/closed-store page. This template therefore checks for product cards first; if products are present it extracts and paginates through enabled Next controls, and if no products are present it writes a fallback row for the input URL with the Houzz unavailable message so the run produces an auditable CSV instead of silently returning zero rows. Multiple input search URLs are processed with navigate.urls[] and loop-continue.",
      "color": "#f1c21b",
      "position_x": 80,
      "position_y": 20,
      "width": 480,
      "height": 160,
      "z_index": 22,
      "data": {}
    },
    {
      "id": "note-block-element-exists-1",
      "element_type": "note",
      "title": "Note: Element Exists",
      "content": "Condition block: checks `[data-component=\"Product Card\"], [data-testid=\"product-card\"], li:has(a[href*=\"/products/\"][href*=\"-prvw-vr-\"]), div:has`. True / False branches control which path runs next. Keep enough space between branches so both connector lines are visible.",
      "color": "#ee5396",
      "position_x": 1400,
      "position_y": 240,
      "width": 340,
      "height": 170,
      "z_index": 22,
      "data": {
        "block_id": "element-exists-1"
      }
    },
    {
      "id": "note-block-structured-export-1",
      "element_type": "note",
      "title": "Note: Structured Export",
      "content": "Structured export with JS columns (Original_URL, product_title, price, rating, reviews). These selectors are fragile — update if the site layout changes.",
      "color": "#ee5396",
      "position_x": 1760,
      "position_y": 240,
      "width": 340,
      "height": 131,
      "z_index": 22,
      "data": {
        "block_id": "structured-export-1"
      }
    },
    {
      "id": "note-block-structured-export-2",
      "element_type": "note",
      "title": "Note: Structured Export",
      "content": "Structured export with JS columns (Original_URL, product_title, price, rating, reviews). These selectors are fragile — update if the site layout changes.",
      "color": "#ee5396",
      "position_x": 1760,
      "position_y": 600,
      "width": 340,
      "height": 131,
      "z_index": 22,
      "data": {
        "block_id": "structured-export-2"
      }
    },
    {
      "id": "note-block-element-exists-2",
      "element_type": "note",
      "title": "Note: Element Exists",
      "content": "Condition block: checks `a[rel=\"next\"], a[aria-label*=\"Next\"]:not([aria-disabled=\"true\"]):not(.disabled), button[aria-label*=\"Next\"]:not([disable`. True / False branches control which path runs next. Keep enough space between branches so both connector lines are visible.",
      "color": "#ee5396",
      "position_x": 2120,
      "position_y": 240,
      "width": 340,
      "height": 170,
      "z_index": 22,
      "data": {
        "block_id": "element-exists-2"
      }
    },
    {
      "id": "note-block-click-1",
      "element_type": "note",
      "title": "Note: Click",
      "content": "Pagination click — add waits after this block; the page reloads asynchronously.",
      "color": "#ee5396",
      "position_x": 2480,
      "position_y": 600,
      "width": 316,
      "height": 106,
      "z_index": 22,
      "data": {
        "block_id": "click-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": 2120,
      "position_y": 920,
      "width": 340,
      "height": 123,
      "z_index": 22,
      "data": {
        "block_id": "loop-continue-1"
      }
    }
  ]
}