如何通过 JavaScript 大规模生成 JSON-LD 架构,带来不可思议的搜索引擎优化效果!

本教程展示了如何利用 Screaming Frog SEO Spider 最近发布的自定义 JavaScript 功能来大规模创建 JSON-LD 架构标记。我将通过少量代码演示如何从网页中提取元素,并将每个元素集成到包含变量的结构化数据脚本中。

对于那些可能不了解 JavaScript 或害怕任何代码的人,您可以使用您最喜欢的 LLM,例如 ChatGPT,来帮助您。我个人会推荐 Mike King 的 Kermit——一个自定义 GPT,旨在专门帮助您使用 Screaming Frog SEO Spider JS 片段!

在本教程的最后,我的目标是让您使用本指南通过 Screaming Frog SEO Spider 自动创建结构化数据——无论是数十个、数百个还是数千个网页!

什么是自定义 JavaScript 代码段?

2024 年 5 月,Screaming Frog SEO Spider 在其 20.0 版更新中引入了自定义 JavaScript 片段。根据发布说明,JS 片段允许您“作页面或提取数据,以及与 OpenAI 的 ChatGPT、本地 LLM 或其他库等 API 进行通信。

自定义 JS Snippet 设置预打包了一些开箱即用的片段,例如使用 AI 生成图像替代文本、查询 ChatGPT、提取嵌入等。

通过一些创造性的思考,您可以使用 JS 片段功能的功能将标准爬取输出转换为应对各种 SEO 挑战的强大工具,从而使网站爬取更加有效。

为什么使用 Screaming Frog 大规模创建结构化数据?

结构化数据是一种机器代码,有助于向搜索引擎描述网页。它可以影响搜索引擎解释和显示您的内容的方式。实施 JSON-LD 架构标记可以在 SERP 中产生丰富的片段,增强知识图谱,提高可见性,并可能提高点击率。

更可取的选项是始终以编程方式创建架构标记,无论是使用预构建的 CMS 功能、插件还是开发人员的支持。

但是,在某些情况下,您无权访问编程选项。这就是 Screaming Frog 的 JS 片段提供了一个实用的替代方案的时候,无需依赖在线模式标记生成器或为每个页面手动编写代码。所有这些都可能非常耗时且效率低下。

选择适当的架构类型和页面模板

在开始之前,我们需要确定需要什么结构化数据脚本。出于本文的目的,我使用了一个虚构的场景,在该场景中,我将使用 “article” 架构填充 Search Engine Journal (SEJ) 的每篇博客文章。

对于您网站的每个页面模板,您可能需要不同的脚本。例如,电子商务网站的主页、产品页面和博客文章可能需要不同的架构。

此外,您需要弄清楚网页上有哪些元素可用,您可以从文本或 HTML 中提取这些元素。这将确定 schema 标记的可能性。例如,如果您的博客文章没有引用作者,则添加 “author” 架构类型将毫无意义。

JSON-LD 架构标记模板

如前所述,在这个虚构的示例中,我将基于“article”架构类型创建一个 JSON-LD 脚本,该架构类型还将包括 “person”、“organization” 和 “website” 类型。通过一次抓取,该脚本将允许我们为 SEJ 的每篇博客文章生成一个有效的 schema。

我将使用 “Getting Started In International SEO: A Quick Reference Guide” 文章作为我的豚鼠测试页面,如下所示:

