steemblog: Steem时光机 | Steem Time Machine

本文我们将介绍 steemblog 的最新的进展。‌




‌Image Source: Pixabay



#1 回顾


‌steemblog 是一项 Steem 文章镜像服务。‌


如果你还不了解这个服务,或许你可以通过访问:https://steemblog.github.io/@team-cn/ 作为一个案例体验一下。‌


在文章《用GitHub创建Steem文章镜像 | Mirroring Your Steem Blogs on GitHub | 免费博客备份服务: steemblog》中,我们提到过创建steemblog 服务的缘由,主要是为了更好的查看历史文章。‌


现在来看,它主要带来了以下几点助益:‌


  1. 首先,steemblog 作为一份档案和历史记录,将 steem 数据以便于阅读和查询的方式保存;虽然区块链技术被认为具有数据不可毁灭的潜力,但有一份额外的备份也存在价值;
  2. 其次,它提供了更好的用户体验访问速度,对于查询、搜索、整合个人或组织的文章,效率更高、更有愉悦感;
  3. 再次,它目前每日更新,虽然是存档,但基本可以保持是近实时的,多于过去几日的文章,也可以一并处理。


根据上一篇文章和微信群中的留言,我们已经帮以下12个账号部署了每日更新的博客镜像服务:‌


  1. @robertyan
  2. @andrewma
  3. @ericet
  4. @julian2013
  5. @annepink
  6. @bring
  7. @sunai
  8. @nostalgic1212
  9. @m18207319997
  10. @iguazi123
  11. @team-cn
  12. @lemooljiang

‌每日北京时间凌晨0点,steemblog 会自动同步镜像一次。‌



总体而言,对我来说 steemblog 是一个还比较好用的时光机(Time Machine),可以更好地学习和了解 steem 上各种有趣的事情。


下面举几个例子来看看吧:‌



#2 场景


‌以下是steemblog的几个有意思的应用场景。



(1)新手村访谈


初入CN区或者新手村的新人,想要了解一下社区内的朋友,其实没有看上去那么容易。去逐个翻看各个用户的历史文章,效率很低。‌


但新手村访谈部分解决了这个问题,我们只要看 #cn-interview 这个标签下的文章就行,但由于steemit / busy 查看标签历史或多或少都有点问题,所以也很难流畅地阅读。在以前,大概有2种方式:‌


  1. 看新手村的索引贴,例如2018版的:https://busy.org/@team-cn/2018-x61niax2i4/
  2. 阅读《Steem指南》的新手村计划部分:https://steem-guides.github.io/steemh/fl.html#section-17.3


但这两种方法更新可能不太及时,最近的访谈没有录入,而 steemblog 是及时更新的。 steemblog 的搜索功能也更快速和有针对性。‌


通过链接:https://steemblog.github.io/@team-cn/tags/cn-interview/,我们可以看到新手村历史上所有的访谈的记录:‌


最新的访谈:




最初的访谈:




按村名搜索:




screenshots from steemblog: https://steemblog.github.io/@team-cn/tags/cn-interview/


想要了解新手村 @team-cn 里的各位朋友,用 steemblog 看一看就一目了然了,确实很有趣。 这对新人也很有帮助,建议你们来看看。‌



(2)社区名人录


‌Steem是个社区,社区是由人组成的,其中有些人由于各种原因成为舆论的焦点或实际的掌权派,总是在历史上不断发生的事情。‌


—-‌


最近 #sct 标签称霸舆论,对于创始人之一的 Jack @jack8831 也接受到了大量的八卦和追捧。但要看家里有矿的男神的生辰八字,也不是这么容易的事啊。‌


steemblog 可以帮你解决这个问题。昨天服务器花了6分钟备份了 Jack @jack8831 的历史文章:https://steemblog.github.io/@jack8831/


然后,要阅读 Jack 的最新动态、历史观点、常用的标签,就容易多了。




不过韩文/朝鲜语我不懂,所以读起来还是需要借助机器翻译的力量。但不可思议的是,下面早期文章的图中的“基本英语”我居然看懂了!!




Jack 好像曾经喜欢给韩国朋友们分享英文,标题中也有“鲑鱼”(연어)的前缀:https://steemblog.github.io/@jack8831/tags/englishforkorean/


所以 steemcoinpan 上鲑鱼泛滥,似乎也在情理之中了。



‌screenshots from steemblog: https://steemblog.github.io/@jack8831/


Jack和团队做的 steemcoinpan 风头正劲,要向韩国朋友们学习,也不妨了解下他们的过往~‌


—-‌


回到CN区。‌


我之前读过《Steem指南》中的 《Steem 十讲》,觉得 刘美女 @deanliu 的观点和文章很独到,所以特地同步了刘美女的文章到 steemblog (服务器时间 18 分钟,刘美女的文章和标签都很多,同步较慢):https://steemblog.github.io/@deanliu/



刘美女已经写了 1400+ 文章,可谓笔耕不辍、涉猎广泛。




最常用的首标签(类别):除了CN区话题,摄影、旅游、美食、讨论 steem / blockchain、以及聊娃(TT)是日常。




最初的文章都是英文,拓荒时期CN区还没有形成规模吧:




老道茶馆的一些过往:https://steemblog.github.io/@deanliu/tags/laodr-teahouse/



‌screenshots from steemblog: https://steemblog.github.io/@deanliu/


不过其实之前 大鹏 @dapeng 也曾经帮助 刘美女 @deanliu 备份过文章( GitHub上的 文章 Markdown 源码 / Netlify 备份站点 ),只是并没有设置成自动更新、以及网站模板较简单罢了。从备份的角度,steemblog 也只是另一种工具和服务而已,并不新奇,但希望它也有所帮助。‌


—‌


我不是很喜欢八卦,所以八卦社区名人和豪杰们的任务,就交给大家自己去完成了。不过想要用 steemblog 来八卦哪些名人倒是可以给我提供一个名单,我可以帮助备份到 steemblog。‌



(3)活动的回顾


‌活动一般以标签的形式进行推广,所以与上面看到的以个人账户为主的文章展示方式并不相同。对于标签文章的备份,steemblog 也可以支持,但目前并没有部署单独对标签进行备份的案例,而且需要对界面做少量修改。‌


这里展示一下 我 @robertyan 主办的对联活动如何查看。‌


https://steemblog.github.io/@{username}/tags/{tag}/ 的格式,就能查看标签下的文章。由于对联活动的文章都是我发表的,所以可以这样查看:https://steemblog.github.io/@robertyan/tags/cn-couplet/



‌screenshot from steemblog: https://steemblog.github.io/@robertyan/tags/cn-couplet/


已经办到第四期了,欢迎对语言和文化有兴趣的朋友参与。这期的题目是给刚刚升级到50/60/70级的朋友们写寿联https://busy.org/@robertyan/qs55nyh8x9



(4)奇文共欣赏


‌第四种用途对我还是挺有帮助的:Steem上有一些极度认真的作者,对于喜欢看好文章用户,现在的steemit/busy/steempeak等界面都满足不了需求。‌


上面提到过我推崇 刘美女 @deanliu 的文章并在昨天同步到了 steemblog。其实更早之前,我还同步了 维参 @crowflew 的文章,是我之前在 steem 看到的最有意思的文章系列之一吧:https://steemblog.github.io/@crowflew/


对于阅读博客,steemblog 会显示目录方便作者通览全文和跳转到章节:




可以方便的跳转到前后文章、找到 steem的原文,以及回到顶部等:



‌screenshots from steemblog: https://steemblog.github.io/@crowflew/or-or-or-or/


当然,steem 上的奇文还有很多,如果你有觉得仔细希望阅读的作者,也不妨把他的账户分享给我,一起欣赏一下。‌



(5)揭秘黑历史


‌steemblog 不但能让有用的信息能够被快速查询到,还能让罪行无处遁形。是不是听着挺像一位侦探的?‌


好吧,下面我们来看看 机机 @julian2013 和 村长 @ericet 是如何互黑的。(其实你们是不是更愿意看 软哥 @softmetal 和 小P @pgr 互黑的桥段?)‌


到 steemblog 页面上,点击右上角的搜索进行“侦查”。‌


—-‌


机机谈论村长:




‌screenshots from https://steemblog.github.io/@julian2013/


—-‌


村长评价机机:




‌screenshots from https://steemblog.github.io/@ericet/


—-‌


看上去机机从来没说过村长“坏”话,而村长可不是这样。真的是这样吗?


那么到底究竟说了什么呢?还是要各位自己去看才行。真相永远只有一个,看了以后不妨告诉我。‌



(6)组织的起源


‌其实相比查看人的文章,我更感兴趣组织的发展历史和起源,以及它们的生长过程,比如 @team-cn、@steemstem、@utopian-io,这里也为他们备份了镜像以便于追根溯源、温故知新:‌


@team-cn



‌screenshot from https://steemblog.github.io/@team-cn/page/45/


—-‌


@steemstem



‌screenshot from https://steemblog.github.io/@steemstem/page/28/


—-‌


@utopian-io



‌screenshot from https://steemblog.github.io/@utopian-io/page/33/


希望这些团队不忘初心,破除艰险、勇往直前。‌


—-‌


以上这些例子,不知对你来说是不是也同样有趣或有帮助。‌


如果需要帮你镜像博客,可以在文章后面留言,本次提供5个免费服务名额,先到先得~‌



#3 进展


‌相比于《用GitHub创建Steem文章镜像 | Mirroring Your Steem Blogs on GitHub | 免费博客备份服务: steemblog》中的描述,steemblog 最近也有一些功能和性能更新,这里简要介绍:‌


  1. 编译性能优化:编译速度提高了大约有20~100倍。
  2. 多平台部署:支持备份镜像到Netlify等平台。
  3. 渲染优化:由于采用了组件,进行了优化以保证用户体验
  4. 问题修复:比如类别(category)显示顺序、编译内存溢出等


(1)编译性能优化


‌其实 steemblog 的功能实现是相对比较简单的(可以参考上一篇文章)。但真正有困难的是在 hexo 的编译速度。‌


hexo 虽然号称速度极快,但实际编译的时候简直是灾难。当然,原因不能完全归罪与 hexo 框架本身,更多是 hexo theme的问题。‌


例如,以下是几个账户一开始的编译所有文章花费的时间:


@andrewma: 400+ posts, 80+ tags, ~20 mins

@ericet: 630+ posts, 260+ tags, > 60 mins




