{
  "version": "1.0.0",
  "exported_at": "2026-06-02T17:15:00.000Z",
  "project": {
    "name": "Ekiten Review Scraper",
    "description": "Best-effort UScraper equivalent of the Octoparse Ekiten Review Scraper. Targets public Ekiten shop pages such as https://www.ekiten.jp/shop_1055997/ and extracts review fields: store name, page URL, reviewer, review title, rating, posted date, visit date, budget, review text, likes count, and shop reply. Ekiten served AWS WAF human verification during automated testing. The workflow clicks the WAF Begin button, pauses for manual verification in a visible/persistent browser session, refreshes, then extracts real review rows if available. If WAF remains active, it exports one diagnostic row to the same CSV explaining that review extraction was blocked. Review pagination is handled by detecting/clicking an enabled Japanese Next control; multiple shop URLs can be added to navigate.urls and are advanced with 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": 220,
      "config": {
        "urls": [
          "https://www.ekiten.jp/shop_1055997/"
        ],
        "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": 456,
      "position_y": 220,
      "config": {
        "timeout": 30
      }
    },
    {
      "block_id": "text-contains-1",
      "block_type": "process",
      "title": "Text Contains",
      "description": "Check if page contains text",
      "position_x": 792,
      "position_y": 220,
      "config": {
        "text": "Let's confirm you are human",
        "caseSensitive": false
      }
    },
    {
      "block_id": "click-2",
      "block_type": "process",
      "title": "Click",
      "description": "Click on element",
      "position_x": 1128,
      "position_y": 520,
      "config": {
        "selector": "#amzn-captcha-verify-button",
        "timeout": 10
      }
    },
    {
      "block_id": "sleep-1",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 1464,
      "position_y": 520,
      "config": {
        "duration": 60
      }
    },
    {
      "block_id": "refresh-1",
      "block_type": "process",
      "title": "Refresh",
      "description": "Refresh current page",
      "position_x": 1800,
      "position_y": 520,
      "config": {}
    },
    {
      "block_id": "wait-for-page-load-2",
      "block_type": "process",
      "title": "Wait for Page Load",
      "description": "Wait for page to finish loading",
      "position_x": 2136,
      "position_y": 520,
      "config": {
        "timeout": 30
      }
    },
    {
      "block_id": "text-contains-2",
      "block_type": "process",
      "title": "Text Contains",
      "description": "Check if page contains text",
      "position_x": 2472,
      "position_y": 520,
      "config": {
        "text": "Let's confirm you are human",
        "caseSensitive": false
      }
    },
    {
      "block_id": "inject-javascript-2",
      "block_type": "process",
      "title": "Inject JavaScript",
      "description": "Run custom JavaScript",
      "position_x": 2808,
      "position_y": 520,
      "config": {
        "jsCode": "(() => { document.querySelectorAll('[data-uscraper-review-row],[data-uscraper-next-review-page]').forEach(el=>{ el.removeAttribute('data-uscraper-review-row'); el.removeAttribute('data-uscraper-next-review-page'); }); const row=document.querySelector('#captcha-container') || document.body; const base=(location.href.match(new RegExp('https?://www[.]ekiten[.]jp/shop_[0-9]+/?'))||[location.href.split('?')[0]])[0]; row.setAttribute('data-uscraper-review-row','1'); row.setAttribute('data-uscraper-store-name',''); row.setAttribute('data-uscraper-page-url',base); row.setAttribute('data-uscraper-reviewer',''); row.setAttribute('data-uscraper-review-title','BLOCKED_BY_AWS_WAF_HUMAN_VERIFICATION'); row.setAttribute('data-uscraper-rating',''); row.setAttribute('data-uscraper-posted-date',''); row.setAttribute('data-uscraper-visit-date',''); row.setAttribute('data-uscraper-budget',''); row.setAttribute('data-uscraper-review-text','Ekiten returned an AWS WAF human-verification/CAPTCHA page, so public review rows were not accessible to the automated browser session. Run non-headless and solve the challenge, reuse a verified persistent browser profile, or retry from a trusted network.'); row.setAttribute('data-uscraper-likes-count',''); row.setAttribute('data-uscraper-shop-reply',''); })();",
        "waitForCompletion": true,
        "timeout": 10
      }
    },
    {
      "block_id": "inject-javascript-1",
      "block_type": "process",
      "title": "Inject JavaScript",
      "description": "Run custom JavaScript",
      "position_x": 4824,
      "position_y": 520,
      "config": {
        "jsCode": "(() => { const clean=s=>(s||'').replace(/\\s+/g,' ').trim(); const text=el=>clean(el && (el.innerText||el.textContent)||''); const firstText=(root,sel)=>{ const el=root.querySelector(sel); return el?text(el):''; }; document.querySelectorAll('[data-uscraper-review-row],[data-uscraper-next-review-page]').forEach(el=>{el.removeAttribute('data-uscraper-review-row');el.removeAttribute('data-uscraper-next-review-page');}); if(/confirm you are human|security check|captcha/i.test(document.body.innerText||'')) return; const base=(location.href.match(new RegExp('https?://www[.]ekiten[.]jp/shop_[0-9]+/?'))||[location.href.split('?')[0]])[0]; const store=clean((document.querySelector('h1')||{}).innerText||document.title.replace(/[｜|].*$/,'')); const dateRe=new RegExp('20[0-9]{2}[/年.][0-9]{1,2}[/月.][0-9]{1,2}日?','g'); const candidates=Array.from(document.querySelectorAll('article, li, section, div')).filter(el=>{ const t=text(el); if(t.length<80||t.length>7000) return false; const dates=t.match(dateRe)||[]; if(dates.length<1) return false; const hasReviewWords=/口コミ|レビュー|投稿|利用日|予算|いいね|返信|さん/.test(t); const hasRating=/[1-5][.][0-9]{1,2}|点数|評価/.test(t); return hasReviewWords && hasRating; }); let rows=candidates.filter(el=>!candidates.some(o=>o!==el&&el.contains(o)&&text(o).length>60)); if(rows.length===0) rows=candidates; rows.slice(0,200).forEach(row=>{ const t=text(row); const lines=(row.innerText||row.textContent||'').split('\\n').map(clean).filter(Boolean); const dates=t.match(dateRe)||[]; const posted=dates[0]||''; const used=dates[1]||''; const budget=((t.match(/￥[ ]?[0-9,]+/)||[])[0]||''); const rating=clean(firstText(row,'[class*=rating],[class*=score],[class*=star]') || ((t.match(/[1-5][.][0-9]{1,2}/)||[])[0]||'')); const reviewer=clean(firstText(row,'[class*=user],[class*=reviewer],[class*=nickname],[class*=member],[class*=name]') || ((t.match(/[^ 　\\n]{1,30}さん/)||[])[0]||'')); const heading=Array.from(row.querySelectorAll('h2,h3,h4,strong,b')).map(text).find(v=>v&&v!==store&&v!==reviewer&&!/[0-9]{4}[/年.]/.test(v))||''; const title=heading || (lines.find(v=>v.length<90&&!v.includes('口コミ')&&!v.includes('利用日')&&!v.includes('予算')&&!/[0-9]{4}[/年.]/.test(v)&&!/[1-5][.][0-9]{1,2}/.test(v)&&v!==reviewer)||''); const likeText=firstText(row,'[class*=like],[class*=good],[class*=helpful]'); const likes=((likeText||t).match(/[0-9]+[ ]*(?:いいね|役に立った)/)||[''])[0].replace(/[^0-9]/g,''); let reply=firstText(row,'[class*=reply],[class*=owner],[class*=response],[class*=comment-owner]'); if(!reply){ const parts=t.split(/お店からの返信|店舗からの返信|返信/); if(parts.length>1) reply=clean(parts.slice(1).join(' ')); } let body=t; [store,reviewer,title,rating,posted,used,budget,reply].filter(Boolean).forEach(v=>{ body=body.split(v).join(' '); }); body=clean(body.replace(/店舗名|ページURL|投稿者|口コミタイトル|点数|投稿日|利用日|予算|口コミ|いいね数|お店からの返信/g,' ')); row.setAttribute('data-uscraper-review-row','1'); row.setAttribute('data-uscraper-store-name',store); row.setAttribute('data-uscraper-page-url',base); row.setAttribute('data-uscraper-reviewer',reviewer); row.setAttribute('data-uscraper-review-title',title); row.setAttribute('data-uscraper-rating',rating); row.setAttribute('data-uscraper-posted-date',posted); row.setAttribute('data-uscraper-visit-date',used); row.setAttribute('data-uscraper-budget',budget); row.setAttribute('data-uscraper-review-text',body); row.setAttribute('data-uscraper-likes-count',likes); row.setAttribute('data-uscraper-shop-reply',reply); }); const next=Array.from(document.querySelectorAll('a,button')).find(el=>{ const tt=text(el); const disabled=el.disabled||el.getAttribute('aria-disabled')==='true'||/disabled/.test(el.className||''); return !disabled && (/^(次|次へ|次のページ|>)$/.test(tt)||tt.includes('次へ')); }); if(next) next.setAttribute('data-uscraper-next-review-page','1'); })();",
        "waitForCompletion": true,
        "timeout": 10
      }
    },
    {
      "block_id": "element-exists-2",
      "block_type": "process",
      "title": "Element Exists",
      "description": "Check if element exists",
      "position_x": 5160,
      "position_y": 758,
      "config": {
        "selector": "[data-uscraper-review-row=\"1\"]"
      }
    },
    {
      "block_id": "structured-export-1",
      "block_type": "process",
      "title": "Structured Export",
      "description": "Export data with custom columns",
      "position_x": 3144,
      "position_y": 520,
      "config": {
        "rowSelector": "[data-uscraper-review-row=\"1\"]",
        "fileName": "ekiten-review-scraper.csv",
        "saveLocation": "C:\\Users\\theskd\\Documents\\UScraper\\templates",
        "includeHeaders": true,
        "fileMode": "append",
        "columns": [
          {
            "name": "store_name",
            "selector": "",
            "attribute": "data-uscraper-store-name"
          },
          {
            "name": "page_url",
            "selector": "",
            "attribute": "data-uscraper-page-url"
          },
          {
            "name": "reviewer",
            "selector": "",
            "attribute": "data-uscraper-reviewer"
          },
          {
            "name": "review_title",
            "selector": "",
            "attribute": "data-uscraper-review-title"
          },
          {
            "name": "rating",
            "selector": "",
            "attribute": "data-uscraper-rating"
          },
          {
            "name": "posted_date",
            "selector": "",
            "attribute": "data-uscraper-posted-date"
          },
          {
            "name": "visit_date",
            "selector": "",
            "attribute": "data-uscraper-visit-date"
          },
          {
            "name": "budget",
            "selector": "",
            "attribute": "data-uscraper-budget"
          },
          {
            "name": "review_text",
            "selector": "",
            "attribute": "data-uscraper-review-text"
          },
          {
            "name": "likes_count",
            "selector": "",
            "attribute": "data-uscraper-likes-count"
          },
          {
            "name": "shop_reply",
            "selector": "",
            "attribute": "data-uscraper-shop-reply"
          }
        ]
      }
    },
    {
      "block_id": "element-exists-1",
      "block_type": "process",
      "title": "Element Exists",
      "description": "Check if element exists",
      "position_x": 3480,
      "position_y": 520,
      "config": {
        "selector": "[data-uscraper-next-review-page=\"1\"]"
      }
    },
    {
      "block_id": "click-1",
      "block_type": "process",
      "title": "Click",
      "description": "Click on element",
      "position_x": 3816,
      "position_y": 520,
      "config": {
        "selector": "[data-uscraper-next-review-page=\"1\"]",
        "timeout": 10
      }
    },
    {
      "block_id": "wait-for-page-load-3",
      "block_type": "process",
      "title": "Wait for Page Load",
      "description": "Wait for page to finish loading",
      "position_x": 4152,
      "position_y": 520,
      "config": {
        "timeout": 30
      }
    },
    {
      "block_id": "sleep-2",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 4488,
      "position_y": 520,
      "config": {
        "duration": 2
      }
    },
    {
      "block_id": "loop-continue-1",
      "block_type": "process",
      "title": "Loop Continue",
      "description": "Continue multi-input loop",
      "position_x": 3480,
      "position_y": 520,
      "config": {}
    },
    {
      "block_id": "inject-javascript-3",
      "block_type": "process",
      "title": "Inject JavaScript",
      "description": "Run custom JavaScript",
      "position_x": 5160,
      "position_y": 758,
      "config": {
        "jsCode": "(() => { document.querySelectorAll('[data-uscraper-review-row],[data-uscraper-next-review-page]').forEach(el=>{ el.removeAttribute('data-uscraper-review-row'); el.removeAttribute('data-uscraper-next-review-page'); }); const row=document.body; const base=(location.href.match(new RegExp('https?://www[.]ekiten[.]jp/shop_[0-9]+/?'))||[location.href.split('?')[0]])[0]; row.setAttribute('data-uscraper-review-row','1'); row.setAttribute('data-uscraper-store-name',(document.querySelector('h1')||{}).innerText||''); row.setAttribute('data-uscraper-page-url',base); row.setAttribute('data-uscraper-reviewer',''); row.setAttribute('data-uscraper-review-title','NO_REVIEW_ROWS_DETECTED'); row.setAttribute('data-uscraper-rating',''); row.setAttribute('data-uscraper-posted-date',''); row.setAttribute('data-uscraper-visit-date',''); row.setAttribute('data-uscraper-budget',''); row.setAttribute('data-uscraper-review-text','The real Ekiten page loaded, but the heuristic review-row detector did not find review rows. The shop may have no visible reviews, reviews may be on another tab/page, or Ekiten markup may have changed.'); row.setAttribute('data-uscraper-likes-count',''); row.setAttribute('data-uscraper-shop-reply',''); })();",
        "waitForCompletion": true,
        "timeout": 10
      }
    }
  ],
  "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": "text-contains-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "text-contains-1",
      "from_connector_id": "true",
      "to_block_id": "click-2",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "click-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": "refresh-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "refresh-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": "text-contains-2",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "text-contains-2",
      "from_connector_id": "true",
      "to_block_id": "inject-javascript-2",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "text-contains-2",
      "from_connector_id": "false",
      "to_block_id": "inject-javascript-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "text-contains-1",
      "from_connector_id": "false",
      "to_block_id": "inject-javascript-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "inject-javascript-1",
      "from_connector_id": "right",
      "to_block_id": "element-exists-2",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "element-exists-2",
      "from_connector_id": "true",
      "to_block_id": "structured-export-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "element-exists-2",
      "from_connector_id": "false",
      "to_block_id": "inject-javascript-3",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "inject-javascript-3",
      "from_connector_id": "right",
      "to_block_id": "structured-export-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "inject-javascript-2",
      "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": "loop-continue-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "click-1",
      "from_connector_id": "right",
      "to_block_id": "wait-for-page-load-3",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "wait-for-page-load-3",
      "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": "inject-javascript-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": 4688,
      "height": 596,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "navigate-1",
          "wait-for-page-load-1",
          "sleep-1",
          "refresh-1",
          "wait-for-page-load-2",
          "wait-for-page-load-3",
          "sleep-2"
        ]
      }
    },
    {
      "id": "group-pagination",
      "element_type": "group",
      "title": "Pagination Loop",
      "color": "#ff832b",
      "position_x": 720,
      "position_y": 116,
      "width": 4688,
      "height": 834,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "text-contains-1",
          "click-2",
          "text-contains-2",
          "element-exists-2",
          "element-exists-1",
          "click-1",
          "loop-continue-1"
        ]
      }
    },
    {
      "id": "group-interaction",
      "element_type": "group",
      "title": "Interaction",
      "color": "#a56eff",
      "position_x": 2736,
      "position_y": 416,
      "width": 2672,
      "height": 534,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "inject-javascript-2",
          "inject-javascript-1",
          "inject-javascript-3"
        ]
      }
    },
    {
      "id": "group-extract",
      "element_type": "group",
      "title": "Data Extraction",
      "color": "#42be65",
      "position_x": 3072,
      "position_y": 416,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "structured-export-1"
        ]
      }
    },
    {
      "id": "note-overview",
      "element_type": "note",
      "title": "Overview",
      "content": "Best-effort UScraper equivalent of the Octoparse Ekiten Review Scraper. Targets public Ekiten shop pages such as https://www.ekiten.jp/shop_1055997/ and extracts review fields: store name, page URL, reviewer, review title, rating, posted date, visit date, budget, review text, likes count, and shop reply. Ekiten served AWS WAF human verification during automated testing. The workflow clicks the WAF Begin button, pauses for manual verification in a visible/persistent browser session, refreshes, then extracts real review rows if available. If WAF remains active, it exports one diagnostic row to the same CSV explaining that review extraction was blocked. Review pagination is handled by detecting/clicking an enabled Japanese Next control; multiple shop URLs can be added to navigate.urls and are advanced with loop-continue.",
      "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 ``. True / False branches control which path runs next. Keep enough space between branches so both connector lines are visible.",
      "color": "#ee5396",
      "position_x": 992,
      "position_y": 200,
      "width": 340,
      "height": 130,
      "z_index": 22,
      "data": {
        "block_id": "text-contains-1"
      }
    },
    {
      "id": "note-block-inject-javascript-2",
      "element_type": "note",
      "title": "Note: Inject JavaScript",
      "content": "Runs custom JavaScript in the page: `(() => { document.querySelectorAll('[data-uscraper-review-row],[data-uscraper-next-review-page]').fo...` Verify in browser if results are empty.",
      "color": "#ee5396",
      "position_x": 3008,
      "position_y": 500,
      "width": 340,
      "height": 140,
      "z_index": 22,
      "data": {
        "block_id": "inject-javascript-2"
      }
    },
    {
      "id": "note-block-inject-javascript-1",
      "element_type": "note",
      "title": "Note: Inject JavaScript",
      "content": "Runs custom JavaScript in the page: `(() => { const clean=s=>(s||'').replace(/\\s+/g,' ').trim(); const text=el=>clean(el && (el.innerText...` Verify in browser if results are empty.",
      "color": "#ee5396",
      "position_x": 5024,
      "position_y": 500,
      "width": 340,
      "height": 140,
      "z_index": 22,
      "data": {
        "block_id": "inject-javascript-1"
      }
    },
    {
      "id": "note-block-element-exists-2",
      "element_type": "note",
      "title": "Note: Element Exists",
      "content": "Condition block: checks `[data-uscraper-review-row=\"1\"]`. True / False branches control which path runs next. Keep enough space between branches so both connector lines are visible.",
      "color": "#ee5396",
      "position_x": 5360,
      "position_y": 738,
      "width": 340,
      "height": 140,
      "z_index": 22,
      "data": {
        "block_id": "element-exists-2"
      }
    },
    {
      "id": "note-block-element-exists-1",
      "element_type": "note",
      "title": "Note: Element Exists",
      "content": "Condition block: checks `[data-uscraper-next-review-page=\"1\"]`. True / False branches control which path runs next. Keep enough space between branches so both connector lines are visible.",
      "color": "#ee5396",
      "position_x": 3680,
      "position_y": 500,
      "width": 340,
      "height": 142,
      "z_index": 22,
      "data": {
        "block_id": "element-exists-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": 3680,
      "position_y": 500,
      "width": 340,
      "height": 123,
      "z_index": 22,
      "data": {
        "block_id": "loop-continue-1"
      }
    }
  ]
}