{
  "version": "1.0.0",
  "exported_at": "2026-06-01T02:45:00.000Z",
  "project": {
    "name": "Baseconnect Company Info Scraper",
    "description": "Extracts company profile information from Baseconnect company detail URLs, including company name, detail URL, industry, listing market, summary, address, hiring count, employee count, update date, corporate number, securities number, founding date, listing date, capital, revenue, and revenue growth. This template uses multi-URL navigation with loop-continue; replace or extend the sample URLs in the Navigate block to scrape more Baseconnect company pages. Some fields such as employee count may be unavailable or shown as 情報あり unless visible in the browser or available with Baseconnect/Musubu access.",
    "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": 220,
      "config": {
        "urls": [
          "https://baseconnect.in/companies/64f5357b-2090-4b31-af31-8cefb075753a",
          "https://baseconnect.in/companies/7e8d00f2-c3d8-4cd2-bccc-f42ebb9eb840",
          "https://baseconnect.in/companies/fb7905e9-ed90-4509-a61f-1aced6946b7c"
        ],
        "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": 220,
      "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": 220,
      "config": {
        "selector": "main h1",
        "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": 220,
      "config": {
        "rowSelector": "main",
        "fileName": "baseconnect-company-info-scraper.csv",
        "saveLocation": "C:\\Users\\theskd\\Documents\\UScraper\\templates",
        "includeHeaders": true,
        "fileMode": "append",
        "columns": [
          {
            "name": "company_name",
            "selector": "(() => { const h = ROW.querySelector('h1'); if (!h) return ''; return (h.textContent || '').replace(/（.*?）/g, '').trim(); })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "detail_page_url",
            "selector": "(() => ROW.ownerDocument.location.href)()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "industry",
            "selector": "(() => { const items = Array.from(ROW.querySelectorAll('ul[aria-label=\"業界一覧\"] span')).map(e => (e.textContent || '').trim()).filter(Boolean); return Array.from(new Set(items)).join(' | '); })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "listing_place",
            "selector": "(() => { const label = '上場市場'; const norm = s => (s || '').replace(/\\s+/g, ' ').trim(); const exact = Array.from(ROW.querySelectorAll('*')).find(e => norm(e.textContent) === label); if (exact && exact.nextElementSibling) return norm(exact.nextElementSibling.textContent); if (exact && exact.parentElement) { const v = norm(exact.parentElement.textContent).replace(label, '').trim(); if (v) return v; } const h = norm(ROW.querySelector('h1')?.textContent || ''); const m = h.match(/\\/\\s*([^）]+)）/); return m ? m[1].trim() : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "summary",
            "selector": "(() => { const header = ROW.querySelector('section[aria-label=\"企業詳細ヘッダー\"] h2'); return (header?.textContent || '').replace(/\\s+/g, ' ').trim(); })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "description",
            "selector": "(() => { const header = ROW.querySelector('section[aria-label=\"企業詳細ヘッダー\"] h2'); const container = header?.parentElement; if (!container) return ''; const texts = Array.from(container.querySelectorAll('button div, p')).map(e => (e.textContent || '').replace(/企業情報を購入する/g, '').replace(/\\s+/g, ' ').trim()).filter(t => t && !t.includes('法人番号') && !t.includes('証券番号')); return texts[0] || ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "address",
            "selector": "(() => { const norm = s => (s || '').replace(/\\s+/g, ' ').trim(); const labels = ['住所（登記住所）', '住所']; for (const label of labels) { const exact = Array.from(ROW.querySelectorAll('*')).find(e => norm(e.textContent) === label); if (exact) { if (exact.nextElementSibling) { const v = norm(exact.nextElementSibling.textContent); if (v) return v; } const p = exact.parentElement; if (p) { const v = norm(p.textContent).replace(label, '').trim(); if (v) return v; } } const nodes = Array.from(ROW.querySelectorAll('p,li,div,dd')); for (const n of nodes) { const t = norm(n.textContent); if (t.startsWith(label) && t.length < 160) { const v = t.replace(label, '').trim(); if (v) return v; } } } const body = norm(ROW.innerText); const m = body.match(/住所（登記住所）\\s*(?:〒[0-9\\-]+\\s*)?([^\\n]+?)(?:\\s*企業情報を購入する|\\s*業界・地域内順位|$)/); return m ? m[1].trim() : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "new_graduate_hires",
            "selector": "(() => { const label = '新卒採用人数'; const norm = s => (s || '').replace(/\\s+/g, ' ').trim(); const exact = Array.from(ROW.querySelectorAll('*')).find(e => norm(e.textContent) === label); if (exact && exact.nextElementSibling) return norm(exact.nextElementSibling.textContent); if (exact && exact.parentElement) { const v = norm(exact.parentElement.textContent).replace(label, '').trim(); if (v) return v; } for (const n of Array.from(ROW.querySelectorAll('p,li,div,dd'))) { const t = norm(n.textContent); if (t.startsWith(label) && t.length < 80) return t.replace(label, '').trim(); } return ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "employee_count",
            "selector": "(() => { const label = '従業員数'; const norm = s => (s || '').replace(/\\s+/g, ' ').trim(); const exact = Array.from(ROW.querySelectorAll('*')).find(e => norm(e.textContent) === label); if (exact && exact.nextElementSibling) return norm(exact.nextElementSibling.textContent); if (exact && exact.parentElement) { const v = norm(exact.parentElement.textContent).replace(label, '').trim(); if (v) return v; } for (const n of Array.from(ROW.querySelectorAll('p,li,div,dd'))) { const t = norm(n.textContent); if (t.startsWith(label) && t.length < 80 && !t.includes('従業員増加率')) return t.replace(label, '').trim(); } return ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "updated_date",
            "selector": "(() => { const norm = s => (s || '').replace(/\\s+/g, ' ').trim(); const p = Array.from(ROW.querySelectorAll('section[aria-label*=\"最終更新日\"] p')).find(e => norm(e.textContent).includes('最終更新日')); if (p) return norm(p.textContent).replace('最終更新日', '').trim(); const m = norm(ROW.innerText).match(/最終更新日\\s*([0-9０-９]{4}年[0-9０-９]{1,2}月[0-9０-９]{1,2}日)/); return m ? m[1] : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "corporate_number",
            "selector": "(() => { const norm = s => (s || '').replace(/\\s+/g, ' ').trim(); const p = Array.from(ROW.querySelectorAll('section[aria-label*=\"法人番号\"] p')).find(e => norm(e.textContent).includes('法人番号')); if (p) return norm(p.textContent).replace('法人番号', '').trim(); const m = norm(ROW.innerText).match(/法人番号\\s*([0-9]+)/); return m ? m[1] : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "securities_number",
            "selector": "(() => { const norm = s => (s || '').replace(/\\s+/g, ' ').trim(); const p = Array.from(ROW.querySelectorAll('section[aria-label*=\"証券番号\"] p')).find(e => norm(e.textContent).includes('証券番号')); if (p) return norm(p.textContent).replace('証券番号', '').trim(); const m = norm(ROW.innerText).match(/証券番号\\s*([0-9]+)/); return m ? m[1] : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "founded_date",
            "selector": "(() => { const label = '設立年月'; const norm = s => (s || '').replace(/\\s+/g, ' ').trim(); const exact = Array.from(ROW.querySelectorAll('*')).find(e => norm(e.textContent) === label); if (exact && exact.nextElementSibling) return norm(exact.nextElementSibling.textContent); if (exact && exact.parentElement) { const v = norm(exact.parentElement.textContent).replace(label, '').trim(); if (v) return v; } for (const n of Array.from(ROW.querySelectorAll('p,li,div,dd'))) { const t = norm(n.textContent); if (t.startsWith(label) && t.length < 80) return t.replace(label, '').trim(); } return ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "listed_date",
            "selector": "(() => { const label = '上場日'; const norm = s => (s || '').replace(/\\s+/g, ' ').trim(); const exact = Array.from(ROW.querySelectorAll('*')).find(e => norm(e.textContent) === label); if (exact && exact.nextElementSibling) return norm(exact.nextElementSibling.textContent); if (exact && exact.parentElement) { const v = norm(exact.parentElement.textContent).replace(label, '').trim(); if (v) return v; } for (const n of Array.from(ROW.querySelectorAll('p,li,div,dd'))) { const t = norm(n.textContent); if (t.startsWith(label) && t.length < 80) return t.replace(label, '').trim(); } return ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "capital",
            "selector": "(() => { const label = '資本金'; const norm = s => (s || '').replace(/\\s+/g, ' ').trim(); const exact = Array.from(ROW.querySelectorAll('*')).find(e => norm(e.textContent) === label); if (exact && exact.nextElementSibling) return norm(exact.nextElementSibling.textContent); if (exact && exact.parentElement) { const v = norm(exact.parentElement.textContent).replace(label, '').trim(); if (v) return v; } for (const n of Array.from(ROW.querySelectorAll('p,li,div,dd'))) { const t = norm(n.textContent); if (t.startsWith(label) && t.length < 80) return t.replace(label, '').trim(); } return ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "revenue",
            "selector": "(() => { const label = '売上高'; const norm = s => (s || '').replace(/\\s+/g, ' ').trim(); const exact = Array.from(ROW.querySelectorAll('*')).find(e => norm(e.textContent) === label); if (exact && exact.nextElementSibling) return norm(exact.nextElementSibling.textContent); if (exact && exact.parentElement) { const v = norm(exact.parentElement.textContent).replace(label, '').trim(); if (v && !v.startsWith('増加率')) return v; } for (const n of Array.from(ROW.querySelectorAll('p,li,div,dd'))) { const t = norm(n.textContent); if (t.startsWith(label) && !t.startsWith('売上高増加率') && t.length < 100) return t.replace(label, '').trim(); } return ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "revenue_growth_rate",
            "selector": "(() => { const norm = s => (s || '').replace(/\\s+/g, ' ').trim(); const text = norm(ROW.innerText); const m = text.match(/売上高増加率\\s*(?:（[^）]*）\\s*)?([\\-−~〜ー0-9０-９]+%)/); if (m) return m[1].trim(); const label = '売上高増加率'; const exact = Array.from(ROW.querySelectorAll('*')).find(e => norm(e.textContent) === label || norm(e.textContent).startsWith(label)); if (exact && exact.nextElementSibling) { const v = norm(exact.nextElementSibling.textContent); const m2 = v.match(/[\\-−~〜ー0-9０-９]+%/); return m2 ? m2[0] : v; } return ''; })()",
            "attribute": "text",
            "isJs": true
          }
        ]
      }
    },
    {
      "block_id": "loop-continue-1",
      "block_type": "process",
      "title": "Loop Continue",
      "description": "Continue multi-input loop",
      "position_x": 1560,
      "position_y": 220,
      "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": "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": 1040,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "navigate-1",
          "wait-for-page-load-1",
          "wait-for-element-1"
        ]
      }
    },
    {
      "id": "group-extract",
      "element_type": "group",
      "title": "Data Extraction",
      "color": "#42be65",
      "position_x": 1128,
      "position_y": 116,
      "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": 116,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "loop-continue-1"
        ]
      }
    },
    {
      "id": "note-overview",
      "element_type": "note",
      "title": "Overview",
      "content": "Extracts company profile information from Baseconnect company detail URLs, including company name, detail URL, industry, listing market, summary, address, hiring count, employee count, update date, corporate number, securities number, founding date, listing date, capital, revenue, and revenue growth. This template uses multi-URL navigation with loop-continue; replace or extend the sample URLs in the Navigate block to scrape more Baseconnect company pages. Some fields such as employee count may be unavailable or shown as 情報あり unless visible in the browser or available with Baseconnect/Musubu access.",
      "color": "#f1c21b",
      "position_x": 80,
      "position_y": 20,
      "width": 480,
      "height": 160,
      "z_index": 22,
      "data": {}
    },
    {
      "id": "note-block-navigate-1",
      "element_type": "note",
      "title": "Note: Navigate",
      "content": "Multi-URL loop over 3 pages. Pair with loop-continue at the end of each iteration.",
      "color": "#ee5396",
      "position_x": 320,
      "position_y": 200,
      "width": 328,
      "height": 107,
      "z_index": 22,
      "data": {
        "block_id": "navigate-1"
      }
    },
    {
      "id": "note-block-structured-export-1",
      "element_type": "note",
      "title": "Note: Structured Export",
      "content": "Structured export with JS columns (company_name, detail_page_url, industry, listing_place, summary). These selectors are fragile — update if the site layout changes.",
      "color": "#ee5396",
      "position_x": 1400,
      "position_y": 200,
      "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": 1760,
      "position_y": 200,
      "width": 340,
      "height": 123,
      "z_index": 22,
      "data": {
        "block_id": "loop-continue-1"
      }
    }
  ]
}