2024年,如何打造惊艳的个人博客/出版系统并且赚点小钱?

news2024/11/16 19:41:12

几年前,我就推荐过用Markdown写作静态博客。静态博客几乎是零托管成本,比较适合个人博客起步。Markdown便于本地搜索,也可当作是个人知识库方案。

现在有了新的进展。我不仅构建了一个视觉上相当不错的个人网站,还美化了github、构建了个人出版系统 – 将文章导出为排版精美的图片和pdf的能力。


这一方案的核心是Mkdocs和Mkdocs-material。前者是Python技术文档构建系统,后者是与之适配的主题。我在《Python能做大项目》这本书中,深入介绍过这两种技术。

现在,基于这两种技术,我们可以走得更远:不仅可以撰写技术文档,更可以打造博客和门户网站。

下图就是截取的大富翁量化的网站界面:

在这里插入图片描述


你可以在大富翁量化网站上看到它最新的样式。更有创意的是,虽然它只是一个静态网站,但你每次刷新它,都能看到一些新的内容 – 至少配图会变!

这是首页。菜单栏、搜索这些是常规配置。标签云、首页的卡片式布局,是提升站点气质的地方。

所有的文档都有版本管理,我使用了github来托管文档和图片,所以,也顺便把github的个人主页美化了一下:

75%

实在说,之前我没有想过,github主页也可以做得像个人网站。不得不说一张好图,能大大提升颜值。


此外,作为创作者,我还希望自己的文章能在多个渠道上发布,包括公众号、知乎、CSDN和小红书,有时候还需要把文章导出为PDF。这些渠道采用的技术大不相同,它们的排版要求也不一样,所以,要想不把时间无谓地浪费在枯燥、重复的排版上,我们就得用好各种工具。

1. Mkdocs + Mkdocs - Material

基础搭建我都写在《Python能做大项目》这本书的第10章中了,这里我们只介绍如何开通博客功能,以及定制首页。

Material自带了博客插件,我们只需要在配置中启用它(以及其它相关插件):

plugins:
  - awesome-pages:
      collapse_single_pages: true
  - blog:
      post_excerpt_separator: <!--more-->
  - tags:
      tags_file: tags.md
  - rss:
      match_path: "(blog|articles)/.*"
      categories:
        - categories
        - tags
      date_from_meta:
        as_creation: "date"
        as_update: true
        datetime_format: "%Y-%m-%d %H:%M"
        default_timezone: Asia/Shanghai
      use_git: false

其中rss插件需要安装mkdocs-rss-plugin插件。关于如何定制标签云,请参考Code Inside Out上的这篇文章。

该文也提到了如何实现最新博文的功能。不过,博主最后决定自己用Python撸一个方案,以实现本文开头提到的效果 – 动态和卡片式,并且能自动更新github的profile。

2. 自定义脚本生成卡片式首页和github profile

这个方案主要使用了python-frontmatter库。通过一个脚本,搜索articles和blog目录下所有的md文件,读取它们的front matter,再按日期排序,将最新发表的文章和博文的摘要、日期、title和首图抽取出来,通过模板生成一个新的README.md文件,放到项目根目录下。

github会读取这个文件作为我们的profile,mkdocs-material也会根据这个文件,生成网站首页。

这个readme.md实际上是一个带部分html标签的md片段。我先用脚本生成了供mkdocs使用的README.md,待网站发布后,再生成供github的profile使用的readme.md。区别主要在于,github的profile中不能使用’<style>'标签来定义样式,它只允许在README.md中html标签中,使用少量的样式语法。

!!! tip
如果对这个方案的细节感兴趣,可以直接访问zillionare这个项目。mkdocs-material的定制在docs/overrides目录下。构建Readme.md的脚本在根目录下,名字为publish.py。

为了生成响应式的卡片布局,我使用了bootstrap中card样式。它简单到只要把父容器声明为card-columns类,card元素的样式声明为card就可以了。此外,为了根据不同的屏幕大小,显示不同的card列数,可以使用媒体查询和column-count属性,在docs/assets/templates/homepage.tpl文件中有示例。

在publish.py脚本中,我使用了frontmatter来提取文章中的meta,但它的速度有点慢。不过这是一个使用多进程加速的好场景:

metas = []
articles = glob.glob("./docs/articles/**/*.md", recursive=True)
with ProcessPoolExecutor() as executor:
    results = executor.map(extract_article_meta, articles)
    metas.extend([meta for meta in results if meta is not None])

