{
  "version": "1.0.0",
  "exported_at": "2026-06-02T00:00:00.000Z",
  "project": {
    "name": "Gaitame Forex Rates Scraper",
    "description": "Extracts Gaitame forex rate/swap rows from https://www.gaitame.com/markets/rate/, including currency pair URL, pair name, Bid, Spread, Ask, Change, High, Low, buy/sell swap, required margin, and field value. This is a single-page rate-board scrape with no pagination. The template normalizes the live rate table and related same-origin swap/margin pages into a generated table before CSV export.",
    "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": {
        "url": "https://www.gaitame.com/markets/rate/",
        "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": "sleep-1",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 840,
      "position_y": 220,
      "config": {
        "duration": 3
      }
    },
    {
      "block_id": "inject-javascript-1",
      "block_type": "process",
      "title": "Inject JavaScript",
      "description": "Run custom JavaScript on page",
      "position_x": 1200,
      "position_y": 220,
      "config": {
        "waitForCompletion": true,
        "timeout": 90,
        "jsCode": "(async()=>{const rootId='uscraper-gaitame-rate-export';document.getElementById(rootId)?.remove();const root=document.createElement('div');root.id=rootId;root.style.cssText='display:block!important;position:relative;z-index:2147483647;background:#fff;color:#000;padding:8px;';root.innerHTML='<table><thead><tr><th>通貨ペア_URL</th><th>通貨ペア</th><th>Bid</th><th>Spread</th><th>Ask</th><th>Change</th><th>High</th><th>Low</th><th>買い_スワップ</th><th>売り_スワップ</th><th>必要保証金</th><th>フィールド</th></tr></thead><tbody></tbody></table>';document.body.prepend(root);const tbody=root.querySelector('tbody');const clean=v=>String(v==null?'':v).replace(/\\s+/g,' ').trim();const isNum=v=>/^[-+]?\\d+(?:,\\d{3})*(?:\\.\\d+)?$/.test(clean(v));const numVal=v=>Number(clean(v).replace(/,/g,''));const codeFromUrl=u=>{const m=String(u||'').match(/\\/markets\\/chart\\/([^\\/]+)\\.html/i);return m?m[1].toUpperCase():''};const jp={'米ドル':'USD','ドル':'USD','ユーロ':'EUR','ポンド':'GBP','豪ドル':'AUD','NZドル':'NZD','ＮＺドル':'NZD','カナダドル':'CAD','スイスフラン':'CHF','スイス':'CHF','円':'JPY','日本円':'JPY','トルコリラ':'TRY','南アランド':'ZAR','南アフリカランド':'ZAR','メキシコペソ':'MXN','人民元':'CNH','香港ドル':'HKD','シンガポールドル':'SGD','スウェーデン':'SEK','スウェーデンクローナ':'SEK','ノルウェー':'NOK','ノルウェークローネ':'NOK','チェココルナ':'CZK','ポーランドズロチ':'PLN','ハンガリーフォリント':'HUF'};const codeFromText=t=>{t=clean(t);let m=t.match(/\\b(USD|EUR|GBP|AUD|NZD|CAD|CHF|JPY|TRY|ZAR|MXN|CNH|HKD|SGD|SEK|NOK|CZK|PLN|HUF)\\s*[\\/／-]?\\s*(USD|EUR|GBP|AUD|NZD|CAD|CHF|JPY|TRY|ZAR|MXN|CNH|HKD|SGD|SEK|NOK|CZK|PLN|HUF)\\b/i);if(m)return(m[1]+m[2]).toUpperCase();m=t.match(/\\b(USD|EUR|GBP|AUD|NZD|CAD|CHF|JPY|TRY|ZAR|MXN|CNH|HKD|SGD|SEK|NOK|CZK|PLN|HUF){2}\\b/i);if(m&&m[0].length===6)return m[0].toUpperCase();const parts=t.split(/[\\/／]/).map(clean);if(parts.length>=2){const a=Object.keys(jp).find(k=>parts[0].includes(k));const b=Object.keys(jp).find(k=>parts[1].includes(k));if(a&&b)return jp[a]+jp[b]}return''};const store=new Map();const pairMap=new Map();document.querySelectorAll('a[href*=\"/markets/chart/\"][href$=\".html\"]').forEach(a=>{const url=new URL(a.getAttribute('href')||'',location.href).href;const code=codeFromUrl(url);const pair=clean(a.textContent);if(code&&pair&&!pairMap.has(code))pairMap.set(code,{code,url,pair})});const merge=r=>{const code=clean(r.code)||codeFromUrl(r.url)||codeFromText(r.pair);if(!code&&!r.url&&!r.pair)return;const base=pairMap.get(code)||{};const key=code||r.url||r.pair;const prev=store.get(key)||{};const out={code:code||prev.code||base.code||'',url:clean(r.url)||prev.url||base.url||(code?`https://www.gaitame.com/markets/chart/${code.toLowerCase()}.html`:''),pair:clean(r.pair)||prev.pair||base.pair||'',bid:clean(r.bid)||prev.bid||'',spread:clean(r.spread)||prev.spread||'',ask:clean(r.ask)||prev.ask||'',change:clean(r.change)||prev.change||'',high:clean(r.high)||prev.high||'',low:clean(r.low)||prev.low||'',buySwap:clean(r.buySwap)||prev.buySwap||'',sellSwap:clean(r.sellSwap)||prev.sellSwap||'',margin:clean(r.margin)||prev.margin||'',field:clean(r.field)||prev.field||''};store.set(key,out)};pairMap.forEach(v=>merge(v));const hval=(headers,cells,pats)=>{const i=headers.findIndex(h=>pats.some(p=>p.test(h)));return i>=0?clean(cells[i]):''};const findPair=cells=>{const joined=cells.join(' ');let code=codeFromText(joined);if(code&&pairMap.has(code))return pairMap.get(code);for(const [c,p] of pairMap){if(joined.includes(p.pair)||joined.includes(c)||joined.toLowerCase().includes(c.toLowerCase()))return p}return code?{code,url:`https://www.gaitame.com/markets/chart/${code.toLowerCase()}.html`,pair:''}:{}};const parseTables=doc=>{doc.querySelectorAll('table').forEach(table=>{const tableText=clean(table.textContent);let headers=Array.from(table.querySelectorAll('thead th,thead td')).map(e=>clean(e.textContent));const trs=Array.from(table.querySelectorAll('tr'));if(!headers.length&&trs.length)headers=Array.from(trs[0].querySelectorAll('th,td')).map(e=>clean(e.textContent));const isRate=/(Bid|Ask|Spread|スプレッド|高値|安値|前日比)/i.test(tableText);const isSwap=/(スワップ|swap|買い|売り)/i.test(tableText);const isMargin=/(必要保証金|保証金|証拠金|取引単位|法人)/i.test(tableText);trs.forEach(tr=>{const cells=Array.from(tr.querySelectorAll('th,td')).map(e=>clean(e.textContent)).filter(Boolean);if(cells.length<2)return;const link=tr.querySelector('a[href*=\"/markets/chart/\"][href$=\".html\"]');let p=link?{code:codeFromUrl(link.href),url:new URL(link.getAttribute('href')||'',location.href).href,pair:clean(link.textContent)}:findPair(cells);if(!p.code&&!p.url&&!p.pair)return;const nums=cells.filter(isNum);let r={code:p.code,url:p.url,pair:p.pair};if(isRate){r.bid=hval(headers,cells,[/^Bid$/i,/売値/])||nums[0]||'';r.spread=hval(headers,cells,[/Spread/i,/スプレッド/])||nums[1]||'';r.ask=hval(headers,cells,[/^Ask$/i,/買値/])||nums[2]||'';r.change=hval(headers,cells,[/Change/i,/前日比/,/騰落/])||nums[3]||'';r.high=hval(headers,cells,[/High/i,/高値/])||nums[4]||'';r.low=hval(headers,cells,[/Low/i,/安値/])||nums[5]||''}if(isSwap){let buy=hval(headers,cells,[/買.*スワップ/,/買い/,/買/,/Buy/i]);let sell=hval(headers,cells,[/売.*スワップ/,/売り/,/売/,/Sell/i]);if(!buy||!sell){const signed=nums.filter(n=>/^[-+]/.test(n));if(signed.length>=2){buy=buy||signed[0];sell=sell||signed[1]}else if(nums.length>=2){buy=buy||nums[nums.length-2];sell=sell||nums[nums.length-1]}}r.buySwap=buy;r.sellSwap=sell}if(isMargin){let margin=hval(headers,cells,[/必要保証金/,/保証金/,/証拠金/,/個人/,/Margin/i]);let field=hval(headers,cells,[/フィールド/,/法人/,/取引単位/,/Lot/i,/単位/]);const big=nums.filter(n=>Math.abs(numVal(n))>=100);if(!margin&&big.length)margin=big[0];if(!field&&big.length>1)field=big[1];r.margin=margin;r.field=field}merge(r)})})};parseTables(document);const sleep=ms=>new Promise(r=>setTimeout(r,ms));const loadFrame=src=>new Promise(resolve=>{const f=document.createElement('iframe');f.style.display='none';let done=false;const finish=async()=>{if(done)return;done=true;await sleep(2500);try{parseTables(f.contentDocument)}catch(e){}resolve();setTimeout(()=>f.remove(),1000)};f.onload=finish;f.src=new URL(src,location.href).href;document.body.appendChild(f);setTimeout(finish,9000)});for(const src of ['/markets/swap/','/markets/swap.html','/markets/swap-point/','/markets/margin/','/markets/margin.html','/service/fx/margin/','/service/fx/currency-pair/'])await loadFrame(src);const tbodyRows=Array.from(store.values()).filter(r=>r.url||r.pair).sort((a,b)=>(a.url||'').localeCompare(b.url||''));tbody.innerHTML='';tbodyRows.forEach(r=>{const tr=document.createElement('tr');['url','pair','bid','spread','ask','change','high','low','buySwap','sellSwap','margin','field'].forEach(k=>{const td=document.createElement('td');td.textContent=clean(r[k]);tr.appendChild(td)});tbody.appendChild(tr)})})();"
      }
    },
    {
      "block_id": "wait-for-element-1",
      "block_type": "process",
      "title": "Wait for Element",
      "description": "Wait until element appears",
      "position_x": 1560,
      "position_y": 220,
      "config": {
        "selector": "#uscraper-gaitame-rate-export tbody tr",
        "timeout": 30,
        "visible": false
      }
    },
    {
      "block_id": "structured-export-1",
      "block_type": "process",
      "title": "Structured Export",
      "description": "Export data with custom columns",
      "position_x": 1920,
      "position_y": 220,
      "config": {
        "rowSelector": "#uscraper-gaitame-rate-export tbody tr",
        "fileName": "gaitame-forex-rates-scraper.csv",
        "saveLocation": "C:\\Users\\theskd\\Documents\\UScraper\\templates",
        "includeHeaders": true,
        "fileMode": "create",
        "columns": [
          {
            "name": "通貨ペア_URL",
            "selector": "td:nth-child(1)",
            "attribute": "text"
          },
          {
            "name": "通貨ペア",
            "selector": "td:nth-child(2)",
            "attribute": "text"
          },
          {
            "name": "Bid",
            "selector": "td:nth-child(3)",
            "attribute": "text"
          },
          {
            "name": "Spread",
            "selector": "td:nth-child(4)",
            "attribute": "text"
          },
          {
            "name": "Ask",
            "selector": "td:nth-child(5)",
            "attribute": "text"
          },
          {
            "name": "Change",
            "selector": "td:nth-child(6)",
            "attribute": "text"
          },
          {
            "name": "High",
            "selector": "td:nth-child(7)",
            "attribute": "text"
          },
          {
            "name": "Low",
            "selector": "td:nth-child(8)",
            "attribute": "text"
          },
          {
            "name": "買い_スワップ",
            "selector": "td:nth-child(9)",
            "attribute": "text"
          },
          {
            "name": "売り_スワップ",
            "selector": "td:nth-child(10)",
            "attribute": "text"
          },
          {
            "name": "必要保証金",
            "selector": "td:nth-child(11)",
            "attribute": "text"
          },
          {
            "name": "フィールド",
            "selector": "td:nth-child(12)",
            "attribute": "text"
          }
        ]
      }
    }
  ],
  "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": "inject-javascript-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "inject-javascript-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"
    }
  ],
  "canvas_elements": [
    {
      "id": "group-load",
      "element_type": "group",
      "title": "Page Load",
      "color": "#08bdba",
      "position_x": 48,
      "position_y": 116,
      "width": 1760,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "navigate-1",
          "wait-for-page-load-1",
          "sleep-1",
          "wait-for-element-1"
        ]
      }
    },
    {
      "id": "group-interaction",
      "element_type": "group",
      "title": "Interaction",
      "color": "#a56eff",
      "position_x": 1128,
      "position_y": 116,
      "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": 1848,
      "position_y": 116,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "structured-export-1"
        ]
      }
    },
    {
      "id": "note-overview",
      "element_type": "note",
      "title": "Overview",
      "content": "Extracts Gaitame forex rate/swap rows from https://www.gaitame.com/markets/rate/, including currency pair URL, pair name, Bid, Spread, Ask, Change, High, Low, buy/sell swap, required margin, and field value. This is a single-page rate-board scrape with no pagination. The template normalizes the live rate table and related same-origin swap/margin pages into a generated table before CSV export.",
      "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 rootId='uscraper-gaitame-rate-export';document.getElementById(rootId)?.remove();con...` Verify in browser if results are empty.",
      "color": "#ee5396",
      "position_x": 1400,
      "position_y": 200,
      "width": 340,
      "height": 140,
      "z_index": 22,
      "data": {
        "block_id": "inject-javascript-1"
      }
    },
    {
      "id": "note-block-structured-export-1",
      "element_type": "note",
      "title": "Note: Structured Export",
      "content": "Extracts rows matching `#uscraper-gaitame-rate-export tbody tr`. Confirm row count > 0 before running at scale.",
      "color": "#ee5396",
      "position_x": 2120,
      "position_y": 200,
      "width": 340,
      "height": 117,
      "z_index": 22,
      "data": {
        "block_id": "structured-export-1"
      }
    }
  ]
}