{
  "version": "1.0.0",
  "exported_at": "2026-06-03T06:30:00.000Z",
  "project": {
    "name": "LinkedIn Posts Scraper Login Required",
    "description": "Best-effort LinkedIn posts scraper equivalent to the Octoparse LinkedIn Posts Scraper. Extracts poster name, follower number, posted time, post content, comments, reposts, and reactions from LinkedIn post search or organization posts URLs. Login is required: after navigation, complete LinkedIn sign-in or verification manually if prompted. Navigation strategy: processes multiple input URLs with navigate.urls[], auto-scrolls LinkedIn's infinite feed until content stabilizes or a max scroll limit is reached, exports all loaded posts with append mode, then continues to the next URL.",
    "color": "bg-[#0a66c2]",
    "template_id": "ai-generated"
  },
  "blocks": [
    {
      "block_id": "set-window-size-1",
      "block_type": "process",
      "title": "Set Window Size",
      "description": "Set browser window dimensions",
      "position_x": 120,
      "position_y": 240,
      "config": {
        "width": 1920,
        "height": 1080,
        "color": "bg-[#4589ff]"
      }
    },
    {
      "block_id": "navigate-1",
      "block_type": "process",
      "title": "Navigate",
      "description": "Go to a URL",
      "position_x": 480,
      "position_y": 240,
      "config": {
        "urls": [
          "https://www.linkedin.com/search/results/content/?keywords=web%20scraping&origin=SWITCH_SEARCH_VERTICAL&sid=6hP",
          "https://www.linkedin.com/school/the-data-analytics-academy/posts/"
        ],
        "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": 840,
      "position_y": 240,
      "config": {
        "timeout": 30,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "wait-for-element-1",
      "block_type": "process",
      "title": "Wait for Element",
      "description": "Wait until element appears",
      "position_x": 1200,
      "position_y": 240,
      "config": {
        "selector": "div.feed-shared-update-v2, div[data-urn*='urn:li:activity']",
        "timeout": 300,
        "visible": true,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "inject-javascript-1",
      "block_type": "process",
      "title": "Inject JavaScript",
      "description": "Execute JavaScript on page",
      "position_x": 1560,
      "position_y": 240,
      "config": {
        "jsCode": "return (async () => { const maxScrolls = 25; const baseDelay = 1500; let lastHeight = 0; let stableRounds = 0; const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); for (let i = 0; i < maxScrolls && stableRounds < 3; i++) { window.scrollTo(0, document.body.scrollHeight); await sleep(baseDelay + Math.floor(Math.random() * 1000)); const currentHeight = document.body.scrollHeight; if (currentHeight === lastHeight) { stableRounds++; } else { stableRounds = 0; lastHeight = currentHeight; } } return true; })();",
        "waitForCompletion": true,
        "timeout": 120,
        "color": "bg-[#a56eff]"
      }
    },
    {
      "block_id": "sleep-1",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 1920,
      "position_y": 240,
      "config": {
        "duration": 3,
        "color": "bg-[#a56eff]"
      }
    },
    {
      "block_id": "wait-for-element-2",
      "block_type": "process",
      "title": "Wait for Element",
      "description": "Wait until element appears",
      "position_x": 2280,
      "position_y": 240,
      "config": {
        "selector": "div.feed-shared-update-v2, div[data-urn*='urn:li:activity']",
        "timeout": 60,
        "visible": true,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "structured-export-1",
      "block_type": "process",
      "title": "Structured Export",
      "description": "Export data with custom columns",
      "position_x": 2640,
      "position_y": 240,
      "config": {
        "rowSelector": "div.feed-shared-update-v2, div[data-urn*='urn:li:activity']",
        "fileName": "linkedin-posts-scraper.csv",
        "saveLocation": "C:\\Users\\theskd\\Documents\\UScraper\\templates",
        "includeHeaders": true,
        "fileMode": "append",
        "color": "bg-[#42be65]",
        "columns": [
          {
            "name": "name",
            "selector": "(() => { const el = ROW.querySelector('.update-components-actor__name .visually-hidden, .update-components-actor__name span[aria-hidden=true], .update-components-actor__title span[aria-hidden=true], a.update-components-actor__meta-link span[aria-hidden=true]'); return (el?.textContent || '').replace(/\\s+/g, ' ').trim(); })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "follower_number",
            "selector": "(() => { const texts = Array.from(ROW.querySelectorAll('.update-components-actor__description, .update-components-actor__sub-description, span')).map(e => (e.textContent || '').replace(/\\s+/g, ' ').trim()).filter(Boolean); return texts.find(t => /followers?/i.test(t)) || ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "posted",
            "selector": "(() => { const el = ROW.querySelector('.update-components-actor__sub-description span[aria-hidden=true], .update-components-actor__sub-description'); let txt = (el?.textContent || '').replace(/\\s+/g, ' ').trim(); if (txt) return txt.split('•')[0].trim(); const texts = Array.from(ROW.querySelectorAll('span')).map(e => (e.textContent || '').replace(/\\s+/g, ' ').trim()).filter(Boolean); return texts.find(t => /^(now|\\d+\\s*(s|m|h|d|w|mo|yr))\\b/i.test(t)) || ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "post_content",
            "selector": "(() => { const el = ROW.querySelector('.update-components-text, .feed-shared-update-v2__description, .update-components-update-v2__commentary, div[dir=ltr]'); return (el?.innerText || el?.textContent || '').replace(/\\s+/g, ' ').trim(); })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "comment",
            "selector": "(() => { const texts = Array.from(ROW.querySelectorAll('button, span, li')).map(e => (e.textContent || '').replace(/\\s+/g, ' ').trim()).filter(Boolean); return texts.find(t => /\\bcomments?\\b/i.test(t)) || ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "repost",
            "selector": "(() => { const texts = Array.from(ROW.querySelectorAll('button, span, li')).map(e => (e.textContent || '').replace(/\\s+/g, ' ').trim()).filter(Boolean); return texts.find(t => /\\breposts?\\b/i.test(t)) || ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "reaction",
            "selector": "(() => { const el = ROW.querySelector('.social-details-social-counts__reactions-count, .social-details-social-counts__social-proof-text, button[aria-label*=reaction], span[aria-label*=reaction]'); const txt = (el?.textContent || el?.getAttribute?.('aria-label') || '').replace(/\\s+/g, ' ').trim(); const match = txt.match(/[\\d,.]+/); return match ? match[0] : txt; })()",
            "attribute": "text",
            "isJs": true
          }
        ]
      }
    },
    {
      "block_id": "loop-continue-1",
      "block_type": "process",
      "title": "Loop Continue",
      "description": "Continue multi-input loop",
      "position_x": 3000,
      "position_y": 240,
      "config": {
        "color": "bg-[#8d8d8d]"
      }
    }
  ],
  "connections": [
    {
      "from_block_id": "set-window-size-1",
      "from_connector_id": "right",
      "to_block_id": "navigate-1",
      "to_connector_id": "left"
    },
    {
      "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": "sleep-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "sleep-1",
      "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": "loop-continue-1",
      "to_connector_id": "left"
    }
  ],
  "canvas_elements": [
    {
      "id": "group-entry",
      "element_type": "group",
      "title": "Entry & Setup",
      "color": "#4589ff",
      "position_x": 48,
      "position_y": 136,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "set-window-size-1"
        ]
      }
    },
    {
      "id": "group-load",
      "element_type": "group",
      "title": "Page Load",
      "color": "#08bdba",
      "position_x": 408,
      "position_y": 136,
      "width": 2120,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "navigate-1",
          "wait-for-page-load-1",
          "wait-for-element-1",
          "sleep-1",
          "wait-for-element-2"
        ]
      }
    },
    {
      "id": "group-interaction",
      "element_type": "group",
      "title": "Interaction",
      "color": "#a56eff",
      "position_x": 1488,
      "position_y": 136,
      "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": 2568,
      "position_y": 136,
      "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": 2928,
      "position_y": 136,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "loop-continue-1"
        ]
      }
    },
    {
      "id": "note-overview",
      "element_type": "note",
      "title": "Overview",
      "content": "Best-effort LinkedIn posts scraper equivalent to the Octoparse LinkedIn Posts Scraper. Extracts poster name, follower number, posted time, post content, comments, reposts, and reactions from LinkedIn post search or organization posts URLs. Login is required: after navigation, complete LinkedIn sign-in or verification manually if prompted. Navigation strategy: processes multiple input URLs with navigate.urls[], auto-scrolls LinkedIn's infinite feed until content stabilizes or a max scroll limit is reached, exports all loaded posts with append mode, then continues to the next URL.",
      "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: `return (async () => { const maxScrolls = 25; const baseDelay = 1500; let lastHeight = 0; let stableR...` Verify in browser if results are empty.",
      "color": "#ee5396",
      "position_x": 1760,
      "position_y": 220,
      "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": "Structured export with JS columns (name, follower_number, posted, post_content, comment). These selectors are fragile — update if the site layout changes.",
      "color": "#ee5396",
      "position_x": 2840,
      "position_y": 220,
      "width": 340,
      "height": 131,
      "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": 3200,
      "position_y": 220,
      "width": 340,
      "height": 123,
      "z_index": 22,
      "data": {
        "block_id": "loop-continue-1"
      }
    }
  ]
}