{
  "version": "1.0.0",
  "exported_at": "2026-06-03T15:20:00.000Z",
  "project": {
    "name": "Yanolja Accommodation Review Scraperby URL",
    "description": "Scrapes Yanolja/NOL accommodation review data by lodging URL. Extracts URL, lodging name, address, overall rating, review count, reviewer ID, review rating, review date, room, companion type, review text, and review photo URLs. Navigation strategy: loads the accommodation page first to capture JSON-LD lodging metadata, then navigates to the dedicated Yanolja reviews URL and scrolls/clicks review load-more controls until lazy-loaded review content stabilizes before exporting normalized generated review rows. Best effort if Yanolja changes its DOM, hides reviewer metadata, requires login, or presents CAPTCHA/bot protection.",
    "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": 240,
      "config": {
        "url": "https://place-site.yanolja.com/places/10061283?srpKeyword=%EA%B0%95%EB%A6%89&checkInDate=2025-10-16&checkOutDate=2025-10-17&adultPax=2",
        "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": 45
      }
    },
    {
      "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": "h1, script#ld_json",
        "timeout": 45,
        "visible": false
      }
    },
    {
      "block_id": "inject-javascript-1",
      "block_type": "process",
      "title": "Inject JavaScript",
      "description": "Execute custom JavaScript",
      "position_x": 1200,
      "position_y": 240,
      "config": {
        "waitForCompletion": true,
        "timeout": 20,
        "jsCode": "(function(){const clean=s=>(s||'').replace(/\\s+/g,' ').trim();function readJsonLd(){try{const raw=document.querySelector('script#ld_json, script[type=\"application/ld+json\"]')?.textContent||'';return raw?JSON.parse(raw):{};}catch(e){return {};}}const ld=readJsonLd();const agg=ld.aggregateRating||{};const placeId=(location.pathname.match(/(?:places|domestic)\\/(\\d+)/)||[])[1]||'10061283';const meta={original_url:location.href,place_id:placeId,name:clean(ld.name||document.querySelector('h1')?.innerText||document.title.replace(/\\s*\\|.*$/,'')),address:clean(ld.address||''),overall_rating:clean(String(agg.ratingValue||'')),review_count:clean(String(agg.reviewCount||''))};try{localStorage.setItem('uscraper_yanolja_meta',JSON.stringify(meta));}catch(e){}return meta;})()"
      }
    },
    {
      "block_id": "navigate-2",
      "block_type": "process",
      "title": "Navigate",
      "description": "Go to a URL",
      "position_x": 1560,
      "position_y": 240,
      "config": {
        "url": "https://nol.yanolja.com/reviews/domestic/10061283",
        "color": "bg-[#ff832b]"
      }
    },
    {
      "block_id": "wait-for-page-load-2",
      "block_type": "process",
      "title": "Wait for Page Load",
      "description": "Wait for page to finish loading",
      "position_x": 1920,
      "position_y": 240,
      "config": {
        "timeout": 45
      }
    },
    {
      "block_id": "sleep-1",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 2280,
      "position_y": 240,
      "config": {
        "duration": 4
      }
    },
    {
      "block_id": "inject-javascript-2",
      "block_type": "process",
      "title": "Inject JavaScript",
      "description": "Execute custom JavaScript",
      "position_x": 2640,
      "position_y": 240,
      "config": {
        "waitForCompletion": true,
        "timeout": 180,
        "jsCode": "(async function(){const delay=ms=>new Promise(r=>setTimeout(r,ms));const clean=s=>(s||'').replace(/\\s+/g,' ').trim();const cleanMultiline=s=>(s||'').replace(/\\r/g,'').replace(/[ \\t]+\\n/g,'\\n').replace(/\\n[ \\t]+/g,'\\n').replace(/\\n{3,}/g,'\\n\\n').trim();let meta={};try{meta=JSON.parse(localStorage.getItem('uscraper_yanolja_meta')||'{}');}catch(e){meta={};}const placeId=meta.place_id||(location.pathname.match(/(?:reviews\\/domestic|domestic|places)\\/(\\d+)/)||[])[1]||'10061283';const lodgingUrl=meta.original_url||('https://nol.yanolja.com/stay/domestic/'+placeId);const lodgingName=meta.name||'';const address=meta.address||'';const overallRating=meta.overall_rating||'';const reviewCount=meta.review_count||'';function clickLoadMore(){const patterns=[/더보기/i,/더\\s*보기/i,/more/i,/전체보기/i];const els=Array.from(document.querySelectorAll('button,[role=\"button\"],a'));let clicked=0;for(const el of els){const t=clean(el.innerText||el.getAttribute('aria-label')||'');if(!t)continue;if(patterns.some(p=>p.test(t))&&!/뒤로|Back|Home|Cart|검색|공유|찜/.test(t)){try{el.scrollIntoView({block:'center'});el.click();clicked++;}catch(e){}}}return clicked;}let stable=0,lastTextLen=0,lastImgCount=0;for(let i=0;i<45;i++){clickLoadMore();window.scrollTo(0,document.body.scrollHeight);await delay(1100);const txtLen=(document.body.innerText||'').length;const imgCount=document.querySelectorAll('img[src*=\"yaimg.yanolja.com\"]').length;if(Math.abs(txtLen-lastTextLen)<80&&imgCount===lastImgCount){stable++;}else{stable=0;}lastTextLen=txtLen;lastImgCount=imgCount;if(stable>=6)break;}await delay(500);const dateRe=/20\\d{2}[.\\-\\/]\\d{1,2}[.\\-\\/]\\d{1,2}/;function visibleText(el){return cleanMultiline(el&&el.innerText?el.innerText:'');}function lineParts(t){return cleanMultiline(t).split(/\\n+/).map(x=>clean(x)).filter(Boolean);}function isBadContainer(t){return /객실\\s*선택|예약하기|위치\\/교통|숙소\\s*소개|시설\\/서비스|이용\\s*안내|예약\\s*공지|결제\\s*시|체크인|체크아웃|사업자|고객센터|로그인|마이|장바구니|최근 본 상품/.test(t);}function candidateElements(){const els=Array.from(document.querySelectorAll('article,li,section,div'));let c=els.filter(el=>{const t=visibleText(el);return dateRe.test(t)&&t.length>=25&&t.length<=2800&&!isBadContainer(t);});c=c.filter(el=>!c.some(other=>other!==el&&el.contains(other)&&visibleText(other).length<visibleText(el).length*0.92));const seen=new Set();const out=[];for(const el of c){const key=visibleText(el).slice(0,260);if(!seen.has(key)){seen.add(key);out.push(el);}}return out;}function extractFromElement(el){const t=visibleText(el);const lines=lineParts(t);const date=(t.match(dateRe)||[''])[0];const dateIndex=lines.findIndex(l=>dateRe.test(l));let reviewerId='';for(let i=0;i<(dateIndex>=0?dateIndex:Math.min(lines.length,4));i++){const l=lines[i];if(!/^([1-5](\\.0)?|\\d+)$/.test(l)&&!/후기|답변|사진|도움|더보기|전체/.test(l)&&l.length<=40){reviewerId=l;break;}}let rating='';const ratingLine=lines.find(l=>/^([1-5](\\.0)?)$/.test(l));if(ratingLine)rating=ratingLine;let room='';let companion='';for(const l of lines){if(!room&&/(\\d{3}\\s*호|\\d{2,4}\\s*호|객실|룸|스위트|복층)/.test(l)&&l.length<45)room=l;if(!companion&&(/(연인|가족|친구|혼자|출장|아이|부모님).*함께/.test(l)||/^(연인|가족|친구|혼자|출장).*$/i.test(l))&&l.length<45)companion=l;}let reviewText='';if(dateIndex>=0){reviewText=lines.slice(dateIndex+1).filter(l=>l!==rating&&l!==room&&l!==companion&&!/^사진\\s*\\d*/.test(l)&&!/더보기|접기|숙소답변|도움돼요/.test(l)).join('\\n');}else{reviewText=t.replace(dateRe,'').trim();}if(!reviewText&&date){reviewText=t.slice(t.indexOf(date)+date.length).trim();}const photos=Array.from(el.querySelectorAll('img')).map(img=>img.currentSrc||img.src||'').filter(src=>/yaimg\\.yanolja\\.com/.test(src)).filter((v,i,a)=>a.indexOf(v)===i).join('\\n');return {url:lodgingUrl,name:lodgingName,address,overallRating,reviewCount,reviewerId,rating,date,room,companion,review:cleanMultiline(reviewText),photos};}function fallbackRowsFromText(){const text=document.body.innerText||'';let start=text.search(/후기|리뷰|전체/);if(start<0)start=text.search(dateRe);if(start<0)start=0;let end=text.search(/객실\\s*선택|위치\\/교통|숙소\\s*소개|시설\\/서비스|이용\\s*안내|예약\\s*공지|사업자|고객센터/);if(end<0||end<start)end=text.length;const zone=text.slice(Math.max(0,start),end);const matches=[...zone.matchAll(new RegExp(dateRe.source,'g'))];const rows=[];for(let i=0;i<matches.length;i++){const s=matches[i].index;const e=i+1<matches.length?matches[i+1].index:zone.length;let seg=zone.slice(s,e).trim();if(seg.length<20||isBadContainer(seg))continue;const date=(seg.match(dateRe)||[''])[0];let lines=lineParts(seg);let rating='';let ratingLine=lines.find(l=>/^([1-5](\\.0)?)$/.test(l));if(ratingLine)rating=ratingLine;let review=seg.replace(date,'').replace(/^\\s*\\d+\\s*/,'').replace(/더보기|접기/g,'').trim();rows.push({url:lodgingUrl,name:lodgingName,address,overallRating,reviewCount,reviewerId:'',rating,date,room:'',companion:'',review:cleanMultiline(review),photos:''});}return rows;}let rows=candidateElements().map(extractFromElement).filter(r=>r.date&&r.review&&r.review.length>5);if(rows.length<2){rows=fallbackRowsFromText();}const seenRows=new Set();rows=rows.filter(r=>{const key=[r.date,r.reviewerId,r.review.slice(0,140)].join('|');if(seenRows.has(key))return false;seenRows.add(key);return true;});let box=document.querySelector('#uscraper-yanolja-reviews');if(box)box.remove();box=document.createElement('div');box.id='uscraper-yanolja-reviews';box.style.cssText='display:block;position:relative;z-index:999999;background:#fff;color:#000;padding:8px;border:2px solid #4589ff;';box.setAttribute('data-row-count',String(rows.length));for(const r of rows){const row=document.createElement('div');row.className='uscraper-review-row';row.setAttribute('data-url',r.url||'');row.setAttribute('data-name',r.name||'');row.setAttribute('data-address',r.address||'');row.setAttribute('data-overall-rating',r.overallRating||'');row.setAttribute('data-review-count',r.reviewCount||'');row.setAttribute('data-reviewer-id',r.reviewerId||'');row.setAttribute('data-rating',r.rating||'');row.setAttribute('data-date',r.date||'');row.setAttribute('data-room',r.room||'');row.setAttribute('data-companion',r.companion||'');row.setAttribute('data-photos',r.photos||'');const p=document.createElement('div');p.className='uscraper-review-text';p.textContent=r.review||'';row.appendChild(p);box.appendChild(row);}document.body.prepend(box);window.scrollTo(0,0);return {rows:rows.length,lodgingName,address,overallRating,reviewCount,currentUrl:location.href};})()"
      }
    },
    {
      "block_id": "wait-for-element-2",
      "block_type": "process",
      "title": "Wait for Element",
      "description": "Wait until element appears",
      "position_x": 3000,
      "position_y": 240,
      "config": {
        "selector": "#uscraper-yanolja-reviews .uscraper-review-row",
        "timeout": 30,
        "visible": false
      }
    },
    {
      "block_id": "structured-export-1",
      "block_type": "process",
      "title": "Structured Export",
      "description": "Export data with custom columns",
      "position_x": 3360,
      "position_y": 240,
      "config": {
        "rowSelector": "#uscraper-yanolja-reviews .uscraper-review-row",
        "fileName": "yanolja_accommodation_review.csv",
        "saveLocation": "C:\\Users\\theskd\\Documents\\UScraper\\templates",
        "includeHeaders": true,
        "fileMode": "create",
        "columns": [
          {
            "name": "링크",
            "selector": "",
            "attribute": "data-url"
          },
          {
            "name": "숙박업체",
            "selector": "",
            "attribute": "data-name"
          },
          {
            "name": "주소",
            "selector": "",
            "attribute": "data-address"
          },
          {
            "name": "숙소_평점",
            "selector": "",
            "attribute": "data-overall-rating"
          },
          {
            "name": "후기_수",
            "selector": "",
            "attribute": "data-review-count"
          },
          {
            "name": "아이디",
            "selector": "",
            "attribute": "data-reviewer-id"
          },
          {
            "name": "별점",
            "selector": "",
            "attribute": "data-rating"
          },
          {
            "name": "후기_날짜",
            "selector": "",
            "attribute": "data-date"
          },
          {
            "name": "입주_객실",
            "selector": "",
            "attribute": "data-room"
          },
          {
            "name": "동행_유형",
            "selector": "",
            "attribute": "data-companion"
          },
          {
            "name": "후기",
            "selector": ".uscraper-review-text",
            "attribute": "text"
          },
          {
            "name": "후기_사진_url",
            "selector": "",
            "attribute": "data-photos"
          }
        ]
      }
    },
    {
      "block_id": "end-1",
      "block_type": "output",
      "title": "End",
      "description": "Terminate execution flow",
      "position_x": 3720,
      "position_y": 240,
      "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": "inject-javascript-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "inject-javascript-1",
      "from_connector_id": "right",
      "to_block_id": "navigate-2",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "navigate-2",
      "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": "inject-javascript-2",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "inject-javascript-2",
      "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"
    },
    {
      "from_block_id": "structured-export-1",
      "from_connector_id": "right",
      "to_block_id": "end-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": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "navigate-1",
          "wait-for-page-load-1",
          "wait-for-element-1",
          "navigate-2",
          "wait-for-page-load-2",
          "sleep-1",
          "wait-for-element-2"
        ]
      }
    },
    {
      "id": "group-interaction",
      "element_type": "group",
      "title": "Interaction",
      "color": "#a56eff",
      "position_x": 1128,
      "position_y": 136,
      "width": 1760,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "inject-javascript-1",
          "inject-javascript-2"
        ]
      }
    },
    {
      "id": "group-extract",
      "element_type": "group",
      "title": "Data Extraction",
      "color": "#42be65",
      "position_x": 3288,
      "position_y": 136,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "structured-export-1"
        ]
      }
    },
    {
      "id": "group-control",
      "element_type": "group",
      "title": "Control Flow",
      "color": "#8d8d8d",
      "position_x": 3648,
      "position_y": 136,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "end-1"
        ]
      }
    },
    {
      "id": "note-overview",
      "element_type": "note",
      "title": "Overview",
      "content": "Scrapes Yanolja/NOL accommodation review data by lodging URL. Extracts URL, lodging name, address, overall rating, review count, reviewer ID, review rating, review date, room, companion type, review text, and review photo URLs. Navigation strategy: loads the accommodation page first to capture JSON-LD lodging metadata, then navigates to the dedicated Yanolja reviews URL and scrolls/clicks review load-more controls until lazy-loaded review content stabilizes before exporting normalized generated review rows. Best effort if Yanolja changes its DOM, hides reviewer metadata, requires login, or presents CAPTCHA/bot protection.",
      "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: `(function(){const clean=s=>(s||'').replace(/\\s+/g,' ').trim();function readJsonLd(){try{const raw=do...` Verify in browser if results are empty.",
      "color": "#ee5396",
      "position_x": 1400,
      "position_y": 220,
      "width": 340,
      "height": 140,
      "z_index": 22,
      "data": {
        "block_id": "inject-javascript-1"
      }
    },
    {
      "id": "note-block-inject-javascript-2",
      "element_type": "note",
      "title": "Note: Inject JavaScript",
      "content": "Runs custom JavaScript in the page: `(async function(){const delay=ms=>new Promise(r=>setTimeout(r,ms));const clean=s=>(s||'').replace(/\\...` Verify in browser if results are empty.",
      "color": "#ee5396",
      "position_x": 2840,
      "position_y": 220,
      "width": 340,
      "height": 140,
      "z_index": 22,
      "data": {
        "block_id": "inject-javascript-2"
      }
    },
    {
      "id": "note-block-structured-export-1",
      "element_type": "note",
      "title": "Note: Structured Export",
      "content": "Extracts rows matching `#uscraper-yanolja-reviews .uscraper-review-row`. Confirm row count > 0 before running at scale.",
      "color": "#ee5396",
      "position_x": 3560,
      "position_y": 220,
      "width": 340,
      "height": 119,
      "z_index": 22,
      "data": {
        "block_id": "structured-export-1"
      }
    }
  ]
}