无需登录 数据私有 本地保存

结构化数据测试 - 粘贴代码实时预览

31
0
0
0
代码输入 JSON-LD
字符: 0 行: 0
在左侧输入结构化数据
即可在此查看树形解析结果
解析结构化数据后
在此模拟 Google 搜索结果展示
在左侧输入代码
在此查看美化后的输出
常见问题与知识点

结构化数据是一种标准化格式,用于向搜索引擎提供关于网页内容的明确信息。它使用 Schema.org 词汇表,帮助搜索引擎理解页面内容(如产品、文章、活动、食谱等),从而在搜索结果中展示富媒体摘要(Rich Results),如星级评分、价格、面包屑导航等。最常见的格式是 JSON-LD(Google推荐)、Microdata和RDFa。

  • JSON-LD:使用JavaScript对象表示法,独立于HTML标签,放在 <script type="application/ld+json"> 中,Google推荐使用,维护方便。
  • Microdata:直接在HTML标签中使用 itemscopeitemtypeitemprop 属性标记数据,与HTML紧密耦合。
  • RDFa:使用属性扩展HTML,支持更复杂的语义关系,但语法较为繁琐,使用较少。

非常简单: 复制您的JSON-LD代码或包含结构化数据的HTML片段; 粘贴到左侧输入框(或点击"粘贴"按钮); 右侧实时显示解析结果——"结构化数据树"展示完整数据层级,"Rich Result模拟"预览Google搜索展示效果,"美化代码"提供格式化输出。您也可以点击"加载示例"快速体验。

Google Rich Results是搜索结果中超越普通蓝色链接的增强展示形式,包括星级评分、产品价格、库存状态、食谱图片、面包屑导航、FAQ折叠、活动时间地点等。这些丰富展示依赖于页面中正确实施的结构化数据。使用本工具的"Rich Result模拟"标签页,可以直观预览您的结构化数据在Google搜索中可能呈现的效果。

常见错误包括:①JSON语法错误(缺少逗号、引号不匹配、尾随逗号);②缺少@context(必须包含 "@context": "https://schema.org");③缺少必填字段(如Product类型需要name字段);④类型错误(如价格应为数字而非字符串);⑤嵌套结构不正确。本工具会高亮显示解析错误和缺失字段。

