从精通到晕头转向:基于Listpages与CSS计数器的进阶数据处理
2025年1月21日
修订 3
评分
38
↑ 38
↓ 0
支持率
100%
总票数 38
Wilson 95% 下界
90.8%
在相同票数下更稳健的支持率估计
争议指数
0.000
评分趋势
按周聚合 加载图表中...
最近修订
1 / 2
SOURCE_CHANGED
20 天前
修改标签
8 个月前
已新增标签:原创, 文章。
SOURCE_CHANGED
8 个月前
最近投票
1 / 4
2025-08-28
2025-08-03
2025-06-06
2025-05-28
2025-03-22
2025-03-22
2025-03-19
2025-02-02
2025-01-30
2025-01-23
相关页面
暂无推荐
页面源码
[[=]]
[[module ListPages range="." ]]
字数:%%size%%
[[/module]]
[[/=]]
[[include :scp-wiki-cn:theme:parallel deep-diamond=*]]
[[module css]]
:root{
--basalt-bright-element-color: 93, 153, 101;
--basalt-dark-element-color: 65, 174, 60;
--basalt-darker-element-color: 140, 194, 105;
}
h1, h3, h4, h5, h6 {
margin: 1em 0;
}
h1, h2, h3 { text-align: center; }
hr {
background-color: rgb(var(--basalt-main-text-color)) !important;
}
.footnotes-footer {display:none;}
strong {
color: rgb(var(--basalt-darker-element-color));
text-decoration-color: inherit;
}
.sample strong {
color: inherit;
}
li:not(:first-child) {margin-top: 0.5em;}
.cent td {
text-align: center;
}
.list2 {
display: grid;
grid-template-columns: repeat(2, 1fr);
}
@media (max-width: 1020px)
.list2 {grid-template-columns: 1fr;}
}
/* ruby fix from scp-cn-2512 */
p:has(span.ruby) {
line-height: 1.7;
}
span.ruby {
height: unset;
display: inline-flex;
flex-direction: column-reverse;
align-items: center;
vertical-align: top;
}
span.rt {
display: block;
margin-top: -0.625em;
margin-bottom: 0;
line-height: 1.25;
}
/* code fix */
.code {
--_reserved: 114, 162, 247;
--_number: 237, 123, 163;
--_string: 93, 178, 107;
--_quotes: 93, 178, 107;
--_special: 0, 183, 159;
--_var: 193, 154, 73;
margin: 1.5rem 0;
box-shadow: none !important;
background-color: #ffffff11;
}
.blockquote > .blockquote {
max-width: 100% !important;
margin: 1.5rem 0;
box-shadow: none !important;
background-color: #ffffff11;
}
[[/module]]
[[>]]
[[module rate]]
[[/>]]
@@ @@
各位下午好,这里是[[user Sharia Vanilla]],欢迎回到我们的Listpages进阶教程——在上一期的[[[listpages-entry-again|入门讲座]]]中,我们介绍了通过Listpages抓取单页数据并对其进行简单处理与展示的小技巧,以及共计(n-1)位中分成员都非常喜欢的Listpages迭代。然而,对抓取到的多个页面进行整体上的数据统计似乎一直以来都是个麻烦事。在大多数人的理解中,如果仅凭借基础的Listpages统计功能,我们只能对抓取到的页面做一些类似于最大值、最小值以及总数之类的常规统计。但是,如果我们在这里面混入一点点//小技巧//,我们就能得到一些可能会有更多人感兴趣的数据:例如,总分数、总投票数或总作者数。
这期的相关文档包括[https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_counter_styles/Using_CSS_counters CSS计数器的使用]以及[https://www.wikidot.com/doc-modules:listpages-module Listpages的官方文档]。那么话不多说,让我们开始吧。
@@ @@
[[div style="border-bottom:2px solid #777;border-top:2px solid #777; padding:1px;"]]
[[div style="border-bottom:1px solid #777;border-top:1px solid #777; text-align: center;"]]
++ 1.回顾/预备知识
[[/div]]
[[/div]]
@@ @@
+++ 1.1. Listpages生成结构
这一章节中的部分内容在上一期教程中已经有过讲解,此处仅作简单说明。
在不添加任何参数的情况下,Listpages的生成结构如下:
[[div class="blockquote title" data-title="FTML"]]
[[code type="CSS"]]
[[div class="list-pages-box"]]
[[div class="list-pages-item"]]
内容1
[[/div]]
[[div class="list-pages-item"]]
内容2
[[/div]]
......
[[/div]]
[[/code]]
[[/div]]
调用Listpages模块时,**wrapper=**与**separate=**这两个参数分别被用于控制上述结构的外部div和内部div。它们的缺省值是"yes"。当**wrapper="no"**时,将不会在编译时添加**list-pages-box**这一div块,当**separate="no"**时,则不会添加**list-pages-item**这一div块。
为了避免模块额外添加的分割块影响CSS调整,本文中的Listpages模块将默认使用**wrapper="no" separate="no"**。同时,为了尽可能避免分页模块的影响,建议额外添加**perPage="250"**以最大化可用列举范围。
+++ 1.2. 在Listpages模块中插入CSS修改
这一技术同样在入门篇中做过简单介绍。你可以通过使用
[[div class="blockquote title" data-title="FTML"]]
[[code type="CSS"]]
[[%%content{0}%%module css]]
你需要的css代码
[[%%content{0}%%/module]]
[[/code]]
[[/div]]
在Listpages模块中插入CSS修改。其原理是任何页面的%%content{0}%%均会被Listpages模块解析为空字符串。
+++ 1.3. CSS计数器
CSS计数器是CSS提供的一个功能。它由三个属性**counter-reset**、**counter-set**与**counter-increment**,以及两个函数**counter()**与**counters()**组成。它们的格式与用法简单列举如下:
[[div class="blockquote title" data-title="CSS"]]
[[code type="CSS"]]
div.reset {
counter-reset: counter1; /* 创建计数器counter1,并将counter1的值设为0。 */
counter-reset: counter1 2 counter2 4;
/* 可以为计数器提供初始值,也可以一次初始化多个计数器。 */
}
div.set {
counter-set: counter1 8; /* 将计数器的值直接设置为指定值。此处同样可以一次设置多个计数器。 */
}
div.increment {
counter-increment: counter1; /* 缺省参数时,将计数器的值增加1。 */
counter-increment: counter1 -1 counter2 0; /* 参数可以填写0或负数。 */
}
div.print::before {
content: counter(counter1); /* counter函数只能被用在伪元素的content上,是一个字符串变量。 */
content: "(" counter(counter1, lower-roman) ").";
/* 可以为counter的第二个参数填写计数器样式,或将函数通过空格与其它字符串连接。
例如,当counter1的值为7时,此处展示为(vii). */
}
[[/code]]
[[/div]]
以[[[scp-cn-001|001提案中心页]]]为例,其CSS/FTML结构简化后如下所示。此处CSS计数器“series”在进入**div.content-panel**时被创建并初始化为0,并在每次经过**div.divider**时自增1。如此即可实现自动的系列编号标注。
[[div class="blockquote title" data-title="CSS/FTML"]]
[[code type="CSS"]]
[[module css]]
.content-panel {
counter-reset: series;
}
.content-panel .divider {
font-size: 0;
counter-increment: series;
}
.content-panel .divider::after {
font-size: .675rem;
content: counter(series, upper-roman);
}
[[/module]]
[[div class="content-panel standalone series"]]
[[div class="divider"]]
此处Series=1。本段文字内容不会展示,仅用于确保所属div不会被隐藏,下同。
[[/div]]
[[div class="divider"]]
此处Series=2。
[[/div]]
[[div class="divider"]]
此处Series=3。
[[/div]]
[[div class="divider"]]
此处Series=4,实际展示内容为"IV"。
[[/div]]
[[/div]]
[[/code]]
[[/div]]
@@ @@
[[div style="border-bottom:2px solid #777;border-top:2px solid #777; padding:1px;"]]
[[div style="border-bottom:1px solid #777;border-top:1px solid #777; text-align: center;"]]
++ 2.基于CSS/Listpages的总分计算
[[/div]]
[[/div]]
@@ @@
计算一系列文章的总分几乎可以说是Listpages不自带的所有功能中需求量最大的一个。你或许愿意在你的作者页中加上一个这玩意来展示你的累累战果,也可能需要在某些[[[2023-canon-contest-hub|团队]]][[[location-contest-hub|竞赛]]]进行的时候为每个队伍在你的作者页中导演一场分数的皇城PK。一段简单的总分计算代码如下所示:
[[div class="blockquote title" data-title="CSS/FTML"]]
[[code type="CSS"]]
[[module css]]
span.hidden {
display: none;
}
div.sumVotes {
counter-reset: sumVotes;
}
div.sumVotes > div:last-of-type::before {
content: "标签包含'猫科'的原创作品总分为" counter(sumVotes) "。";
}
[[/module]]
[[div class="sumVotes"]]
[[module Listpages tags="+原创 +猫科" category="-deleted" wrapper="no" separate="no" perPage="250"]]
[[div style="counter-increment: sumVotes %%rating%%;"]]
[[span class="hidden"]]@@ @@[[/span]]
[[/div]]
[[/module]]
[[/div]]
[[/code]]
----
[[div class="blockquote"]]
[[module css]]
span.hidden {
display: none;
}
div.sumVotes {
counter-reset: sumVotes;
}
div.sumVotes > div:last-of-type::before {
content: "标签包含'猫科'的原创作品总分为" counter(sumVotes) "。";
}
[[/module]]
[[div class="sumVotes"]]
[[module Listpages tags="+原创 +猫科" category="-deleted" wrapper="no" separate="no" perPage="250"]]
[[div style="counter-increment: sumVotes %%rating%%;"]]
[[span class="hidden"]]@@ @@[[/span]]
[[/div]]
[[/module]]
[[/div]]
[[/div]]
[[/div]]
这段代码能够实现总分计算的具体原理在于:
# 在**div.sumVotes**中,我们创建了一个名为**sumVotes**的CSS计数器。这一计数器被用于统计待抓取篇目的总分。
# 在通过Listpages创建的,每一篇文章各自对应的独立内容部分,我们通过内联样式**counter-increment: sumVotes %%rating%%**将计数器**sumVotes**的值增加**%%rating%%**,后者是Listpages提供的,获取文章当前分数的参数。
# 在**div.sumVotes**下的最后一个子div中,我们通过伪元素展示最终的统计结果。
这里的%%rating%%可以被替换为%%rating_votes%%/%%comments%%以统计总投票数/总评论数,也可以使用表达式结构(**@@[[#expr ]]@@**等)对数据进行一定的预处理。
如果需要统计的文章篇目过多(超过250篇),或是有某些不满足筛选条件但仍需统计的例外篇目,可以直接通过串联Listpages的方式扩展上述代码。例如,统计全站原创goi格式总分的代码如下所示:
[[div class="blockquote title" data-title="CSS/FTML"]]
[[code type="CSS"]]
[[module css]]
span.hidden {
display: none;
}
div.sumVotes { /* 如果你希望在一个页面中添加多个展示文本不同的计分器,请修改每一处父div的class名。 */
counter-reset: sumVotes; /* 计数器名称无需修改。 */
}
div.sumVotes > div:last-of-type::before {
content: "原创GoI格式总分为" counter(sumVotes) "。";
}
[[/module]]
[[div class="sumVotes"]]
[[module Listpages tags="+原创 +goi格式" category="-deleted" wrapper="no" separate="no" limit="250" perPage="250"]]
[[div style="counter-increment: sumVotes %%rating%%;"]]
[[span class="hidden"]]@@ @@[[/span]]
[[/div]]
[[/module]]
[[module Listpages tags="+原创 +goi格式" category="-deleted" wrapper="no" separate="no" offset="250" limit="250" perPage="250"]]
[[div style="counter-increment: sumVotes %%rating%%;"]]
[[span class="hidden"]]@@ @@[[/span]]
[[/div]]
[[/module]]
[[/div]]
[[/code]]
----
[[div class="blockquote"]]
[[module css]]
span.hidden {
display: none;
}
div.sumVotes2 {
counter-reset: sumVotes;
}
div.sumVotes2 > div:last-of-type::before {
content: "原创GoI格式总分为" counter(sumVotes) "。";
}
[[/module]]
[[div class="sumVotes2"]]
[[module Listpages tags="+原创 +goi格式" category="-deleted" wrapper="no" separate="no" limit="250" perPage="250"]]
[[div style="counter-increment: sumVotes %%rating%%;"]]
[[span class="hidden"]]@@ @@[[/span]]
[[/div]]
[[/module]]
[[module Listpages tags="+原创 +goi格式" category="-deleted" wrapper="no" separate="no" offset="250" limit="250" perPage="250"]]
[[div style="counter-increment: sumVotes %%rating%%;"]]
[[span class="hidden"]]@@ @@[[/span]]
[[/div]]
[[/module]]
[[/div]]
[[/div]]
[[/div]]
此外,你还可以用类似的技巧对实现多组Listpages参数的可视化。例如,下面的这段代码在实现统计朝颜文学报、旅行者之书以及妖精们三个标签的总分的同时,在一定程度上按照总分分配了对应进度条的占比:
[[div class="blockquote title" data-title="CSS/FTML"]]
[[code type="CSS"]]
[[module css]]
span.hidden {
display: none;
}
.display-area {
display: grid;
grid-template-columns: 1fr 2fr 1fr;
}
.flex-line {
grid-area: 1 / 1 / span 1 / span 3;
display: flex;
flex-wrap: wrap;
counter-set: counter1 0 counter2 0 counter3 0;
}
.display-area p {
margin: 0;
color: transparent;
user-select: none;
}
.item.c1 {background-color: #ff8889;}
.item.c2 {background-color: #5fc572;}
.item.c3 {background-color: #00c1ff;}
.counter-displayer {
text-align: center;
color: black;
}
.counter-displayer.c1 {grid-area: 1 / 1 / span 1 / span 1;}
.counter-displayer.c2 {grid-area: 1 / 2 / span 1 / span 1;}
.counter-displayer.c3 {grid-area: 1 / 3 / span 1 / span 1;}
.counter-displayer.c1::before {content: counter(counter1);}
.counter-displayer.c2::before {content: counter(counter2);}
.counter-displayer.c3::before {content: counter(counter3);}
[[/module]]
[[div class="display-area"]]
[[div class="flex-line"]]
[[module Listpages tags="+原创 +朝颜文学报" category="-deleted" separate="no" wrapper="no" perPage="250"]]
[[div class="item c1" style="flex: %%rating%%; counter-increment: counter1 %%rating%%;"]]
.
[[/div]]
[[/module]]
[[module Listpages tags="+原创 +旅行者之书" category="-deleted" separate="no" wrapper="no" perPage="250"]]
[[div class="item c2" style="flex: %%rating%%; counter-increment: counter2 %%rating%%;"]]
.
[[/div]]
[[/module]]
[[module Listpages tags="+原创 +妖精们" category="-deleted" separate="no" wrapper="no" perPage="250"]]
[[div class="item c3" style="flex: %%rating%%; counter-increment: counter3 %%rating%%;"]]
.
[[/div]]
[[/module]]
[[/div]]
[[div class="counter-displayer c1"]]
[[span class="hidden"]]@@ @@[[/span]]
[[/div]]
[[div class="counter-displayer c2"]]
[[span class="hidden"]]@@ @@[[/span]]
[[/div]]
[[div class="counter-displayer c3"]]
[[span class="hidden"]]@@ @@[[/span]]
[[/div]]
[[/div]]
[[/code]]
----
[[div class="blockquote"]]
[[module css]]
span.hidden {
display: none;
}
.display-area {
display: grid;
grid-template-columns: 1fr 2fr 1fr;
}
.flex-line {
grid-area: 1 / 1 / span 1 / span 3;
display: flex;
flex-wrap: wrap;
counter-set: counter1 0 counter2 0 counter3 0;
}
.display-area p {
margin: 0;
color: transparent;
user-select: none;
}
.item.c1 {background-color: #ff8889;}
.item.c2 {background-color: #5fc572;}
.item.c3 {background-color: #00c1ff;}
.counter-displayer {
text-align: center;
color: black;
}
.counter-displayer.c1 {grid-area: 1 / 1 / span 1 / span 1;}
.counter-displayer.c2 {grid-area: 1 / 2 / span 1 / span 1;}
.counter-displayer.c3 {grid-area: 1 / 3 / span 1 / span 1;}
.counter-displayer.c1::before {content: counter(counter1);}
.counter-displayer.c2::before {content: counter(counter2);}
.counter-displayer.c3::before {content: counter(counter3);}
[[/module]]
[[div class="display-area"]]
[[div class="flex-line"]]
[[module Listpages tags="+原创 +朝颜文学报" category="-deleted" separate="no" wrapper="no" perPage="250"]]
[[div class="item c1" style="flex: %%rating%%; counter-increment: counter1 %%rating%%;"]]
.
[[/div]]
[[/module]]
[[module Listpages tags="+原创 +旅行者之书" category="-deleted" separate="no" wrapper="no" perPage="250"]]
[[div class="item c2" style="flex: %%rating%%; counter-increment: counter2 %%rating%%;"]]
.
[[/div]]
[[/module]]
[[module Listpages tags="+原创 +妖精们" category="-deleted" separate="no" wrapper="no" perPage="250"]]
[[div class="item c3" style="flex: %%rating%%; counter-increment: counter3 %%rating%%;"]]
.
[[/div]]
[[/module]]
[[/div]]
[[div class="counter-displayer c1"]]
[[span class="hidden"]]@@ @@[[/span]]
[[/div]]
[[div class="counter-displayer c2"]]
[[span class="hidden"]]@@ @@[[/span]]
[[/div]]
[[div class="counter-displayer c3"]]
[[span class="hidden"]]@@ @@[[/span]]
[[/div]]
[[/div]]
[[/div]]
[[/div]]
这一组件通过为一个flex布局下的子div调整其flex属性以动态调整宽度。需注意当抓取到的篇目总数过多时,组件可能强制换行从而影响展示效果。
@@ @@
[[div style="border-bottom:2px solid #777;border-top:2px solid #777; padding:1px;"]]
[[div style="border-bottom:1px solid #777;border-top:1px solid #777; text-align: center;"]]
++ 3.基于CSS/Listpages的作者数统计
[[/div]]
[[/div]]
@@ @@
为某个标签统计其(发布页面的)不同贡献者总数对于希望构建中心页的用户来说可能有重要意义。其效果可以参考[[[kitchen|中央厨房中心]]]。这一功能的一种实现方式如下:
[[div class="blockquote title" data-title="CSS/FTML"]]
[[code type="CSS"]]
[[module css]]
span.hidden {
display: none;
}
div.count-author {
counter-reset: listAuthor;
}
div.count-author > div.listAuthor {
counter-increment: listAuthor;
}
div.count-author > div:last-of-type::before {
content: "共有" counter(listAuthor) "名作者参与短竞。";
}
[[/module]]
[[div class="count-author"]]
[[module listpages tags="+2024噤声短文竞赛" category="-deleted" wrapper="no" separate="no" order="created_by" perPage="250"]]
[[div class="listAuthor n%%created_by_id%%"]]
[[span class="hidden"]]@@ @@[[/span]]
[[/div]]
[[%%content{0}%%module css]]
div.n%%created_by_id%% + div.n%%created_by_id%% {
counter-increment: none;
}
[[%%content{0}%%/module]]
[[/module]]
[[/div]]
[[/code]]
----
[[div class="blockquote"]]
[[module css]]
span.hidden {
display: none;
}
div.count-author {
counter-reset: listAuthor;
}
div.count-author > div.listAuthor {
counter-increment: listAuthor;
}
div.count-author > div:last-of-type::before {
content: "共有" counter(listAuthor) "名作者参与短竞。";
}
[[/module]]
[[div class="count-author"]]
[[module listpages tags="+2024噤声短文竞赛" category="-deleted" wrapper="no" separate="no" order="created_by" perPage="250"]]
[[div class="listAuthor n%%created_by_id%%"]]
[[span class="hidden"]]@@ @@[[/span]]
[[/div]]
[[%%content{0}%%module css]]
div.n%%created_by_id%% + div.n%%created_by_id%% {
counter-increment: none;
}
[[%%content{0}%%/module]]
[[/module]]
[[/div]]
[[/div]]
[[/div]]
在前置的固定CSS代码部分,我们创建了一个名为**listAuthor**的CSS计数器,每当遇见一次**div.listAuthor**,这个计数器就会增加1。而在Listpages内部的div块默认带有listAuthor类,这也就代表着在没有其他修改的情况下,每统计到一篇文章就会将**listAuthor**这一计数器增加一次。
本段代码中出现的Listpages模块有一个与前述分数统计不同的地方:它拥有**order="created_by"**这一参数,这意味着所有被捕捉到的文章在这个模块中将按照页面创建者的名称顺序排列,也即每一位用户创建的所有页面将会彼此相邻。同时,**n%%created_by_id%%**被用来作为div块的另一个类:此处的**%%created_by_id%%**是页面创建者的数字ID,可以避免用户名中特殊符号(如空格)对代码的影响,这同样可以被**%%created_by_unix%%**(用户内部名)等效替换。
[[code type="CSS"]]
[[%%content{0}%%module css]]
div.n%%created_by_id%% + div.n%%created_by_id%% {
counter-increment: none;
}
[[%%content{0}%%/module]]
[[/code]]
在这段代码中,我们用到了在入门篇中提到过的,在Listpages模块中插入CSS修改的小技巧。这段CSS代码的作用是,如果前后相继的两个div块所属的文章由同一位用户创建,也即拥有同一个**n%%created_by_id%%**类名,那么靠后的那个div块将不会使计数器增加,下面的引用框中展示了这一算法的简单工作原理:
[[div class="blockquote"]]
[[div style="display: flex; text-align: center;"]]
[[div style="flex: 1;"]]
未添加CSS修改
**user-A** - listAuthor = 1
**user-A** - listAuthor = 2
**user-B** - listAuthor = 3
**user-C** - listAuthor = 4
**user-C** - listAuthor = 5
**user-C** - listAuthor = 6
[[/div]]
[[div style="flex: 1;"]]
添加CSS修改
**user-A** - listAuthor = 1
**user-A** - listAuthor = 1
**user-B** - listAuthor = 2
**user-C** - listAuthor = 3
**user-C** - listAuthor = 3
**user-C** - listAuthor = 3
[[/div]]
[[/div]]
[[/div]]
这样一来,我们很容易就获取到了在某个筛选条件下的贡献者总数。需要注意的是,由于删除账号用户对应的%%commented_by_unix%%与%%commented_by_id%%均为空值,因此即便使用前者,也需要在变量前添加一个字符,以确保后者总是能够组成一个具体的类名。
那么,本篇指南的主要内容到这里就结束了。最后,如果你对这篇文章中的内容有任何疑惑,欢迎在评论区留言或通过各种途径联系笔者,即[[user Sharia Vanilla]]。
@@ @@
[[include :scp-wiki-cn:component:earthworm
| first=false | last=true | hub=yes
| previous-url=https://scp-wiki-cn.wikidot.com/listpages-entry-again | previous-title=入门篇
| hub-url=# | hub-title=进阶篇1
]]
@@ @@
[[=]]
[[include :scp-wiki-cn:component:license-box
|lang=CN
|author=Sharia Vanilla
]]
[[include :scp-wiki-cn:component:license-box-end]]
[[/=]]