这是全互联网最小巧的博客,没有比这更小的了。虽然小巧,但功能一点儿也不弱,支持文章的分页展示,文章表格,图片和代码语法高亮。文章无限制分类,访问量统计,按时间和按点击量排序,展示最新文章,最热文章,文章留言评论等功能。
前言
如果你也想拥有一个属于自己的个性博客,这种尝试将大幅降低准入门槛,让你看到实现一个个人博客网站是多么的简单。其它的又是建库建表的,或是需要登录管理后台管理,我还是觉得不够简单好用。
用这种写好的markdown文档的形式,你可以直接把markdown笔记变成动态的博客展示出来。后续可基于微信小程序做个入口,如果想要发布文章,则直接把编写好的markdown文件提交发送过去就可以啦,操作使用上更加简单和快捷。这才应该是现在博客该有的特色和创新,原有的旧的登录网站后台编辑和使用的方式,太落后了。
况且使用这种方式还有个好处,可以方便多人同时使用。比如可以在公司内部搭建一个公共博客平台,做个简单入口界面,让每个人都可以往上面提交文档,这也是个不错的内部方案交流平台和技术分享平台。相比传统word文档的方式优点很多,方便文档的查看、归档和线上展示及分享,连留言评论都有啦。
项目开源地址:
tiny-blog: 极小的个人博客,虽然很小,但是很全。使用python+flask+html实现。
GitHub - yangyongzhen/tiny-blog
体验地址:
我的个人博客
实现原理
其实就是一个基于markdown的轻量级博客,需要按一定格式编写好markdown文件,提交到后台服务。当用户访问网页时由后台读取文件内容并经由流行的markdown-it组件渲染。
后台使用python的flask框架,整个代码仅三百多行。相当的小巧,很容易根据需要修改源码去增加一些其他的功能。当然一个css或js文件都不可能这么短,这里仅指的是后台代码。网页使用了流行的markdown-it(MarkDown渲染),highlight.js(语法高亮)和mermaid.js(画流程图、时序图等的js库)组件。留言评论功能,使用比较火的Valine博客评论系统。
网站模板我是从素材火网站上找了一个好看的博客网站模板。如果你有中意的其他博客网站模板,也可以很容易的改造过来用。文末介绍和分享几个好看的博客模板。
效果展示:
Flask介绍
Flask是一个轻量级的Python Web框架,用于构建Web应用程序和API。它基于Werkzeug工具箱和Jinja2模板引擎,并且具有灵活性和可扩展性。
Flask提供了一些基本的功能,例如路由、请求和响应处理、会话管理等,同时支持各种扩展,可以轻松添加身份验证、数据库集成、邮件发送等功能。由于其简单的学习曲线和活跃的社区支持,Flask已成为最受欢迎的Python Web框架之一。
源码实现
源码实现很简单,仅有三个文件,总代码量三百多行。为了提高访问速度,设计了一种基于字典map的简单数据结构。只有应用启动时加载一次文件,后续文章访问直接从字典数据中取数据,避免每次访问都读文件,提高访问效率。为了方便的查看数据结构,加载完文件后保存了几个json格式的文件。通过json文件很容易看出来。
发表说说和发表文章只需要进入admin界面,输入授权码,任何人都能发表说说和提交文章。
默认授权码111111,你也可以提交个文章进去看看效果。
main.py
# encoding: utf-8
# author:yangyongzhen
from flask import Flask,render_template,request,redirect
import articles
import atexit
import json
import sys
app=Flask(__name__,template_folder="templates", static_folder="static", static_url_path="/static")
# 首页
@app.route('/')
def index():
articles.Stat.totalVisit += 1
page = 1
page = request.args.get('page',1,int)
print(page)
nums = len(articles.AllArts)
print(nums)
allpage = nums / 5
if nums %5 != 0:
allpage = int(nums/5) + 1
print(allpage)
curArts = articles.AllArts
if (page * 5) < nums :
curArts = articles.AllArts[(page-1)*5 : page*5]
else:
curArts = articles.AllArts[(page-1)*5 : nums]
#分页表
tabs = []
for i in range(0,allpage):
print(i)
tabs.append(i)
print(tabs)
return render_template("index.html",arts=curArts,news=articles.NewArts[:9],hots=articles.HotArts[:9],items=articles.ItemList,curPage=page,tab=tabs) #加入变量传递
@app.route('/about')
def about():
msg="my name is caojianhua, China up!"
return render_template("about.html",data=msg) #加入变量传递
#分类页
@app.route('/items')
def items():
try:
id = request.args.get('id')
print(id)
allArts = []
allMaps = articles.ItemMap[id]
#遍历 hash
for v in allMaps.values():
allArts.append(v)
#按日期排序
allArts.sort(key=lambda x: x['date'],reverse=True)
#print(allArts)
page = 1
page = request.args.get('page',1,int)
print(page)
nums = len(allArts)
print(nums)
allpage = int(nums / 5)
if nums %5 != 0:
allpage = int(nums/5) + 1
print(allpage)
curArts = allArts
if (page * 5) < nums :
curArts = allArts[(page-1)*5 : page*5]
else:
curArts = allArts[(page-1)*5 : nums]
#分页表
tabs = []
for i in range(0,allpage):
#print(i)
tabs.append(i)
#print(tabs)
return render_template("items.html",arts=curArts,item=id,news=articles.NewArts[:9],hots=articles.HotArts[:9],curPage=page,tab=tabs)
except Exception as e:
print("Exception occured")
print(e)
return render_template('404.html')
# 文章详情页
@app.route('/article_detail')
def article_detail():
art = articles.Article("","","","","","","","",0,0)
item_index = 0
try:
id = request.args.get('id')
#print(id)
#取出分类
item = articles.ArtRouteMap[id]['item']
print(item)
art = articles.ItemMap[item][id]
#art_index = NewPosts.index(id)
articles.Stat.artStat[id]['visitCnt'] += 1
articles.Stat.totalVisit += 1
articles.ItemMap[item][id]['visitCnt'] = articles.Stat.artStat[id]['visitCnt']
#print(art)
#print(item_index)
#print(art_index)
return render_template("article_detail.html",data=art,news=articles.NewArts[:9],hots=articles.HotArts[:9] )
except Exception as e:
print("Exception occured")
return render_template('404.html')
# 说说页面
@app.route('/moodList')
def moodList():
msg="my name is caojianhua, China up!"
return render_template("moodList.html",data=msg)
# 留言页面
@app.route('/comment')
def comment():
msg="my name is caojianhua, China up!"
return render_template("comment.html",data=msg)
@app.route('/404')
def error_404():
print("error_404")
return render_template("404.html")
@app.errorhandler(404)
def page_not_found(e):
print("page_not_found")
print(e)
return render_template('404.html'), 404
@app.errorhandler(Exception)
def handle_exception(e):
# 捕获其他异常
print("handle_exception")
print(e)
return render_template('404.html'), 500
def saveData():
#保存访问量数据
with open('statistic.json1', 'w',encoding='utf-8') as f:
json.dump(articles.Stat.__dict__, f,ensure_ascii=False)
# 监控退出时保存统计信息
@atexit.register
def exit_handler():
print("应用程序退出")
saveData()
if __name__=="__main__":
#启动时加载访问量数据
articles.getPosts()
app.run(port=8000,host="127.0.0.1",debug=False)
print("over")
articles.py
# encoding: utf-8
# author:yangyongzhen
import glob
import os
import pprint
import hashlib
import json
import statistic
#访问量阅读量统计信息
Stat = statistic.AllStat()
# AllArts 总的文章,(按时间排过序的)
AllArts = []
# NewArts 最新文章
NewArts = []
# HotArts 热门文章
HotArts = []
# 文章分类信息
ItemList = []
ItemMap = {}
ArtRouteMap = {}
#文章信息类定义
class Article(object):
def __init__(self, id, item, title, date, summary, body, imgFile, author, cmtCnt, visitCnt):
self.id = id
self.item = item
self.title = title
self.date = date
self.summary = summary
self.body = body
self.imgFile = imgFile
self.author = author
self.cmtCnt = cmtCnt
self.visitCnt = visitCnt
def mPrint(self):
print(self.id)
print(self.item)
print(self.title)
print(self.date)
print(self.summary)
print(self.body)
print(self.imgFile)
print(self.author)
class ArticlesData():
def __init__(self):
self.item = ""
self.articlesMap = {}
# ArticleRoute 文章的分类和路径信息
class ArticleRoute:
def __init__(self, item, name):
self.item = item
self.name = name
#文章分类 是个集合类型,无重复元素
class ItemCfg:
def __init__(self):
self.items = set()
def strTrip(src):
str = src.replace(" ", "")
str = str.replace("\r", "")
str = str.replace("\n", "")
return str
#从文件中读取信息,并返回itemMap,artRouteMap,items
def getPosts():
global ItemMap,ArtRouteMap,ItemList,NewArts
# 打开访问量统计JSON文件并读取内容
with open('statistic.json1', 'r',encoding='utf-8') as file:
json_data = file.read()
# 将JSON字符串反序列化为字典对象
Stat.__dict__ = json.loads(json_data)
print(Stat.totalVisit)
# 获取"posts/"目录下的所有文件
files = glob.glob("posts/*")
# 按修改日期进行排序
files = sorted(files, key=lambda x: os.path.getmtime(x))
articleMap = {}
itemMap = {}
artRouteMap = {}
#文章分类,是个集合类型,无重复
items = set()
# 遍历文件列表
for i, f in enumerate(files):
#print(i)
#print(f)
file = f
file = os.path.basename(file) # 转换路径分隔符
print(file)
file = os.path.splitext(file)[0] # 移除".md"后缀
fileread = open(f, 'r',encoding='utf-8').read()
lines = fileread.split('\n')
#print(lines)
title = lines[0].strip()
date = strTrip(lines[1].strip())
summary = lines[2].strip()
imgfile = lines[3].strip()
id = hashlib.md5(file.encode(encoding='UTF-8'))
id = id.hexdigest()
#print(id.hexdigest())
item = strTrip(lines[4].strip())
author = lines[5].strip()
imgfile = strTrip(imgfile)
body = '\n'.join(lines[6:]) # 获取第6行及以后的行
ar = ArticleRoute(item, title)
artRouteMap[id] = ar.__dict__
visitCnt = 0
if id in Stat.artStat:
visitCnt = Stat.artStat[id]['visitCnt']
#print(visitCnt)
else:
Stat.artStat[id] = statistic.ArtStat(title,visitCnt,0).__dict__
post = Article(id,item,title, date, summary, body, imgfile, author, 0,visitCnt)
articleMap[id] = post
items.add(item)
#json_data = json.dumps(posts,ensure_ascii=False)
#print(json_data)
itemList = []
for itm in items:
itmMp= {}
for v in articleMap.values():
if(v.item == itm):
itmMp[v.id] = v.__dict__
itemMap[itm] = itmMp
itemList.append(itm)
#保存为json文件
with open('Articles.json1', 'w',encoding='utf-8') as f:
json.dump(itemMap, f,ensure_ascii=False)
with open('artRouteMap.json1', 'w',encoding='utf-8') as f:
json.dump(artRouteMap, f,ensure_ascii=False)
itemList.sort()
#遍历字典
for key, val in itemMap.items():
for idx,art in val.items():
AllArts.append(art)
HotArts.append(art)
#按日期排序
AllArts.sort(key=lambda x: x['date'],reverse=True)
#按访问量排序
HotArts.sort(key=lambda x: x['visitCnt'],reverse=True)
NewArts = AllArts
ItemList = itemList
#print((HotArts[0]))
ItemMap = itemMap
ArtRouteMap = artRouteMap
return itemMap,artRouteMap,itemList
if __name__=="__main__":
p,p1,items = getPosts()
#print(p)
print(type(p))
print(type(p1))
print(p['go学习笔记'])
print(p1['3eaf98ad2b949b3f93d16aeaa1f45ab0'])
print(items)
statistic.py
# encoding: utf-8
# author:yangyongzhen
# 文章和总阅读量统计
#文章访问量统计
#param:名称,访问量,评论量
class ArtStat:
def __init__(self, title,visitCnt,cmtCnt):
self.title = title
self.visitCnt = visitCnt
self.commentCnt = cmtCnt
#总的统计
class AllStat:
def __init__(self):
#总访问量
self.totalVisit = 0
self.artStat = {}
if __name__=="__main__":
pass
如何使用
除了flask,无其它依赖。下载源码,pip install flask安装好依赖。然后python main.py即可运行起来。编写新文章,需要按照特定格式写前面几行。因为文章作者,文章分类和日期等信息,都在一个文件里写的。
文章内容格式如下(test.md):
This is a title!
2020-10-22
你好aaaaaaaasssssssssssssssss! 这是你第一次使用 **Markdown编辑器** 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。
08.jpg
感悟
yangyongzhen
This is the main post!
# Markdown1!
## Markdown2!
### 欢迎使用Markdown编辑器
Name | Age
--------|------
Bob | 27
Alice | 23
你好! 这是你第一次使用 **Markdown编辑器** 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。
```golang
package main
import "fmt"
func main() {
fmt.Println("hello ")
}
```
网页前端小技巧
导航栏选中高亮
网站头部导航栏根据选择自动高亮显示,代码在silder.js中实现:
var obj=null;
var As=document.querySelector(".w_header_nav").getElementsByTagName('a');
obj = As[0];
for(i=1;i<As.length;i++){if(window.location.href.indexOf(As[i].href)>=0)
obj=As[i];}
console.log(obj)
// 添加class属性
obj.classList.add("active");
返回到顶部
返回到顶部,可复用js代码:
var $backToTopEle=$('<a href="javascript:void(0)" class="Hui-iconfont toTop" title="返回顶部" alt="返回顶部" style="display:none">^^</a>').appendTo($("body")).click(function(){
$("html, body").animate({ scrollTop: 0 }, 120);
});
var backToTopFun = function() {
var st = $(document).scrollTop(), winh = $(window).height();
(st > 0)? $backToTopEle.show(): $backToTopEle.hide();
/*IE6下的定位*/
if(!window.XMLHttpRequest){
$backToTopEle.css("top", st + winh - 166);
}
};
$(function(){
$(window).on("scroll",backToTopFun);
backToTopFun();
});
分页显示js代码 :
/*
*page plugin 1.0
*/
(function ($) {
//默认参数
var defaults = {
totalPages: 9,//总页数
liNums: 9,//分页的数字按钮数(建议取奇数)
activeClass: 'active' ,//active类
firstPage: '首页',//首页按钮名称
lastPage: '末页',//末页按钮名称
prv: '«',//前一页按钮名称
next: '»',//后一页按钮名称
hasFirstPage: true,//是否有首页按钮
hasLastPage: true,//是否有末页按钮
hasPrv: true,//是否有前一页按钮
hasNext: true,//是否有后一页按钮
callBack : function(page){
//回掉,page选中页数
}
};
//插件名称
$.fn.Page = function (options) {
//覆盖默认参数
var opts = $.extend(defaults, options);
//主函数
return this.each(function () {
var obj = $(this);
var l = opts.totalPages;
var n = opts.liNums;
var active = opts.activeClass;
var str = '';
var str1 = '<li><a href="javascript:" class="'+ active +'">1</a></li>';
if (l > 1&&l < n+1) {
for (i = 2; i < l + 1; i++) {
str += '<li><a href="javascript:">' + i + '</a></li>';
}
}else if(l > n){
for (i = 2; i < n + 1; i++) {
str += '<li><a href="javascript:">' + i + '</a></li>';
}
}
var dataHtml = '';
if(opts.hasNext){
dataHtml += '<div class="next fr">' + opts.next + '</div>';
}
if(opts.hasLastPage){
dataHtml += '<div class="last fr">' + opts.lastPage + '</div>';
}
dataHtml += '<ul class="pagingUl">' + str1 + str + '</ul>';
if(opts.hasFirstPage){
dataHtml += '<div class="first fr">' + opts.firstPage + '</div>';
}
if(opts.hasPrv){
dataHtml += '<div class="prv fr">' + opts.prv + '</div>';
}
obj.html(dataHtml).off("click");//防止插件重复调用时,重复绑定事件
obj.on('click', '.next', function () {
var pageshow = parseInt($('.' + active).html());
var nums,flag;
var a = n % 2;
if(a == 0){
//偶数
nums = n;
flag = true;
}else if(a == 1){
//奇数
nums = (n+1);
flag = false;
}
if(pageshow >= l) {
return;
}else if(pageshow > 0&&pageshow <= nums/2){
//最前几项
$('.' + active).removeClass(active).parent().next().find('a').addClass(active);
}else if((pageshow > l-nums/2&&pageshow < l&&flag==false)||(pageshow > l-nums/2-1&&pageshow < l&&flag==true)){
//最后几项
$('.' + active).removeClass(active).parent().next().find('a').addClass(active);
}else{
$('.' + active).removeClass(active).parent().next().find('a').addClass(active);
fpageShow(pageshow+1);
}
opts.callBack(pageshow+1);
});
obj.on('click', '.prv', function () {
var pageshow = parseInt($('.' + active).html());
var nums = odevity(n);
if (pageshow <= 1) {
return;
}else if((pageshow > 1&&pageshow <= nums/2)||(pageshow > l-nums/2&&pageshow < l+1)){
//最前几项或最后几项
$('.' + active).removeClass(active).parent().prev().find('a').addClass(active);
}else {
$('.' + active).removeClass(active).parent().prev().find('a').addClass(active);
fpageShow(pageshow-1);
}
opts.callBack(pageshow-1);
});
obj.on('click', '.first', function(){
var activepage = parseInt($('.' + active).html());
if (activepage <= 1){
return
}//当前第一页
opts.callBack(1);
fpagePrv(0);
});
obj.on('click', '.last', function(){
var activepage = parseInt($('.' + active).html());
if (activepage >= l){
return;
}//当前最后一页
opts.callBack(l);
if(l>n){
fpageNext(n-1);
}else{
fpageNext(l-1);
}
});
obj.on('click', 'li', function(){
var $this = $(this);
var pageshow = parseInt($this.find('a').html());
var nums = odevity(n);
opts.callBack(pageshow);
if(l>n){
if(pageshow > l-nums/2&&pageshow < l+1){
//最后几项
fpageNext((n-1)-(l-pageshow));
}else if(pageshow > 0&&pageshow < nums/2){
//最前几项
fpagePrv(pageshow-1);
}else{
fpageShow(pageshow);
}
}else{
$('.' + active).removeClass(active);
$this.find('a').addClass(active);
}
});
//重新渲染结构
/*activePage 当前项*/
function fpageShow(activePage){
var nums = odevity(n);
var pageStart = activePage - (nums/2-1);
var str1 = '';
for(i=0;i<n;i++){
str1 += '<li><a href="javascript:" class="">' + (pageStart+i) + '</a></li>'
}
obj.find('ul').html(str1);
obj.find('ul li').eq(nums/2-1).find('a').addClass(active);
}
/*index 选中项索引*/
function fpagePrv(index){
var str1 = '';
if(l>n-1){
for(i=0;i<n;i++){
str1 += '<li><a href="javascript:" class="">' + (i+1) + '</a></li>'
}
}else{
for(i=0;i<l;i++){
str1 += '<li><a href="javascript:" class="">' + (i+1) + '</a></li>'
}
}
obj.find('ul').html(str1);
obj.find('ul li').eq(index).find('a').addClass(active);
}
/*index 选中项索引*/
function fpageNext(index){
var str1 = '';
if(l>n-1){
for(i=l-(n-1);i<l+1;i++){
str1 += '<li><a href="javascript:" class="">' + i + '</a></li>'
}
obj.find('ul').html(str1);
obj.find('ul li').eq(index).find('a').addClass(active);
}else{
for(i=0;i<l;i++){
str1 += '<li><a href="javascript:" class="">' + (i+1) + '</a></li>'
}
obj.find('ul').html(str1);
obj.find('ul li').eq(index).find('a').addClass(active);
}
}
//判断liNums的奇偶性
function odevity(n){
var a = n % 2;
if(a == 0){
//偶数
return n;
}else if(a == 1){
//奇数
return (n+1);
}
}
});
}
})(jQuery);
使用方法,在需要分页展示的文章下方添加以下div标签:
<div class="page">
</div>
在网页头部引入以下js:
<script type="text/javascript" src="plugin/fenye.page.js"></script>
<script type="text/javascript">
$(function(){
$("#page").Page({
totalPages: 7,//分页总数
liNums: 5,//分页的数字按钮数(建议取奇数)
activeClass: 'activP', //active 类样式定义
callBack : function(page){
//console.log(page)
}
});
})
</script>
markdown文档渲染
<ul class="info>
<div class="md_render">
</div>
</ul>
<script>
var md;
var defaults = {
html: false, // Enable HTML tags in source
xhtmlOut: false, // Use '/' to close single tags (<br />)
breaks: false, // Convert '\n' in paragraphs into <br>
langPrefix: 'language-', // CSS language prefix for fenced blocks
linkify: true, // autoconvert URL-like texts to links
typographer: true, // Enable smartypants and other sweet transforms
// options below are for demo only
_highlight: true,
_strict: false,
_view: 'html' // html / src / debug
};
defaults.highlight = function (str, lang) {
var esc = md.utils.escapeHtml;
if (lang && hljs.getLanguage(lang)) {
try {
return '<pre class="hljs"><code>' +
hljs.highlight(lang, str, true).value +
'</code></pre>';
} catch (__) {}
}
return '<pre class="hljs"><code>' + esc(str) + '</code></pre>';
};
md = window.markdownit(defaults).use(window.markdownitCheckbox).use(window.markdownitEmoji).use(window.markdownitFootnote);
var dat = document.querySelector('#enrolled_id').value;
$('.md_render').html(md.render(dat));
</script>
这里有个坑需要注意,jinjia2模版是在后端渲染的,所以在网页的js代码中想使用模版变量不是那么美,不能像网页中{{data}}的方式使用。这里采用了一种取巧的方式,通过一个隐藏标签,先把需要的内容在html网页中渲染出来,再在js中通过操作dom的方式去获取。
<input type="hidden" id="enrolled_id" value="{{ data.body }}">
其他优秀的个人博客推荐
二猫blog-一个简单的Blog站点
POETIZE
樱花庄的白猫 | ねこ・しろ・ましろ
loading...文章来源:https://www.toymoban.com/news/detail-756866.html
Fomalhaut🥝
Twikoo评论系统配置教程(基于腾讯云开发) | 张洪Heo
LinkinStar's Blog
其他资源
(自适应手机端)响应式个人博客网站模板
我的博客模版
JSON在线 | JSON解析格式化—SO JSON在线工具
风宇个人博客
丰富的计算机技术分享,多样化的互联网资源分享,第1页 - 吱吱工具箱butterPig
POETIZE
loading...
Fomalhaut🥝文章来源地址https://www.toymoban.com/news/detail-756866.html
到了这里,关于Python三百行代码实现一简约个人博客网站(全网最小巧)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!