维基百科:模板限制

维基百科,自由的百科全书
跳转至: 导航搜索
快捷方式
WP:TL

维基百科所用的Mediawiki使用一组参数来限制页面的复杂度和页面包含其他页面的数量。这些限制作用于解释一个页面时进行包含页面或者替换引用页面是怎样工作,而不包含解释该页面时的原始源码的情况。本页面是解释该限制是如何工作的,和如何在这些限制下正常使用模板等功能。

背景[编辑]

这是什么?[编辑]

Mediawiki是使用一个語法分析器来将wikicode转化为HTML显示的。它是通过“预处理器”将wikicode整理为一种类似XML的数据结构,然后再将其“展开”,将双尖括号(包括:包含页(也就是模板),变量,魔术字,解释器函数等)和三尖括号(例如:模板变量)括着的内容替换为相应的值。

当页面进行解释时,会生成若干个计数器用于跟踪页面生成的复杂度,当页面开始解释时,计数器初始为0,当页面的解释行为达到计数器的限值时,解释处理会被限制。

为什么要限制?[编辑]

一个非常长的或复杂的页面解释起来非常费时。对于用户的请求体验来说相当不好,而且很容易被黑客利用来进行DDOS攻击——也就是请求Mediawiki去处理解释极为大量的不合理数据。这些限制可以用来防御这种攻击,同时控制页面的渲染在合理的时间内。当然,有时过于复杂的页面可能会显示为请求超时,这取决于服务器的运行负载。

关于限制的作用[编辑]

当页面达到限制时,最常见的处理办法是,参见下面的方法,将模板的大小缩小。如果实在办不到,尽量将模板的内容直接展开到原始的源代码中,而非通过模板嵌入来让渲染时展开。(例如:直接使用<references />代替{{reflist}})。不过另一方面,模板有助于服务器避免重复处理一些相同的解释数据。

什么时候出现问题?[编辑]

页面包含上限通常出现大量地调用同一个模板,例如在一个非常长的表格里每行调用一次。虽然每次调用可能只会往解释页面中添加很小的内容字节数,但其每次调用依然会被统计到计数器,导致页面过早地达到包含上限。一般情况,页面只使用很少量的模板并不会这么快达到限制,除非每个模板都包含大量的内容字节数。

如何找到他们[编辑]

注意:本功能仍处于实验阶段状态,其获得方式可能会发生变动。

当页面完成渲染后,会在页面内容层(也就是<div id="bodyContent" class="mw-body-content">)的结尾输出一段以HTML注释标注的名为“NewPP limit report”的限制报告,将会包含各计数器最终计数和一些模板用时信息。由于计数器的统计方式,Preprocessor visited node count、Preprocessor generated node count、Post‐expand include size这三个计数器通常会少于限值的,如果这三个值逼近限制的话,可能会出现部分模板内容没有展开(而链接的方式显现)。没有展开的模板位置会标注出来并包含相应的错误信息。

下面是Wikipedia:沙盒的限制报告例子。

<!-- 
NewPP limit report
Parsed by mw1211
Cached time: 20161121021614
Cache expiry: 2592000
Dynamic content: false
CPU time usage: 0.108 seconds
Real time usage: 0.123 seconds
Preprocessor visited node count: 276/1000000
Preprocessor generated node count: 0/1500000
Post‐expand include size: 11116/2097152 bytes
Template argument size: 4108/2097152 bytes
Highest expansion depth: 12/40
Expensive parser function count: 0/500
-->

<!-- 
Transclusion expansion time report (%,ms,calls,template)
100.00%   32.737      1 - -total
 88.48%   28.965      1 - Template:請注意:請在這行文字底下進行您的測試,請不要刪除或變更這行文字以及這行文字以上的部份。
 33.91%   11.102      1 - Template:Columns
 29.47%    9.648      1 - Template:Shortcut
 17.80%    5.826      1 - Template:Column
 11.60%    3.798      1 - Template:Tl
  9.26%    3.031      1 - Template:请注意:请在这行文字底下进行您的测试,请不要删除或变更这行文字以及这行文字以上的部分。
  6.74%    2.208      1 - Template:Transclude
  3.64%    1.192      1 - Template:NoEdit
-->

另外还有使用JavaScript脚本注入相应的报告数据,可以通过调用JavaScript APImw.config.get("wgPageParseReport")来获得。

或者可以通过调用Mediawiki APIaction=parse&prop=limitreportdata|limitreporthtml来获得本次API查询即时的页面或wikicode的解释器限制报告的信息(包括用于JavaScript的可读数据和人类可读的HTML数据。),注意的是,只限于本次API查询(可以理解为重新调用parse来渲染本次的页面解释请求),其数值可能与现有的页面输出有少许差异。

关于展开[编辑]

