Install
$ agentstack add skill-clawcap-manobrowser-web-data-extractor ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
网页数据提取器
概述
此skill通过执行内联的 DOM 提取脚本,从网页中提取结构化数据。通过以下流程:
- 直接调用
mcp__browser__chrome_execute_script执行内联脚本(脚本内部会自动滚动页面触发所有懒加载内容) - 使用
async () => await (脚本)等待Promise完成并获取JSON结果 - 返回包含文本节点(带上下文)、链接、标题的结构化数据
🚨 最关键要求: 调用 mcp__browser__chrome_execute_script 工具时,必须完整传递步骤2中定义的 jsScript 参数,不能省略、简化或修改任何字符,否则会导致数据提取失败!
核心原则:自动触发数据加载,一次性提取所有结构化内容及上下文,智能过滤和优化数据。
前置要求:
- Chrome MCP服务器已启动并连接
- 无需外部插件或扩展
何时使用此Skill
当用户提出以下请求时调用此skill:
- 从当前网页提取数据
- 抓取网站中的结构化信息
- 获取产品详情、价格、评论或其他电商数据
- 提取社交媒体帖子、评论或互动统计数据
- 导出网页中的表格或列表数据
- 获取任何可见的结构化内容的JSON格式
触发此skill的用户请求示例:
- "提取这个页面的所有产品信息"
- "把这个表格的数据转成JSON"
- "抓取这个社交媒体帖子的内容"
- "获取这个页面的结构化数据"
- "这个页面上有什么数据?"
- "提取当前网页的内容"
数据提取流程
步骤1:验证Chrome MCP工具可用性
在提取数据之前,确认以下工具可用:
mcp__browser__chrome_execute_script- 用于执行JavaScript脚本
如果工具不可访问,告知用户需要启动并连接Chrome MCP服务器。
步骤2:执行提取脚本获取DOM数据
🚨 关键要求:必须完整传递下面的 jsScript 参数,不能省略、简化或修改任何部分!
直接调用 mcp__browser__chrome_execute_script 工具执行内联的提取脚本:
工具参数(必须严格按照下面的完整参数调用):
tabId: 目标标签页ID(可选,不提供则使用当前活动标签页)jsScript: 必须使用下面的完整脚本,一个字符都不能少 →async () => await ((async () => { 'use strict'; window.scrollTo({ top: 0, behavior: 'instant' }); await new Promise(resolve => setTimeout(resolve, 300)); const documentHeight = Math.max(document.body.scrollHeight, document.documentElement.scrollHeight, document.body.offsetHeight, document.documentElement.offsetHeight, document.body.clientHeight, document.documentElement.clientHeight); window.scrollTo({ top: documentHeight, behavior: 'smooth' }); await new Promise(resolve => setTimeout(resolve, 1500)); window.scrollTo({ top: 0, behavior: 'smooth' }); await new Promise(resolve => setTimeout(resolve, 1000)); function shouldSkipElement(element) { if (!element) return true; const skipClassPrefixes = ['vjs-', 'video-js', 'plyr', 'jwplayer', 'ad-', 'advertisement', 'cookie-', 'gdpr-']; if (element.className && typeof element.className === 'string') { const classes = element.className.toLowerCase(); if (skipClassPrefixes.some(prefix => classes.includes(prefix))) { return true; } } let parent = element.parentElement; let depth = 0; while (parent && depth parentClasses.includes(prefix))) { return true; } } parent = parent.parentElement; depth++; } try { const style = window.getComputedStyle(element); if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') { return true; } } catch (e) { } return false; } function extractFilenameFromUrl(url) { if (!url) return url; try { const pathname = url.split('?')[0].split('#')[0]; const parts = pathname.split('/'); const filename = parts[parts.length - 1]; return filename || url; } catch (e) { return url; } } function extractImagesFromElement(element) { if (!element) return []; const images = []; const imgElements = element.querySelectorAll('img[src], img[data-src]'); for (let i = 0; i { const url = match.replace(/url\\(['"]?([^'"()]+)['"]?\\)/, '$1'); if (url && url.trim() !== '' && !url.startsWith('data:')) { images.push(extractFilenameFromUrl(url)); } }); } } } catch (e) { } } return [...new Set(images)]; } function extractSVGPathsFromElement(element) { if (!element) return null; const paths = []; const pathElements = element.querySelectorAll('svg path[d]'); for (let i = 0; i 0 ? paths.join(' ') : null; } function extractTextWithContext(element) { if (!element) return []; if (element.tagName && ['SCRIPT', 'STYLE', 'NOSCRIPT', 'HEAD'].includes(element.tagName)) { return []; } let results = []; if (element.shadowRoot) { const shadowResults = extractTextWithContext(element.shadowRoot); results.push(...shadowResults); } if (element.childNodes && element.childNodes.length > 0) { for (let i = 0; i 0) { nodeData.parentImages = parentImages; } } if (grandParent) { const grandParentImages = extractImagesFromElement(grandParent); if (grandParentImages.length > 0) { nodeData.grandParentImages = grandParentImages; } } let svgPaths = null; if (parent) { svgPaths = extractSVGPathsFromElement(parent); } if (!svgPaths && grandParent) { svgPaths = extractSVGPathsFromElement(grandParent); } if (!svgPaths && greatGrandParent) { svgPaths = extractSVGPathsFromElement(greatGrandParent); } if (svgPaths && svgPaths.length { ['parentImages', 'grandParentImages'].forEach(field => { if (node[field]) { node[field].forEach(url => { urlCounts.set(url, (urlCounts.get(url) || 0) + 1); }); } }); }); const seenUrls = new Map(); nodes.forEach(node => { ['parentImages', 'grandParentImages'].forEach(field => { if (node[field]) { node[field] = node[field].filter(url => { const totalCount = urlCounts.get(url) || 0; if (totalCount { const hasClassNames = node.className || node.parentClassName || node.grandParentClassName; if (hasClassNames) { const key = (node.className || '') + '|' + (node.parentClassName || '') + '|' + (node.grandParentClassName || ''); if (seenKeys.has(key)) { delete node.className; delete node.parentClassName; delete node.grandParentClassName; } else { seenKeys.add(key); } } }); return nodes; } let textNodes = extractTextWithContext(document.body); textNodes = deduplicateImagesGlobally(textNodes); textNodes = deduplicateNodesByClassNames(textNodes); const result = { url: window.location.href, title: document.title, textNodes: textNodes }; window.extractedData = result; console.log('✅ v0.05'); return JSON.stringify(result); })())world:MAIN(在页面主上下文中执行)timeout:15000(超时时间15秒,因为包含智能滚动和数据提取)
⚠️ 特别强调:
- 上面的
jsScript参数值必须完整复制,不能简化、不能用变量替代、不能省略任何部分 - 这是一个完整的内联脚本,包含智能滚动 + 数据提取 + 控制台输出的完整逻辑
- 任何修改都可能导致数据提取不完整或失败
⚠️ 重要说明:
- 脚本会自动执行智能滚动策略触发所有懒加载内容,然后提取数据
- 脚本返回Promise,需要使用
async () => await (脚本内容)来获取结果 - 返回的数据是完整的 JSON 字符串(已经用
JSON.stringify()包裹) - 返回的数据包含:url、title、textNodes(文本节点数组)
脚本执行流程:
- 滚动到页面顶部(重置位置)
- 滚动到页面底部并等待1.5秒(触发所有懒加载内容)
- 滚动回顶部并等待1秒(确保内容稳定)
- 从 document.body 开始提取所有文本节点及其上下文(包括 Shadow DOM、相关图片URL 和 SVG path 数据)
- 🔥 执行全局图片URL去重(只对出现次数>2的URL去重,保留前2次;URL已转换为文件名格式)
- 🔥 执行className字段去重(相同的 className 组合只保留第一次出现,后续节点删除 className 字段但保留 text、images 和 svg 字段)
- 🆕 提取SVG path信息(从父、祖父、曾祖父元素中查找SVG图标,只保留长度<1000的path数据)
- 打印版本号到控制台(v0.05)
- 返回结构化的 JSON 字符串(包装在Promise中)
图片URL处理逻辑说明:
- 所有图片URL自动转换为文件名格式(只保留最后的文件名部分,如
like_icon.png) - 去重策略:只对出现次数超过2次的URL进行去重,保留前2次出现
- 如果某个图片文件名在全局只出现1-2次,则全部保留
- 如果某个图片文件名在全局出现3次及以上,则只保留前2次,后续删除
className 去重逻辑说明:
- 遍历所有文本节点,基于
className + parentClassName + grandParentClassName组合生成唯一键 - 对于相同的组合,只保留第一次出现的 className 字段,后续节点删除这些字段
- 所有节点的 text 字段和 images 字段都会保留,不会删除任何节点
- 这样可以大幅减少重复的 className 数据,同时保留所有文本内容
返回数据格式(JSON 字符串,优化后):
{
"url": "https://example.com/page",
"title": "页面标题",
"textNodes": [
{
"text": "123",
"className": "like-count",
"parentClassName": "stats-wrapper",
"grandParentClassName": "video-info",
"parentImages": ["like_icon.png"]
},
{
"text": "456",
"parentImages": ["share_icon.png"]
},
{
"text": "274",
"svg": "M-4.644999980926514,4.482999801635742 C-7.25,7.818999767303467..."
},
{
"text": "评论"
},
{
"text": "1211"
}
]
}
🆕 SVG Path 字段说明:
- 从 v0.05 版本开始,新增
svg字段用于存储文本节点关联的 SVG 图标路径数据 - 提取逻辑:在文本节点的父、祖父、曾祖父元素中查找 SVG path 元素
- 长度限制:只保留长度小于 1000 字符的 path 数据(过滤复杂动画SVG)
- 用途:通过 SVG path 的形状特征推断数字的含义(如评论图标、点赞图标等)
💡 如何利用 SVG Path 推断数字含义: 当文本节点包含 svg 字段时,应该根据 path 数据推断该数字代表的含义:
- 评论图标特征:通常包含聊天气泡形状,path中会有圆角矩形、三角形(气泡尾巴)等几何形状
- 点赞图标特征:通常为竖起的大拇指或爱心形状
- 分享图标特征:通常为箭头、链接符号或分享符号
- 收藏图标特征:通常为星星、书签或旗帜形状
- 播放/观看图标特征:通常为眼睛、播放按钮(三角形)等形状
通过分析 svg 字段的 path 数据,可以更准确地判断纯数字节点代表的含义。
⚠️ 通用的语义映射规则: 语义字段 等价表达 阅读量 次观看、次播放、阅读、浏览、views、plays 点赞量 赞、点赞、likes、👍 评论量 评论、条评论、comments、replies 转发量 转发、分享、shares、retweets 收藏量 收藏、favorites、bookmarks
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: ClawCap
- Source: ClawCap/ManoBrowser
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.