这样就可以多进程处理文件了。最终结果会汇总到metas这个数组中。

!!! tip “图片自动换新技巧”
图片自动换新,可以给静态网站增加动态内容。让读者每次访问,都有不一样的感觉。这是通过unsplash网站的gallery功能来实现的。unsplash是一个免费图片资源共享网站,提供了大量高清、很高质量的免费图片。如果我们将img元素的url指向’https://source.unsplash.com/random/360x200?{word}'这样的地址,unsplash就会返回一个360x200,并且其类别为word的图片。

3. 发布到小红书

小红书发文不能超过1000字,并且很难格式化。如果要分享文字、代码并且做到混排,惟一的方法就是将其转换成为图片。这个步骤有点繁琐,不过,这也成为一种壁垒,导致深度内容在小红书上比较稀缺。因此,如果能搞定格式排版,这样就可以有效地利用小红书的分发。

我的方案是使用slidev。它是一个基于markdown语法来创作在线演示的方案,提供了转换为图片的功能。因此,我们基于mkdocs+material创作的文档,只要加上少量的标记和定制,就可以很容易地转换成为图片。



要实现这个功能,在安装slidev之后,先进行主题定制。最重要的是cover layout的定制。左图就是我之前为小红书日更设计的一个首页样式。

我们可以设计多个样式,在导出时,使用下面的命令:

npx slidev export --format png -t /path/to/slidev_themes/special_theme_dir --output /tmp/xhs /path/to/src.md

我们需要在markdown内容中的合适位置,增加"—"作为分页符,这样就能导出一页页适合在小红书上发布的图片了。

4. 发布到微信公众号

微信公众号排版一直是个问题。我甚至一度放弃了公众号创作。作为技术极客,我拒绝了几乎任何不是基于Markdown的排版方案 – 都什么年代了,写个自媒体都是赔钱的,平台还好意思要求我们专门为你们排版?

直到我遇到了mdnice。

它甚至比我自己使用mkdocs-material的排版还要漂亮 – 特别是它对代码的处理部分 – 我超爱她深色主题下的50道阴影!

我现在也超爱写公众号了!不过,什么时候mdnice能实现markdown的admonition语法就更好了。毕竟,这是个信息过载的时代,我们必须用一些留白来缓解密集信息恐惧症,同时用一些闪耀的装饰吸引读者,避免他们走掉。



当然,mdnice也可以直接发到CSDN上。不过,这样会失去定时发布以及自定义标签、选择专栏、定制首图的能力,所以,我宁愿自己登录到CSDN的网站上去编辑,好在,它提供了markdown编辑器,并且能自动处理图片链接。因此,我并不需要一个个地从本地上传,这省了我不少时间。

5. 发布到知乎

如果你使用的是vscode来编辑markdown的话,就可以使用一个名为Zhihu On VSCode的扩展。它支持定时发布(一天以内)、选择专栏,但不支持添加话题,另外,它不能正确处理(去掉)frontmatter。

但是它能处理markdown中的图片链接。这样就省去了我上传图片的时间。它对代码也能很好处理。

不过,知乎的文档格式确实太素净了。

6. 转换为pdf

slidev也可以转换成为pdf。不过,我更喜欢使用vscode的markdown preview enhanced来转换pdf。最终提供转换的是chrome+puppetter。通过在文档的frontmatter中加上适当的标记,就能生成页眉和页脚。

右图就是它导出的一例。

此外,slidev也可以导出pdf。要实现页眉和页脚,需要要定制global-top和global-bottom,一旦定制完成,这种方案似乎更好,毕竟,它是手动分页,我们对页面的控制力更强。

7. 赚点小钱

辛苦辛苦写了博客,想办法赚点钱吧。我们可以通过前面提到的ovrrides方案,让mkdocs-material为每个页面插入一个js脚本。

如果你加入了广告联盟,它们一般就会为你提供一个js脚本,把这个js插入进去就可以了。也可以自己写一个js,把自己要发布的广告,以html片段的形式,放在docs目录下的某一个子目录中,然后通过下面的代码把这些片段插入进来:

function insertAd(minParas, minWords){
    var links = document.querySelectorAll("a[href*='" + link + "']");
    if (links.length > 0){
        console.log("已添加")
        return
    }

    var paras = document.querySelectorAll("article p");
    var wordCount = 0
    var paraCount = 0
    var inserted = 0
    for (var i = 0; i < paras.length; i++){
        var p = paras[i];
        paraCount ++
        wordCount += p.innerText.length
        if (inserted >= 2){
            break
        }

        if (paraCount >= minParas && wordCount >= minWords) {
            console.log("find para", p, paraCount, wordCount)
            p.insertAdjacentHTML("afterend", ad)
            paraCount = 0
            wordCount = 0
            inserted += 1
        }
    }
    if (inserted == 0 & paras.length >= 5){
        var article = document.getElementsByTagName("article")[0]
        article.insertAdjacentHTML("beforeend", ad)
    }
}

document$.subscribe(function() {
    console.log("call in ad")
    insertAd(40, 4000)
})

这段代码实现了文中和文末插入。只有在文章内容足够长时,才会插入广告。同样地,完整的代码可以在zillionare这个项目下找到。具体位置是docs/overrides/javascripts/course.js。注意不要使用ad.js这样好听好记的名字,它会被adblock这样的浏览器扩展拦截!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1386775.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux/Frolic

Enumeration nmap 还是扫描系统对外开放的端口情况&#xff0c;对外开放了22,139,445,还有9999端口&#xff0c;显示是http服务&#xff0c;使用了nginx 1.10.3 ┌──(kali㉿kali)-[~/HTB/Frolic] └─$ nmap -sC -sV -oA nmap -Pn 10.10.10.111 Starting Nmap 7.93 ( http…

pandas进行数据计算时如何处理空值的问题?

目录 1.数据预览&#xff1a; 2.解决方法 &#xff08;1&#xff09;问题示例 &#xff08;2&#xff09;方法 A.方法一 B.方法二 1.数据预览&#xff1a; 2.解决方法 &#xff08;1&#xff09;问题示例 如下图如果不理睬这些空值的话&#xff0c;计算总分便也会是空值…

uni-app购物车页面详细代码

效果图&#xff1a; 这里的购物车加减用的是uni-app中的sku插件 代码附下&#xff08;全&#xff09;&#xff1a; <script setup lang"ts"> import {reqMemberCartList,reqMemberdelentCart,reqMemberPutCart,putMemberCartSelectedAPI, } from /services/…

linux后台进程的总结

文章目录 方案1 nohup &方案2 screen 方案1 nohup & 1、单独使用 nohup 执行脚本&#xff0c;如下图所示&#xff0c;终端会被接管&#xff0c;就是标准输入stdin 被关闭了&#xff0c;使用ctrlc会导致终止执行&#xff0c;但是可以关闭这个终端&#xff0c;重新打开终…

c5060:out can‘t be used with used with non-varying visibility

openGL系列文章目录 文章目录 openGL系列文章目录前言一、GLSL language integration是什么&#xff1f;二、GLSL language integration配置二、GLSL language integration编译报错解决 前言 GLSL插件下载和安装&#xff1a;GLSL language integration下载地址 你也可以在visu…

【python可视化大屏】使用python实现可拖拽数据可视化大屏

介绍&#xff1a; 我在前几期分享了关于爬取weibo评论的爬虫&#xff0c;同时也分享了如何去进行数据可视化的操作。但是之前的可视化都是单独的&#xff0c;没有办法在一个界面上展示的。这样一来呢&#xff0c;大家在看的时候其实是很不方便的&#xff0c;就是没有办法一目了…

软件测试|教你使用dataclass

前言 当我们需要在Python中定义一种简单的数据容器类时&#xff0c;dataclass是一个非常有用的工具。它允许我们轻松地创建具有一些自动化特性的类&#xff0c;例如自动生成__init__()、__repr__()和__eq__()等方法。本文将详细介绍dataclass的使用&#xff0c;并提供示例来说…

Qt点击按钮在其附近弹出一个窗口

效果 FS_PopupWidget.h #ifndef FS_POPUPWIDGET_H #define FS_POPUPWIDGET_H#pragma once#include <QToolButton> #include <QWidgetAction> #include <QPointer>class QMenu;class FS_PopupWidget : public QToolButton {Q_OBJECTpublic:FS_PopupWidget(QW…

3dmax有哪些技巧?3damx不为人知的秘密

在装修设计和建筑可视化领域&#xff0c;3D MAX是一款强大的工具&#xff0c;可以帮助我们创建出高质量的3D模型和动画。然而&#xff0c;要充分发挥其潜力&#xff0c;我们需要掌握一些3D MAX渲染的技巧。以下是一些实用的技巧&#xff0c;帮助你提高渲染效率和质量。 合理设…

【总结】Dinky学习笔记

