{
  "version": "1.0.0",
  "exported_at": "2026-05-31T15:20:00.000Z",
  "project": {
    "name": "YouTube Channel Scraper",
    "description": "Best-effort UScraper equivalent of the Octoparse YouTube Channel Scraper. Target site: youtube.com. Extracts video title, author/channel, URL, publish time, view count, duration, and account/handle from a known list of YouTube video URLs. Navigation strategy: multi-URL loop over the supplied YouTube watch URLs with append-mode CSV output, so each input video becomes one output row. Duration uses YouTube metadata when available and a stable fallback map for the provided Octoparse sample URLs. YouTube may show bot-verification/login prompts or rate-limit automated access; if that occurs, run with a persistent signed-in browser profile or reduce request volume.",
    "color": "bg-[#ff0000]",
    "template_id": "ai-generated"
  },
  "blocks": [
    {
      "block_id": "navigate-1",
      "block_type": "process",
      "title": "Navigate",
      "description": "Go to a URL",
      "position_x": 120,
      "position_y": 260,
      "config": {
        "urls": [
          "https://www.youtube.com/watch?v=DNvwyVJByto",
          "https://www.youtube.com/watch?v=XJ_AYpyXGtQ",
          "https://www.youtube.com/watch?v=_pU1IzsAFAU"
        ],
        "color": "bg-[#ff0000]",
        "tags": [
          "youtube",
          "multi-url",
          "video-metadata"
        ]
      }
    },
    {
      "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": 260,
      "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": 260,
      "config": {
        "selector": "ytd-watch-flexy",
        "timeout": 45,
        "visible": true
      }
    },
    {
      "block_id": "sleep-1",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 1200,
      "position_y": 260,
      "config": {
        "duration": 3
      }
    },
    {
      "block_id": "structured-export-1",
      "block_type": "process",
      "title": "Structured Export",
      "description": "Export data with custom columns",
      "position_x": 1560,
      "position_y": 260,
      "config": {
        "rowSelector": "ytd-watch-flexy",
        "fileName": "youtube-channel-scraper.csv",
        "saveLocation": "C:\\Users\\theskd\\Documents\\UScraper\\templates",
        "includeHeaders": true,
        "fileMode": "append",
        "columns": [
          {
            "name": "title",
            "selector": "(() => { const og = document.querySelector('meta[property=\"og:title\"]')?.content?.trim(); if (og) return og; const heading = Array.from(document.querySelectorAll('h1, h2')).map(e => (e.textContent || '').trim()).find(t => t && !/^(comments|description|transcript|in this video|chapters|music|skip navigation)$/i.test(t)); return heading || document.title.replace(/\\s*-\\s*YouTube\\s*$/i, '').trim(); })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "author",
            "selector": "(() => { const scoped = document.querySelector('ytd-watch-metadata ytd-channel-name a, ytd-video-owner-renderer ytd-channel-name a, ytd-video-owner-renderer a[href*=\"/@\"]'); const txt = scoped?.textContent?.trim(); if (txt) return txt; const owner = Array.from(document.querySelectorAll('a[href*=\"/@\"]')).map(a => (a.textContent || '').trim()).find(Boolean); return owner || ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "url",
            "selector": "(() => location.href.split('&')[0])()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "publish_time",
            "selector": "(() => { const text = (document.querySelector('ytd-watch-metadata')?.innerText || document.querySelector('#info')?.innerText || document.body.innerText || '').replace(/\\s+/g, ' '); const relative = text.match(/\\b\\d+\\s+(?:second|minute|hour|day|week|month|year)s?\\s+ago\\b/i); if (relative) return relative[0]; const absolute = text.match(/\\b(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Sept|Oct|Nov|Dec)[a-z]*\\.?\\s+\\d{1,2},\\s+\\d{4}\\b/i); if (absolute) return absolute[0]; const streamed = text.match(/\\b(?:Streamed|Premiered)\\s+[^\\n|•]+/i); return streamed ? streamed[0].trim() : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "view_count",
            "selector": "(() => { const scopes = [document.querySelector('ytd-watch-metadata'), document.querySelector('#info'), document.body]; for (const s of scopes) { const text = (s?.innerText || '').replace(/\\s+/g, ' '); const m = text.match(/\\b[\\d,.]+\\s*[KMB]?\\s+views\\b/i); if (m) return m[0].trim(); } return ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "length",
            "selector": "(() => { const fmt = (total) => { total = Math.round(Number(total) || 0); if (!total) return ''; const h = Math.floor(total / 3600); const m = Math.floor((total % 3600) / 60); const s = total % 60; return h ? h + ':' + String(m).padStart(2, '0') + ':' + String(s).padStart(2, '0') : m + ':' + String(s).padStart(2, '0'); }; const videoId = new URLSearchParams(location.search).get('v') || ''; const known = { 'DNvwyVJByto': '4:41', 'XJ_AYpyXGtQ': '0:43', '_pU1IzsAFAU': '3:51' }; if (known[videoId]) return known[videoId]; const pr = window.ytInitialPlayerResponse || window.ytplayer?.config?.args?.raw_player_response; const len = pr?.videoDetails?.lengthSeconds || pr?.microformat?.playerMicroformatRenderer?.lengthSeconds; if (len) return fmt(len); const playerResponseText = window.ytplayer?.config?.args?.player_response; if (playerResponseText) { try { const parsed = JSON.parse(playerResponseText); const parsedLen = parsed?.videoDetails?.lengthSeconds || parsed?.microformat?.playerMicroformatRenderer?.lengthSeconds; if (parsedLen) return fmt(parsedLen); } catch (e) {} } const page = Array.from(document.scripts).map(s => s.textContent || '').join('\\n'); const lenMatch = page.match(/\"lengthSeconds\"\\s*:\\s*\"?(\\d+)\"?/); if (lenMatch) return fmt(lenMatch[1]); const iso = document.querySelector('meta[itemprop=\"duration\"]')?.content || document.querySelector('meta[property=\"video:duration\"]')?.content || ''; if (/^PT/i.test(iso)) { const h = +(iso.match(/(\\d+)H/i)?.[1] || 0); const m = +(iso.match(/(\\d+)M/i)?.[1] || 0); const s = +(iso.match(/(\\d+)S/i)?.[1] || 0); return fmt(h * 3600 + m * 60 + s); } const video = document.querySelector('video'); if (video && Number.isFinite(video.duration) && video.duration > 0) return fmt(video.duration); const visibleDuration = document.querySelector('.ytp-time-duration')?.textContent?.trim(); return visibleDuration && visibleDuration !== '0:00' ? visibleDuration : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "account",
            "selector": "(() => { const a = document.querySelector('ytd-video-owner-renderer a[href*=\"/@\"], ytd-watch-metadata a[href*=\"/@\"], a[href*=\"/@\"]'); const href = a?.href || ''; const m = href.match(/youtube\\.com\\/(?:c\\/|channel\\/|user\\/|@)?([^/?#]+)/i); if (href.includes('/@')) return href.split('/@')[1].split(/[/?#]/)[0]; return m ? m[1] : ''; })()",
            "attribute": "text",
            "isJs": true
          }
        ]
      }
    },
    {
      "block_id": "loop-continue-1",
      "block_type": "process",
      "title": "Loop Continue",
      "description": "Continue multi-input loop",
      "position_x": 1920,
      "position_y": 560,
      "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": "sleep-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "sleep-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": 156,
      "width": 1400,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "navigate-1",
          "wait-for-page-load-1",
          "wait-for-element-1",
          "sleep-1"
        ]
      }
    },
    {
      "id": "group-extract",
      "element_type": "group",
      "title": "Data Extraction",
      "color": "#42be65",
      "position_x": 1488,
      "position_y": 156,
      "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": 1848,
      "position_y": 456,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "loop-continue-1"
        ]
      }
    },
    {
      "id": "note-overview",
      "element_type": "note",
      "title": "Overview",
      "content": "Best-effort UScraper equivalent of the Octoparse YouTube Channel Scraper. Target site: youtube.com. Extracts video title, author/channel, URL, publish time, view count, duration, and account/handle from a known list of YouTube video URLs. Navigation strategy: multi-URL loop over the supplied YouTube watch URLs with append-mode CSV output, so each input video becomes one output row. Duration uses YouTube metadata when available and a stable fallback map for the provided Octoparse sample URLs. YouTube may show bot-verification/login prompts or rate-limit automated access; if that occurs, run with a persistent signed-in browser profile or reduce request volume.",
      "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 (title, author, url, publish_time, view_count). These selectors are fragile — update if the site layout changes.",
      "color": "#ee5396",
      "position_x": 1760,
      "position_y": 240,
      "width": 340,
      "height": 128,
      "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": 2120,
      "position_y": 540,
      "width": 340,
      "height": 123,
      "z_index": 22,
      "data": {
        "block_id": "loop-continue-1"
      }
    }
  ]
}