Ghost博客熊掌号改造苦逼过程

  • W_Z_C
  • 阅读约 6 分钟

1. 前言

百度的熊掌号 11 月份就发布了,当我准备搞的时候已经黄花菜都凉了,百度移动端内容早就被无数站长的内容覆盖,为了迎合一下热点,本着提升点网站流量的目的,开始了 ghost 博客无比艰辛的改造过程。

2. 高兴的太早了

当注册审核通过之后,查看熊掌号后台,发现改造过程貌似非常简单,对于我这种前端门外汉来说除了校验工具的校验过程让人有点费解之外,其它的都比叫容易操作。

首先,粉丝关注改造。主要的目的是显示一个站长卡牌,让别人可以方便的关注。

其次,结构化改造。主要的目的就是便于百度搜集网站的数据,并在移动端展示。

半个小时搞定后,在验证页面尝试,发现怎么都不成功,但是页面中确有一个奇葩的 application/ld+json 格式数据。看着能够打开关注的文章页面,所以也就没有在意,以为是百度识别了我添加的 application/ld+json 数据,并使用脚本自动生成下面的样子,我其实内心还觉得百度太牛叉了,能够自动识别文章标签,真准啊……

{
    "@context": "https://schema.org",
    "@type": "Article",
    "publisher": {
        "@type": "Organization",
        "name": "第二纪元",
        "logo": {
            "@type": "ImageObject",
            "url": "https://wangzhechao.com/favicon.ico",
            "width": 60,
            "height": 60
        }
    },
    "author": {
        "@type": "Person",
        "name": "W_Z_C",
        "image": {
            "@type": "ImageObject",
            "url": "//www.gravatar.com/avatar/0d4bf6032c133d0f1fa9d1506887491e?s=250&d=mm&r=x",
            "width": 250,
            "height": 250
        },
        "url": "https://wangzhechao.com/author/w_z_c/",
        "sameAs": [
            "https://wangzhechao.com"
        ]
    },
    "headline": "Jmeter 安装教程",
    "url": "https://wangzhechao.com/jmeter-an-zhuang-jiao-cheng/",
    "datePublished": "2017-12-14T14:04:56.000Z",
    "dateModified": "2017-12-14T14:12:58.000Z",
    "image": {
        "@type": "ImageObject",
        "url": "https://wangzhechao.com/content/images/2017/12/jmeter-2-1.png",
        "width": 700,
        "height": 385
    },
    "keywords": "安装教程, Jmeter, HTTP, HTTP测试, HTTP压力测试, Jmeter安装, 压力测试, 性能测试",
    "description": "这一阵工作中涉及到HTTP接口测试的需求,主要做一些简单的压力测试和性能测试。用nodejs手撸了一份,运行后发现离自己想要的还是有些差距的,上网搜索研究了一下相关的测试工具,接下来准备写几篇文章总结一下使用心得,本篇文章算是开篇,介绍一下Jmeter的安装使用。",
    "mainEntityOfPage": {
        "@type": "WebPage",
        "@id": "https://wangzhechao.com/"
    }
}

一直到我闲着蛋疼,查看了其他站长的改造后的效果,和我的完全不一样,然后潜心花费了几个小时研究怎样改造,接下来记录一下其中思考的过程,以儆效尤!

3. 苦逼的开始

第一步,因为开始以为那些数据是百度生成的,所以想要验证一下,将上次添加所有熊掌号相关的添加内容全部删除后,发现这个结构数据仍然存在,百度一下,发现这个结构不是百度原创,而是一种标准,名词叫做:JSON-LD,是一种基于 JSON 表示和传输互联数据(Linked Data)的方法。

受教育之后,自然想到 ghost 博客本身是支持 JSON-LD 的输出,谷歌之后,发现因为 ghost 博客支持 google 移动端页面,所以有 AMP 的支持,具体设置可以 参考这篇文章

我突然感觉到找到组织了,赶快去博客后台,将 amp 插件禁用,保存,然后人就懵逼了。

因为 AMP 这个插件应用默认根本没有开启

4. 禁用AMP之路

继续搜索,查找 AMP 相关,怎样禁用 ghost 博客的 JSON-LD 结构输出,只有禁止后才能添加百度熊掌号的结构。

还真让我搜到了一篇佳作:Google AMP is shit!

感觉又迎来了希望,立刻远程登录 ssh,进入 /core/server/config/index.js 目录,vim 打开,如下:

var Nconf = require('nconf'),
    path = require('path'),
    _debug = require('ghost-ignition').debug._base,
    debug = _debug('ghost:config'),
    localUtils = require('./utils'),
    env = process.env.NODE_ENV || 'development',
    _private = {};
_private.loadNconf = function loadNconf(options) {
    debug('config start');
    options = options || {};
    var baseConfigPath = options.baseConfigPath || __dirname,
        customConfigPath = options.customConfigPath || process.cwd(),
        nconf = new Nconf.Provider();
    /**
     * no channel can override the overrides
     */
    nconf.file('overrides', path.join(baseConfigPath, 'overrides.json'));
    /**
     * command line arguments
     */
    nconf.argv();
    /**
     * env arguments
     */
    nconf.env({
        separator: '__'
    });
    nconf.file('custom-env', path.join(customConfigPath, 'config.' + env + '.json'));
    nconf.file('default-env', path.join(baseConfigPath, 'env', 'config.' + env + '.json'));
    nconf.file('defaults', path.join(baseConfigPath, 'defaults.json'));
    /**
     * transform all relative paths to absolute paths
     * transform sqlite filename path for Ghost-CLI
     */
    nconf.makePathsAbsolute = localUtils.makePathsAbsolute.bind(nconf);
    nconf.isPrivacyDisabled = localUtils.isPrivacyDisabled.bind(nconf);
    nconf.getContentPath = localUtils.getContentPath.bind(nconf);
    nconf.sanitizeDatabaseProperties = localUtils.sanitizeDatabaseProperties.bind(nconf);
    nconf.doesContentPathExist = localUtils.doesContentPathExist.bind(nconf);
    nconf.sanitizeDatabaseProperties();
    nconf.makePathsAbsolute(nconf.get('paths'), 'paths');
    nconf.makePathsAbsolute(nconf.get('database:connection'), 'database:connection');
    /**
     * Check if the URL in config has a protocol
     */
    nconf.checkUrlProtocol = localUtils.checkUrlProtocol.bind(nconf);
    nconf.checkUrlProtocol();
    /**
     * Ensure that the content path exists
     */
    nconf.doesContentPathExist();
    /**
     * values we have to set manual
     */
    nconf.set('env', env);
    // Wrap this in a check, because else nconf.get() is executed unnecessarily
    // To output this, use DEBUG=ghost:*,ghost-config
    if (_debug.enabled('ghost-config')) {
        debug(nconf.get());
    }
    debug('config end');
    return nconf;
};
module.exports = _private.loadNconf();
module.exports.loadNconf = _private.loadNconf;

请问 AMP 在哪里?

难道是 ghost 没事总升级,这篇文章的内容太久了?立刻 git 下载 ghost 源码,搜索所有 amp 单词相关信息,果然被我找到了类似的配置信息。

在config目录下的overrides.json配置文件:

    "apps": {
        "internal": [
            "private-blogging",
            "subscribers",
            "amp"
        ]
    },
    "routeKeywords": {
        "tag": "tag",
        "author": "author",
        "page": "page",
        "preview": "p",
        "private": "private",
        "subscribe": "subscribe",
        "amp": "amp",
        "primaryTagFallback": "all"
    },
    "slugs": {
        "reserved": ["admin", "app", "apps", "categories",
            "category", "dashboard", "feed", "ghost-admin", "login", "logout",
            "page", "pages", "post", "posts", "public", "register", "setup",
            "signin", "signout", "signup", "user", "users", "wp-admin", "wp-login"],
        "protected": ["ghost", "rss", "amp"]
    },

我立刻就被自己感动了,我实在是太聪明了,按照他的方法删除 amp 相关信息,重启 ghost,我满心期待的刷新了网页……

一切仍然涛声依旧……

5. 老老实实读源码

是时候秀出自己的 10 年功力了,读源码!!!

多亏我以前研究过 ghost 源码,如今梅开二度,应该没有任何难度。老司机上路,然后我发现 ghost 博客开发者的成果了,和我上一次研究的代码貌似完全不一样啊……

