使用nikola制作静态博客
为什么是静态博客?
折腾博客很久了,尝试过CSDN、博客园等第三方托管,尝试过WordPress自己维护,运维WordPress时也是关闭了Comment功能,其实我只是需要一个写博客的地方,能够记录我的技术研究过程,分享给大家作参考,静态页面完全够用了,这可以让我专注于内容,而不是折腾各种插件、主题等等。
为什么是Nikola?
MarkDown是大家比较熟知的标记语言,简单方便著称,但是MarkDown有多种实现,各自都有不同的扩展,整体扩展能力偏弱,reStructuredText(简称rst)也是一种标记语言,相比于MarkDown有更丰富的表达能力,相比于LaTex使用更加简单,reStructuredText的解析实现Sphinx是Python实现,与一众Python Web框架容易结合。
因此,笔者选择了ReStructuredText作为写作语言。
调研了Nikola,Pelican,Jekyll等静态博客生成器,Nikola优先支持rst,其他功能可以满足基本需求,因此选择了Nikola。 几种静态博客生成器都很优秀,感兴趣的可以自己尝试。
开始上手-安装与初始化
Nikola是基于Python语言实现的,首先创建Python VENV环境,并安装nikola及相关依赖。
# 创建工程目录 mkdir blog cd blog # 创建虚拟环境,并激活 python3 -m venv venv source venv/bin/activate # 安装 nikola pip install nikola # 安装本地调试服务需要的依赖 pip install aiohttp watchdog
使用nikola命令初始化项目
cd .. nikola init blog
上述命令会交互式提示输入一些基本信息,包括博客的名称、语言、时区等等。
命令执行成功之后会生成一个conf.py文件,是所有的配置信息,后续需要一些自定义的配置都是这里修改。
修改一些基本配置
一些初始化向导会提示填写的配置,如果需要也可以自己修改
# Data about this site BLOG_AUTHOR = "姜福泉" # (translatable) BLOG_TITLE = "老姜讲技术" # (translatable) # This is the main URL for your site. It will be used # in a prominent link. Don't forget the protocol (http/https)! SITE_URL = "https://blog.jiangfuquan.com/" # This is the URL where Nikola's output will be deployed. # If not set, defaults to SITE_URL # BASE_URL = "https://blog.jiangfuquan.com/" BLOG_EMAIL = "jiangfuquan@hotmail.com" BLOG_DESCRIPTION = "老姜讲技术-技术博客" # (translatable) # What is the default language? DEFAULT_LANG = "zh_cn"
设置导航菜单,archive,categories,tags,是自动生成的,about需要自己创建页面,参考 创建页面 。
NAVIGATION_LINKS = { DEFAULT_LANG: ( ("/", "首页"), ("/archive/", "文章归档"), ("/categories/", "分类"), ("/tags/", "标签"), # ("/rss.xml", "RSS 源"), ("/pages/about/", "关于"), ), }
设置archive,categories,tags,页面生成规则
TAG_PATH = "tags" CATEGORY_PATH = "categories" CREATE_MONTHLY_ARCHIVE = True CREATE_ARCHIVE_NAVIGATION = True ARCHIVE_PATH = "archive" ARCHIVE_FILENAME = "index.html"
设置license(参考 给你的博客添加CC许可协议 ),footer,logo等: 其中我的footer还加入了友盟统计(cnzz)的统计脚本,可以掌握你的博客的访问量。
LOGO_URL = '/images/logo_small.jpg' # A HTML fragment describing the license, for the sidebar. # (translatable) LICENSE = """ <a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/4.0/"> <img alt="知识共享许可协议" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-nd/4.0/88x31.png"/> </a> <br/> 本作品采用 <a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/4.0/"> 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 </a> 进行许可。 <br/> <!-- <a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/4.0/"> <img alt="Creative Commons License" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-nd/4.0/88x31.png"/> </a> <br/> --> This work is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/4.0/"> Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License </a>. """ # I recommend using the Creative Commons' wizard: # https://creativecommons.org/choose/ # LICENSE = """ # <a rel="license" href="https://creativecommons.org/licenses/by-nc-sa/4.0/"> # <img alt="Creative Commons License BY-NC-SA" # style="border-width:0; margin-bottom:12px;" # src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png"></a>""" # A small copyright notice for the page footer (in HTML). # (translatable) CONTENT_FOOTER = """ {license} <br/><br/> Contents © {date} <a href="mailto:{email}">{author}</a> - Powered by <a href="https://getnikola.com" rel="nofollow">Nikola</a> <script type="text/javascript">document.write(unescape("%3Cspan id='cnzz_stat_icon_1278825800'%3E%3C/span%3E%3Cscript src='https://v1.cnzz.com/z_stat.php%3Fid%3D1278825800%26show%3Dpic1' type='text/javascript'%3E%3C/script%3E"));</script> """ # Things that will be passed to CONTENT_FOOTER.format(). This is done # for translatability, as dicts are not formattable. Nikola will # intelligently format the setting properly. # The setting takes a dict. The keys are languages. The values are # tuples of tuples of positional arguments and dicts of keyword arguments # to format(). For example, {'en': (('Hello'), {'target': 'World'})} # results in CONTENT_FOOTER['en'].format('Hello', target='World'). # If you need to use the literal braces '{' and '}' in your footer text, use # '{{' and '}}' to escape them (str.format is used) # WARNING: If you do not use multiple languages with CONTENT_FOOTER, this # still needs to be a dict of this format. (it can be empty if you # do not need formatting) # (translatable) CONTENT_FOOTER_FORMATS = { DEFAULT_LANG: ( (), { "email": BLOG_EMAIL, "author": BLOG_AUTHOR, "date": '2020-%d' % time.gmtime().tm_year, "license": LICENSE } ) }
给博客添加百度搜索框,这里si参数可以起到过滤站点的作用,但是对于我这小博客来说,结果页广告比博客多,通过给关键词加入" site:blog.jiangfuquan.com"居然可以没有广告,所以在提交之前,通过js修改一下搜索词。
SEARCH_FORM = """ <script type="text/javascript"> function baidu_submit() { let wd = document.getElementById("baidu_wd").value let site = " site:blog.jiangfuquan.com" if (!wd.endsWith(site)) { document.getElementById("baidu_wd").value += site } document.getElementById("baidu_form").submit(); } </script> <form id="baidu_form" action="http://www.baidu.com/s" method="GET" target="_blank" submit=> <!-- img src="http://www.baidu.com/img/baidu_jgylogo3.gif" alt="百度Logo" / --> <input id="baidu_wd" type="text" name="wd" size="" /> <input type="submit" onclick="baidu_submit()" value="百度一下" /> <input type="hidden" name="ie" value="utf-8" /> <input type="hidden" name="tn" value="jiangfuquan" /> <input type="hidden" name="si" value="blog.jiangfuquan.com" /> <input type="hidden" name="ct" value="2097152" /> </form> """
创建页面
nikola提供好用的命令行,帮你创建页面和博客。
nikola new_page
根据提示输入页面名称即可。
创建博客
同样使用命令行工具:
nikola new_post
根据提示输入名称,可以自己修改一些元数据,如category,tags,title,date等。
本地预览与部署
本地编译输出到output文件夹。
nikola build
本地编译加预览可以用:
nikola auto
线上部署只需要nginx即可,我使用了https复杂一些,证书申请参考 docker下使用letsencrypt申请证书
server { listen 80; listen 443 ssl; # ssl on; server_name blog.jiangfuquan.com; #charset koi8-r; access_log /var/log/nginx/blog.access.log main; error_log /var/log/nginx/blog.error.log; ssl_certificate /etc/letsencrypt/live/blog.jiangfuquan.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/blog.jiangfuquan.com/privkey.pem; ssl_session_timeout 5m; ssl_protocols TLSv1.1 TLSv1.2; # ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv3:+EXP; ssl_ciphers HIGH:!ADH:!MD5; ssl_prefer_server_ciphers on; #301转移 #if ($server_port = 80) { # return 301 https://$server_name$request_uri; #} if ( $scheme = http ){ return 301 https://$server_name$request_uri; } location ^~ /.well-known/acme-challenge/ { alias /var/www/challenges/; # try_files $uri =404; } location / { root /var/www/html; index index.html index.htm; autoindex off; } error_page 404 /404.html; # redirect server error pages to the static page /50x.html # error_page 500 502 503 504 /50x.html; location = /50x.html { root /var/www/html; } # deny access to .htaccess files, if Apache's document root # concurs with nginx's one # location ~ /\.ht { deny all; } }
version: '3.1' services: nginx: image: nginx:1.21.3 restart: always ports: - 80:80 - 443:443 volumes: - $PWD/nginx/conf:/etc/nginx/conf.d - $PWD/nginx/logs:/var/log/nginx - $PWD/output:/var/www/html - $PWD/cert/challenges:/var/www/challenges - $PWD/cert/letsencrypt:/etc/letsencrypt