概述 Dinky 是一个开箱即用、易扩展&#xff0c;以 Apache Flink 为基础&#xff0c;连接 OLAP 和数据湖等众多框架的一站式实时计算平台&#xff0c;致力于流批一体和湖仓一体的探索与实践 官网&#xff1a;Dinky 核心特性 沉浸式&#xff1a;提供专业的 DataStudio 功能&a…

mysql8 源码编译 客户端连接运行 报段异常解决

mysql8 源码编译 客户端连接运行 报段异常解决。解决方案&#xff1a;删除之前编译的文件。先安装libncurses-dev依赖&#xff0c;在重新编译。原因&#xff1a;第一次编译没有libncurses-dev依赖&#xff0c;编译告警&#xff0c;再次编译有缓存&#xff0c;没有引入声明头文件…

Umi3 创建,配置环境,路由传参(代码示例)

目录 创建项目 配置环境 创建脚手架 项目结构及其目录、 路由 配置路由 嵌套路由 编程式导航和声明式导航 声明式导航 编程式导航 约定式路由 路由传参 query传参&#xff08;问号&#xff09; 接收参数 params传参&#xff08;动态传参&#xff09; 接收参数 创…

[zabbix] zabbix监控

一、温习zabbix自定义监控 二、zabbix 自动发现与自动注册 2.1 zabbix 自动发现 //zabbix 自动发现&#xff08;对于 agent2 是被动模式&#xff09; zabbix server 主动的去发现所有的客户端&#xff0c;然后将客户端的信息登记在服务端上。 缺点是如果定义的网段中的主机数…

SpringBoot教程(五) | SpringBoot中Controller详解

SpringBoot教程(五) | SpringBoot中Controller详解 SpringBoot整合SpringMvc其实千面一直讲的都是。只需要我们在pom文件中引入 web的starter就可以了&#xff0c;然后我们就可以正常使用springMvc中的功能了。所以本篇文章可能更多的是回顾&#xff0c;回顾一下springMVC中的…

Centos7 制作系统镜像iso文件

Centos7 制作系统镜像iso文件 1. 系统备份镜像1.1 安装mondo1.2 制作备份镜像1.3 恢复系统 1. 系统备份镜像 这部分针对有系统备份需求&#xff0c;防止系统遭受意外状况&#xff0c;无法紧急恢复。整体备份内容较大&#xff0c;建议考虑后再进行操作 本次使用mondo软件进行备…

代码随想录算法训练营第28天 | 93.复原IP地址 78.子集 90.子集II

目录 93.复原IP地址 &#x1f4a1;解题思路 回溯三部曲 # 判断子串是否合法 &#x1f4bb;实现代码 78.子集 &#x1f4a1;解题思路 回溯三部曲 &#x1f4bb;实现代码 90.子集II &#x1f4a1;解题思路 &#x1f4bb;实现代码 93.复原IP地址 题目链接&#x…

Rocketmq rust版本-开篇

我是蚂蚁背大象(Apache EventMesh PMC&Committer)&#xff0c;文章对你有帮助给Rocketmq-rust star,关注我GitHub:mxsm&#xff0c;文章有不正确的地方请您斧正,创建ISSUE提交PR~谢谢! Emal:mxsmapache.com Rust重构Rocketmq,大家好我是mxsm(Apache EventMesh PMC&Comm…

Linux网络编程---IP 地址格式转换函数

Linux网络编程—IP 地址格式转换函数 我们更容易阅读的IP地址是以点分十进制表示的&#xff0c;例如&#xff1a;192.168.5.10 &#xff0c;这是一种字符串的形式&#xff0c;但是计算器所需要的IP地址是以二进制进行表示&#xff0c;这便需要我们在点分十进制字符串和二进制地…

Swift 周报 第四十五期

文章目录 前言新闻和社区苹果或将扩充健康版图&#xff0c;为Apple Watch X铺路更新后的《Apple Developer Program 许可协议》现已发布 提案通过的提案 Swift论坛推荐博文话题讨论关于我们 前言 本期是 Swift 编辑组整理周报的第四十五期&#xff0c;每个模块已初步成型。各位…

【Linux】初识Linux及几个基本指令

Hello everybody!算算时间我已经有一个多月没有更新啦&#xff01;因为本专业是纺织工程&#xff0c;所以一直在复习应付期末考试\(0^◇^0)/。那好&#xff0c;废话不多说。让我们进入今天的主题&#xff01; 关于Linux系统可能很多同学不是很熟悉&#xff0c;有的人可能听过&…