啥都别说了,冷静一下的我,灵机一动抱着尝试的态度所搜了一下 application/ld+json 关键字。

    return getMetaData(dataRoot, dataRoot)
        .then(function handleMetaData(metaData) {
            debug('end fetch');
...
                if (!_.includes(context, 'paged') && useStructuredData) {
                    head.push('');
                    head.push.apply(head, finaliseStructuredData(metaData));
                    head.push('');
                    if (metaData.schema) {
                        head.push('n'); } } ... }; 

喜从天降啊,仔细研究,发现具体信息是从 getMetaData 函数返回,搜索发现具体的 schema 信息是从 getSchema 函数返回的。

return Promise.props(getImageDimensions(metaData)).then(function () {
    metaData.structuredData = getStructuredData(metaData);
    metaData.schema = getSchema(metaData, data);
    return metaData;
}).catch(function (err) {
    common.logging.error(err);
    return metaData;
});

继续搜索,前方遇见 schema 大军,各种 schema 数据。

function getSchema(metaData, data) {
    if (!config.isPrivacyDisabled('useStructuredData')) {
        var context = data.context ? data.context : null;
        if (_.includes(context, 'post') || _.includes(context, 'page') || _.includes(context, 'amp')) {
            return getPostSchema(metaData, data);
        } else if (_.includes(context, 'home')) {
            return getHomeSchema(metaData);
        } else if (_.includes(context, 'tag')) {
            return getTagSchema(metaData, data);
        } else if (_.includes(context, 'author')) {
            return getAuthorSchema(metaData, data);
        }
    }
    return null;
}

包括 post、home、tag、author 等等,每个都会生成一种 schema 数据,格式就是 JSON-LD 结构,本来想把这了直接改成百度想要的格式,但是后来仔细一看,发现配置中有个isPrivacyDisabled函数,该函数返回 true,则 getSchema 函数返回 null,页面中肯定不会再有 JSON-LD 的结构了。

查看该函数:

exports.isPrivacyDisabled = function isPrivacyDisabled(privacyFlag) {
    if (!this.get('privacy')) {
        return false;
    }
    // CASE: disable all privacy features
    if (this.get('privacy').useTinfoil === true) {
        // CASE: you can still enable single features
        if (this.get('privacy')[privacyFlag] === true) {
            return false;
        }
        return true;
    }
    return this.get('privacy')[privacyFlag] === false;
};

也就是说配置中会有一个 privacy 的配置项,该配置项中的 useStructuredData 字段如果为 false,则不会输出 JSON-LD 的结构。

总结一下,如果想删除页面中的结构数据,只需要在配置中添加下面的配置即可。

privacy: {
  useStructuredData: false
}

立刻加上,圆满完成任务。google 搜索ghost privacy useStructuredData,发现 ghost 官方文档 提到了这个配置。

这回再次证明:研究帮助文档的重要性

ghost 自带的 JSON-LD 数据已经消除,是时候开始真正的熊掌号页面改造了。

6. 熊掌号页面改造

6.1 粉丝页面改造

ghost 博客的后台其实已经增加了头部数据添加框,可以直接在其中加入粉丝改造相关的内容。

如果你不喜欢,也可以手动在 default.hbs 模板文件中增加。本人添加的具体位置如下:

...

{{ghost_head}}

    <script src="//msite.baidu.com/sdk/c.js?appid=xxxxxxx"></script>

</head>
<body class="{{body_class}}">

    <div class="site-wrapper">

        {{!-- All the main content gets inserted here, index.hbs, post.hbs, etc --}}
        {{{body}}}


        <script>cambrian.render('tail')</script>
        
...

6.2 结构化改造

因为添加改造的内容是与文章数据相关,所以这里将改造的数据添加到了 post.hbs 模板文件中,具体可以放到 disqus 评论上方(默认 disqus 评论是注释的)。

<link rel="canonical" href='{{url absolute="true"}}'/>

<script type="application/ld+json">
    {
        "@context": "https://ziyuan.baidu.com/contexts/cambrian.jsonld",
        "@id": "{{url absolute='true'}}",
        "appid": "xxxxxxxx",
        "title": "{{title}}",
        "images": ["{{img_url feature_image absolute='true'}}"],
        "description": "{{excerpt characters='120'}}",
        "pubDate": "{{date published_at format='YYYY-MM-DDTHH:mm:ss'}}",
        "upDate": "{{date updated_at format='YYYY-MM-DDTHH:mm:ss'}}"                
    }
</script>

这里面的图像列表我选择了 ghost 每篇文章的特色图片,你也可以换成别的图片内容,具体需要查看 ghost 的主题帮助文档。

最后可以去在线校验工具输入链接和文章的最终 html 代码进行验证。

7. 总结

啥问题就怕刨根问底儿……

大佬,给点反馈?
阅读数:1评分:0.0投票数:0