<script type="application/ld+json">
{
  "@context": "https://schema.org/",
  "@type": "Article",
  "url": "https://www.searchenginejournal.com/getting-started-in-international-seo-a-quick-reference-guide/529763/",
  "@id": "https://www.searchenginejournal.com/getting-started-in-international-seo-a-quick-reference-guide/529763/#article",
  "headline": "Getting Started In International SEO: A Quick Reference Guide",
  "description": "Expand your reach with international SEO. This guide explores the unique challenges and strategies for succeeding in global markets.",
  "datePublished": "2024-11-11T10:00:43+00:00",
  "wordCount": 2060,
  "timeRequired": "PT10M",
  "image": {
    "@type": "ImageObject",
    "url": "https://www.searchenginejournal.com/wp-content/uploads/2024/10/international-seo-794.png",
    "height": "840",
    "width": "1600"
  },
  "speakable": {
    "@type": "SpeakableSpecification",
    "cssSelector": [
      "h1",
      ".sej-article entrycontent"
    ]
  },
  "author": {
    "@type": "Person",
    "name": "Motoko Hunt",
    "url": "https://www.searchenginejournal.com/author/motoko-hunt/",
    "@id": "https://www.searchenginejournal.com/author/motoko-hunt//#person",
    "jobTitle": "President, International Search Marketing",
    "worksFor": {
      "@type": "Organization",
      "name": "AJPR",
      "url": "https://www.ajpr.com/"
    },
    "sameAs": [
      "https://www.searchenginejournal.com/author/motoko-hunt/feed/",
      "https://twitter.com/motokohunt",
      "https://www.linkedin.com/in/japaneseseo/"
    ]
  },
  "publisher": {
    "@type": "Organization",
    "@id": "https://www.searchenginejournal.com/#organization",
    "url": "https://www.searchenginejournal.com",
    "sameAs": [
      "https://twitter.com/sejournal",
      "https://www.facebook.com/SearchEngineJournal",
      "https://www.linkedin.com/company/search-engine-journal",
      "https://www.youtube.com/c/searchenginejournal",
      "https://www.reddit.com/user/SearchEngineJournal",
      "https://www.google.com/search?kgmid=/m/011sh7hw",
      "http://www.pinterest.com/sejournal/"
    ],
    "name": "Search Engine Journal",
    "logo": [
      {
        "@type": "ImageObject",
        "@id": "https://www.searchenginejournal.com/#logo",
        "inLanguage": "en-US",
        "url": "https://www.searchenginejournal.com/wp-content/themes/sej/images/schema/compact.png",
        "width": 1000,
        "height": 1000,
        "caption": "Search Engine Journal"
      }
    ],
    "foundingDate": "2003",
    "slogan": "In a world ruled by algorithms, SEJ brings timely, relevant information for SEOs, marketers, and entrepreneurs to optimize and grow their businesses -- and careers.",
    "description": "Search Engine Journal is dedicated to producing the latest news, the best guides and how-tos for the SEO and marketer community.",
    "legalName": "Search Engine Journal",
    "alternateName": "SEJ"
  },
  "isPartOf": {
    "@type": "WebSite",
    "name": "Search Engine Journal",
    "@id": "https://www.searchenginejournal.com/#website",
    "url": "https://www.searchenginejournal.com/",
    "mainEntity": {
      "@id": "https://www.searchenginejournal.com/getting-started-in-international-seo-a-quick-reference-guide/529763/#article"
    }
  }
}
</script>

该脚本使用 “article” 架构类型。首先,我将 “person” 架构类型嵌套在 “author” 属性中。其次,“organization” 将嵌套在 “publisher” 属性中,第三,“website” 架构类型将嵌套在 “isPartOf” 属性中。

更简单的 AI 编辑选项

要成功实施此方法,您需要对爬虫 Web 抓取和自定义提取功能有一定的基本了解。

您可以轻松地将下面的 JS 代码片段代码复制粘贴到您最喜欢的 LLM 或 Mike King 的 Kermit(Screaming Frog SEO Spider JS 片段 GPT)作为参考和起点。您必须创建自己的 JSON-LD 脚本,并向 LLM 详细说明要从网页中提取的元素。但是,繁重的工作应该为您完成,并且使用 AI 进行此编辑非常高效且具有时间效益。

对于那些想要更好地了解代码和每个部分的目的的人,您可以深入研究以下信息。

JS 代码段代码

当 Screaming Frog SEO Spider 抓取每个页面时,我将使用 JavaScript 代码从页面中提取各种元素,并将它们作为变量添加到 JSON-LD 结构化数据中。

以下是完整 JS Snippet 代码的参考:

// Extract the URL of the webpage
let url = window.location.href;
let urlId = `${url}#article`;


// Extract the first H1 tag
let h1 = document.querySelector('h1') ? document.querySelector('h1').textContent.trim() : '';


// Extract the meta description
let metaDescription = document.querySelector('meta[name="description"]') ? document.querySelector('meta[name="description"]').content.trim() : 'No Meta Description Found';


// Extract the published date from the datetime attribute in the <time> tag within .sej-auth-t
let datePublished = 'No Date Found';
let dateElement = document.querySelector('.sej-auth-t time');
if (dateElement) {
    datePublished = dateElement.getAttribute('datetime');
}


// Select the element containing the author information
let authorElement = document.querySelector('.dark-link.sej-auth-h');


