{
  "version": "1.0.0",
  "exported_at": "2026-06-03T12:45:00.000Z",
  "project": {
    "name": "Suumo Land Listings Scraper",
    "description": "Extracts SUUMO land listing data from the provided SUUMO search-results URL, including title, detail URL, property name, price, location, access, land area, price per tsubo, building coverage/FAR, agency/contact fields, phone number, and original search URL. Pagination is handled with a click-next loop using the Japanese “次へ” pagination link so all available result pages are scraped until no next page remains. Some expired detail pages may return 404, so extraction is performed from the listing/search pages.",
    "color": "bg-[#42be65]",
    "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": {
        "url": "https://suumo.jp/jj/bukken/ichiran/JJ010FJ001/?ar=030&bs=030&ta=14&jspIdFlg=patternShikugun&sc=14101&sc=14102&sc=14103&sc=14104&sc=14105&sc=14106&sc=14107&sc=14108&sc=14109&sc=14110&sc=14111&sc=14112&sc=14113&sc=14114&sc=14115&sc=14116&sc=14117&sc=14118&kb=1&kt=9999999&km=1&tb=0&tt=9999999&ekTjCd=&ekTjNm=&tj=0&kj=9&srch_navi=1",
        "color": "bg-[#4589ff]"
      }
    },
    {
      "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": 30
      }
    },
    {
      "block_id": "wait-for-element-1",
      "block_type": "process",
      "title": "Wait for Element",
      "description": "Wait until element appears",
      "position_x": 840,
      "position_y": 240,
      "config": {
        "selector": ".property_unit",
        "timeout": 30,
        "visible": true
      }
    },
    {
      "block_id": "structured-export-1",
      "block_type": "process",
      "title": "Structured Export",
      "description": "Export data with custom columns",
      "position_x": 1200,
      "position_y": 240,
      "config": {
        "rowSelector": ".property_unit",
        "fileName": "suumo-land-listings-scraper.csv",
        "saveLocation": "C:\\Users\\theskd\\Documents\\UScraper\\templates",
        "includeHeaders": true,
        "fileMode": "append",
        "columns": [
          {
            "name": "区別",
            "selector": "(() => { return '土地'; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "タイトル",
            "selector": "(() => { const h = ROW.querySelector('h2'); return h ? h.innerText.replace(/\\s+/g, ' ').trim() : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "詳細ページ_URL",
            "selector": "(() => { const a = ROW.querySelector('h2 a[href], .property_unit-title a[href], a.js-cassetLinkHref[href]'); return a ? new URL(a.getAttribute('href'), location.href).href : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "物件名",
            "selector": "(() => { const get = (name) => { const dts = Array.from(ROW.querySelectorAll('.dottable-line dt, dt')); const key = name.replace(/\\s/g, ''); const dt = dts.find(el => el.textContent.replace(/\\s/g, '').includes(key)); if (!dt) return ''; const dl = dt.closest('dl') || dt.parentElement; const dd = dl ? dl.querySelector('dd') : null; return dd ? dd.innerText.replace(/\\s+/g, ' ').trim() : ''; }; return get('物件名'); })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "販売価格",
            "selector": "(() => { const get = (name) => { const dts = Array.from(ROW.querySelectorAll('.dottable-line dt, dt')); const key = name.replace(/\\s/g, ''); const dt = dts.find(el => el.textContent.replace(/\\s/g, '').includes(key)); if (!dt) return ''; const dl = dt.closest('dl') || dt.parentElement; const dd = dl ? dl.querySelector('dd') : null; return dd ? dd.innerText.replace(/\\s+/g, ' ').trim() : ''; }; return get('販売価格'); })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "所在地",
            "selector": "(() => { const get = (name) => { const dts = Array.from(ROW.querySelectorAll('.dottable-line dt, dt')); const key = name.replace(/\\s/g, ''); const dt = dts.find(el => el.textContent.replace(/\\s/g, '').includes(key)); if (!dt) return ''; const dl = dt.closest('dl') || dt.parentElement; const dd = dl ? dl.querySelector('dd') : null; return dd ? dd.innerText.replace(/\\s+/g, ' ').trim() : ''; }; return get('所在地'); })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "沿線_駅",
            "selector": "(() => { const get = (name) => { const dts = Array.from(ROW.querySelectorAll('.dottable-line dt, dt')); const key = name.replace(/\\s/g, ''); const dt = dts.find(el => el.textContent.replace(/\\s/g, '').includes(key)); if (!dt) return ''; const dl = dt.closest('dl') || dt.parentElement; const dd = dl ? dl.querySelector('dd') : null; return dd ? dd.innerText.replace(/\\s+/g, ' ').trim() : ''; }; return get('沿線・駅') || get('沿線_駅'); })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "専有面積",
            "selector": "(() => { const get = (name) => { const dts = Array.from(ROW.querySelectorAll('.dottable-line dt, dt')); const key = name.replace(/\\s/g, ''); const dt = dts.find(el => el.textContent.replace(/\\s/g, '').includes(key)); if (!dt) return ''; const dl = dt.closest('dl') || dt.parentElement; const dd = dl ? dl.querySelector('dd') : null; return dd ? dd.innerText.replace(/\\s+/g, ' ').trim() : ''; }; return get('土地面積') || get('専有面積'); })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "間取り",
            "selector": "(() => { const get = (name) => { const dts = Array.from(ROW.querySelectorAll('.dottable-line dt, dt')); const key = name.replace(/\\s/g, ''); const dt = dts.find(el => el.textContent.replace(/\\s/g, '').includes(key)); if (!dt) return ''; const dl = dt.closest('dl') || dt.parentElement; const dd = dl ? dl.querySelector('dd') : null; return dd ? dd.innerText.replace(/\\s+/g, ' ').trim() : ''; }; return get('坪単価') || get('間取り'); })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "バルコニー",
            "selector": "(() => { const get = (name) => { const dts = Array.from(ROW.querySelectorAll('.dottable-line dt, dt')); const key = name.replace(/\\s/g, ''); const dt = dts.find(el => el.textContent.replace(/\\s/g, '').includes(key)); if (!dt) return ''; const dl = dt.closest('dl') || dt.parentElement; const dd = dl ? dl.querySelector('dd') : null; return dd ? dd.innerText.replace(/\\s+/g, ' ').trim() : ''; }; return get('建ぺい率・容積率') || get('建ぺい率') || get('バルコニー'); })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "会員名",
            "selector": "(() => { const phone = (ROW.innerText.match(/0\\d{1,4}-\\d{2,4}-\\d{3,4}|0120-\\d{6}|0\\d{9,10}/) || [])[0]; const lines = ROW.innerText.split(/\\n+/).map(s => s.trim()).filter(Boolean); if (phone) { const i = lines.findIndex(l => l.includes(phone)); for (let j = i - 1; j >= 0; j--) { if (!/担当|TEL|電話|お気軽|資料請求|掲載写真|チェック/.test(lines[j])) return lines[j]; } } const cand = lines.find(l => /(株|（株）|株式会社|有限会社|センター|支店|営業所)/.test(l)); return cand || ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "接客評価コメント",
            "selector": "(() => { const lines = ROW.innerText.split(/\\n+/).map(s => s.trim()).filter(Boolean); const line = lines.find(l => /接客評価|評価コメント|口コミ|レビュー/.test(l)); if (!line) return ''; const m = line.match(/\\d+/); return m ? m[0] : line; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "電話番号",
            "selector": "(() => { const m = ROW.innerText.match(/0\\d{1,4}-\\d{2,4}-\\d{3,4}|0120-\\d{6}|0\\d{9,10}/); return m ? m[0] : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Original_URL",
            "selector": "(() => { const u = new URL(location.href); u.searchParams.delete('page'); return u.href; })()",
            "attribute": "text",
            "isJs": true
          }
        ]
      }
    },
    {
      "block_id": "element-exists-1",
      "block_type": "process",
      "title": "Element Exists",
      "description": "Check if element exists",
      "position_x": 1560,
      "position_y": 240,
      "config": {
        "selector": "//div[contains(@class,'pagination')]//a[normalize-space(.)='次へ']"
      }
    },
    {
      "block_id": "click-1",
      "block_type": "process",
      "title": "Click",
      "description": "Click on element",
      "position_x": 1920,
      "position_y": 240,
      "config": {
        "selector": "//div[contains(@class,'pagination')]//a[normalize-space(.)='次へ']",
        "timeout": 10
      }
    },
    {
      "block_id": "wait-for-page-load-2",
      "block_type": "process",
      "title": "Wait for Page Load",
      "description": "Wait for page to finish loading",
      "position_x": 2280,
      "position_y": 240,
      "config": {
        "timeout": 30
      }
    },
    {
      "block_id": "sleep-1",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 2640,
      "position_y": 240,
      "config": {
        "duration": 1
      }
    },
    {
      "block_id": "wait-for-element-2",
      "block_type": "process",
      "title": "Wait for Element",
      "description": "Wait until element appears",
      "position_x": 3000,
      "position_y": 520,
      "config": {
        "selector": ".property_unit",
        "timeout": 30,
        "visible": true
      }
    },
    {
      "block_id": "end-1",
      "block_type": "output",
      "title": "End",
      "description": "Terminate execution flow",
      "position_x": 1200,
      "position_y": 600,
      "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": "structured-export-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "structured-export-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": "click-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "element-exists-1",
      "from_connector_id": "false",
      "to_block_id": "end-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-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "sleep-1",
      "from_connector_id": "right",
      "to_block_id": "wait-for-element-2",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "wait-for-element-2",
      "from_connector_id": "right",
      "to_block_id": "structured-export-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": 3200,
      "height": 576,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "navigate-1",
          "wait-for-page-load-1",
          "wait-for-element-1",
          "wait-for-page-load-2",
          "sleep-1",
          "wait-for-element-2"
        ]
      }
    },
    {
      "id": "group-extract",
      "element_type": "group",
      "title": "Data Extraction",
      "color": "#42be65",
      "position_x": 1128,
      "position_y": 136,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "structured-export-1"
        ]
      }
    },
    {
      "id": "group-pagination",
      "element_type": "group",
      "title": "Pagination Loop",
      "color": "#ff832b",
      "position_x": 1488,
      "position_y": 136,
      "width": 680,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "element-exists-1",
          "click-1"
        ]
      }
    },
    {
      "id": "group-control",
      "element_type": "group",
      "title": "Control Flow",
      "color": "#8d8d8d",
      "position_x": 1128,
      "position_y": 496,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "end-1"
        ]
      }
    },
    {
      "id": "note-overview",
      "element_type": "note",
      "title": "Overview",
      "content": "Extracts SUUMO land listing data from the provided SUUMO search-results URL, including title, detail URL, property name, price, location, access, land area, price per tsubo, building coverage/FAR, agency/contact fields, phone number, and original search URL. Pagination is handled with a click-next loop using the Japanese “次へ” pagination link so all available result pages are scraped until no next page remains. Some expired detail pages may return 404, so extraction is performed from the listing/search pages.",
      "color": "#f1c21b",
      "position_x": 80,
      "position_y": 20,
      "width": 480,
      "height": 160,
      "z_index": 22,
      "data": {}
    },
    {
      "id": "note-block-structured-export-1",
      "element_type": "note",
      "title": "Note: Structured Export",
      "content": "Structured export with JS columns (区別, タイトル, 詳細ページ_URL, 物件名, 販売価格). These selectors are fragile — update if the site layout changes.",
      "color": "#ee5396",
      "position_x": 1400,
      "position_y": 220,
      "width": 340,
      "height": 124,
      "z_index": 22,
      "data": {
        "block_id": "structured-export-1"
      }
    },
    {
      "id": "note-block-element-exists-1",
      "element_type": "note",
      "title": "Note: Element Exists",
      "content": "Condition block: checks `//div[contains(@class,'pagination')]//a[normalize-space(.)='次へ']`. True / False branches control which path runs next. Keep enough space between branches so both connector lines are visible.",
      "color": "#ee5396",
      "position_x": 1760,
      "position_y": 220,
      "width": 340,
      "height": 151,
      "z_index": 22,
      "data": {
        "block_id": "element-exists-1"
      }
    },
    {
      "id": "note-block-click-1",
      "element_type": "note",
      "title": "Note: Click",
      "content": "Uses XPath `//div[contains(@class,'pagination')]//a[normalize-space(.)='次へ']`. XPath breaks easily if DOM structure changes.",
      "color": "#ee5396",
      "position_x": 2120,
      "position_y": 220,
      "width": 340,
      "height": 121,
      "z_index": 22,
      "data": {
        "block_id": "click-1"
      }
    }
  ]
}