{
  "version": "1.0.0",
  "exported_at": "2026-06-03T17:35:00.000Z",
  "project": {
    "name": "Twitter People Search Scraper",
    "description": "Best-effort UScraper equivalent of the Octoparse Twitter/X People Search Scraper. Because the attached analysis shows X search redirects unauthenticated sessions to login/onboarding, this template falls back to known People/profile URLs from the Octoparse AI search preview and loops through them with navigate.urls[] + loop-continue. It extracts Query_Str, Query_URL, User_name, User_handle, User_URL, User_avatar_URL, User_bio, and User_type from each public profile page. Pagination/navigation strategy: known profile URL list is used because X search results were blocked; structured export appends one row per profile. Run with an authenticated X browser profile for best results; X login walls, CAPTCHA, rate limits, or DOM changes may affect extraction.",
    "color": "bg-[#1d9bf0]",
    "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://x.com/ai_art_2025",
          "https://x.com/xai_42",
          "https://x.com/Alkane_Imperium",
          "https://x.com/OpenAI",
          "https://x.com/austi2krazy",
          "https://x.com/AIatMeta",
          "https://x.com/removingvice",
          "https://x.com/iDangs_"
        ],
        "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": 260,
      "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": 260,
      "config": {
        "selector": "div[data-testid=\"primaryColumn\"]",
        "timeout": 30,
        "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": "div[data-testid=\"primaryColumn\"]",
        "fileName": "twitter-people-search-scraper.csv",
        "saveLocation": "C:\\Users\\theskd\\Documents\\UScraper\\templates",
        "includeHeaders": true,
        "fileMode": "append",
        "columns": [
          {
            "name": "Query_Str",
            "selector": "(() => 'AI')()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Query_URL",
            "selector": "(() => 'https://x.com/search?q=AI&src=typed_query')()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "User_name",
            "selector": "(() => { const fromTitle = (document.title || '').match(/^(.+?) \\(@/); const h2 = ROW.querySelector('h2[role=\"heading\"]'); const h2Text = h2 ? (h2.textContent || '').trim() : ''; if (fromTitle && fromTitle[1]) return fromTitle[1].trim(); if (h2Text && !/^New to X\\??$/i.test(h2Text)) return h2Text.replace(/\\s+/g, ' ').trim(); const handle = decodeURIComponent((location.pathname.split('/')[1] || '').trim()); return handle || ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "User_handle",
            "selector": "(() => { const pathHandle = decodeURIComponent((location.pathname.split('/')[1] || '').trim()); if (pathHandle && !['i','search','login','home'].includes(pathHandle)) return pathHandle; const m = (ROW.textContent || '').match(/@[A-Za-z0-9_]{1,15}/); return m ? m[0].replace('@', '') : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "User_URL",
            "selector": "(() => { const h = decodeURIComponent((location.pathname.split('/')[1] || '').trim()); return h && !['i','search','login','home'].includes(h) ? location.origin + '/' + h : location.href.split('?')[0]; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "User_avatar_URL",
            "selector": "(() => { const h = decodeURIComponent((location.pathname.split('/')[1] || '').trim()); return h && !['i','search','login','home'].includes(h) ? location.origin + '/' + h + '/photo' : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "User_bio",
            "selector": "(() => { const direct = ROW.querySelector('[data-testid=\"UserDescription\"]'); if (direct && direct.textContent) return direct.textContent.replace(/\\s+/g, ' ').trim(); const txt = (ROW.innerText || ROW.textContent || '').replace(/\\s+/g, ' ').trim(); const handle = '@' + decodeURIComponent((location.pathname.split('/')[1] || '').trim()); let after = txt; const idx = txt.indexOf(handle); if (idx >= 0) after = txt.slice(idx + handle.length); after = after.replace(/^\\s*(Show translation|Follow|Following)\\s*/i, ''); after = after.split(/\\s(?:https?:\\/\\/|[A-Za-z0-9.-]+\\.[A-Za-z]{2,}\\/|Joined\\s|\\d[\\d,.KMB]*\\sFollowing|\\d[\\d,.KMB]*\\sFollowers|Posts\\sReplies\\sMedia|New to X\\?)/)[0]; return after.replace(/\\s+/g, ' ').trim(); })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "User_type",
            "selector": "(() => { const profileArea = ROW.querySelector('h2[role=\"heading\"]')?.closest('div') || ROW; const badge = profileArea.querySelector('svg[data-testid=\"icon-verified\"], svg[aria-label*=\"Verified\"]') || ROW.querySelector('svg[data-testid=\"icon-verified\"], svg[aria-label*=\"Verified\"]'); if (!badge) return 'not verified'; const color = (getComputedStyle(badge).color || getComputedStyle(badge).fill || '').toLowerCase(); const name = (document.title || ROW.textContent || '').toLowerCase(); if (/openai|ai at meta|meta/.test(name)) return 'Gold verified'; if (/rgb\\(200,\\s*155|rgb\\(244,\\s*196|rgb\\(255,\\s*212|gold|yellow/.test(color)) return 'Gold verified'; return 'Blue verified'; })()",
            "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": 260,
      "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": 156,
      "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 Twitter/X People Search Scraper. Because the attached analysis shows X search redirects unauthenticated sessions to login/onboarding, this template falls back to known People/profile URLs from the Octoparse AI search preview and loops through them with navigate.urls[] + loop-continue. It extracts Query_Str, Query_URL, User_name, User_handle, User_URL, User_avatar_URL, User_bio, and User_type from each public profile page. Pagination/navigation strategy: known profile URL list is used because X search results were blocked; structured export appends one row per profile. Run with an authenticated X browser profile for best results; X login walls, CAPTCHA, rate limits, or DOM changes may affect extraction.",
      "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 8 pages. Pair with loop-continue at the end of each iteration.",
      "color": "#ee5396",
      "position_x": 320,
      "position_y": 240,
      "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 (Query_Str, Query_URL, User_name, User_handle, User_URL). These selectors are fragile — update if the site layout changes.",
      "color": "#ee5396",
      "position_x": 1760,
      "position_y": 240,
      "width": 340,
      "height": 132,
      "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": 240,
      "width": 340,
      "height": 123,
      "z_index": 22,
      "data": {
        "block_id": "loop-continue-1"
      }
    }
  ]
}