当然可以!一个页面可以包含多个 <script type="application/ld+json"> 标签,每个标签定义不同类型或不同实体的结构化数据。例如,一个产品页面可以同时包含Product、BreadcrumbList和Organization三种结构化数据。本工具会自动检测并分别解析每个JSON-LD块。
提示:本工具在浏览器本地解析数据,不会上传您的代码到任何服务器。支持纯JSON-LD及HTML中嵌入的多个结构化数据块。
const scriptRegex = /]*type\s*=\s*["']application\/ld\+json["'][^>]*>([\s\S]*?)<\/script>/gi; let match; while ((match = scriptRegex.exec(text)) !== null) { blocks.push({ raw: match[1].trim(), isHTML: true, fullMatch: match[0] }); } // 如果没有找到script标签,尝试作为纯JSON解析 if (blocks.length === 0) { const trimmed = text.trim(); if (trimmed.startsWith('{') || trimmed.startsWith('[')) { blocks.push({ raw: trimmed, isHTML: false, fullMatch: trimmed }); } else if (trimmed.length > 0) { // 可能是HTML但没找到JSON-LD,尝试提取任何JSON对象 const jsonObjRegex = /\{[\s\S]*"@(?:context|type)"[\s\S]*\}/g; while ((match = jsonObjRegex.exec(trimmed)) !== null) { blocks.push({ raw: match[0].trim(), isHTML: true, fullMatch: match[0] }); } } } return blocks; } // 解析所有块 function parseAllBlocks(blocks) { const results = []; detectedTypes = []; for (const block of blocks) { try { const parsed = JSON.parse(block.raw); results.push({ success: true, data: parsed, raw: block.raw, isHTML: block.isHTML }); // 提取类型 if (parsed['@type']) { const type = parsed['@type']; if (Array.isArray(type)) { detectedTypes.push(...type); } else { detectedTypes.push(type); } } if (Array.isArray(parsed)) { parsed.forEach(item => { if (item && item['@type']) { const t = item['@type']; if (Array.isArray(t)) detectedTypes.push(...t); else detectedTypes.push(t); } }); } } catch (e) { results.push({ success: false, error: e.message, raw: block.raw, isHTML: block.isHTML }); } } detectedTypes = [...new Set(detectedTypes)]; return results; } // 渲染JSON树 function renderJSONTree(data, depth = 0, maxDepth = 12) { if (depth > maxDepth) return '...(嵌套过深)'; if (data === null) return 'null'; if (typeof data === 'boolean') return '' + data + ''; if (typeof data === 'number') return '' + data + ''; if (typeof data === 'string') { const escaped = data.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); return '"' + escaped + '"'; } if (Array.isArray(data)) { if (data.length === 0) return '[] 0项'; const id = 'tree-' + Math.random().toString(36).substr(2, 8); let html = ' [ ' + data.length + '项'; html += '
'; data.forEach((item, i) => { html += '
[' + i + ']: ' + renderJSONTree(item, depth + 1, maxDepth) + '
'; }); html += '
]'; return html; } if (typeof data === 'object') { const keys = Object.keys(data); if (keys.length === 0) return '{} 0属性'; const id = 'tree-' + Math.random().toString(36).substr(2, 8); let html = ' { ' + keys.length + '属性'; html += '
'; keys.forEach(key => { html += '
"' + key.replace(/&/g, '&').replace(//g, '>') + '": ' + renderJSONTree(data[key], depth + 1, maxDepth) + '
'; }); html += '
}'; return html; } return String(data); } // 模拟Rich Result function renderRichResult(blocks) { let allData = []; blocks.forEach(block => { if (block.success) { if (Array.isArray(block.data)) { allData.push(...block.data); } else { allData.push(block.data); } } }); if (allData.length === 0) return '
无法解析结构化数据以生成Rich Result预览
'; let html = ''; allData.forEach((data, idx) => { if (!data || !data['@type']) return; const types = Array.isArray(data['@type']) ? data['@type'] : [data['@type']]; types.forEach(type => { html += renderRichResultByType(type, data, idx); }); }); if (!html) html = '
当前结构化数据类型暂不支持Rich Result模拟预览。请查看"结构化数据树"标签页。
'; return html; } function renderRichResultByType(type, data, idx) { const typeLower = type.toLowerCase(); let card = ''; if (typeLower.includes('product')) { card = renderProductCard(data); } else if (typeLower.includes('article') || typeLower.includes('blogposting') || typeLower.includes('newsarticle')) { card = renderArticleCard(data); } else if (typeLower.includes('localbusiness') || typeLower.includes('organization') || typeLower.includes('store')) { card = renderBusinessCard(data); } else if (typeLower.includes('breadcrumblist')) { card = renderBreadcrumbCard(data); } else if (typeLower.includes('faqpage') || typeLower.includes('question')) { card = renderFAQCard(data); } else if (typeLower.includes('recipe')) { card = renderRecipeCard(data); } else if (typeLower.includes('event')) { card = renderEventCard(data); } else if (typeLower.includes('howto')) { card = renderHowToCard(data); } else { card = renderGenericCard(data, type); } if (card && allBlocks.length > 1) { card = '' + card; } return card; } function getVal(obj, ...keys) { for (const key of keys) { if (obj && obj[key] !== undefined) return obj[key]; } return null; } function renderProductCard(data) { const name = getVal(data, 'name') || '未命名产品'; const description = getVal(data, 'description') || ''; const image = getVal(data, 'image'); const imgUrl = typeof image === 'object' ? (image.url || image['@id'] || '') : (image || ''); const offers = data.offers; const price = offers ? (offers.price || (typeof offers === 'object' && offers.price)) : null; const currency = offers && offers.priceCurrency ? offers.priceCurrency : 'CNY'; const availability = offers && offers.availability ? String(offers.availability).replace('https://schema.org/', '') : ''; const rating = data.aggregateRating; const ratingValue = rating ? parseFloat(rating.ratingValue) : null; const reviewCount = rating ? (rating.reviewCount || rating.ratingCount) : null; const brand = getVal(data, 'brand'); const brandName = typeof brand === 'object' ? (brand.name || '') : (brand || ''); let card = '
'; card += '
example.com › products › ' + encodeURIComponent(String(name).toLowerCase().replace(/\s+/g, '-')) + '
'; card += '
' + escapeHTML(String(name)) + (brandName ? ' - ' + escapeHTML(String(brandName)) : '') + '
'; if (ratingValue) { card += '
'; card += ''; const fullStars = Math.floor(ratingValue); const halfStar = ratingValue - fullStars >= 0.5; for (let i = 0; i < 5; i++) { if (i < fullStars) card += '★'; else if (i === fullStars && halfStar) card += '⯪'; else card += '☆'; } card += ''; card += '' + ratingValue.toFixed(1) + (reviewCount ? ' (' + reviewCount + '条评价)' : '') + ''; card += '
'; } if (price !== null && price !== undefined) { const currencySymbol = currency === 'CNY' ? '¥' : currency === 'USD' ? '$' : currency === 'EUR' ? '€' : currency + ' '; card += '
' + currencySymbol + parseFloat(price).toFixed(2) + '
'; } if (availability) { const isInStock = availability.toLowerCase().includes('instock'); card += '
' + (isInStock ? '✓ 有货' : '✗ ' + availability.replace('InStock', '有货').replace('OutOfStock', '缺货').replace('PreOrder', '预售')) + '
'; } if (description) { card += '
' + escapeHTML(String(description)).substring(0, 160) + (String(description).length > 160 ? '...' : '') + '
'; } card += '
'; return card; } function renderArticleCard(data) { const headline = getVal(data, 'headline', 'name') || '未命名文章'; const description = getVal(data, 'description', 'abstract') || ''; const author = data.author; const authorName = typeof author === 'object' ? (author.name || '') : (author || ''); const datePublished = getVal(data, 'datePublished', 'dateCreated') || ''; const image = getVal(data, 'image'); const imgUrl = typeof image === 'object' ? (image.url || '') : (image || ''); let card = '
'; card += '
example.com › article › ' + encodeURIComponent(String(headline).toLowerCase().replace(/\s+/g, '-').substring(0, 40)) + '
'; card += '
' + escapeHTML(String(headline)) + '
'; card += '
'; if (datePublished) card += ' ' + escapeHTML(String(datePublished)).substring(0, 10) + ''; if (authorName) card += ' ' + escapeHTML(String(authorName)) + ''; card += '
'; if (description) { card += '
' + escapeHTML(String(description)).substring(0, 160) + (String(description).length > 160 ? '...' : '') + '
'; } card += '
'; return card; } function renderBusinessCard(data) { const name = getVal(data, 'name') || '未命名商家'; const address = data.address; const addrStr = typeof address === 'object' ? (address.streetAddress || address.addressLocality || '') : (address || ''); const telephone = getVal(data, 'telephone', 'phone') || ''; const rating = data.aggregateRating; const ratingValue = rating ? parseFloat(rating.ratingValue) : null; let card = '
'; card += '
' + escapeHTML(String(name)) + '
'; if (ratingValue) { card += '
'; const fs = Math.floor(ratingValue); for (let i = 0; i < 5; i++) card += i < fs ? '★' : '☆'; card += '' + ratingValue.toFixed(1) + '
'; } card += '
'; if (addrStr) card += ' ' + escapeHTML(String(addrStr)) + ''; if (telephone) card += ' ' + escapeHTML(String(telephone)) + ''; card += '
'; card += '
'; return card; } function renderBreadcrumbCard(data) { const items = data.itemListElement || []; let card = '
'; card += '
'; if (Array.isArray(items) && items.length > 0) { items.forEach((item, i) => { const itemName = typeof item === 'object' ? (item.name || item.item?.name || '') : String(item); if (i > 0) card += ' '; if (i === items.length - 1) { card += '' + escapeHTML(String(itemName)) + ''; } else { card += '' + escapeHTML(String(itemName)) + ''; } }); } else { card += '首页 当前页面'; } card += '
'; return card; } function renderFAQCard(data) { const entities = data.mainEntity || (Array.isArray(data) ? data : [data]); const questions = []; if (Array.isArray(entities)) { entities.forEach(e => { if (e && e.name && e.acceptedAnswer) { questions.push({ q: e.name, a: typeof e.acceptedAnswer === 'object' ? (e.acceptedAnswer.text || '') : String(e.acceptedAnswer) }); } }); } if (questions.length === 0 && data.mainEntity && Array.isArray(data.mainEntity)) { data.mainEntity.forEach(e => { if (e && e.name && e.acceptedAnswer) { questions.push({ q: e.name, a: typeof e.acceptedAnswer === 'object' ? (e.acceptedAnswer.text || '') : String(e.acceptedAnswer) }); } }); } if (questions.length === 0) { return '
FAQ 结构化数据
检测到FAQPage类型,但未找到具体问答内容。请确保包含 mainEntity 数组。
'; } let card = '
example.com › faq
常见问题
'; questions.forEach((q, i) => { const qid = 'rr-faq-' + Math.random().toString(36).substr(2, 6); card += '
'; card += '
' + escapeHTML(String(q.q)) + '
'; card += '
' + escapeHTML(String(q.a)).substring(0, 200) + '
'; card += '
'; }); card += '
'; return card; } function renderRecipeCard(data) { const name = getVal(data, 'name') || '未命名食谱'; const description = getVal(data, 'description') || ''; const cookTime = getVal(data, 'cookTime', 'totalTime') || ''; const calories = getVal(data, 'nutrition', 'calories') || (data.nutrition ? data.nutrition.calories : ''); const rating = data.aggregateRating; const ratingValue = rating ? parseFloat(rating.ratingValue) : null; let card = '
'; card += '
example.com › recipes › ' + encodeURIComponent(String(name).toLowerCase().replace(/\s+/g, '-')) + '
'; card += '
' + escapeHTML(String(name)) + '
'; if (ratingValue) { card += '
'; const fs = Math.floor(ratingValue); for (let i = 0; i < 5; i++) card += i < fs ? '★' : '☆'; card += '' + ratingValue.toFixed(1) + '
'; } card += '
'; if (cookTime) card += ' ' + escapeHTML(String(cookTime)) + ''; if (calories) card += ' ' + escapeHTML(String(calories)) + ' cal'; card += '
'; if (description) { card += '
' + escapeHTML(String(description)).substring(0, 140) + '
'; } card += '
'; return card; } function renderEventCard(data) { const name = getVal(data, 'name') || '未命名事件'; const startDate = getVal(data, 'startDate') || ''; const location = data.location; const locName = typeof location === 'object' ? (location.name || location.address?.addressLocality || '') : (location || ''); const description = getVal(data, 'description') || ''; let card = '
'; card += '
' + escapeHTML(String(name)) + '
'; card += '
'; if (startDate) card += ' ' + escapeHTML(String(startDate)).substring(0, 16) + ''; if (locName) card += ' ' + escapeHTML(String(locName)) + ''; card += '
'; if (description) card += '
' + escapeHTML(String(description)).substring(0, 140) + '
'; card += '
'; return card; } function renderHowToCard(data) { const name = getVal(data, 'name') || '操作指南'; const steps = data.step || []; const estCost = getVal(data, 'estimatedCost', 'totalTime') || ''; let card = '
'; card += '
📋 ' + escapeHTML(String(name)) + '
'; if (estCost) card += '
' + escapeHTML(String(estCost)) + '
'; if (Array.isArray(steps) && steps.length > 0) { card += '
'; steps.forEach((step, i) => { const stepText = typeof step === 'object' ? (step.text || step.name || step.description || '') : String(step); card += '
步骤' + (i + 1) + ':' + escapeHTML(String(stepText)).substring(0, 100) + '
'; }); card += '
'; } card += '
'; return card; } function renderGenericCard(data, type) { const name = getVal(data, 'name', 'headline') || type; const description = getVal(data, 'description') || ''; let card = '
'; card += '
example.com
'; card += '
' + escapeHTML(String(name)) + '
'; card += '
类型: ' + escapeHTML(type) + '
'; if (description) card += '
' + escapeHTML(String(description)).substring(0, 150) + '
'; card += '
'; return card; } function escapeHTML(str) { const div = document.createElement('div'); div.textContent = str; return div.innerHTML; } // 更新预览 function updatePreview() { const text = $textarea.val(); // 更新统计 const charCount = text.length; const lineCount = text ? text.split('\n').length : 0; $codeStats.find('strong').eq(0).text(charCount); $codeStats.find('strong').eq(1).text(lineCount); allBlocks = extractJSONLDBlocks(text); const parseResults = parseAllBlocks(allBlocks); parsedData = parseResults.filter(r => r.success); parseError = parseResults.find(r => !r.success); // 更新格式标签 if (allBlocks.length > 0 && allBlocks[0].isHTML) { $formatBadge.text('HTML + JSON-LD').removeClass('bg-secondary').addClass('bg-info'); } else if (allBlocks.length > 0 && !allBlocks[0].isHTML) { $formatBadge.text('JSON-LD').removeClass('bg-info').addClass('bg-secondary'); } else { $formatBadge.text('等待输入').removeClass('bg-info').addClass('bg-secondary'); } // 更新块计数 if (allBlocks.length > 0) { $blockCount.show().find('strong').text(allBlocks.length); } else { $blockCount.hide(); } // 更新类型信息 if (detectedTypes.length > 0) { $typeInfo.show().find('strong').text(detectedTypes.join(', ')); } else if (allBlocks.length > 0 && parseResults.some(r => r.success)) { $typeInfo.show().find('strong').text('未指定@type'); } else { $typeInfo.hide(); } // 渲染树形视图 if (parsedData.length > 0) { $treePlaceholder.hide(); $treeOutput.show(); let treeHTML = ''; if (parseError) { treeHTML += '
部分数据解析失败:' + escapeHTML(parseError.error) + '
'; } if (detectedTypes.length > 0) { treeHTML += '
'; detectedTypes.forEach(t => { treeHTML += '' + escapeHTML(t) + ''; }); treeHTML += '
'; } if (parsedData.length > 1) { treeHTML += ''; } parsedData.forEach((block, idx) => { if (parsedData.length > 1) { treeHTML += ''; } treeHTML += '
' + renderJSONTree(block.data) + '
'; }); $treeOutput.html(treeHTML); // 绑定折叠事件 $treeOutput.find('.json-toggle').off('click').on('click', function() { const target = $(this).data('target'); $(target).slideToggle(150); $(this).toggleClass('collapsed'); }); } else if (allBlocks.length > 0 && parseResults.every(r => !r.success)) { $treePlaceholder.hide(); $treeOutput.show(); $treeOutput.html('
JSON解析错误:' + escapeHTML(parseResults[0]?.error || '未知错误') + '
请检查代码中的引号、逗号、括号是否匹配
'); } else { $treePlaceholder.show(); $treeOutput.hide().html(''); } // 渲染Rich Result if (parsedData.length > 0) { $richPlaceholder.hide(); $richOutput.show(); $richOutput.html(renderRichResult(parsedData)); } else if (allBlocks.length > 0 && parseResults.every(r => !r.success)) { $richPlaceholder.hide(); $richOutput.show(); $richOutput.html('
代码解析失败,无法生成Rich Result预览
'); } else { $richPlaceholder.show(); $richOutput.hide().html(''); } // 渲染美化代码 if (parsedData.length > 0) { $rawPlaceholder.hide(); $rawOutput.show(); let rawHTML = ''; parsedData.forEach((block, idx) => { if (parsedData.length > 1) { rawHTML += '// 数据块 #' + (idx + 1) + '\n'; } rawHTML += JSON.stringify(block.data, null, 2); if (idx < parsedData.length - 1) rawHTML += '\n\n'; }); $rawOutput.text(rawHTML); } else if (allBlocks.length > 0 && parseResults.every(r => !r.success)) { $rawPlaceholder.hide(); $rawOutput.show(); $rawOutput.text('// 解析失败:' + parseResults[0]?.error + '\n// 请检查JSON语法'); } else { $rawPlaceholder.show(); $rawOutput.hide().text(''); } } const debouncedUpdate = debounce(updatePreview, 300); // 输入事件 $textarea.on('input', function() { debouncedUpdate(); }); // 标签切换 $(document).on('click', '#stt-preview-pane .preview-nav-btn', function() { const tab = $(this).data('tab'); $('#stt-preview-pane .preview-nav-btn').removeClass('active'); $(this).addClass('active'); $('#stt-preview-content .preview-tab').removeClass('active'); $('#stt-tab-' + tab).addClass('active'); }); // 粘贴按钮 $('#stt-btn-paste').on('click', function() { if (navigator.clipboard && navigator.clipboard.readText) { navigator.clipboard.readText().then(text => { $textarea.val(text).trigger('input'); updatePreview(); }).catch(() => { $textarea.focus(); alert('无法访问剪贴板,请手动粘贴 (Ctrl+V / Cmd+V)'); }); } else { $textarea.focus(); alert('您的浏览器不支持剪贴板访问,请手动粘贴 (Ctrl+V / Cmd+V)'); } }); // 清空按钮 $('#stt-btn-clear').on('click', function() { $textarea.val('').trigger('input'); updatePreview(); $textarea.focus(); }); // 美化按钮 $('#stt-btn-beautify').on('click', function() { if (parsedData.length > 0) { let beautified = ''; parsedData.forEach((block, idx) => { if (parsedData.length > 1) beautified += '// 数据块 #' + (idx + 1) + '\n'; beautified += JSON.stringify(block.data, null, 2); if (idx < parsedData.length - 1) beautified += '\n\n'; }); $textarea.val(beautified); updatePreview(); } else if (allBlocks.length > 0) { // 尝试格式化原始JSON try { const formatted = JSON.stringify(JSON.parse(allBlocks[0].raw), null, 2); $textarea.val(formatted); updatePreview(); } catch (e) { alert('无法美化:' + e.message); } } }); // 复制结果按钮 $('#stt-btn-copy-result').on('click', function() { if (parsedData.length > 0) { let text = ''; parsedData.forEach((block, idx) => { if (parsedData.length > 1) text += '// 数据块 #' + (idx + 1) + '\n'; text += JSON.stringify(block.data, null, 2); if (idx < parsedData.length - 1) text += '\n\n'; }); if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(text).then(() => { const btn = $('#stt-btn-copy-result'); const origHTML = btn.html(); btn.html(' 已复制'); btn.removeClass('btn-outline-info').addClass('btn-success'); setTimeout(() => { btn.html(origHTML); btn.removeClass('btn-success').addClass('btn-outline-info'); }, 1500); }).catch(() => { alert('复制失败,请手动选择并复制'); }); } else { // 回退 const tempTextarea = $('