很多人的文章数量都接近这个量级,如果要全都同步的话,时间耗费还是很大的,每天更新就要消耗很多资源。‌


除了优化编译方式,另一种是采用增量更新(即只编译新增加的文章), hexo 虽然自称是支持增量更新的,但实际情况是基本不支持、且有很多潜在的问题。‌


—-‌


(a) 优化编译过程


其实原先我只打算做一个简单的服务,不想在主题(theme)优化上花太多功夫,但真正用起来才发现开源世界并不是那么美好 :) ‌


为了完成给大家的每天更新的承诺,以下是优化的方案。‌


  1. 修改主题:由于我们采用的主题(https://github.com/ppoffice/hexo-theme-icarus)较多的使用了各种控件和大量使用了标签和分类等,使得功能较为强大,但带来的问题是编译成静态页面时的成本也很高,为了修复这个主题的问题,我们fork原来的项目,采用了修改后的主题:https://github.com/steemblog/hexo-theme-icarus
  2. 大量使用缓存(cache):hexo 自带有fragment_cache和partial方法,以方便缓存重复使用的组件(如head, footer, sidebar等),但 theme 的作者有时候不清楚可以这样使用,所以我们需要修改。
  3. 控件页面分离:除了使用cache,像很多组件是可以通过 iframe 或者 ajax 异步加载的形式来展示的,这样可以在实现静态页面的时候减少很多冗余的 控件的 html,减少文件大小、提高效率。
  4. 采用时间线布局:原来的 index、tag、archive等页面,都采用了直接展示文章内容的布局,其实不是很有必要,不方便阅读而且使得静态 html 页面变得很庞大。所以我们改成了 timeline 的 layout。
  5. 增加每页文章数量:默认的分页是 6 篇文章一页,对于时间线模式是太少了,所以改成了 20 篇文章一页。编译的速度受页面数量影响很大,而一页展示的文章多了,html 页面总数自然就少了、速度会有明显提高。


策略1:修改主题:


.gitmodules

1
[submodule "theme"] path = themes/icarus url = https://github.com/steemblog/hexo-theme-icarus.git

‌‌code from https://github.com/steemblog/blog | MIT License

策略2:大量使用缓存(cache)


themes/icarus/layout/layout.ejs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<% if (page.__widget) { %>
<%- body %>
<% } else { %>
<!DOCTYPE html>
<html <%- has_config('language') ? ' lang="' + get_config('language').substring(0, 2) + '"' : '' %>>
<head>
<%- partial('common/head', {}, {cache: false}) %>
</head>
<body class="is-<%= column_count() %>-column">
<%- partial('common/navbar', { page }) %>
<% function main_column_class() {
switch (column_count()) {
case 1:
return 'is-12';
case 2:
return 'is-8-tablet is-8-desktop is-8-widescreen';
case 3:
return 'is-8-tablet is-8-desktop is-6-widescreen'
}
return '';
} %>
<section class="section">
<div class="container">
<div class="columns">
<%- partial('common/widget', { position: 'left' }) %>
<%- partial('common/widget', { position: 'right' }) %>
<%- partial('component/pjax_widget_js', {}, {cache: true}) %>
<div class="column <%= main_column_class() %> has-order-2 column-main"><%- body %></div>
</div>
</div>
</section>
<%- partial('common/footer', {}, {cache: true}) %>
<%- partial('common/scripts', {}, {cache: true}) %>

<% if (has_config('search.type')) { %>
<%- partial('search/' + get_config('search.type'), {}, {cache: true}) %>
<% } %>
</body>
</html>
<% } %>

code from https://github.com/steemblog/hexo-theme-icarus | MIT License


对于重复的模块,使用 partial(‘component’, {param}, {cache: true}) 来替换原来的实现‌



策略3:控件页面分离


将原来的 profile, recent_posts, category, tag, tagcloud, archive 等组件,生成独立的 html 页面,减少冗余的 html。此外,组件采用独立的页面也是实现增量编译所必须完成的,所以这一步必须要做。‌


themes/icarus/layout/layout.ejs 需要修改 generators,生成新的 widgets 页面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* Widget page generator
*/
module.exports = function (hexo) {
hexo.extend.generator.register('widget', function (locals) {
const widgets = hexo.extend.helper.get('get_config').bind(this)('widgets');
const component_widgets = widgets.filter((w) => (w.component))

return component_widgets.map(function(widget){
return {
path: `widgets/${widget.type}.html`,
layout: 'component/pjax_widget_src',
data: {
widget: widget,
__widget: true
}
};
});
});
}

‌code from https://github.com/steemblog/hexo-theme-icarus | MIT License



themes/icarus/layout/component/pjax_widget_ref.ejs 将原来的组件用对页面的引用替换。

1
2
3
4
5
<div class="card widget">
<div class="card-content">
<div id="widget-<%= widget.type %>" data-pjax="<%= `${get_config("root")}widgets/${widget.type}` %>.html" style="position: relative; width: 100%; display: block;"></div>
</div>
</div>

code from https://github.com/steemblog/hexo-theme-icarus | MIT License



themes/icarus/layout/component/pjax_widget_src.ejs 对不同的组件,获取对应的内容

1
<%- partial(`widget/content/${page.widget.type}`, {widget: page.widget}) %>

code from https://github.com/steemblog/hexo-theme-icarus | MIT License



themes/icarus/source/js/pjax_widget.js 在页面加载时通过 ajax 来通过url获取组件的 html,替换控件的内容为实际内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
if (typeof(jQuery) !== 'undefined') {
(function($){
function load_pjax(element) {
// $.pjax({url: element.attr("data-pjax"), container: "#"+element.attr("id")})
$.get(element.attr("data-pjax"), function(data) {
element.html(data);
});
}
$("div[data-pjax]").each(function(){
load_pjax($(this));

})
})(jQuery);
} else {
(function(){
window.$ = document.querySelectorAll.bind(document);
function load_pjax(element) {
const url = element.getAttribute("data-pjax")
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onload = function() {
element.innerHTML = xhr.responseText;
};
xhr.send();
}
for (const e of $("div[data-pjax]")) {
load_pjax(e);
}
})();
}

code from https://github.com/steemblog/hexo-theme-icarus | MIT License



themes/icarus/layout/common/widget.ejs 对控件,也要添加 cache 模式,减少冗余。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<% function render_widget(widget) {
const component = widget.component || false;
const cache = widget.cache || false;
if (component) {
const cahced_prefix = cache ? 'cached_' : '';
const template = `component/${cahced_prefix}pjax_widget_ref`;
return partial(template, { widget })
} else {
return partial('widget/' + widget.type, { widget, post: page }, {cache: cache});
}
} %>
<div class="column <%= side_column_class() %> <%= visibility_class() %> <%= order_class() %> column-<%= position %> <%= sticky_class(position) %>">
<% get_widgets(position).forEach(widget => {%>
<%- render_widget(widget) %>
<% }) %>
<% if (position === 'left') { %>
<div class="column-right-shadow is-hidden-widescreen <%= sticky_class('right') %>">
<% get_widgets('right').forEach(widget => {%>
<%- render_widget(widget) %>
<% }) %>
</div>
<% } %>
</div>
<% } %>

‌code from https://github.com/steemblog/hexo-theme-icarus | MIT License



此外,每个组件都要重构将内容独立出来,这里不详细解释了。‌



策略4:采用时间线布局


将categories和tags页面的布局都显示为时间线模式,更容易浏览,且页面所占空间更小。‌


以 category 为例,展示为时间线 + 分页:‌


themes/icarus/layout/category.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<div class="card">
<div class="card-content">
<nav class="breadcrumb" aria-label="breadcrumbs">
<ul>
<li><a href="<%- url_for('/categories') %>"><%= _p('common.category', Infinity) %></a></li>
<% page.parents.forEach(category => { %>
<li><a href="<%- url_for(category.path) %>"><%= category.name %></a></li>
<% }) %>
<li class="is-active"><a href="#" aria-current="page"><%= page.category %></a></li>
</ul>
</nav>
</div>
</div>
<%- partial("component/timeline", {posts: page.posts}) %>
<% if (page.total > 1) { %>
<%- partial('common/paginator') %>
<% } %>

code from https://github.com/steemblog/hexo-theme-icarus | MIT License



采用时间线以后,同时也会每个展示的 item 添加 cache:‌


themes/icarus/layout/component/timeline.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div class="card widget">
<div class="card-content">
<% if (typeof(head) !== 'undefined' &amp;&amp; head) { %>
<h3 class="tag is-link">
<%= head %>
</h3>
<% } %>
<div class="timeline">
<% posts.each(post => { %>
<%- partial("component/cached_item", {post}) %>
<% }) %>
</div>
</div>
</div>

code from https://github.com/steemblog/hexo-theme-icarus | MIT License



策略5:增加每页的文章数量


可以有效减少页面总数,减少编译的负担。‌


blog/message.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Pagination
## Set per_page to 0 to disable pagination
per_page: 6
pagination_dir: page

index_generator:
per_page: 6

archive_generator:
per_page: 20
yearly: true
monthly: true

category_generator:
per_page: 20

tag_generator:
per_page: 20

code from https://github.com/steemblog/blog | MIT License



经过以上各类方案,编译所有文章的速度已经得到的极大的提高,大约已经提到了10~50倍了。


@andrewma: 400+ posts, 80+ tags, 1~2 mins

@ericet: 630+ posts, 260+ tags, 1~2 mins




(b) 实现增量编译


但我们的目标是增量更新,为了实现这个目的,我们需要做两方面修改:‌


  1. 增量生成markdown:无须每次都从 steem 获取 posts 并生成 markdown;
  2. 只编译受影响的 html 文件:修改 hexo 的 tag, category, archive, 和 post的generators,只在 html 受影响时生成。


步骤1:增量生成 markdown


为了不重复生成过去的posts的 markdown 文件,我们用 git repo 来管理过去已经生成的 markdown 文件,并只更新最近1天左右的文章。‌


同时,我们需要和之前的 source 对比,从而知道哪些文章需要更新。‌


blog/builder.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def setup_source_repo(self):
git_clone_cmd = "git clone --depth 1 --branch {} --single-branch https://{}github.com/{}.git {}".format(SOURCE_BRANCH, self._get_github_pat(), self._get_repo(prefix=False), SOURCE_REPO_FOLDER)
os.system(git_clone_cmd)
# on `source` branch after clone
logger.info("Cloned source repo into workspace: {}".format(SOURCE_REPO_FOLDER))

