两个整整齐齐的零
音乐 84 条标记里 0 条抽到了短评,舞台剧 5 条也是 0 条。一句告警都没有——因为「这条本来就没写短评」是合法的。
这一批修的都是同一个形状的 bug:产出看起来完全正常。 没有异常、没有报错,只是有些字不在里面了。
一个选择器只属于一种媒介
影视的短评在 <span class="comment"> 里。三种媒介共用了这个选择器,
于是:
影视 <li><span class="comment">这讲的是个啥…</span></li>
音乐 <li>
欢乐
</li>
舞台剧 <li>团建选了看舞台剧/音乐剧的项目…</li>
音乐和舞台剧的短评裸在 <li> 里,没有任何 class。
音乐 84 条标记 0 条有短评,舞台剧 5 条 0 条——两个整整齐齐的零。
它不报错,因为「这条标记没写短评」本来就是最常见的形态。
这已经是第二次撞上「每种媒介一套,这不是可以统一的东西」
(上一次是游戏的评分与短评),所以这回不再按 class 找,改成按位置找:
操作栏 <li class="clearfix opt-ln"> 前面那个 <li>。
实测短评 2027 → 2109(舞台剧 0→4,音乐 0→78)。
改的过程中,端到端那条断言从 3 跳到 4
项目里有一条端到端断言,钉住「有多版本的标记有几条」。改抽取器的时候它从 3 变成 4, 逼出了两个只会静默出错的坑。
「(N 有用)」和短评住在同一个 <li> 里
<span class="pl">(5 有用)</span> 是豆瓣的计数器,不是用户写的字。
实测同一条标记两次抓取之间从 (5 有用) 变成了 (1 有用),短评一个字没动。
把它算进短评,就会凭空多出一条「用户编辑了这条标记」的记录——
与之前那次「1740人浏览」混进日记正文是同一个错。
只有标题的条目,把片名当成了用户短评
有些标记只有一个标题,没有评分、日期、标签、短评。这种条目上, 兜底逻辑取到的是作品自己的名字,于是 8 部电影的「短评」变成了:
V字仇杀队
铁西区第一部分:工厂
它产出的是像样的中文。 这才是最危险的地方——扫一眼档案,
那看起来完全像是有人真的写过这么一句。
判据漏在于只检查了 <li> 的内容,而那个能识别它的 class
正好写在开标签上,已经被切掉了。
修完之后影视仍是 1506 条短评(没动),修订总数仍是 2943(没有伪造出任何编辑)。
顺带:页面上那些 "
样张站点上肉眼可见 "、'。
追下去发现两边各自都没做错事:
- 站点生成器按规矩把用户文本里的
&转义成&——不转义会丢字; - 解析器留下了一个没解开的
"。
于是 " → &#34; → 原样印在页面上。
解析器漏解一次,下游就会忠实地把它展示出来。
根因不是某一处漏了,而是四个抽取器各写各的解码表:一个认八种、
一个认六种、一个认五种,而标题与短评的来源——最要紧的那个——一种都不认。
收敛成一份实现之后,实测未解开的实体 196 → 1,
而剩下那一处是对的(原文就是 HITMAN&amp;trade;,解一次得到字面的 &trade;)。
两条规矩,旧的四张表全都写错了:
-
一遍扫完,不要链式 replace。 四份实现都把
&排在最前面解, 于是&lt;先变成<、再变成<。 而它的原文是字面的四个字符<——正好是一条讨论 HTML 转义的短评会写出来的东西, 吃掉之后还原不回去。 -
认不出来的原样留着。 越界码位、没见过的实体,一律不猜。
猜错是不可逆的,留着则是可以 grep 出来的。同理
解成不换行空格而不是普通空格: 页面上写的就是不换行空格,改掉属于静默改写。
还有:长文的正文粘成了一坨
日记正文要变成 Markdown,所以结构必须在剥标签之后活下来。三处都是静默的:
- 点列表变成一坨。 五个
<li>只剥标签就粘成了一长串。 - 段落粘连。
</p>只给一个换行,CommonMark 当作段内软换行——三段在页面上并成了一段。 - 正文末尾挂着豆瓣的版权声明。 「科技 生活 本文版权归 ⋯ 所有」跟在用户日记后面。
往里收一层到 <div class="note"> 就解决了,但认不出就退回整段,绝不返回 null——
手上只有 2 篇带这个容器,n=2 推不出一个封闭集合。
这个项目在「从手上的样本推出封闭集合」上已经栽过四次了。