{
  "version": "1.0.0",
  "exported_at": "2026-06-03T08:30:00.000Z",
  "project": {
    "name": "Xiaohongshu Search Result Scraper by keyword",
    "description": "Best-effort Xiaohongshu / Red Note search-result scraper equivalent to the Octoparse template. It exports input keyword, post title, image URL, details page URL, author, datetime, author URL, like count, and recommended reason. Navigation uses editable keyword search URLs in a multi-URL loop. For each keyword, the template loads Xiaohongshu, attempts search UI submission, attempts same-origin API/DOM extraction, infinite-scrolls, normalizes detected notes into stable hidden rows, and appends results to one CSV. If Xiaohongshu renders no public cards because login, CAPTCHA, invalid anti-bot state, or security restrictions are present, the template writes a diagnostic row for that keyword instead of silently producing no file.",
    "color": "bg-[#ff2442]",
    "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.xiaohongshu.com/search_result?keyword=web%20scraping&source=web_search_result_notes",
          "https://www.xiaohongshu.com/search_result?keyword=TikTok%20Refugee&source=web_search_result_notes",
          "https://www.xiaohongshu.com/search_result?keyword=Red%20Note&source=web_search_result_notes",
          "https://www.xiaohongshu.com/search_result?keyword=Cat%20tax&source=web_search_result_notes"
        ],
        "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": 45,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "sleep-1",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 1200,
      "position_y": 240,
      "config": {
        "duration": 8,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "inject-javascript-1",
      "block_type": "process",
      "title": "Inject JavaScript",
      "description": "Execute custom JavaScript",
      "position_x": 1560,
      "position_y": 240,
      "config": {
        "jsCode": "return new Promise(async (resolve) => { const sleep = ms => new Promise(r => setTimeout(r, ms)); let keyword = ''; try { keyword = decodeURIComponent(new URLSearchParams(location.search).get('keyword') || ''); } catch(e) {} const cardSelector = '.feeds-container .note-item, section.note-item, div.note-item, [class*=\"note-item\"], a[href*=\"/search_result/\"], a[href*=\"/explore/\"]'; const beforeCards = document.querySelectorAll(cardSelector).length; if (keyword && beforeCards === 0) { const inputs = Array.from(document.querySelectorAll('input, textarea')).filter(el => { const ph = (el.getAttribute('placeholder') || '').toLowerCase(); const cls = (el.className || '').toString().toLowerCase(); const type = (el.getAttribute('type') || '').toLowerCase(); return type === 'search' || ph.includes('搜索') || ph.includes('search') || cls.includes('search') || cls.includes('input'); }); const input = inputs[0]; if (input) { input.focus(); const proto = input.tagName === 'TEXTAREA' ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype; const desc = Object.getOwnPropertyDescriptor(proto, 'value'); if (desc && desc.set) desc.set.call(input, keyword); else input.value = keyword; input.dispatchEvent(new Event('input', { bubbles: true })); input.dispatchEvent(new Event('change', { bubbles: true })); await sleep(300); input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true })); input.dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true })); await sleep(800); const buttons = Array.from(document.querySelectorAll('button, [role=\"button\"], .search-icon, .search-btn, [class*=\"search\"]')).filter(el => el !== input && (el.textContent.includes('搜索') || /search|icon|btn/i.test((el.className || '').toString()))); if (buttons[0]) buttons[0].click(); } } await sleep(6000); resolve({ keyword, url: location.href, title: document.title, beforeCards, afterCards: document.querySelectorAll(cardSelector).length, bodyText: document.body ? document.body.innerText.slice(0, 300) : '' }); });",
        "waitForCompletion": true,
        "timeout": 35,
        "color": "bg-[#a56eff]"
      }
    },
    {
      "block_id": "sleep-2",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 1920,
      "position_y": 240,
      "config": {
        "duration": 5,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "inject-javascript-2",
      "block_type": "process",
      "title": "Inject JavaScript",
      "description": "Execute custom JavaScript",
      "position_x": 2280,
      "position_y": 240,
      "config": {
        "jsCode": "return new Promise(async (resolve) => { const sleep = ms => new Promise(r => setTimeout(r, ms)); let keyword = ''; try { keyword = decodeURIComponent(new URLSearchParams(location.search).get('keyword') || ''); } catch(e) {} function clean(v) { return (v == null ? '' : String(v)).replace(/\\s+/g, ' ').trim(); } function abs(url) { try { return url ? new URL(url, location.href).href : ''; } catch(e) { return url || ''; } } function addRow(rows, r) { const key = r.details_page_url || r.image_url || (r.title + '|' + r.author); if (!key || rows._seen.has(key)) return; rows._seen.add(key); rows.push(r); } const rows = []; rows._seen = new Set(); const pageText = document.body ? document.body.innerText || '' : ''; Array.from(document.querySelectorAll('[class*=\"login\"], [class*=\"modal\"], [class*=\"mask\"], [class*=\"overlay\"], [class*=\"popup\"]')).forEach(el => { const text = clean(el.textContent); const cls = (el.className || '').toString(); const rect = el.getBoundingClientRect ? el.getBoundingClientRect() : { width: 0, height: 0 }; if ((/登录|验证码|手机号|扫码/.test(text) || /login|modal|mask|overlay|popup/i.test(cls)) && rect.width > 250 && rect.height > 180) { el.style.pointerEvents = 'none'; el.style.opacity = '0.05'; el.style.zIndex = '-1'; } }); async function tryApi() { try { let searchId = ''; for (let page = 1; page <= 5; page++) { const payload = { keyword, page, page_size: 20, search_id: searchId, sort: 'general', note_type: 0, ext_flags: [] }; const res = await fetch('/api/sns/web/v1/search/notes', { method: 'POST', credentials: 'include', headers: { 'content-type': 'application/json' }, body: JSON.stringify(payload) }); const json = await res.json().catch(() => null); if (!json || !json.data) break; searchId = json.data.search_id || searchId || ''; const items = json.data.items || json.data.notes || json.data.note_list || []; if (!items.length) break; for (const it of items) { const card = it.note_card || it.note || it; const user = card.user || card.user_info || it.user || {}; const cover = card.cover || card.image || (card.images_list && card.images_list[0]) || {}; const noteId = card.note_id || card.id || it.id || it.note_id || ''; const xsec = it.xsec_token || card.xsec_token || ''; const detail = noteId ? abs('/search_result/' + noteId + (xsec ? '?xsec_token=' + encodeURIComponent(xsec) + '&xsec_source=pc_search' : '')) : ''; const authorId = user.user_id || user.id || card.user_id || ''; addRow(rows, { input_keywords: keyword, title: clean(card.display_title || card.title || card.desc || card.desc_v2 || ''), image_url: abs(cover.url_pre || cover.url_default || cover.url || cover.src || ''), details_page_url: detail, author: clean(user.nickname || user.name || ''), datetime: clean(card.time || card.last_update_time || card.create_time || ''), author_url: authorId ? abs('/user/profile/' + authorId + '?xsec_source=pc_search') : '', like_count: clean(card.interact_info && (card.interact_info.liked_count || card.interact_info.like_count) || card.likes || ''), recommended_reason: clean(it.recommend_reason || it.reason || '') }); } await sleep(500); } } catch(e) {} } await tryApi(); const rowSelector = '.feeds-container .note-item, section.note-item, div.note-item, [class*=\"note-item\"], section:has(a[href*=\"/search_result/\"]), section:has(a[href*=\"/explore/\"]), div:has(a[href*=\"/search_result/\"]):has(img), div:has(a[href*=\"/explore/\"]):has(img), a[href*=\"/search_result/\"]:has(img), a[href*=\"/explore/\"]:has(img)'; let stableRounds = 0; let lastHeight = 0; let lastCount = 0; for (let i = 0; i < 35 && stableRounds < 5; i++) { window.scrollTo(0, document.body.scrollHeight); await sleep(1200); const height = Math.max(document.body.scrollHeight || 0, document.documentElement.scrollHeight || 0); const count = document.querySelectorAll(rowSelector).length; if (height === lastHeight && count === lastCount) stableRounds++; else stableRounds = 0; lastHeight = height; lastCount = count; } window.scrollTo(0, 0); await sleep(800); function textFrom(root, selectors) { for (const s of selectors) { const el = root.querySelector(s); const t = clean(el && el.textContent); if (t) return t; } return ''; } function hrefFrom(root, pred) { const a = Array.from(root.querySelectorAll('a[href]')).find(pred); return a ? a.href : ''; } function imgFrom(root) { const img = root.querySelector('img'); return img ? (img.currentSrc || img.src || img.getAttribute('data-src') || img.getAttribute('src') || '') : ''; } const sourceNodes = []; document.querySelectorAll(rowSelector).forEach(node => { let root = node; if (node.matches && node.matches('a')) root = node.closest('section, div') || node; if (!sourceNodes.includes(root)) sourceNodes.push(root); }); for (const root of sourceNodes) { const detail = hrefFrom(root, a => /\\/search_result\\//.test(a.href) || /\\/explore\\//.test(a.href) || /\\/discovery\\/item\\//.test(a.href)); const authorUrl = hrefFrom(root, a => /\\/user\\/profile\\//.test(a.href)); const authorLink = authorUrl ? Array.from(root.querySelectorAll('a[href]')).find(a => a.href === authorUrl) : null; addRow(rows, { input_keywords: keyword, title: textFrom(root, ['.title', '.note-title', '.footer .title', 'a.title', '[class*=\"title\"]', 'span[class*=\"title\"]', 'p']) || clean(root.getAttribute && root.getAttribute('title')), image_url: abs(imgFrom(root)), details_page_url: abs(detail), author: textFrom(root, ['.author .name', '.author-wrapper .name', '.user-name', '.name', '[class*=\"author\"] [class*=\"name\"]', '[class*=\"user\"] [class*=\"name\"]']) || clean(authorLink && authorLink.textContent), datetime: textFrom(root, ['time', '.date', '.time', '.publish-time', '.publish-date', '[class*=\"date\"]', '[class*=\"time\"]']), author_url: abs(authorUrl), like_count: textFrom(root, ['.like-wrapper .count', '.like .count', '[class*=\"like\"] [class*=\"count\"]', '[class*=\"like\"]', '.count']), recommended_reason: textFrom(root, ['.recommend-reason', '.recommended-reason', '[class*=\"recommend\"]', '[class*=\"reason\"]']) }); } try { const html = document.documentElement.innerHTML; const noteMatches = Array.from(html.matchAll(/(?:https?:\\\\?\\/\\\\?\\/www\\.xiaohongshu\\.com)?\\\\?\\/(?:search_result|explore)\\\\?\\/([A-Za-z0-9_-]{8,40})/g)).slice(0, 80); const imgMatches = Array.from(html.matchAll(/https?:\\\\?\\/\\\\?\\/[^\"'<>\\s]+xhscdn\\.com[^\"'<>\\s]+/g)).map(m => m[0].replace(/\\\\\\//g, '/')); const userMatches = Array.from(html.matchAll(/(?:https?:\\\\?\\/\\\\?\\/www\\.xiaohongshu\\.com)?\\\\?\\/user\\\\?\\/profile\\\\?\\/([A-Za-z0-9_-]{8,40})/g)); for (let i = 0; i < noteMatches.length; i++) { const id = noteMatches[i][1]; addRow(rows, { input_keywords: keyword, title: '', image_url: abs(imgMatches[i] || ''), details_page_url: abs('/search_result/' + id), author: '', datetime: '', author_url: userMatches[i] ? abs('/user/profile/' + userMatches[i][1]) : '', like_count: '', recommended_reason: '' }); } } catch(e) {} if (!rows.length) { let reason = 'NO_PUBLIC_RESULTS_RENDERED'; if (/安全限制|url is invalid|300017/.test(pageText)) reason = 'XIAOHONGSHU_SECURITY_RESTRICTION_OR_INVALID_URL'; else if (/验证码|登录|手机号|扫码|请完成验证|verify|captcha/i.test(pageText)) reason = 'LOGIN_OR_CAPTCHA_REQUIRED_IN_BROWSER_SESSION'; else if (/Access Denied|forbidden|blocked/i.test(pageText)) reason = 'ACCESS_BLOCKED_BY_TARGET_SITE'; addRow(rows, { input_keywords: keyword, title: '', image_url: '', details_page_url: location.href, author: '', datetime: '', author_url: '', like_count: '', recommended_reason: reason + ' — no Xiaohongshu result cards were available to scrape in this browser session.' }); } const old = document.querySelector('#uscraper-xhs-normalized'); if (old) old.remove(); const box = document.createElement('div'); box.id = 'uscraper-xhs-normalized'; box.style.display = 'none'; rows.forEach(r => { const row = document.createElement('div'); row.className = 'uscraper-xhs-row'; row.dataset.inputKeywords = clean(r.input_keywords); row.dataset.title = clean(r.title); row.dataset.imageUrl = clean(r.image_url); row.dataset.detailsPageUrl = clean(r.details_page_url); row.dataset.author = clean(r.author); row.dataset.datetime = clean(r.datetime); row.dataset.authorUrl = clean(r.author_url); row.dataset.likeCount = clean(r.like_count); row.dataset.recommendedReason = clean(r.recommended_reason); box.appendChild(row); }); document.body.appendChild(box); resolve({ keyword, normalized_rows: box.querySelectorAll('.uscraper-xhs-row').length, raw_matches: document.querySelectorAll(rowSelector).length, url: location.href, title: document.title }); });",
        "waitForCompletion": true,
        "timeout": 170,
        "color": "bg-[#ff832b]"
      }
    },
    {
      "block_id": "sleep-3",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 2640,
      "position_y": 240,
      "config": {
        "duration": 2,
        "color": "bg-[#ff832b]"
      }
    },
    {
      "block_id": "structured-export-1",
      "block_type": "process",
      "title": "Structured Export",
      "description": "Export data with custom columns",
      "position_x": 3000,
      "position_y": 240,
      "config": {
        "rowSelector": "#uscraper-xhs-normalized .uscraper-xhs-row",
        "fileName": "xiaohongshu-search-result-scraper-by-keyword.csv",
        "saveLocation": "C:\\Users\\theskd\\Documents\\UScraper\\templates",
        "includeHeaders": true,
        "fileMode": "append",
        "color": "bg-[#42be65]",
        "columns": [
          {
            "name": "input_keywords",
            "selector": "ROW.dataset.inputKeywords || ''",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "title",
            "selector": "ROW.dataset.title || ''",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "image_url",
            "selector": "ROW.dataset.imageUrl || ''",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "details_page_url",
            "selector": "ROW.dataset.detailsPageUrl || ''",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "author",
            "selector": "ROW.dataset.author || ''",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "datetime",
            "selector": "ROW.dataset.datetime || ''",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "author_url",
            "selector": "ROW.dataset.authorUrl || ''",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "like_count",
            "selector": "ROW.dataset.likeCount || ''",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "recommended_reason",
            "selector": "ROW.dataset.recommendedReason || ''",
            "attribute": "text",
            "isJs": true
          }
        ]
      }
    },
    {
      "block_id": "loop-continue-1",
      "block_type": "process",
      "title": "Loop Continue",
      "description": "Continue multi-input loop",
      "position_x": 3360,
      "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": "sleep-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "sleep-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-2",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "sleep-2",
      "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": "sleep-3",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "sleep-3",
      "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": 2480,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "navigate-1",
          "wait-for-page-load-1",
          "sleep-1",
          "sleep-2",
          "sleep-3"
        ]
      }
    },
    {
      "id": "group-interaction",
      "element_type": "group",
      "title": "Interaction",
      "color": "#a56eff",
      "position_x": 1488,
      "position_y": 136,
      "width": 1040,
      "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": 2928,
      "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": 3288,
      "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 Xiaohongshu / Red Note search-result scraper equivalent to the Octoparse template. It exports input keyword, post title, image URL, details page URL, author, datetime, author URL, like count, and recommended reason. Navigation uses editable keyword search URLs in a multi-URL loop. For each keyword, the template loads Xiaohongshu, attempts search UI submission, attempts same-origin API/DOM extraction, infinite-scrolls, normalizes detected notes into stable hidden rows, and appends results to one CSV. If Xiaohongshu renders no public cards because login, CAPTCHA, invalid anti-bot state, or security restrictions are present, the template writes a diagnostic row for that keyword instead of silently producing no file.",
      "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 4 pages. Pair with loop-continue at the end of each iteration.",
      "color": "#ee5396",
      "position_x": 680,
      "position_y": 220,
      "width": 328,
      "height": 107,
      "z_index": 22,
      "data": {
        "block_id": "navigate-1"
      }
    },
    {
      "id": "note-block-inject-javascript-1",
      "element_type": "note",
      "title": "Note: Inject JavaScript",
      "content": "Runs custom JavaScript in the page: `return new Promise(async (resolve) => { const sleep = ms => new Promise(r => setTimeout(r, ms)); let...` 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-inject-javascript-2",
      "element_type": "note",
      "title": "Note: Inject JavaScript",
      "content": "Runs custom JavaScript in the page: `return new Promise(async (resolve) => { const sleep = ms => new Promise(r => setTimeout(r, ms)); let...` Verify in browser if results are empty.",
      "color": "#ee5396",
      "position_x": 2480,
      "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": "Structured export with JS columns (input_keywords, title, image_url, details_page_url, author). These selectors are fragile — update if the site layout changes.",
      "color": "#ee5396",
      "position_x": 3200,
      "position_y": 220,
      "width": 340,
      "height": 133,
      "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": 3560,
      "position_y": 220,
      "width": 340,
      "height": 123,
      "z_index": 22,
      "data": {
        "block_id": "loop-continue-1"
      }
    }
  ]
}