def _commit_source(self):
os.chdir(SOURCE_REPO_FOLDER)
# commit the files into source repo
os.system("git add --all *")
res = os.system('git commit -m "Source updated: {}"'.format(get_uct_time_str()))
os.chdir("..")

if res == 0:
logger.info("Commited source into [{}] folder".format(SOURCE_REPO_FOLDER))
return True
else:
logger.info("Failed to add new source into [{}] folder".format(SOURCE_REPO_FOLDER))
return False

def _diff_files(self):
os.chdir(SOURCE_REPO_FOLDER)
res = subprocess.run(['git', 'diff', 'HEAD', 'HEAD~1', '--name-only'], stdout=subprocess.PIPE).stdout.decode('utf-8')
os.chdir("..")
files = [f for f in res.split("\n") if len(f) > 0]
logger.info("{} different files:\n{}".format(len(files), res))
return files

code from https://github.com/steemblog/blog | MIT License


为了保证每个用户运行时,文章的内容不会混乱,需要多source的workspace也做一些管理,这里不详细叙述了。‌



步骤2:只编译受影响的 html 文件


在用主题进行编译时,也需要知道哪些 markdown 文件需要更新,以及由此判断更新哪些 html。‌


themes/icarus/includes/helpers/diff.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
hexo.extend.helper.register('list_updated_posts', function (link) {
if (_posts.length == 0) {
process.chdir("source")
const res = execSync("git diff --name-only --cached").toString('utf8');
process.chdir("..")

const paths = res.split("\n").filter((p)=>(p.length > 0))
_posts = paths
}
return _posts
});

hexo.extend.helper.register('list_updated_categories', function (link) {
if (_categoreis.length == 0) {
const _list_updated_posts = hexo.extend.helper.get('list_updated_posts').bind(this)
const updated_posts = _list_updated_posts();

if (updated_posts.length > 0) {
for (const path of updated_posts) {
let post = Post.findOne({source: path})
if (post) {
_merge_categories(post.categories);
}
}
}
}
return _categoreis
});

hexo.extend.helper.register('list_updated_tags', function (link) {
if (_tags.length == 0) {
const _list_updated_posts = hexo.extend.helper.get('list_updated_posts').bind(this)
const updated_posts = _list_updated_posts();

if (updated_posts.length > 0) {
for (const path of updated_posts) {
let post = Post.findOne({source: path})
if (post) {
_merge_tags(post.tags);
}
}
}
}
return _tags
});

code from https://github.com/steemblog/hexo-theme-icarus | MIT License



在 generators 中,只考虑那些受影响的文件。以category 为例:‌


themes/icarus/includes/generators/category.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function needs_update(category) {
if (config.incremental) {
// in incremental mode, update the affected category pages only
const updated_categories = list_updated_categories();
if (updated_categories &amp;&amp; updated_categories.length > 0 &amp;&amp;
updated_categories.indexOf(category['name']) != -1) {
return true;
}
return false;
}
return true;
}

return locals.categories.reduce(function(result, category){
if (! needs_update(category)) {
return result;
}

const posts = category.posts.sort('-date');
const data = pagination(category.path, posts, {
perPage: perPage,
layout: ['category', 'archive', 'index'],
format: paginationDir + '/%d/',
data: {
category: category.name,
parents: findParent(category)
}
});

return result.concat(data);
}, []);

code from https://github.com/steemblog/hexo-theme-icarus | MIT License


需要注意的是,这里需要找到 hexo 对应的几个 generator 的源码,并将其进行替换,例如 hexo-generator-tag、hexo-generator-category、hexo-generator-archive ‌


—‌


实现增量编译以后,经过测试,大概每天更新时,每个账户消耗的时间大约在几秒钟 到1分钟之间。12个账户更新时,总的耗时大约在2分钟至10分钟左右,包括环境部署的时间。


@andrewma: 400+ posts, 80+ tags, ~17s

@ericet: 630+ posts, 260+ tags, < 1min




—-‌


至此,编译性能优化的工作告一段落,可以支持更多账户的镜像同步了。‌



(2)多平台部署


‌除了 GitHub pages,现在常用的静态网站部署方式还有Netlify等。只需要按照Netlify的界面上步骤操作即可,或者参考 hexo deployment 一节的描述。‌


作为结果,我们可以将页面部署到不同环境:‌



‌注意:Netlify上目前只部署了 @robertyan 的文章,但也可以支持其他账户。‌



(3)渲染优化


‌由于采用了组件化的 html 页面内,在上文提到过需要动态加载 widget 的页面,由于页面加载资源的阻塞等问题,我们需要对页面做一些优化才能保证 widget 的渲染不被图片等资源影响太大。‌


这里主要是在 widget 的占位符组件被渲染以后,立即调用 ajax 加载组件的代码,以尽早显示这些 widgets;否则,首页打开时的卡顿还是挺明显的。‌


themes/icarus/layout/layout.ejs

1
2
3
4
5
6
7
8
9
10
<section class="section">
<div class="container">
<div class="columns">
<%- partial('common/widget', { position: 'left' }) %>
<%- partial('common/widget', { position: 'right' }) %>
<%- partial('component/pjax_widget_js', {}, {cache: true}) %>
<div class="column <%= main_column_class() %> has-order-2 column-main"><%- body %></div>
</div>
</div>
</section>

code from https://github.com/steemblog/hexo-theme-icarus | MIT License


主要需要优化的渲染过程就是组件的渲染,其余目前并未做更多测试。‌



(4)问题修复


‌除了以上提到的问题,由于在编译或展示时还需要一些别的问题,也一同修复了。‌


  1. @andrewma 想可以将 category 和 tag 等按使用频率从大到小排序;
  2. 编译 @lemooljiang 的博客时,产生 node / v8 的内存溢出问题,编译失败。

这些问题较容易修改:前者只需渲染前进行排序;后者需要对node运行的参数进行配置,改上限为8GB。后者的修改在:‌


blog/command.py

1
2
3
def configure():
settings.set_env_var("NODE_OPTIONS", "--max-old-space-size=8192")
os.system("cp -f _config.theme.yml themes/icarus/_config.yml")

#4 未来

‌steemblog 实现时 只是为了满足更好的阅读、搜索文章的需要。简言之,它是一个更好的博客“阅读器”(当然也是一个有趣的“时光机”),并不具有写的功能。‌

这是它想要解决的问题决定的,但它的未来可以有着有趣的发展方向,我们从小和大两个方面来讲。‌

—-‌

:从steemblog的角度来讲,我们可以做各类改进,包括功能、性能、运营等。‌

  • 功能:支持语言切换、主题切换、留言(原来就有,为了性能隐藏了)、图片本地保存等
  • 性能:hexo 模板的渲染速度可进一步优化
  • 运营:可以扩展现有系统以支持更多用户、可以考虑付费模式以满足更多需求(费用可能是一次性 10 STEEM 之类的)
  • 扩展:用户可以借助此框架和其他建站服务,快速构建基于 steem 数据的各种网站,包括博客、简历、图库等,以及各种基于 steem 数据的展示组件、基于 steem-js 的写操作 等等;

‌—-

:steemblog 展示了一种基于blockchain 的 app 的更多的可能性。为什么这么说呢?主要原因如下:‌

  • 一份数据、多种展示:与传统数据库相比,blockchain 数据库的公开性和可访问性使得它可以被方便用于基于同一份数据构建多种应用。这也是dApp有趣的方面。steemblog 通过 将 steem blockchain 的数据,进行二次加工,形成了不同的展示方式,这体现了一份数据、多种应用场景的特点。 steemblog 的意图和 steemit 等客户端不完全相同。这里强调的展示有更多 “数据应用”(data apps)的意味,并不全然是 web app 或者 mobile app的视角。
  • 分久必合、强调综合:steem 等区块链平台,现在流行的 dApp 以web app的思维,不断产生新的应用和平台,使得数据不断分化、分裂,到了一定复杂度后,需要一个可以集中管理各类数据与事务的调度中心,steemblog 对于历史的总览体现了这一种思想的一个方面。
  • 数据基础的改进方向:steem 类应用的用户体验的局限性一部分在于其数据基础设施的不灵活与低效,steemblog 通过分离数据读和写,改进了用户体验,但这只是一个方向。blockchain 数据基础设施有着更多的发展的可能性。
  • 社会:生产还是消费?:steemblog 把 steem 数据展现出的一个用户的能力、经验、历史等方面,更有效的展现出来,这是一个更具有生产、企业或产业角度的意义的展示。 steemblog 可以让我们更清楚地看到一个用户的时间是如何被应用与转化的。这种方式对于大规模协作的价值大于娱乐消费型 app 的需要。

这只是一个大致的方向,或许随着思考的深入我们可以把它演变成新的应用。‌

但有三点应该是对的:(1)你在 steem 的历史不会消失;(2)更好的时光机将会不断被发明出来;(3)未来必定会来到。‌

基于这些前提,我们或许能不断看到更多有趣的应用吧。‌

—-‌