// Initialize author details with empty strings
let authorName = '';
let authorUrl = '';
let authorId = '';
let sameAsLinks = '';


// Check if the author element exists, then extract the name and URL
if (authorElement) {
    authorName = authorElement.textContent.trim(); // Extract and clean up author name
    authorUrl = authorElement.href;                // Extract author URL
    authorId = `${authorUrl}/#person`;             // Construct @id by appending /#person


    // Extract all social media URLs within the .sej-asocial class for the sameAs array
    sameAsLinks = Array.from(document.querySelectorAll('.sej-asocial li a')).map(link => link.href);
}


// Extract the image URL for the author
let imageUrl = '';
let imageElement = document.querySelector('.avatar.img-circle');
if (imageElement) {
    imageUrl = imageElement.src;
}


// Count the total number of words on the page
let wordCount = document.body.innerText.split(/\s+/).length;


// Extract the reading time and convert to ISO 8601 duration
let timeRequired = '';
let readingTimeElement = document.querySelector('.sej-auth-t li:nth-child(3)');
if (readingTimeElement) {
    let readingTimeText = readingTimeElement.textContent.trim();
    let timeMatch = readingTimeText.match(/(\d+)\s*min/);
    if (timeMatch) {
        let minutes = parseInt(timeMatch[1]);
        timeRequired = `PT${minutes}M`; // Convert to ISO 8601 duration format
    }
}


// Extract hero image URL, width, and height directly from the hero image element
let heroImageUrl = '';
let heroImageWidth = '';
let heroImageHeight = '';


let heroImageElement = document.querySelector('.attachment-full.size-full.wp-post-image');
if (heroImageElement) {
    heroImageUrl = heroImageElement.src;


    // Try to get width and height from attributes first
    heroImageWidth = heroImageElement.getAttribute('width');
    heroImageHeight = heroImageElement.getAttribute('height');


    // If width or height are missing, parse the largest values from srcset
    if (!heroImageWidth || !heroImageHeight) {
        let srcset = heroImageElement.getAttribute('srcset');
        if (srcset) {
            let largestImage = srcset.split(',').map(entry => {
                let [url, size] = entry.trim().split(' ');
                return { url, size: parseInt(size) };
            }).sort((a, b) => b.size - a.size)[0];


            // Set hero image URL, width, and height based on largest image in srcset
            if (largestImage) {
                heroImageUrl = largestImage.url;
                heroImageWidth = largestImage.size;
                heroImageHeight = Math.round((heroImageWidth / 1600) * 840); // Adjust height based on aspect ratio if needed
            }
        }
    }
}


// Create the full JSON-LD object
let jsonLd = {
    "@context": "https://schema.org/",
    "@type": "Article",
    "url": url,
    "@id": urlId,
    "headline": h1,
    "description": metaDescription,
    "datePublished": datePublished,
    "wordCount": wordCount,
    "timeRequired": timeRequired,
    "image": {
        "@type": "ImageObject",
        "url": heroImageUrl,
        "height": heroImageHeight,
        "width": heroImageWidth
    },
    "speakable": {
        "@type": "SpeakableSpecification",
        "cssSelector": [
            "h1",
            ".sej-article entrycontent"
        ]
    },
    "author": {
        "@type": "Person",
        "name": authorName,
        "url": authorUrl,
        "@id": authorId,
        "sameAs":sameAsLinks
    },
    "publisher": {
        "@type": "Organization",
      "@id": "https://www.searchenginejournal.com/#organization",
      "url": "https://www.searchenginejournal.com",
      "sameAs": [
        "https://twitter.com/sejournal",
        "https://www.facebook.com/SearchEngineJournal",
        "https://www.linkedin.com/company/search-engine-journal",
        "https://www.youtube.com/c/searchenginejournal",
        "https://www.reddit.com/user/SearchEngineJournal",
        "https://www.google.com/search?kgmid=/m/011sh7hw",
        "http://www.pinterest.com/sejournal/"
      ],
      "name": "Search Engine Journal",
      "logo": [
        {
          "@type": "ImageObject",
          "@id": "https://www.searchenginejournal.com/#logo",
          "inLanguage": "en-US",
          "url": "https://www.searchenginejournal.com/wp-content/themes/sej/images/schema/compact.png",
          "width": 1000,
          "height": 1000,
          "caption": "Search Engine Journal"
        }
      ],
      "foundingDate": "2003",
      "slogan": "In a world ruled by algorithms, SEJ brings timely, relevant information for SEOs, marketers, and entrepreneurs to optimize and grow their businesses -- and careers.",
      "description": "Search Engine Journal is dedicated to producing the latest news, the best guides and how-tos for the SEO and marketer community.",
      "legalName": "Search Engine Journal",
      "alternateName": "SEJ"
    },
    "isPartOf": {
        "@type": "WebSite",
        "name": "Search Engine Journal",
        "@id": "https://www.searchenginejournal.com/#website",
        "url": "https://www.searchenginejournal.com/",
        "mainEntity": {
            "@id": urlId
        }
    }
};