模板中未被执行的分支数据是不会被展开解释的,所以不会计入计数器中。例如wikicode{{#if:yes|{{bar}}|{{foo}}}}{{bar}}会被展开,而{{foo}}则不会被展开。但相反地,一个模板参数可能导致计数增加,即使最终不会被选择输出到最终结果,例如wikicode{{#if:{{foo}}|yes|no}}在解释时,{{foo}}展开后的内容字节数会被计算入展开后计数中,因为必须将{{foo}}展开后才能判断需要选择哪个显示分支。

部分计数器参数解释[编辑]

预处理器节点计数(Preprocessor node count)[编辑]

预处理器节点(Preprocessor node)表示的是页面的复杂度(但不是页面的内容大小)。在页面被渲染时,会生成类似树形的数据结构用对应其生成的HTML树结构。在展开时树节点的每次访问会被计入到预处理器访问节点计数(Preprocessor visited node count)中,当达到限制时,会生成“Node-count limit exceeded”的显示错误。

纯文本计数为1,一对<nowiki>的内容计数为3,一个标签头计数为2,如此类推。一个链接内容不会计数,而{{#switch}}每添加一个判断条件计数增加2。相同内容的模板如果不传入参数的话会只被计算1次,但传入参数(即使是常数)的话则分别计算。相比之下,如果模板只传入一个固定的模板参数,而且模板被多次同样的调用,该模板的展开结果将会被多次重复利用。

包含该错误的页面将会在Category:页面的节点数超出限制中显示。

展开后计数(Post-expand include size)[编辑]

展开后计数(post-expand include size)是指由模板、变量、解释器函数展开后的wikicode的字节总和。当解释器将一个模板的源码展开出来时(也就是模板被调用页面嵌入或者替换引用),其展开的代码长度会被累加到计数器中。如果该计数器值超过限制,解释流程会被限制,展开内容不会替换上去,并且生成一段包含错误信息的HTML注释插入其中,否则计数器将会替换为新的计数值,并且继续解释。同一个模板被多次展开将会多次贡献计数值。

调用不带参数的模板会缓存其展开后内容的wikicode。所以若模板A包含没参数的模板B,模板B在模板A中多次调用都只会被计算一次展开后的长度值,但是如果模板B需要传入参数的话,则每次调用带参数的模板B,都会贡献一次展开后的长度值,即使每次调用一样的参数。

包含该错误的页面将会在Category:模板包含上限已经超过的页面中显示。

使用注释、<noinclude>、<onlyinclude>[编辑]

只有通过预处理器扩展阶段的数据才会被计算入展开后计数器中。使用HTML注释的代码部分是不会被计算入展开后计数器中,而且其结果也不会被输出到最终HTML代码中。在<noinclude>内或<onlyinclude>外的wikicode也不会被展开而计算到展开后计数中。这也意味着通过包含模板来对页面进行分类时,只有被包含时产生分类效果是才会贡献展开后计数值。

嵌套展开[编辑]

注意,所有被展开的模板和解释器函数的wikicode展开值是累加值,即使是嵌套的情况。(phab:T15260)所以会产生额外的计数重复。例如模板A包含模板B,模板B包含模板C,模板C的展开后字节数会被模板A的计数器计算了2次。类似有,在模板中包含一个解释器函数,或解释器函数使用了模板的输出值作为其输入参数,等。所以有时候需要使用直接产生模板的调用名来代替直接产生模板结果,来避免这种计数重复。

例如:

{{#if:{{{test|}}}|{{template1}}|{{template2}} }}

应该替换为

{{ {{#if:{{{test|}}}|template1|template2}} }}

模板参数字节计数(Template argument size)[编辑]

模板参数字节计数(Template argument size)是用于跟踪被替换的计算模板参数总长度。

例子,假设{{2x}}用于将参数1的内容连续复制2次,同理,{{3x}}为复制3次,则{{3x|{{2x|abcde}}}}计算器记为40字节,参数“abcde”计算了2次,参数“abcdeabcde”计算了3次。

模板传入参数没被模板内变量匹配调用的,不计入计算器中。

如果使用了switch解释器函数,没被匹配的参数不会计入计算器,如果存在匹配的话,匹配的键参数长度会被计算为2次,匹配的值参数长度计算1次,按照其赋值的展开后值长度计算。

包含该页面超出模板参数大小限制的页面将会在Category:含有略过模板参数的页面中显示。

最高扩展深度(Highest expansion depth)[编辑]

该参数用于跟踪模板展开后所达到的最高层级计数,该计数器限值默认为40。

包含该错误的页面将会在Category:扩展深度超出限制的页面中显示。

高开销解析函数数量(Expensive parser function calls)[编辑]

该参数用于跟踪部分高开销的解释器函数的使用次数,该计数器限值默认为500。

包含该错误的页面将会在Category:有过多高开销解析器函数调用的页面中显示。

以下为属于高开销的解释器函数或相应调用:

  • #ifexist——判断是否存在特定页面来选择分支。当达到限制时,将会认为特定页面不存在。
  • PAGESINCATEGORY 或 PAGESINCAT
  • PAGESIZE
  • CASCADINGSOURCES
  • REVISIONUSER
  • REVISIONTIMESTAMP
  • 部分Lua对象方法

在Lua脚本中,可以通过调用mw.incrementExpensiveFunctionCount来手工增加高开销解析函数的调用次数数量。

关于{{#time}}解释器函数[编辑]

{{#time}}的格式化字符串被限制在6000个字符,超出则显示错误。一个格式化字符串或时间表达式展开后的wikicode,将会被缓存起来,能被重复使用。

本方法调用没有计数器统计数据。

Special:展开模板[编辑]

当页面达到限制时,一个比较粗糙的排查方法是通过Special:展开模板来排查。不同于替换,它会递归地展开全部层级,不需要使用safesubst:或类似的代码来将其替换展开。除了预处理器节点计数器,其他计数器将会设定为0,来降低模板展开的限制。

历史[编辑]

蒂姆·斯塔林於2006年8月14日在英語維基百科上實施並啟用一個新的嘗試「限制模板的引用」。而新的解释处理器则在2008年1月投入使用,并且将其中一个参数“pre-expand include limit”改用预处理器节点计数器来代替。其中2006年的功能引入确立了pre-expand include limit的限制为2MB,该限制也延续到相似参数“Post-expand include size”中。

其他参考资料[编辑]