昨天 @holger80 在 discord 和我聊到了关于 hexo theme 的问题,同时创建了 steemsites 这个项目(https://github.com/holgern/steemsites),可能也有通过 steem 构建站点,或者其他方面的考虑。‌

对于 steemblog 和相关的项目,我们在之后会进一步思考和探索。如有任何建议请随时留言讨论~‌

参考文献

  1. Hexo博客框架:https://hexo.io
  2. Hexo icaru 主题:
    1. 修改后:https://github.com/steemblog/hexo-theme-icarus
    2. 原作者:https://github.com/ppoffice/hexo-theme-icarus
  3. steemblog 博客镜像服务:https://steemblog.github.io/ (源码:https://github.com/steemblog/blog | MIT License)



Posted from my blog with SteemPress : https://robertyan.000webhostapp.com/2019/05/steemblog-steem%e6%97%b6%e5%85%89%e6%9c%ba-steem-time-machine


This page is synchronized from the post: steemblog: Steem时光机 | Steem Time Machine

贺寿与建祠

上一期《对偶与春秋》中,我们谈到了对联的对称/阴阳本质,以及人类对时间的观念的变迁。本文循着时间这条路,我们简单谈谈时间相关的对联。‌

写人物的对联大概有婚联、寿联、挽联,如果是兼有祠堂的名人,则还包括祠堂的楹联。今天就说说贺寿和建祠过程中的一些对联。‌

–‌

贺寿

‌大多数寿联都是送给生者的“人情”,阿谀之辞在所难免。寿联大多属于应酬之作,感情一假,便不足为观。不过虽然平庸之作占了大多数,但佳作也是有的。以下选自《名联观止》[1]中一些不俗的寿联,可作一观。‌

如郑板桥的六十寿联,可见真性情:

常如作客,何问康宁;但使囊有余钱,瓮有余酿,釜有余粮,取数叶赏心旧纸,放浪吟哦,兴要阔,皮要顽,五官灵动胜千官,过到六旬犹少;

定欲成仙,空生烦恼;只令耳无俗声,眼无俗物,胸无俗事,将几枝随意新花,纵横穿插,睡得迟,起得早,一日清闲似两日,算来百岁已多。


‌郑板桥曾任山东范县、潍县知县,因得罪地方豪绅,托病弃官,寄居扬州卖画,称为“扬州八怪”之一,诗、书、画均不俗,人称“三绝”。曾做一四言诗:

学诗不成,去而学写。学写不成,去而学画。

日卖万钱,以代耕稼。实救困贫,托名风雅。

免谒当途,乞求官舍。座有清风,门无车马。


与自寿联一道,可见他的真性情。‌

光绪三十四年(1908年,戊申年)八月,袁世凯五十生辰,大开寿筵,有谄媚者献寿联:

戊戌八月,戊申八月;

我佛万年,我公万年。


‌”戊戌八月“为袁世凯得势之始(靠出卖维新变法之立宪派,即谭嗣同等),且此联也颂慈禧(”老佛爷“)。同年十月,慈禧病故,名士祝竹岩改此联为:

戊戌八月,戊申十月;

我佛万年,我公明年。


‌虽然袁项城未能明年就死,但此联也传诵一时。‌

image source: Wikipedia – 生日

建祠

‌寿联贺生人,挽联敬死者,祠联题名士。挽联今次先不谈,我们先看祠联。‌

祠联甚多,其中诸葛祠甚为有名,由杜甫《蜀相》(”丞相祠堂何处寻“)可知。‌

这里录两则短联,分别题张良与韩信。‌

于右任题张良庙联:

辞汉万户;

送秦一椎。


山西霍山题韩信墓联:

生死一知己;

存亡两妇人。


这里且卖一关子,能说明联中典故,奖励1 STEEM~‌

活动

上一期活动,我们以“四时(春夏秋冬)与四方(东南西北)‌”为题,两人一组对联。‌‌

下面为活动结果。‌‌

参与:

(上联)春夏秋冬四时雨 | @cecilian

(下联)东南西北八方客 | @mia-cc

(下联)东南西北四面风 | @doveyan


(上联)东邪西毒南帝北丐中神通 | @andrewma


(下联)春华秋实夏蝉冬雪光似水 | @julian2013

(下联)春生夏长秋收冬藏中养生 | @doveyan


(下联)東暖西凉南熱北寒中誼居 | @hertz300


(上联)东成西就 | @teamcn-shop

(下联)南辕北辙 | @annepink

(下联)南来北往 | julian2013

(下联)春华秋实 | @xiaoshancun


(上联)春夜漫漫无心睡 | @julian2013

(下联)秋昼绵绵惹人醉 | @andrewma

(下联)秋日私语情意长 | @annepnik

(下联)过来让我抱一下 | @softmetal

(下联)秋波漪漪想美丽 | @julian2013

(下联)望穿秋水盼君归 | @doveyan


以下都是上联,但没有人对下联。

春夏秋來又一年,南方四季缺冬天。 | @hertz300

春不覺來秋已去,南方天氣无冬天。 | @hertz300

春夏秋冬四季,南北西东八方。 | @zy-sb


点评:‌‌

  • 这期参与很活跃,也有很多有趣的作品,谢谢大家~

奖励:‌‌

经综合评价,以下两联较为妥当,分享 3 STEEM奖励,每人获得0.75 。其余每人0.1 STEEM奖励。奖励稍后发放。

春夏秋冬四时雨 | @cecilian

东南西北八方客 | @mia-cc


东邪西毒南帝北丐中神通 | @andrewma

春华秋实夏蝉冬雪光似水 | @julian2013


‌‌

—-‌‌

以下为本期对联活动内容:寿联,由本周内刚满50、60或70级的寿星报名,由其他人为其做寿联。

形式:‌

  1. 寿星自由报名
  2. 其他人自由发挥,为寿星做寿联
  3. 上面”建祠“部分有猜谜活动,也有奖励

规则:‌‌

  1. 满足对联的基本规则;
  2. 不许抄袭任何别人的成果;
  3. 不应当使用机器协助,如微软亚洲研究院开发的“自动对联”

奖励:寿星奖励 1 STEEM(有人帮他写对联为有效,最多3名,先到先得);最佳作品奖励 2 STEEM;参与奖,各0.1 STEEM,前10名都有奖 :)‌‌

参与方式:回复本文‌‌

评价方式:由寿星评判‌

时间:本文发布起7日内有效‌‌

参考文献

  1. 梁羽生,《名联观止》,北京大学出版社,2017年



Posted from my blog with SteemPress : https://robertyan.000webhostapp.com/2019/05/%e8%b4%ba%e5%af%bf%e4%b8%8e%e5%bb%ba%e7%a5%a0


This page is synchronized from the post: 贺寿与建祠

用GitHub创建Steem文章镜像 | Mirroring Your Steem Blogs on GitHub | 免费博客备份服务: steemblog

本文介绍将Steem文章同步到GitHub pages的开源工具和免费服务:‌


  1. 开源工具 “blog”:https://think-in-universe.github.io/blog/
  2. 免费服务 “steemblog”:https://steemblog.github.io/



Image Source: Pixabay



缘由


最近在用steem的时候,觉得不管是用busy, steempeak还是steemit等,它们的界面设计对于看自己以前的文章并不太方便(当然想看其他人的旧文章也比较麻烦),并且由于国内的网络访问这些站点的速度都不太快,所以以博客服务来看,用户体验是挺差的;至于要方便地搜索、归类自己的文章就更麻烦了。‌


之前曾经有过一些浏览历史文章较方便的服务(如chinabb和steemitfriends),似乎也或者关闭或者收费了,因此暂时没有找到好用的服务。‌


于是想到可以把Steem上的文章备份成镜像,每日自动同步,便于自己梳理和分析;虽然我文章写的不多,但如同在《标签的本质 | The Nature of Tags(一)》里提到的,组织信息是人类的本能,于是便实现了这里的工具和服务。‌



GitHub镜像博客


关于为什么需要自己写文章同步工具,其实是值得询问的:‌


  1. 首先,博客镜像的工作相信之前有人也已经做过了,这并非什么新想法(但我简单搜了一下也没有找到可以立即复用的工具)。但是搭建博客镜像是一项需要适应自身需求的工作,所以自己动手的话可以有更高的灵活性和掌控度。
  2. 其次,即便之前有类似工作的话,可能也并不是同步到GitHub,或者也未必会做到近实时的更新,所以这项工作也可以作为一种服务,也并非完全没有价值。


为了完成这项工作,我们基于steem API、GitHub pagesHexo[1]框架创建了博客镜像搭建工具,效果如下(示例:https://think-in-universe.github.io/blog/):‌



1. 博客首页


左侧是用户的profile,右侧是近期的文章,中间为最近文章。




‌screenshot from https://think-in-universe.github.io/blog/



2. 侧边栏:类别和标签


Steem上的标签和类别,会同步到hexo框架下面,并能够正常显示。遗憾的地方在于由于steem上没有类别层级的概念,所以要分类文章,相对来说没有那么灵活。






‌screenshots from https://think-in-universe.github.io/blog/



3. 侧边栏:近期文章和归档


‌显示最近5篇文章,以及每个月的文章数量。可以看出,作为写作者而言,我是比较懒惰的 :) 和大家比还有很大差距。






‌screenshots from https://think-in-universe.github.io/blog/



4. 文章展示:右侧目录、代码高亮和原文链接


‌在右侧添加了一个目录控件,对于阅读长文是有帮助的。‌


代码高亮对类似本文的有代码的文章有一定帮助,steemit对代码高亮的处理是比较初级的。‌


原文链接其实是为了方便我自己有时候引用文章需要,在steem上找文章比较低效。








‌screenshots from https://think-in-universe.github.io/blog/



5. 搜索


‌搜索功能对于想要快速查阅或引用自己的文章,较有帮助。点击右上角的搜索按钮,可以进行快速搜索。




‌screenshot from https://think-in-universe.github.io/blog/



6. 独立的归档、类别、标签页等


‌如果要单独查看这些信息,可以到分别的独立页面下查看,如有需要,也可以建立其他的标签页。例如,归档页面的时间线,比steem看起来简洁一点:https://think-in-universe.github.io/blog/archives/








‌screenshots from https://think-in-universe.github.io/blog/