// Beautify JSON-LD
let beautifiedJsonLd = JSON.stringify(jsonLd, null, 2);


// Manually wrap the JSON-LD with <script> tags
let scriptTagWrappedJsonLd = `<script type="application/ld+json">\n${beautifiedJsonLd}\n</script>`;


// Return the wrapped JSON-LD with <script> tags
return seoSpider.data(scriptTagWrappedJsonLd);

JavaScript 代码解释

现在,让我们分解代码的每一位,以解释如何将每个提取的元素作为变量动态添加到 JSON-LD 脚本中。

网页 URL 和@id

为了提取网页 URL 和@id,我使用了以下 JavaScript 代码:

// Extract the URL of the webpage
let url = window.location.href;
let urlId = `${url}#article`;

使用 window.location.href 提取 URL,该 URL 检索当前网页的完整 URL。

相同的 URL 变量用于填充 @id,该变量为 “article” 架构类型提供唯一标识符。但是,这一次,我使用了模板文本 (${url}#article) 将 #article 附加到 url。这种方法有效地使用特定标识符标记 url,以便 urlId 将被 https://example.com/page#article。

然后,它被填充到我的 JSON-LD 架构中,如下所示:

"url": url,
"@id": urlId,

头条新闻

为了填充文章的 “标题”,我将文章的标题定位在下面:

我使用以下代码提取 H1 标签:

// Extract the first H1 tag
let h1 = document.querySelector('h1') ? document.querySelector('h1').textContent.trim() : '""';

document.querySelector(‘h1’) 查找页面的第一个<H1>元素。如果找到一个,则返回该元素;否则,它将在引号内返回空值。

.textContent 提取<H1>和 .trim() 删除该文本周围的任何前导或尾随空格。

在架构标记脚本中,使用以下变量填充标题:

"headline": h1,

元描述

我使用以下 JavaScript 代码来提取元描述:

// Extract the meta description
let metaDescription = document.querySelector('meta[name="description"]') ? document.querySelector('meta[name="description"]').content.trim() : '""';

document.querySelector(‘meta[name=“description”]’) 行用于在 HTML 中查找带有 name=“description” 的标签。如果找到此标签,则返回该元素;否则,它将在引号内返回空值。.trim() 删除任何前导或尾随空格。

在架构标记脚本中,使用以下变量填充元描述值:

"description": metaDescription,

发布日期值

对于发布日期值,我需要确保它遵循 ISO 8601 日期时间格式进行验证。网页将发布日期显示为“2024 年 11 月 11 日”,但值得庆幸的是,代码以正确的 ISO 8601 格式显示日期时间值:

为了提取 datePublshed 变量的 datetime 值,我使用以下代码:

// Extract the published date from the datetime attribute in the <time> tag within .sej-auth-t
let datePublished = '""';
let dateElement = document.querySelector('.sej-auth-t time');
if (dateElement) {
    datePublished = dateElement.getAttribute('datetime');
}

此代码从<time>标记。

如果未找到 datetime 值,则返回引号内的空值。

在架构标记脚本中,使用以下变量填充 datePublished 值:

"datePublished": datePublished,

字数

为了提取和计算文章中的字数,我使用以下代码:

// Select the article element
let articleElement = document.querySelector('article[data-clarity-region="article"]');
// Count the total number of words within the article element
let wordCount = articleElement ? articleElement.innerText.split(/\s+/).length : 0;

在这里,我使用代码将<article>标记,该标记具有唯一标识文章内容的属性。

data-clarity-region="article"

wordCount 变量检查 articleElement 是否存在。如果是这样,innerText 会提取标记。之后,.split(/\s+/) 通过空格将此文本划分为单词数组,而 .length 给出单词数。

.length 给出了这个数组中项目(单词)的总数,而 String(…) 将此数字包装在 String() 中,以确保最终结果是一个字符串(例如,“123”而不是 123),这对于 JSON-LD 验证是必需的。

如果未找到 articleElement,则 wordCount 将设置为空引号作为回退。

输出将在此处填充到 JSON-LD 脚本中:

"wordCount": wordCount,

所需阅读时间

为了提取所需的阅读时间,我必须执行一些 JavaScript 魔法。格式需要采用 ISO 8601,例如“PT14M”,代表 14 分钟。

本文以以下格式显示阅读时间:“10 min read”。所以我们有一些工作要做。

初始代码如下所示:

// Extract the reading time and convert to ISO 8601 duration
let timeRequired = '';
let readingTimeElement = document.querySelector('.sej-auth-t li:nth-child(3)');

首先,我使用空字符串 (‘’) 初始化 timeRequired 变量。如果在页面上找到此变量,则此变量最终会以 ISO 8601 duration 格式存储读取时间。

readingTimeElement 尝试选择页面上的特定元素。它使用 document.querySelector(‘.sej-auth-t li:nth-child(3)’) 来查找第三个<li>元素。

以下代码部分检查并提取文本内容:

if (readingTimeElement) {
    let readingTimeText = readingTimeElement.textContent.trim();
    let timeMatch = readingTimeText.match(/(\d+)\s*min/);

if 语句检查是否找到 readingTimeElement。如果为 null(未找到),则此块中的代码将不会执行。

如果元素存在,readingTimeElement.textContent.trim() 会从此元素中检索可见的文本内容,并使用 .trim() 删除周围的任何空格。

timeMatch = readingTimeText.match(/(\d+)\s*min/);使用正则表达式查找 readingTimeText 中的读取时间。如果找到模式,timeMatch 将是一个数组,其中第一个捕获的组 (timeMatch[1]) 包含以分钟为单位的数字读取时间。

最后:

if (timeMatch) {
        let minutes = parseInt(timeMatch[1]);
        timeRequired = `PT${minutes}M`; // Convert to ISO 8601 duration format
    }
}

我将时间转换为 ISO 8601 持续时间格式。

如果 timeMatch 存在(意味着读取时间已成功匹配),则 timeMatch[1] 将分钟保存为字符串(例如,“10”)。

parseInt(timeMatch[1]) 将此字符串转换为整数(在本例中为 10),该整数存储在 minutes 变量中。

然后,该代码使用模板文本 ‘PT${minutes}M’ 构造 ISO 8601 持续时间格式。

最终的 JSON-LD 将包含以下变量和 timeRequired 输出:

"timeRequired": timeRequired,

ImageObject

我将“ImageObject”嵌套到“article”架构中,该架构以页面模板上的以下主图为目标:

我使用 JavaScript 提取主图的 URL、宽度和高度,并将提取的数据作为嵌套的“ImageObject”插入到标记中。这是代码:

// Extract hero image URL, width, and height directly from the hero image element
let heroImageUrl = '""';
let heroImageWidth = '""';
let heroImageHeight = '""';


let heroImageElement = document.querySelector('.attachment-full.size-full.wp-post-image');
if (heroImageElement) {
    heroImageUrl = heroImageElement.src;


    // Try to get width and height from attributes first
    heroImageWidth = heroImageElement.getAttribute('width');
    heroImageHeight = heroImageElement.getAttribute('height');


    // If width or height are missing, parse the largest values from srcset
    if (!heroImageWidth || !heroImageHeight) {
        let srcset = heroImageElement.getAttribute('srcset');
        if (srcset) {
            let largestImage = srcset.split(',').map(entry => {
                let [url, size] = entry.trim().split(' ');
                return { url, size: parseInt(size) };
            }).sort((a, b) => b.size - a.size)[0];


            // Set hero image URL, width, and height based on largest image in srcset
            if (largestImage) {
                heroImageUrl = largestImage.url;
                heroImageWidth = largestImage.size;
                heroImageHeight = Math.round((heroImageWidth / 1600) * 840); // Adjust height based on aspect ratio if needed
            }
        }
    }
}

让我们看一下代码块的各个部分。为了提取主图,我使用以下代码:

let heroImageElement = document.querySelector('.attachment-full.size-full.wp-post-image');

这将在网页中搜索<img>元素替换为 attachment-full 和 size-full 类。如果找到,则元素将存储在 heroImageElement 变量中。

接下来,我使用以下代码行提取主图 URL:

heroImageUrl = heroImageElement.src;

为了提取主图图像的宽度和高度,代码尝试直接从 image 元素中检索属性:

// Try to get width and height from attributes first
    heroImageWidth = heroImageElement.getAttribute('width');
    heroImageHeight = heroImageElement.getAttribute('height');

如果存在这些属性,则它们的值将分配给 heroImageWidth 和 heroImageHeight。

但是,如果缺少 width 或 height 属性,则代码将回退到 srcset 属性,该属性包含图像 URL 及其相应宽度的列表:

// If width or height are missing, parse the largest values from srcset
    if (!heroImageWidth || !heroImageHeight) {
        let srcset = heroImageElement.getAttribute('srcset');
        if (srcset) {
            let largestImage = srcset.split(',').map(entry => {
                let [url, size] = entry.trim().split(' ');
                return { url, size: parseInt(size) };
            }).sort((a, b) => b.size - a.size)[0];

如果缺少 height 属性,则代码使用假定的纵横比计算它:

// Set hero image URL, width, and height based on largest image in srcset
            if (largestImage) {
                heroImageUrl = largestImage.url;
                heroImageWidth = largestImage.size;
                heroImageHeight = Math.round((heroImageWidth / 1600) * 840); // Adjust height based on aspect ratio if needed
            }

最后,提取的值用于填充 JSON-LD 脚本中的 “ImageObject” 架构:

"image": {
        "@type": "ImageObject",
        "url": heroImageUrl,
        "height": heroImageHeight,
        "width": heroImageWidth
    }

可口述的 JSON-LD

这是可选的。但是,我决定添加“可说”的 JSON-LD 以进行语音搜索优化,并在架构标记中包含了以下代码段:

"speakable": {
        "@type": "SpeakableSpecification",
        "cssSelector": [
            "h1",
            ".sej-article entrycontent"
        ]
    },

该代码是静态的,因为它在所有博客文章网页中都保持不变。“speakable” 架构允许您指定网页中特别适合通过 Google Home 设备或 Alexa 等语音助手进行文本到语音转换的部分。此功能有助于提供内容的音频摘要。

您可以使用 XPATH 或 CSS 选择器来定义 “SpeakableSpecification” 架构类型。对于我的示例,我选择使用 CSS 选择器标识“可说”内容。

cssSelector 属性包含一个 CSS 选择器列表,这些选择器指向网页上应包含在音频摘要中的元素。

当大声朗读内容时,标题 (h1) 提供介绍性上下文或标题,而 .sej-article entrycontent 则以主要正文文本为目标。

作者信息

我将“person”架构用于作者信息,并使用“author”属性将其链接回文章的架构。查看下面的作者信息,我可以看到有许多元素可以提取并用于填充“person”架构属性:

我将提取作者的姓名、个人简介页面 URL、@id、sameAs 社交链接、职称、工作地点和工作地点 URL(如果有)。在 schema 标记中,它如下所示:

"author": {
        "@type": "Person",
        "name": authorName,
        "url": authorUrl,
        "@id": authorId,
        "jobTitle": jobTitle,
        "worksFor": {
            "@type": "Organization",
            "name": worksForName,
            "url": worksForUrl
        },
        "sameAs":sameAsLinks
    },

作者姓名、个人资料 URL 和 ID

authorElement 变体尝试查找具有类 .dark-link 和 .sej-auth-h 的 HTML 元素,该元素应包含作者的信息。

之后,各种变量被初始化为空字符串。如果找到元素,这些元素将存储提取的详细信息。

如果找到 authorElement,则提取 authorName 的文本内容并修剪空格。authorUrl 提取此元素的 href 属性,该属性是指向作者配置文件页面的链接。authorId 是一个构造的标识符,它将 /#person 附加到 authorUrl,从而为结构化数据创建唯一 ID。

代码如下:

// Select the element containing the author information
let authorElement = document.querySelector('.dark-link.sej-auth-h');


// Initialize author details with empty strings
let authorName = "";
let authorUrl = "";
let authorId = "";
let sameAsLinks = "";
let jobTitle = "";       // Initialize job title as an empty string
let worksForName = "";   // Initialize worksFor name as an empty string
let worksForUrl = "";    // Initialize worksFor URL as an empty string


// Check if the author element exists, then extract the name and URL
if (authorElement) {
    authorName = authorElement.textContent.trim(); // Extract and clean up author name
    authorUrl = authorElement.href;                // Extract author URL
    authorId = `${authorUrl}/#person`;             // Construct @id by appending /#person

SameAs 链接

接下来,我将提取作者工具提示中的链接,并将它们作为数组添加到“sameAs”属性中。这些链接的目的是参考作者在网络上的数字足迹,以改进 EEAT 并确定他们是一个真实的实体。

“sameAs”链接提取码如下:

// Extract all social media URLs within the .sej-asocial class for the sameAs array
    sameAsLinks = Array.from(document.querySelectorAll('.sej-asocial li a')).map(link => link.href);

代码查询任何<a>标记 (<li>) 在 .sej-asocial 类中。这些链接代表作者的社交媒体资料。使用 Array.from,它将<a>元素转换为一个 URL 数组中。

职称和组织

为了提取职务、组织名称和 URL,我将选择类为 .sej-auth-dpos 的元素,该元素应包含作者的职务和组织。

具体到职位名称,我将使用以下代码:

// Extract the job title and organization name from 
let jobTitleElement = document.querySelector('.sej-auth-dpos');
if (jobTitleElement) {
    let jobText = jobTitleElement.textContent.trim();
    let jobMatch = jobText.match(/^(.*)\s+at\b/);  // Match text before "at"
    if (jobMatch) {
        jobTitle = jobMatch[1].trim();  // Capture the job title part only
    }

如果 jobTitleElement 存在,则代码将捕获其文本内容。

正则表达式 ^(.*)\s+at\b 将单词 “at” 之前的所有内容提取为 jobTitle。这假定职位名称在文本中位于组织名称之前。

对于组织名称和 URL,我使用以下代码:

// Check if there's an <a> tag for the organization name
    let companyElement = jobTitleElement.querySelector('a');
    if (companyElement) {
        // Extract company name and URL if the <a> tag is present
        worksForName = companyElement.textContent.trim();
        worksForUrl = companyElement.href;
    } else {
        // If no <a> tag, extract organization name directly from the span's text after "at"
        let worksForMatch = jobText.match(/\bat\s+(.+)/);  // Match text after "at"
        if (worksForMatch) {
            worksForName = worksForMatch[1].trim();
        }
    }

companyElement 变量检查<a>标记中,假设它可能链接到组织的网站。

如果存在,则 worksForName 和 worksForUrl 将设置为组织的名称和 URL。

如果<a>标签,则回退正则表达式会直接从“at”后面的文本中提取组织名称。

发布者信息

我使用文章的“publisher”属性来引用“organization”架构类型。值得庆幸的是,JSON-LD 代码的 “publisher” 部分是静态的,因为它不需要从页面到页面进行编辑。

该信息是从 SEJ 的真实 “组织” 架构详细信息中收集的,如下所示:

"publisher": {
        "@type": "Organization",
      "@id": "https://www.searchenginejournal.com/#organization",
      "url": "https://www.searchenginejournal.com",
      "sameAs": [
        "https://twitter.com/sejournal",
        "https://www.facebook.com/SearchEngineJournal",
        "https://www.linkedin.com/company/search-engine-journal",
        "https://www.youtube.com/c/searchenginejournal",
        "https://www.reddit.com/user/SearchEngineJournal",
        "https://www.google.com/search?kgmid=/m/011sh7hw",
        "http://www.pinterest.com/sejournal/"
      ],
      "name": "Search Engine Journal",
      "logo": [
        {
          "@type": "ImageObject",
          "@id": "https://www.searchenginejournal.com/#logo",
          "inLanguage": "en-US",
          "url": "https://www.searchenginejournal.com/wp-content/themes/sej/images/schema/compact.png",
          "width": 1000,
          "height": 1000,
          "caption": "Search Engine Journal"
        }
      ],
      "foundingDate": "2003",
      "slogan": "In a world ruled by algorithms, SEJ brings timely, relevant information for SEOs, marketers, and entrepreneurs to optimize and grow their businesses -- and careers.",
      "description": "Search Engine Journal is dedicated to producing the latest news, the best guides and how-tos for the SEO and marketer community.",
      "legalName": "Search Engine Journal",
      "alternateName": "SEJ"
    },

网站 JSON-LD

我使用文章的 “isPartOf” 属性将 “article” 架构连接到 “website” 架构,以实现更好的语义上下文化。代码中的信息大多是静态的:

"isPartOf": {
        "@type": "WebSite",
        "name": "Search Engine Journal",
        "@id": "https://www.searchenginejournal.com/#website",
        "url": "https://www.searchenginejournal.com/",
        "mainEntity": {
            "@id": urlId
        }
    }

我决定将“mainEntity”属性添加到“website”架构中,以将主要内容(文章)显式声明为网页的主要焦点。

为了填充“mainEntity”属性,我使用了之前为网页 URL 创建的相同 urlId。

美化和包装脚本标签

最后一段代码确保 JSON-LD 脚本得到美化——这意味着我们人类更容易阅读:

// Beautify JSON-LD
let beautifiedJsonLd = JSON.stringify(jsonLd, null, 2);

(也就是说,如果您想添加脚本的缩小版本,这更适合生产 - 请删除这行代码)。

现在,我们可以将经过美化和完全填充的 JSON-LD 代码包装在脚本标签中,以便将其复制粘贴到我们网站的 CMS 页面中:

// Manually wrap the JSON-LD with <script> tags
let scriptTagWrappedJsonLd = `<script type="application/ld+json">\n${beautifiedJsonLd}\n</script>`;

将你的 JavaScript 代码片段添加到 Screaming Frog

现在我已经构建了我的脚本,我可以将其添加到 Screaming Frog SEO Spider 中。

打开 SEO Spider 并打开 JavaScript 渲染。从菜单中,转到 配置 > 爬网配置 > 渲染 > JavaScript。

之后,导航到 自定义 JavaScript 部分,可在此处找到:自定义 > >自定义 JavaScript。

点击 ‘+ Add’ 按钮以插入新的自定义 JavaScript 代码段。为代码段分配您的首选名称。最后,单击“JS”按钮打开 JavaScript 代码段编辑器。

在 JavaScript 代码段编辑器中,将您正在处理的 JavaScript 代码复制粘贴到 JavaScript 编辑器输入框中:

您可以使用 JavaScript Tester 功能来确保脚本正常工作。

将测试 URL 复制粘贴到 URL 输入字段中,然后单击“测试”按钮。测试抓取完成后,您应该会在测试器框中看到 JavaScript 代码段输出:

在构建脚本时,我广泛使用了 JavaScript 测试器。测试器功能非常适合通过标记控制台错误来调试问题,从而为您指明正确的方向。如果您遇到任何错误,您可以将它们输入到 LLM 中以重新生成正确的代码或寻求建议。

完成脚本并对最终输出感到满意后,您可以执行一个可选步骤,将脚本保存到自定义 JavaScript 代码段库中。单击 ‘Add Snippet to User Library’ 按钮来执行此作:

您可以通过单击主“自定义 JavaScript”配置仪表板中的“+ 从库添加”来访问您的私有代码段:

最终 JSON-LD 脚本测试

为确保您的 JSON-LD 脚本在所选页面模板中的 URL 中正常工作,我建议您将抓取切换到“列表模式”:列表>模式

现在,您可以选择“上传”按钮,然后选择“手动输入…”选择。

在弹出的输入框中添加 10 个 URL 的示例作为测试,以确保 JSON-LD 输出在所有页面中都是正确的。

点击 ‘下一步’ 并开始抓取。

使用 Google 和 Schema.org 进行验证

使用架构标记时,始终建议在将架构代码添加到网站之前对其进行验证。我始终建议使用 Google 支持的 Rich Results Test 来验证代码。

此外,我建议通过 Schema.org 的验证器运行代码,尤其是当您的脚本包含不支持的架构类型时。

运行 Crawl 并提取 Schema 标记

这是一切汇集在一起的时候。使用您的完整 URL 列表再次运行“列表模式”爬虫或您网站的标准爬虫爬虫。

抓取完成后,导航到“自定义 JavaScript”选项卡,您将在其中找到 JSON-LD 架构标记脚本,供您复制粘贴到 CMS 中或导出为电子表格。

借助 Screaming Frog SEO Spider 的强大功能,我们现在可以大规模生成架构标记脚本。负责任地使用此功能,并随时添加您的反馈。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值