目前最终展示的平台是独立博客,而自己又有不止一个博客,所以最终打造了这样一个流程。 它的价值在于:
- 只需要在OB中对应输出目录中把内容写好,提交到Git上,后面的事就都不用管了
- 写内容不用关注最终的样式,不同博客可以有不同的样式,可以是Hexo也可以是Jekyll,也可以是其他的
有了这个流程以后,写文章回归到了本质。
下面会介绍为什么这样做,以及如何实现,还有以前的一些尝试思考。
1、只关注内容减少内耗
1.1、输出平台的选择和考虑,为什么没有选公众号等
为什么要输出
这算是老生常谈了,对我来说,最大的感触是,当我开始写的东西,会发现知识有很多卡点,不顺畅,也说不清楚,通过输出能非常好理顺,理清自己的知识体系和结构。随着时间的推移。写作能力会提升,学东西的速度也会加快。决定开始输出,选择什么平台输出呢?其实不重要。随便找个自己认为合适的平台开始写就好了。而我现在选择的方案是自己建立独立博客。原因如下:
创作自由度考虑
选择自己建站发布个人博客是为了追求创作的自由度和灵活性。在传统的自媒体平台上,虽然也可以自定义一些样式和功能,但是总有一些限制,比如特定的模板、插件或者广告。而在 个人博客上,我们几乎可以做任何你想做的事情。可以完全掌控博客的外观和功能,从页面布局到交互效果,全部由自己来决定。这种自由度意味着可以表达自己独特的风格和想法,让博客更符合个人品味和需求。
随时更新和修改
另一个优势是可以随时修改和更新。在传统的自媒体平台上,一旦发布了文章,想要进行修改就会稍显麻烦,可能需要登录到平台后台,进行编辑并保存。公众号文章更是只能修改少数几个字。 而在自己博客上,可以直接在本地编辑文章,然后通过 Git 工具将修改推送到 GitHub 仓库,整个过程非常简单。这意味着可以随时根据需要对文章进行修改和更新,保持内容的新鲜和准确。
这个说起来是优势也是劣势,特别是早期刚开始写的时候,尽量不要让自己心理有这个预期,好像自己随时可以改。尽量让自己一次性搞定。
但是没人看呀
然而,与传统的博客平台相比,自己建站发布个人博客也存在一些劣势。首先是流量问题。相对而言,我们的博客可能会面临流量较少的情况。这意味着需要通过其他途径,比如社交媒体、搜索引擎优化等手段来吸引读者,增加博客的曝光度和访问量。
不来钱啊
其次,个人博客相对于一些专业的博客平台来说,变现能力较弱。传统的博客平台通常提供了各种各样的变现方式,比如广告投放、付费订阅等,可以让博主通过写作获得一定的经济收益。而在自己建站上,由于平台本身的性质和限制,变现的方式相对有限,需要博主通过其他途径来实现。
千金难买我乐意
如何平衡这些劣势呢?一方面,可以通过持续优质内容的输出来吸引读者,提升博客的影响力和知名度,从而间接增加流量。另一方面,可以考虑在博客中设置捐赠通道或者链接到其他平台,比如 Patreon 或者 Ko-fi,让支持者可以通过其他方式给予支持和回报。最重要的是,保持对自己创作的热情和信心,坚持不懈地输出有价值的内容,这才是长期发展的关键。
当然,有一个收益的目标总是好的。真要弄其他平台,这里写好再复制过去也可以,并不冲突。
1.2、背景介绍,多个博客咋办
以前有2个博客:
- Jekyll
- 在VS Code里面写,这个博客从2013年左右,开始接触时间管理,柳比歇夫开始就做了,当时的样式还比较简陋
- Jekyll如果部署在GitHub上有个天然的要求,它必须公开,这点咋说呢
- 如果部署在Github,目前看来只能有一个免费站点。
- Hexo
- 偶然的机会需要写点东西,不想放在Jekyll里面,年初的时候重新折腾过Jekyll,感觉还是有点费劲,很多功能也没有(主要是因为我不熟)。就换了Hexo,漂亮太多了。
问题就在这个时候出现了,我希望写东西在一个地方,但是不管是Jekyll还是Hexo,都会带有很多样式相关的文件夹。它们提交到Git的时候大多数都要带上,但是这2个博客有分属不同的Git仓库。
简单来说,无法分离辅助展示的内容,和真正的内容,让我一度陷入焦灼,也让进展一度停滞。
1.3、能不能自动同步文件夹
因为最后发布到平台这个步骤免不了,我希望的效果:
- OB是很纯粹的知识梳理、整理工具
- 发布是另外的工具干的
能不能用OB插件
这个时候我希望有这样的工具,借助OB的插件,自动/手动把文章复制到对应的文件夹。但是:
- 如果博客内容有改动,或者标题变了,那个目录就很难知道。
- 当然也可以把东西全删了,重新来过那也可以。但是毕竟不直观
当时也想过自己开发个OB插件,无奈水平不到,就把这件事就这么一直给拖了下去。后来听说OB有数据库的功能,一度跃跃欲试,但是考虑到新出的功能,另外似乎这也不是正确的解决办法。
能不能用rsync,rclone之类的工具
这个时候想到另外的一个办法,能不能借助这样的工具呢,理论上完全是可以的,但是这样一来:
- 本机一直要启动一个程序为了它,消耗资源,啥时候死了也不知道。
- 后面还是需要手工提交增加了难度
1.4、转机在Github Action
因为一直想实现CI/CD,但是我的知识储备有限,无奈还是不死心,所有就想着有什么办法可以做这个事,理论上应该是可以的,即使没有Github Action这样的东西,另外做个服务去同步应该也是可行的。
2、从OB到Github到Vercel
为了解决这个问题,首先确定好整体目标:
- 希望自己更多关注在内容上,而不是工具的使用上。
- 而在具体内容创作上,也不希望因为格式而分心。
- 希望仓库私有。
所有就开始这样设计
- 整体上把所有博客和Obsidian的仓库分来开,都做成私有库
- Obsidian里面不存放和展示,theme相关的内容,只放纯粹的内容
- 每个博客站点一个私有库,这样的好处是各个站点独立管展示样式,随时可以换
- 为什么随时换很重要呢,因为最初的Jekyll博客后来完全无法适应移动终端……
2.1、重新找几个好看的样式
Jekyll的样式比较少,就一直沿用当前这个样式,在Github上直接拿来用就可以了,Hexo的样式比较多,我们选择的参考大概是这样:
- 搜索top 10,最新hexo样式…… 可以找出来很多
- 看这个hexo库在Github上的Star和最后更新时间。如果Star少,长久不更新,遇到问题很难快速解决
闪烁之狐
ookamiAntD
辣椒の酱
这几个样式和功能都是很ok的,但是最后没有选,可能是念旧吧。
2.2、创建Github私有库并部署到Vercel
基本上直接拷贝过来就可以了,Vercel里面直接部署。这里一般没有太多问题,后面会介绍一些这次部署Jekyll遇到的几个问题。
sh: jekyll:command not found
这个问题是因为从老博客拷贝过来的时候,希望文件少点,结果Gemfile没拷贝,就报错了
ERROR: YOUR SITE COULD NOT BE BUILT
这个是需要在_config.yml里面添加github仓库名,有个属性设置上就可以了
这里说明一点,用vercel去部署的时候,_config.yml里面的内容不能重复,以前用Github Pages的时候,属性重复它会认最后那个,但是Vercel里面就直接报错了
主页打开了,但是具体文章打不开,发现链接没有后缀
检查发现是我为了简单不想弄文件夹,把permlink改了。 这个属性是jekyll生成文件的一个依据,最后改成 permalink: /:categories/:title/ 可以了。 实际上,jekyll是通过它来生成文件结构的,最后这个/很重要,它会在这个文件夹下面生成一个 Index.html
如何看文件输出结构
在这里有个Source,它是针对每次发布的,从发布那里有个菜单看 view source就可以看到了。下面我们再往前倒推,如何从OB的仓库到博客的仓库呢?
2.3、如何从Ob的仓库到博客的仓库
答案就是利用Github Action。简单理解它就是一个自动化程序。目前对它的理解是,当我们的仓库内容发生变化时候,就会去看这里有哪些Action,调用它。 下面这个代码的逻辑大概是这样的
下面是我写的Action代码,一个name就对应一个步骤
name: 同步内容到ActiLearn的_posts文件夹
on:
push:
paths:
- '02.Area领域/230、写作输出/ActiLearn/**'
jobs:
sync:
runs-on: ubuntu-latest
steps:
- name: 先拿到当前库内容
uses: actions/checkout@v2
with:
fetch-depth: '0'
- name: Setup SSH
run: |
mkdir -p ~/.ssh
echo "$" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H github.com >> ~/.ssh/known_hosts
# Replace TARGET_HOST with your target's hostname or IP address
- name: 设置 Python 环节
uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: 安装python依赖
run: |
pip install pypinyin # 假设 convert_titles.py 需要 pypinyin 库
- name: 配置信息
run: |
git config --global user.email "[email protected]"
git config --global user.name "timetoeasy"
# 替换为您的邮箱和用户名
- name: 克隆 修改 同步
run: |
# 创建临时目录
# 克隆目标路径
# 克隆 rep_hexo 仓库,确保替换为您的实际仓库地址和分支名称
git clone $ ActiLearn # $tmp_dir
cd ActiLearn
# 切换到 hexo 仓库的目标分支
git checkout main
- name: 同步文件信息
run: |
# 同步 内容到 hexo 仓库的 source/_posts 目录
# 注意:这里使用 rsync 命令进行同步,并删除目标中多余的文件
rsync -av --delete ./02.Area领域/230、写作输出/ActiLearn/ ActiLearn/_posts
- name: 执行文件名转换
run: |
echo "当前路径是 $(pwd)"
cd ActiLearn
echo "列举转换前目录的所有文件:"
ls -lah _posts
python ./convert_titles.py # 根据实际路径调整
echo "列举转换后目录的所有文件:"
ls -lah _posts
- name: 执行移动目录
run: |
echo "当前路径是 $(pwd)"
cd ActiLearn
echo "列举转换前目录的所有文件:"
ls -lah _posts
python ./move_category.py # 根据实际路径调整
echo "列举转换后目录的所有文件:"
ls -lah _posts
- name: 正式上传
run: |
# 转到对应的路径
cd ActiLearn
echo "当前路径是 $(pwd)"
echo "列举当前目录的所有文件:"
ls -lah
# Add, commit, and push changes
git add .
git commit -m "Sync updates from obsidian_src"
git push origin main
- name: 清理文件
run: |
# 清理文件
echo "清理文件"
rm -rf $tmp_dir
执行完的结果大概是这样
简单说明一下: Github的Action就是Git为我们构建了一个虚拟的执行环境,把当前仓库的内容弄进去,然后开始操作。
代码比较简单,目前来说够用了。Jekyll对目录有一些要求,最早的博客是用Rakefile来生成一个英文文件名,非常不方便管理,这次既然分离了,就通过Github Action去调用python脚本去修改文件名。
3、图床的选择和考虑
图床是这次才开始使用的,以前一直觉得很难,原因可能是10多年前弄七牛云的时候伤到了,没搞定,另外收费也不低。就一直觉得这事很难,其实PicGo一直是知道的,但是一直觉得它就是一个客户端插件,这次用下来发现完全不是那么回事。
使用的要求基本就一条:无感, 不要去折腾就可以了,以前每次截图都很费劲,用的是VS Code的插件,总会记错快捷……
3.1、为什么用图床:
- 如果图片和文章放一起,那么markdown里面的图片引用就是相对路径,如果换个文件夹就要改很多地方
- 随着时间的推移,这个文件夹会越来越大
- 图片的访问可能会把博客流量撑死
3.2、图床的价值
- 图片存放在对方服务器上,一般来说不容易死
- 链接用他们的就可以了
- cdn加速可以用到,简单理解就是不同省份,不同地域的人访问,它可能会缓存过去
3.3、我选择图床的考虑
- 不自己建了,自建当然是可以的,但是会发现其实不稳定,费心费力
- 从时间成本上看也不划算
- 另外,如果图床是和数据库挂钩了,最好别折腾了,多了一个风险点
- 商业公司至少稳妥,花点钱买保险
- 费用合理
最后选择的方向有几个:
- 阿里云OSS,腾讯云的COS
- 其实没用过,因为还没研究就找了另外的方案
- Github+jsdelivr
- 一直想用,但是也没深入研究
- 免费1G
- AWS+CF
- 原因是因为它有免费的10G,最后选了它
3.4、选择插件
这方面的资料比较多,不再赘述,如果有需要可以在 关于里面发邮件给我。 目前主要有Picgo和PicList,没有深究他们的差异,相对来说使用上PicList更简单一些。
不管是哪个,所有复杂的事都被它给干了,它只是给了我们一个url地址。
使用过程中如果遇到图片无法上传,基本上可以通过卸载重装等搞定,因为它只是一个中间环节,不保留数据,所以就还好。另外它也有个地方可以看日志,辅助问题排查。
4、旧博客迁移
这里说的迁移是以前的博客有2个痛点:
- 文件名是拼音的
- 图片在本地
所以也做了一个图片迁移的代码:
import os
import re
import requests
import time
# 定义全局变量来追踪进度
total_files = 0
processed_files = 0
start_time = time.time()
# PicGo HTTP Server 地址
picgo_server = "http://127.0.0.1:36677/upload"
# 遍历指定文件夹中的所有Markdown文件
def process_markdown_files(folder_path):
global total_files
md_files = [os.path.join(root, file)
for root, dirs, files in os.walk(folder_path)
for file in files if file.endswith(".md")]
total_files = len(md_files)
for file_path in md_files:
process_start_time = time.time()
process_markdown_file(file_path)
process_time = time.time() - process_start_time
print(f"Processed {file_path} in {process_time:.2f} seconds.")
global processed_files
processed_files += 1
elapsed_time = time.time() - start_time
remaining_files = total_files - processed_files
avg_time_per_file = elapsed_time / processed_files
estimated_remaining_time = avg_time_per_file * remaining_files
print(f"Processed {processed_files}/{total_files} files in {elapsed_time:.2f} seconds. Estimated remaining time: {estimated_remaining_time:.2f} seconds.")
# 处理单个Markdown文件
def process_markdown_file(file_path):
with open(file_path, "r+", encoding="utf-8") as file:
print('开始处理:' + file_path)
content = file.read()
updated_content, images = extract_and_upload_images(content)
if updated_content: # 有进行图片替换,则更新文件内容
file.seek(0)
file.write(updated_content)
file.truncate()
print(f"Processed {file_path} with {len(images)} images uploaded.")
# 从Markdown内容中提取图片并上传,返回更新后的内容
def extract_and_upload_images(content):
pattern = r"!\[.*?\]\((images/.*?)\)"
pattern = r"!\[.*?\]\((\.\./\.\./images/.*?\.(?:png|jpg|jpeg|gif))\)"
matches = re.findall(pattern, content)
if not matches:
return None, []
for relative_image_path in matches:
#full_image_path = os.path.join(os.getcwd(), image_path) # 假设图片相对当前脚本位置
print('relative_image_path:' + relative_image_path)
# 移除路径中的 `../../`
relative_image_path_cleaned = relative_image_path .replace("../", "")
# 计算图片的实际文件系统路径,这里使用正斜杠以确保与网络请求兼容
full_image_path = os.path.abspath(os.path.join(os.getcwd(), relative_image_path_cleaned)).replace("\\", "/")
uploaded_url = upload_image_to_picgo(full_image_path)
print('full_image_path:' + full_image_path)
#uploaded_url = upload_image_to_picgo(full_image_path)
if uploaded_url:
content = content.replace(relative_image_path, uploaded_url)
return content, matches
# 上传图片到PicGo
def upload_image_to_picgo(image_path):
files = {'file': open(image_path, 'rb')}
try:
response = requests.post(picgo_server, files=files)
response_json = response.json()
# 检查上传是否成功
if response.status_code == 200 and response_json.get('success'):
# 提取第一个结果的 URL
return response_json['result'][0] # 假设我们总是关心第一个结果
else:
print(f"Upload failed for {image_path}. Response: {response_json}")
except Exception as e:
print(f"Error uploading {image_path}: {e}")
finally:
files['file'].close() # 确保文件在上传后被正确关闭
return None
if __name__ == "__main__":
folder_path = "_post" # 指定Markdown文件夹路径
process_markdown_files(folder_path)
时间还是比较久的
5、聚焦写作的几个小技巧
技巧的目的,是为了让我们整体思路比较连贯写完。
- 先写提纲,大概要写什么先写了
- 如果可以的话先写概述,让自己不偏离
- 如果需要插图等,我就写图图
- 空那里,或者描述一下大概是什么图
- 回头再写
- 标题前面的数字最后放
- 中间不要为这个事分心,最多弄个级别啥的
6、后记
这篇文章其实拖了好几个月,迟迟没开始,写了也就写了,以前囿于流程不畅,费了很长时间。用过的工具很多,vs code可能算是比较奇葩的了,我居然用了10来年,后面用了bear,typora,妙言等,最后还是决定放在OB里面,这个地方放深度知识。方便整理和梳理。
7、Hexo和Jekyll细节记录
7.1、Hexo的db.json
这个文件应该是自动生成的,系统依赖它来产生内容。 因为发现从另外一个博客拷贝过来的时候,它居然保留了原来的内容,通过opdus搜出来才发现是这么回事……