页面展示大体如此,主要的价值在于从文章的角度,信息的组织更为清晰。如果想要获得一个类似的博客镜像,大抵有两种方法:‌


  1. 如果你了解GitHub和GitHub pages如何使用,可以使用本文发布的开源代码(https://github.com/think-in-universe/blog),根据其中的README,搭建一个类似的镜像就行了。
  2. 如果你希望可以使用一个免费的博客镜像服务,可以参考文章最后一章提到的 steemblog 博客镜像服务,或者直接联系我。


如何实现博客镜像工具?


‌为了实现以上功能,我们可以基于博客框架Hexo[1],搭建从Steem同步数据、并发布到GitHub pages的博客镜像工具,可以支持基于用户名、标签、日期等查询方式的数据同步。下面简要介绍如何实现这一博客镜像工具。‌


工具的代码在GitHub开源:https://github.com/think-in-universe/blog


关于具体如何使用此工具,可以参考上面项目中的README的介绍:可以在本地安装后使用,也可以通过travis-ci部署。‌


本项目的代码里重用了 @cn-hello 小门童实现时的一些基本框架,所以需要增加的功能较少。工具的工作流程如下,也比较简单:‌


  1. 下载你的Steem文章;
  2. 用Hexo编译成静态文件;
  3. 用GitHub pages部署博客;


(1)下载你的Steem文章


‌由于重用了之前的SteemReader的方法,我们可以指定通过账户或者标签以及时间(天数)来获取文章。‌

blog/builder.py

1
2
3
4
5
6
7
8
9
10
11
class BlogBuilder(SteemReader):

def __init__(self, account=None, tag=None, days=None):
SteemReader.__init__(self, account=account, tag=tag, days=days)

def download(self):
if len(self.posts) == 0:
self.get_latest_posts()
if len(self.posts) > 0:
for post in self.posts:
self._write_content(post)

‌code from https://github.com/think-in-universe/blog | MIT License



为了将文章下载为hexo可识别的markdown格式,需要在markdown中加入相关ymal或json的元数据。以下为markdown模板,包含了标题、类别、日期、标签等信息,并指定显示文章的目录。‌

blog/message.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
MESSAGES["blog"] = """
---
title: "{title}"
catalog: true
toc_nav_num: true
toc: true
date: {date}
categories:
- {category}
tags:
{tags}
thumbnail: {thumbnail}
---


{body}
"""

code from https://github.com/think-in-universe/blog | MIT License



使用Steem API,获取steem文章的元数据和markdown文本。‌

blog/builder.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def _write_content(self, post):
folder = self._get_content_folder()
c = SteemComment(comment=post)

# retrieve necessary data from steem
title = post.title.replace('"', '')
body = post.body
date_str = post.json()["created"]
date = date_str.replace('T', ' ')
tags = "\n".join(["- {}".format(tag) for tag in c.get_tags()])
category = c.get_tags()[0]
thumbnail = c.get_pic_url() or ''
url = c.get_url()

# build content with template
template = get_message("blog")
content = template.format(title=title, date=date, tags=tags, category=category, thumbnail=thumbnail, body=body, url=url)

# write into MD files
filename = os.path.join(folder, "{}_{}.md".format(date_str.split('T')[0], post["permlink"]))
with open(filename, "w", encoding="utf-8") as f:
f.write(content)

logger.info("Download post [{}] into file {}".format(title, filename))

code from https://github.com/think-in-universe/blog | MIT License


(2)用Hexo编译成静态文件


‌我们需要为建立的博客设置一个美观的主题。‌


我们这里使用了 https://github.com/ppoffice/hexo-theme-icarus 主题,需要将其作为一个git submodule加入到git repository中。‌

.gitmodules

1
2
3
[submodule "theme"]
path = themes/icarus
url = https://github.com/ppoffice/hexo-theme-icarus.git

‌code from https://github.com/think-in-universe/blog | MIT License



随后使用hexo命令来将markdown转换成生成静态的文档。‌

blog/command.py

1
2
3
4
5
6
7
@task(help={
})
def build(ctx):
""" build the static pages from steem posts """

os.system("cp -f _config.theme.yml themes/icarus/_config.yml")
os.system("hexo generate")

‌code from https://github.com/think-in-universe/blog | MIT License



(3)用GitHub pages部署博客


‌正式部署时,我们有两种方式,一是在本地使用hexo命令部署,或者在travis-ci 中定期每日进行同步。‌


hexo命令部署:blog/command.py

1
2
3
4
5
6
7
@task(help={
})
def deploy(ctx):
""" deploy the static blog to the GitHub pages """

build(ctx)
os.system("hexo deploy")

‌code from https://github.com/think-in-universe/blog | MIT License



travis-ci部署:.travis/deploy.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[ -z "${GITHUB_PAT}" ] &amp;&amp; exit 0
[ "${TRAVIS_BRANCH}" != "master" ] &amp;&amp; exit 0

git config --global user.email "${GIT_EMAIL}"
git config --global user.name "${GIT_USERNAME}"

git clone --depth 1 --branch gh-pages --single-branch https://${GITHUB_PAT}@github.com/${TRAVIS_REPO_SLUG}.git site
cd site
cp -r ../public/* ./

NOW=$(date +"%Y-%m-%d %H:%M:%S %z")
git add --all *
git commit -m "Site updated: ${NOW}" || true
git push -q origin gh-pages

‌code from https://github.com/think-in-universe/blog | MIT License



另外,需要注意,由于用travis-ci 部署时需要用户提供Git用户信息(用于commit到gh-pages)以及GitHub的token,所以需要以环境变量的方式进行配置。‌



博客镜像服务 http://steemblog.github.io/


‌今天在微信群中 岩哥 @andrewma 提到想要一个分析文章标签的服务,我想起搭建的这个博客镜像也有标签云和标签统计,所以帮助建一个类似的镜像服务就能解决该问题。‌


但由于岩哥对GitHub并不熟悉,使用上面提到的博客镜像工具可能较为困难,所以在此基于https://github.com/think-in-universe/blog 项目,建一个organization account用户管理博客,帮助有需要的人建博客镜像的子目录,这就是steemblog。‌



1. 如何使用博客镜像服务


‌目前,这个博客镜像服务可以在 https://steemblog.github.io 找到。‌


如果要添加一个新的用户到镜像同步中,只需添加账户到用户列表中即可(目前是手动添加的)。例如,我们添加了 @robertyan 和 @andrewma 到列表中。我们可以在以下链接访问他们的博客镜像:‌


  1. https://steemblog.github.io/@robertyan/
  2. https://steemblog.github.io/@andrewma/


与之前的工具需要手动配置用户信息不同,这里自动从steem同步了用户的profile。(不过跟我手动配置的差不多)




‌screenshot from https://steemblog.github.io/@robertyan/



@andrewma的博客镜像也创建成功了,不过头像和缩略图的处理可能需要做一些改进。



‌screenshot from https://steemblog.github.io/@andrewma/



比如岩哥关心的标签信息,可以在https://steemblog.github.io/@andrewma/ 中找到:






‌screenshots from https://steemblog.github.io/@andrewma/



同样的,我们可以继续添加新的用户,他们的镜像可以在 https://steemblog.github.io/@{账户名} 中找到。‌


对于这样的博客镜像,如有需要或建议,可以在文章后面留言讨论。‌



2. 如何建立博客镜像服务


https://github.com/steemblog/blog 是在 https://github.com/think-in-universe/blog 的基础上构建的,为了适应多用户的子目录的需要,需要对原来的项目的目录结构和部署方式做一些调整。‌



(1)从steem下载文章的同时,自动同步用户信息


通过steem获取的账户信息,自动更新_config.yml_config.theme.yml


相对应的模板在 blog/message.py 中,由于内容太长,这里不贴出。‌

blog/builder.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
def update_config(self):
if not self.account:
return

organization = BLOG_ORGANIZATION
logo = BLOG_AVATAR
favicon = BLOG_FAVICON

language = settings.get_env_var("LANGUAGE") or "en"

a = SteemAccount(self.account)
author = self.account
name = a.get_profile("name") or ""
# about = a.get_profile("about") or ""
location = a.get_profile("location") or ""
avatar = a.get_profile("profile_image") or ""
website = a.get_profile("website") or ""

# build config file with template
template = get_message("config")
config = template.format(organization=organization, language=language,
name=name, author=author)
filename = CONFIG_FILE
with open(filename, "w", encoding="utf-8") as f:
f.write(config)
logger.info("{} file has been updated for the account @{}".format(filename, author))

# build config theme file with template
template = get_message("config.theme")
config = template.format(organization=organization,
favicon=favicon, logo=logo,
author=author, name=name, location=location,
avatar=avatar, website=website)
filename = CONFIG_THEME_FILE
with open(filename, "w", encoding="utf-8") as f:
f.write(config)
logger.info("{} file has been updated for the account @{}".format(filename, author))

‌code from https://github.com/steemblog/blog | MIT License



(2)在生成静态网页时,相互隔离不同用户的路径


将不同账户的页面放置到@account子目录下。‌

blog/message.py

1
2
3
4
5
6
7
8
9
10
# URL
## If your site is put in a subdirectory, set url as 'http://yoursite.com/child' and root as '/child/'
url: http://{organization}.github.io
root: /@{author}/
permalink: :category/:post_title/
permalink_defaults:

# Directory
source_dir: source
public_dir: public/@{author}

‌code from https://github.com/steemblog/blog | MIT License



编译指定的steem用户的文章为静态页面,隔离放置在发布目录下。‌

blog/command.py

1
2
3
4
5
6
7
8
9
10
11
@task(help={
})
def build_all(ctx):
""" download the posts of all the accounts, and generate pages """

accounts = settings.get_env_var("STEEM_ACCOUNTS") or []
if accounts and len(accounts) > 0:
for account in accounts.split(","):
clean(ctx)
download(ctx, account)
build(ctx)

‌code from https://github.com/steemblog/blog | MIT License

(3)部署时,将页面推送到 steemblog.github.io‌

只需修改 .travis/deploy.sh 中的目标代码仓库的参数即可。‌

最后

‌究其本质,本文是对steem上的数据进行处理的一种尝试,在开放的区块链数据的基础上,我们可以根据场景,采取多种灵活的数据展现方式,这里的镜像博客无疑又是其中的一种。‌

希望本文提供的工具或服务对你有帮助,如果需要帮助你开启博客镜像服务,可以在本文留言,我会尽量提供支持。由于travis的使用也有一些限制,优先帮助前5位留言的朋友提供服务 :)‌

参考文献

  1. https://hexo.io
  2. Hexo icarus主题:https://github.com/ppoffice/hexo-theme-icarus
  3. 博客镜像工具:https://think-in-universe.github.io/blog/
  4. 博客镜像服务:https://steemblog.github.io/



Posted from my blog with SteemPress : https://robertyan.000webhostapp.com/2019/05/%e7%94%a8github%e5%88%9b%e5%bb%basteem%e6%96%87%e7%ab%a0%e9%95%9c%e5%83%8f-mirroring-your-steem-blogs-on-github-%e5%85%8d%e8%b4%b9%e5%8d%9a%e5%ae%a2%e5%a4%87%e4%bb%bd%e6%9c%8d%e5%8a%a1%ef%bc%9a-st


This page is synchronized from the post: 用GitHub创建Steem文章镜像 | Mirroring Your Steem Blogs on GitHub | 免费博客备份服务: steemblog

借问师友何处有,门童遥指新手村 | 关于 @cn-hello 小友的来历

最近,CN区多了一位叫 @cn-hello 的小朋友,自称是CN区的小门童,每天在CN区路口守望,看到新人便上去提供一些建议,比如:“少侠,我看你能量很少了啊,休息会来村里喝杯茶坐坐吧” ,“那个谁,请你不要抄别人文章啊,会被举报的啊”,……,顺便给新人点个小小的赞。‌


随后又开始忽悠新人:“少年,我看你骨骼清奇,这本《Steem指南》似乎很适合你,10块钱卖你了吧?什么,没钱?那白送你了。” / “小伙,我们村里藏龙卧虎、灵气充沛,只要你肯按时做作业,可保你练成盖世神功,你来还是来还是来?”‌


他看到新人通常都很热情,但新人有时却一脸懵逼,不明白这货是干嘛来了。。。但cn-hello却很自我,每天都要炫耀一下自己勾搭了几位新人。。。‌


其实,@cn-hello 原来是一为牛角挂书的小牧童,本是勤于读书问道解惑的,但有了一些学问之后,却有些好为人师,看到迷途之人,免不了希望上前寒暄两句、指教一二。结果就从牧童变成了门童,负责村中的接引、指路一事了。‌


本文以下简单介绍一下他的来历,相关的一些想法,以及之后的计划。欢迎大家给这位小朋友提供建议。




image source: Wikipedia – 颐和园长廊彩绘 | CC BY-SA 2.0



小门童的介绍


来历


设计 @cn-hello 这个机器人(bot)的意义是颇为直接的,相信很多人也遇到过这个场景:刚来Steem发帖的时候,兴冲冲发了一个帖子,却无人问津,想要找前辈们请教一下,却不知道应该找谁,到其他作者或大佬帖子下留言,也不见得会被回复。所以cn-hello最初的功能便是为了主动发现身处迷雾之中的新人,告诉他们第一步该怎么走、接下来该怎么做、加入什么组织。‌


然而,本质上说 @cn-hello 也只是另一个欢迎机器人罢了(yet another welcome bot),有些欢迎机器人也确实并不令人喜欢,但或许也有一些差异:‌


  1. 他只关注用中文发帖的新人,只给中文用户留言;提供的建议也只用中文表达;
  2. 他提供的帮助信息,如新手村 @team-cn,如《Steem指南》,都是专为中文社区的新人们所创建的,更有针对性与亲切感
  3. 与其他某些欢迎机器人(welcome bot)相比,他并不以私利为目的而留言或点赞,他不发广告、不求赞,只是希望更好的发现和帮助新人。

虽然只运行了几天,但在帮助新人获取信息、找到团体方面也确实有一些帮助,也帮助我们更好地了解和认识了新人们的生存状态。在后文我们会进一步介绍这些内容。‌


—-‌


除了上面提到的这个显而易见的痛点,开始要写这个机器人的动机也有两方面:‌


  1. 一方面是在讨论《Steem指南》相关问题时,发现虽然有《指南》但很多人还是没有阅读的习惯,所以考虑是否可以实现一个发布攻略的小助手(advisor),根据新人发帖的情况,给他提供相应的修改建议;
  2. 另一方面,源于和村长 @ericet 的交流,关于平时是如何找新人进村的,村长说一般是自己去看的,没用什么工具。所以我想可以写个工具和机器人自动化这部分工作,村长也提到已经注册了 @cn-hello 这个账号,想来做类似的事情。

于是大概简单实现了目前的这位 @cn-hello 小门童,或许以后会有更多的功能,也欢迎大家提供建议,目的仍然是切实帮助到新手们。‌


—-‌


实际上,#cn-hello 过去是一个CN区新人常用的自我介绍的标签,最近似乎用的人少了很多。这里用 @cn-hello 作账户,用意也在于帮助新人,所以在意义上较为接近的。‌



功能


目前 @cn-hello 主要有两方面的功能:‌


(1)欢迎新人并提供建议


搜索1天内在 #cn 标签下用中文发表帖子的新人,并在他们的文章下回复新人可能遇到的困难,以及相关的建议,如下图所示:




image source: screenshot from the account @cn-hello ‘s comment‌


特别地,由于很多新人不了解RC的消耗,回复中添加了新人目前的能量可以发表的文章或回帖的总数,以及每天恢复的能量能发帖的数量。‌


关于中文区新人的定义,目前采用的是如下规则:‌


  1. 声望在 [25, 35] 的区间内;
  2. SP < 100;
  3. 帖子使用中文发表


(2)统计1日和7日的新人动态


现在 @cn-hello 定时于北京时间每日晚10点运行,首先搜索新人并回复,然后发布新人相关的统计信息,如2019年05月10日的帖子。统计信息包括:‌


  1. 文章推荐:今日发布帖子的新人以及推荐其中一篇帖子(通常是最新一篇);
  2. 7日新人统计:罗列过去7日发现的新人,以及他们的声望、能量(RC)、帖子数、粉丝数等信息。

这部分统计设计时主要是为了方便老用户为新人提供建议或支持,也可以帮助关注自己在初期的一些关键指标。如下图所示:




image source: screenshot from the account @cn-hello ‘s post




image source: screenshot from the account @cn-hello ‘s post‌


—-‌


目前功能是较为简单的,但对于发现新人,确实也有一些帮助;村长也会根据统计和@的通知,找到新人的帖子并帮助一些新手加入村内。‌


同时,也存在一些问题,似乎并不太容易解决,例如:‌


  1. 获取反馈困难:一般很难知道新手是不是读了 @cn-hello 的建议,以及对建议的反馈如何;确实也有一些新人看到了相关的回复,然后主动联系,但总体来说相当少;
  2. 新手可能不理解规则或建议:推荐的新手的文章有一部分是有抄袭或搬砖的问题的,虽然回复新人时指出了这一点,但可能新手并不理解实际的情况或不愿意相信,依然我行我素,结果还是被踩了。


源码


总体来说,这是一个较为简单的工程,但基本包括了一个论坛机器人常见的功能:监控、过滤、文本分析、回复、点赞、发表文章、通知,等等。‌


下面是本项目的源代码,开源在 @steem-guides 的GitHub账户下。‌



该机器人提供了如下命令,可供执行:

1
2
3
4
5
6
7
8
Available tasks:

cn-hello.reply reply to a post by cn-hello
cn-hello.search search the latest posts by newbies
cn-hello.summarize publish summary post for daily and weekly update
cn-hello.vote vote a post by cn-hello
cn-hello.welcome send welcome messages to newbies
steem.list-posts list the post by account, tag, keyword, etc.



代码较为简单,这里不赘述。但或许有一些基础类可以被复用,这里简单提一下:‌


  1. steem 模块
    1. collector.py:封装了以账户、标签、天数、数量等查询文章和回复的基本方法,如有需要做此类查询的,或许可以复用其中的 get_posts() / get_comments();
    2. comment.py 和 account.py:封装了一些常用操作,如各类常用信息的查询、是否被某人赞或者踩、纯文本的提取等;
  2. data 模块
    1. page_language.py:对文本使用的语言检测的封装;
    2. reader.py:可帮助实现监听某tag最新文章;
  3. cn_hello 模块
    1. bot.py, command.py, message.py, newbies.py: 稍作修改可创建一个新的机器人。


1
2
3
4
5
6
├── cn_hello
│ ├── __init__.py
│ ├── bot.py # the behaviors of the bot
│ ├── command.py # the commands that trigger the bot to act
│ ├── message.py # the messages for the bot to speak
│ └── newbies.py # the data operations of newbies



对于如何安装、执行命令等,可以参考GitHub repo的readme中的介绍。‌


所以,如果希望自己快速创建一个机器人实现点赞、回复、监控、通知等功能,或许可以参考这里的开源代码。‌


—-‌


关于识别中文,这里直接使用了 langdetect 这个 package,是对https://code.google.com/p/language-detection/ 项目的python移植。‌


具体可参见Language Detection Library for Java 中对算法的介绍。其核心是采用了Naive Bayes with character n-gram,计算不同字符拼写特征在哪种语言中出现的概率最高。‌


核心代码在项目的detector.py文件中,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
def get_probabilities(self):
if self.langprob is None:
self._detect_block()
return self._sort_probability(self.langprob)

def _detect_block(self):
self.cleaning_text()
ngrams = self._extract_ngrams()
if not ngrams:
raise LangDetectException(ErrorCode.CantDetectError, 'No features in text.')

self.langprob = [0.0] * len(self.langlist)

self.random.seed(self.seed)
for t in xrange(self.n_trial):
prob = self._init_probability()
alpha = self.alpha + self.random.gauss(0.0, 1.0) * self.ALPHA_WIDTH

i = 0
while True:
self._update_lang_prob(prob, self.random.choice(ngrams), alpha)
if i % 5 == 0:
if self._normalize_prob(prob) > self.CONV_THRESHOLD or i >= self.ITERATION_LIMIT:
break
if self.verbose:
six.print_('>', self._sort_probability(prob))
i += 1
for j in xrange(len(self.langprob)):
self.langprob[j] += prob[j] / self.n_trial
if self.verbose:
six.print_('==>', self._sort_probability(prob))

def _extract_ngrams(self):
'''Extract n-grams from target text.'''
RANGE = list(xrange(1, NGram.N_GRAM + 1))

result = []
ngram = NGram()
for ch in self.text:
ngram.add_char(ch)
if ngram.capitalword:
continue
for n in RANGE:
# optimized w = ngram.get(n)
if len(ngram.grams) < n:
break
w = ngram.grams[-n:]
if w and w != ' ' and w in self.word_lang_prob_map:
result.append(w)
return result

code from langdetect project | license: Apache License 2.0‌

未来

目前 @cn-hello 小门童的工作主要以接待和引导新人为主。之后的拓展依然以尽力帮助新人为目的,主要的改进可能在以下几个方面,但总体而言目前的优先级并不是非常高:‌

  1. 提供改进建议:根据作者对标签的使用、收益的情况、文章的主题等,提供在标签使用、活动参与等方面的建议,将《Steem指南》中的特定章节也会一并推送;
  2. 优化新人统计信息:给新人推荐其他steem数据分析平台的链接,方便新人了解掌握自己主要的状态和潜在的改进方向,可能只看统计帖子并不是很方便或有效;
  3. 优化文章推荐:推荐新人高质量的文章,以及包括提高新人在snax等注意力奖励平台上的收益等。这点似乎很多机器人都在做了,所以或许并不是 @cn-hello 的重点。

对小门童有任何建议,或者希望他还能帮新人们做些什么,请在文章下面留言哦~

小门童的思考

对新人的观察

透过 @cn-hello 每日对新人的访问和记录,我们也能获得一些关于新人的观察:‌

  1. 搬砖的倾向:在过去7人接待的28位新人中,至少有5位由于搬砖(抄袭)的问题声望被踩到25以下。一方面,我们的提醒没有达成特别好的效果;另一方面,新人不了解规则并希望轻松撸币容易有搬砖的倾向。
  2. “老”新人:大部分 @cn-hello 找到的新人都是1个月以内的创建的(18 / 28),但也有部分账户(6 / 20)已经创建了 1年左右或以上的。或许当时新人来访时,没有很快找到融入社区的方式,所以一直呈现若离若即的状态,希望 cn-hello能帮助缓解这一问题,目前看来似乎也并不容易。
  3. 积极的新人:目前积极回应 @cn-hello 的新人只有 (6 / 28)个,并不算很多,但能得到一些有效回应,也算部分完成任务了。

对新人不够友好(同时也是为了防止小号和机器人的泛滥),是Steem的一个严重的问题,也是比较奇怪的地方,但恐怕也是无奈之举。从实际上确实阻碍了新生力量的入场,给新玩家设置了较高的门槛。或许本质上这是西方文明“人性恶”的假设在作怪吧。‌

为了对抗新人面对的挑战,小门童表示不打算放弃新人“拯救”计划。‌

机器人的印象

小门童 @cn-hello 是机器人,但令他感到不解的是,steem上很多机器人并不打算遵循阿基莫夫的机器人三大定律或者四大定律,甚至恶行昭彰。

第零法则:机器人不得伤害整体人类,或坐视整体人类受到伤害;

第一法则:除非违背第零法则,否则机器人不得伤害人类,或坐视人类受到伤害;

第二法则:除非违背第零或第一法则,否则机器人必须服从人类命令;

第三法则:除非违背第零、第一或第二法则,否则机器人必须保护自己。


‌总体来说,steem上机器人给人的整体印象是不够正面的,既不智能、也不正义(不仁、不智)。‌

事实上,像“三大定律”这样义务化的伦理确实不适合steem上的机器人,他们不过是人的延伸,并不具有被编码的“道德”。或许在可预见的未来,人工智能的潜在危害也不过是“恶”的延伸,问题不在与机器本身有多智能,而首先在于设计和使用它的人是否能约束自身的贪婪和享乐的倾向。‌

—-‌

如果想要进一步了解steem上的机器人,《Steem指南》的机器人篇对于机器人有一些不完全的介绍,可作参考。‌


在帮助新人方面,@cn-hello 也只不过是其中之一。CN区还有很多可爱的机器人,有些依然活跃,有些则已经进入休假状态,比如 @cn-cutie.pie 和 @cn-naughty.boy。‌

之前的一些过去关于机器人问题的讨论,或许也可以作为参考:‌

  1. 聊聊机器人🤖 / Robots on STEEMIT
  2. steemit 还是机器人的天下
  3. 机器人是否会破坏steemit上的生态平衡?

‌@maiyude 的机器人教程,也可供初学者阅读,我们稍后也会整合到《Steem指南》中。‌

  1. steem-python 介绍:(1)(2)
  2. 点赞机器人开发:(1)(2)(3)

—-‌

虽然对于steem上的机器人存在许多争议,但所谓“自反而缩,虽千万人,吾往矣”,小门童希望能尽自己的一点绵薄之力,便已经足够了 :)‌

参考文献

  1. Wikipedia,机器人三定律
  2. Shuyo NakataniLanguage Detection Library for Java,2010年



Posted from my blog with SteemPress : https://robertyan.000webhostapp.com/2019/05/%e5%80%9f%e9%97%ae%e5%b8%88%e5%8f%8b%e4%bd%95%e5%a4%84%e6%9c%89%ef%bc%8c%e9%97%a8%e7%ab%a5%e9%81%a5%e6%8c%87%e6%96%b0%e6%89%8b%e6%9d%91-%e5%85%b3%e4%ba%8e-cn-hello-%e5%b0%8f%e5%8f%8b%e7%9a%84


This page is synchronized from the post: 借问师友何处有,门童遥指新手村 | 关于 @cn-hello 小友的来历

Snax authentication post

I want to link my Steem account with my account on Snax blockchain using https://snax.one
Please, create Snax account for me!
My authentication hash: 28bd868698bd0b64a2d9c10300fdc66e5f30c01ca7dcf91a551270c7f00b23d7


This page is synchronized from the post: Snax authentication post

对偶与春秋

上一期《内外与五四》中,我们谈到了对联的“经世致用”的意义,在于“联“以载道,即白乐天所谓“文章合为时而著,歌诗合为事而作”。‌

“道”的“体”,借“联”的“相”而显现。本文谈谈楹联的“形”与“相”的来源,尤其是对偶的特点,以及对联中常出现的时间这一概念的“对称”观念。‌

—-‌

对偶

‌现代人们常说的对联形态,多是指以书法形式题写、张贴、悬挂或镌刻的“春联”“楹联”。除此之外,还有用于口头的“对句”(口语形式对联,一人出句、一人对句)和用于书面的“联语”(如某些章回体小说的章节名)。这恰与语言的使用形式相对应 ——口头、书面、标贴——由此也可见对联应用之广。‌

对于对联的性质,向来也有不同认识:(1)对联是一种文学样式,与诗词格律相若、与建筑音乐相合;(2)对联是一种实用文体,可用于春节、贺婚祝寿、吊丧悯死,可用于宫殿苑囿、祠庙寺观、亭台楼阁、店铺坊馆;(3)对联仅当做一种文字笔墨游戏,不过雕虫小技。‌

总体来说,对联是一种具有艺术性的应用文体。最根本的,对联是一种对称的文字形式,我们想要探讨的,便是这种对偶文体的来源。‌

—-‌

对于对联的起源,以“楹联”来说,对联的起源常被采纳的是“五代说”(五代后蜀主孟昶所作春联:“新年纳余庆,嘉节号长春”),但实则至少在唐代已经对联已经发展成熟的佐证(如酒令和应对、敦煌遗书等)[2] 以“对句”来说,则有“西晋说”(“云间陆士龙,日下荀鸣鹤”)。但以“联语”或“对偶句”而言,则几乎从殷商甚至有文字记载起便存在了。‌

对联的发展与流行,这里也稍作一些阐发(如对更多历史介绍有兴趣,此处推荐杨大方先生《对联论》中的相关章节)。对联的流行始于宋,而兴盛于明清。宋人开始将对联题于楹柱而称“楹联”。各种形式的对联也被创造出来:如,苏轼挽韩绛:“三登庆历三人第,四入熙宁四辅中”,开创了挽联。至明则有朱元璋,大力提倡春联,使之成为一种制度,民间更是无处不联;如朱元璋赠徐达联:“破虏平蛮,功贯古今人第一;出将入相,才兼文武世无双”。清则是康雍乾极为崇尚对联,对联妙手更是众多,如纪昀、曾国藩、袁枚等。宋以后对联的流行,与对联简洁的形式和极强的实用性,以及宋以后大众文化的兴起、商业的发展有着密切。‌

—-‌

对联或者对偶文体的根源在于(1)汉字的一字一形一音一义的特点,使得音、形、义的对称形态极为丰富;(2)中华文化对对称、阴阳观念的推崇。‌

1. 汉字特征

‌首先,汉字存在以下基本特征:‌

  1. 汉字的形状是均匀而方正的(方块字),在书写多个字时长度保持相等
  2. 汉字是单音节字,每个字的音节长度相等、音律和谐;
  3. 古代汉语主要是一字一词、一字一义,单个字可以表达明确的含义;
  4. 较多汉字本身的形象具有对称结构,如“天”、“人”、“合”、“一”等;
  5. 汉字由以直线为主的笔画组成部首,又可以由部首组成形声字,常兼具表音和表意的功能;
  6. 汉字支持竖排或横排两种书写方式,有几千年竖排书写的历史;

‌这些基本特征都为对偶句式和对联的产生奠定了基础,其中第(5)点我们在之前的“烟锁池塘柳”这一联中有过充分的体现。而且,对于某些趣联的产生,也颇有价值。如以下这个一字联,不但字的音形义相对,上下两部又分别相对,颇为巧妙:


‌第(6)点使得竖向书写时(如楹联),两联相对的效果可以一目了然。‌

—-‌

其次,如果与其他语言(主要是表音语言)做比较,我们会发现汉字还有一些特征使得其为作对联提供了基础:‌

  1. 汉语的字/词不存在时态变化(或者说每个字都是词根),使得单个字的形态比较稳定,所以能保证始终如一的全方位的对称;
  2. 汉语的音节存在“声调”构造,使得当个音节能够对应多种声音元素,加强了单音节的作用,同时促成了“平仄相对”这种对称关系。

‌正是由于汉字和汉语本身的简介、匀称、协调、丰富的特征,使得它能够成为构造对称文体和话语的基本结构。歌赋、诗词、对联的艺术之美,正筑基于汉字本身内在的美学意义之上;这在人类语言之中,是颇为独特的。‌

2. 对称观念

天人合一、取法自然深刻影响中华文化。‌

首先,对天地万物以及自身的观察,使得先人们对于对称有着深刻的观察,如天地、日月、昼夜,如眼、耳、手、足,又如春夏秋冬、东南西北、上下左右等等。‌

其次,进一步对自然的观察,产生了“阴阳”、“八卦”的对立统一的观念(《周易》),适用于对自身、宇宙和社会的观察,如男女、天地、寒暑等一阴一阳二元对立的方式来构建的思维系统。‌

这种对立统一的观念深刻体现在政制、礼仪、音乐、建筑、劳作等各个方面,毫无疑问地也渗透到语言文字与文学样式之中。‌

对联二字,本就有“对立关联”之意,与“阴阳”思想相契合。故而,在对联也极为注重“相对”这一形式,例如以“神州”对“华夏”由于意义完全一致就不合适。所谓“正反合”的辩证法,在对联中则颇为适用。‌

类似地,希腊人对和谐对称的追求,也极为炽烈,如对天体运行轨迹为“圆周运动”的执着追求、对“天球”的设计等等,以及对现代理论物理(如量子物理)、以及分子生物学(如DNA双螺旋结构)的影响等,在此不详述。《哥白尼革命》或可为一种解读的角度。‌

对于对称、阴阳理论的崇尚,也是中华文化与希腊哲学相通融之处,也难怪乎杨振宁先生也曾多次提到对中国古代文学的喜爱与对物理结构中对称结构的美妙的研究相一致之处。‌

—-‌

以汉字和对称为基构建的对联,在今日依然有着强大的生命力。一方面,虽然汉字经历了“简化”和现代汉语经过了“以词表意”为主的变化,但汉字的生命力犹在;另一方面,虽然“科学”似乎取代了“阴阳”,但其中蕴含的对称之美的追求却没有改变,对于“阴阳”的认同反被现代科学和技术所采纳。所以,对联文体以其简洁、实用的特征,或许将不断延续其价值和意义。‌

但是,需要注意的是,由于过去半个多世纪以来,中国大陆的教育体制多模仿苏联或欧美的原因,曾经以“对句”为基础的“文史哲德”融合统一的童蒙教育被现代分科而治的教育所取代,所以现代中国人常常能够读懂或理解对联,而要创作却颇为困难。‌

鲁迅在《从百草园到三味书屋》曾记录他上“对课”的情景。

我就只读书,正午习字,晚上对课。先生最初这几天对我很严厉,后来却好起来了,不过给我读的书渐渐加多,对课也渐渐地加上字去,从三言到五言,终于到七言了。


(对课:旧时学习词句和准备做诗的一种练习。例如老师说“雨”,学生对“风”;老师说“柳绿”,学生对“桃红”)‌

在晚清,“对课”依然是学问的基本,是以民国的诸多名人都有“作对”“成诗”的基本功。过去的童蒙教育如《三字经》《百家姓》《千字文》《增广贤文》《千家诗》《声律启蒙》《笠翁对韵》等也都以三言、四言、五言、七言的对句构成,将韵律的教育融合到文、史、哲、德之中。这是今天“分科”的教育体系可以思考和反省的。

image source: Wikipedia – 阴阳

春秋

日月忽其不淹兮,

春与秋其代序。

惟草木之零落兮,

恐美人之迟暮。(屈原:《离骚》)


中国诗歌的历史上,“悲秋”之作无数,其感叹时间之流逝、与时节之哀伤,大抵如此。‌

在时间的观念上,“春夏秋冬”四时为一循环,春与秋相“对”,春秋在对联中也常使用,一春秋即为一周期。古人这种表面上以“循环”和“对称”为观念的时间观,与现代人以“精致化的测度时间与单向线性时间”为主的时间观念,有很大的差异,而更侧重对“时机”的体认。‌

技术时代的时间观受启蒙运动和演化论(进化论)的影响,有强烈的发展、进步、进化的意味,同时人也受到客观的机械化时间的支配(事实上形成了时间对人的统治,所谓996的讨论是其中一种体现)。‌

“对称”和“进化”入手,这里我们简要谈谈人类时间观念的两种来源。‌

—-‌

现代的“时间”观念,通常被解释为一个空间化的物理学概念,表现为一个可精确测量的(通过时钟来表现)的单向的时间轴(采用儒略历的绝对化的基督纪年,即所谓的公元纪年)。然而,即使在近代科学中,时间被作为单独对象深入考察,也是在进化论、热力学第二定律、宇宙膨胀理论、大爆炸学说、决定性混沌理论等有明确时间方向性的理论纷纷出现后,才全面展开的。‌

在中国古人那里,并没有严格意义上的数学化的自然科学,也还没有广泛使用机械钟,或经历工业革命使得精确的时间统御生活的一切。古人对时间的观察首先来自与对天象、气象、物候的“对称”、“循环”的大量周期现象的发现(与前文提到的“阴阳”观念的产生原因相一致,比如四时、日升日落、月盈月亏),对自然周期的观察与记录产生了“标度”时间,如“日”、“月”、“年”。其初衷是对“时机”的把握,即“正当其时”、“时不我待”等等,强调的是对事件与环境或情境的相协调。对“时”的实际的应用,从二十四节气的名称是显而易见的:

春:立春、雨水、惊蛰、春分、清明、谷雨

夏:立夏、小满、芒种、夏至、小暑、大暑

秋:立秋、处暑、白露、秋分、寒露、霜降

冬:立冬、小雪、大雪、冬至、小寒、大寒


随着对时间掌控的要求的增加,进一步出现了把一天分为10、12或24个部分的要求,以及相应的时间测量工具,如日晷、漏壶等,并逐渐演变成了今天的钟表。通过统一的历法和计时系统,社会中的劳作和习俗可以由时节相互协调。尽管发展出了多种计时工具,但中国并没有发展出纯粹的量化时间观念(如亚里士多德说的:“时间只是某种计数(测度)”),但却保留了尊重“天时”的传统,包括务农、用兵、嫁娶、医治等,例如:

“凡刺之法, 必候日月星辰,四时八正之气,气定乃刺之。”(《素问•八正神明》)


直至今日,这种做事“看日子”的传统在民间也依然流行。然而随着时间测量技术的发展,近代物理学(牛顿力学、乃至相对论)对“测度化”时间概念的确立,“测度”时间逐渐盖过了最初“时机”的概念,一种精确的公共时间观被广泛的使用;“测度”与“时机”之争,也可以认为是“人工”与“自然”之争的体现。‌

今天虽然我们依然能理解“春秋”背后的含义,但对于其背后深刻的“时机”的感受,体会上却已有所不同。‌

—-‌

与时间的周期性变化相对应的,是时间的“流逝”的感觉。与“周期性”的时间更多来自对天地的观察不同,“时间之流”的感受更多来自自身的生命体验。“百川东到海,何时复西归,少壮不努力,老大徒伤悲。” 由于生命的“有终性”(不可逃避的死亡和衰老),人们体会了到了时间流逝的不可抗拒的命运,以及与之相伴的感叹、恐惧与悔恨。同时,也正是时间的有限性,让人感到生命的可贵。‌

时间之流是产生线性时间观的一个重要原因,但也并非总是如此。在希腊和印度文明那里,占主导是循环的时间观。循环的时间观认为宇宙是循环往复的,今天发生的事情,还会再度重演,如印度人对“劫”(尘世的43亿2千万年,是梵天神的一日)的轮回周期的表述,如希腊对圆周运动的偏好。相对应的,希腊和印度的历史感也都很薄弱。‌

有强烈的历史意识与线性时间观的是基督教文化,这是《圣经》中“基督降生”这一事件的不可重复所必然导致的结果。所以基督教的哲学家对当时流行的希腊的循环时间观进行了艰巨的驳斥,其中以奥古斯丁为最:

“如果宇宙有循环,基督受难和再临就丧失了单一无二的至高无上的意义了;我们同苦难作斗争以争得上帝的荣光的企图,由于宇宙的循环而变得十分浅薄,因为我们必将再次陷于苦难,我们 怎么会有对上帝的爱呢?”


相比之下,虽然中国也有很强的历史意识,但却并不抱有极端的线性时间观;对于死亡的态度,也没有宗教般的狂热,更多是一种或平静,或者语焉不详,或者视死如归的泰然处之的心态。如孔子的“未知生,焉知死”,如道家对万物平等的看法:“号物之数谓之万,人处一焉。”(《庄子•秋水》)‌

现代的单向时间观的形成,与地球演化、地球年龄的发现,以及随后19世纪产生的进化论、热力学第二定律有重要关联,而20世纪的宇宙膨胀理论、大爆炸学说、决定性混沌理论等,则进一步奠定了一种“宇宙有时间起点”和“开放未来”的观念,在今天的21世纪技术时代的舆论中也常能感受到这种观点的流行。‌

—-‌

虽然“进化”的观念已经深入人心,但“对称”、“循环”的时间观绝非无可取之处,20世纪的思想家尼采、斯宾格勒、汤因比等也多对“循环时间观”抱有同情。被技术裹挟前进的人类,在面临核战争、基因编辑危害、人工智能武器等的威胁之下,未必没有反思的必要与返归自然的可能性。

‌image source: Pixabay

活动

上一期活动,我们以“百年五四”立意,邀请诸位作对子。‌

下面为活动结果。‌

参与:‌

@julian2013

德赛二先生,启蒙除弊

双子马克斯,执两用中


@doveyan

百年沧桑硝烟刚散狼烟四起暗流涌动

华夏儿女续传五四薪火共筑千年龙魂


@andrewma

惟愿谈笑鸿儒在

不做风流亦相宜


@xiaoshancun

百年五四年年在理应推陈出新

今日薪火日日燃当可星火燎原


@aellly

衣览众衫小因奶大,

傲人巨峰讽笑土丘。


@hertz300

五湖四海鬼怪多

言行举止需谨慎


点评:‌

  • 严格来说大家对的结构上都不太工整,所以以用意来取为主,兼顾结构与音律

奖励:‌

  • @julian2013:切题最佳,又能用典,且别有新意,取为第一,奖励 3 STEEM
  • @xiaoshancun:上下两联“年”与“日”用的颇巧,取为第二,奖励 0.5 STEEM
  • @doveyan,@andrewma:对得也挺有趣, 每人奖励 0.2 STEEM
  • @aellly, @hertz300:跑题了,鼓励0.1 STEEM

—-‌

以下为本期对联活动内容:以“春夏秋冬”或“东南西北”中的若干字为关键字,以二人一组做对联‌

关键字:四时(春夏秋冬)与四方(东南西北)‌

形式:互邀对联;两人合作‌

  1. 由一人出上联,包含“春夏秋冬”或“东南西北”中的若干字,下联要与其相对;例如,出上联“春眠不觉晓”,请朋友作下联;
  2. 上下两联应由不同作者完成;
  3. 一人可以出上联,也可以答下联,也可以参与多次;

规则:‌

  1. 满足对联的基本规则;
  2. 不许抄袭任何别人的成果;
  3. 不应当使用机器协助,如微软亚洲研究院开发的“自动对联”

奖励:3 STEEM,取1对;参与奖,各0.1 STEEM,前10名都有奖 :)‌

参与方式:回复本文‌

评价方式:由@robertyan的评判,以及各回复获得的点赞数,综合评价产生‌

时间:本文发布起7日内有效‌

参考文献

  1. 梁羽生,《名联观止》,北京大学出版社,2017年
  2. 杨大方,《对联论》,中央民族大学出版社,2011年
  3. 吴国盛,《时间的观念》,北京大学出版社,2006年
  4. 鲁迅,《从百草园到三味书屋》,1926年



Posted from my blog with SteemPress : https://robertyan.000webhostapp.com/2019/05/%e5%af%b9%e5%81%b6%e4%b8%8e%e6%98%a5%e7%a7%8b


This page is synchronized from the post: 对偶与春秋

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×