豆备 DOUBAK
← 开发日志

两个整整齐齐的零

音乐 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;

样张站点上肉眼可见 &#34;&#39;。 追下去发现两边各自都没做错事:

  • 站点生成器按规矩把用户文本里的 & 转义成 &amp;——不转义会丢字;
  • 解析器留下了一个没解开的 &#34;

于是 &#34;&amp;#34;原样印在页面上解析器漏解一次,下游就会忠实地把它展示出来。

根因不是某一处漏了,而是四个抽取器各写各的解码表:一个认八种、 一个认六种、一个认五种,而标题与短评的来源——最要紧的那个——一种都不认。 收敛成一份实现之后,实测未解开的实体 196 → 1, 而剩下那一处是对的(原文就是 HITMAN&amp;amp;trade;,解一次得到字面的 &amp;trade;)。

两条规矩,旧的四张表全都写错了:

  • 一遍扫完,不要链式 replace。 四份实现都把 &amp; 排在最前面解, 于是 &amp;lt; 先变成 &lt;、再变成 <。 而它的原文是字面的四个字符 &lt;——正好是一条讨论 HTML 转义的短评会写出来的东西, 吃掉之后还原不回去。
  • 认不出来的原样留着。 越界码位、没见过的实体,一律不猜。 猜错是不可逆的,留着则是可以 grep 出来的。同理 &nbsp; 解成不换行空格而不是普通空格: 页面上写的就是不换行空格,改掉属于静默改写。

还有:长文的正文粘成了一坨

日记正文要变成 Markdown,所以结构必须在剥标签之后活下来。三处都是静默的:

  • 点列表变成一坨。 五个 <li> 只剥标签就粘成了一长串。
  • 段落粘连。 </p> 只给一个换行,CommonMark 当作段内软换行——三段在页面上并成了一段。
  • 正文末尾挂着豆瓣的版权声明。 「科技 生活 本文版权归 ⋯ 所有」跟在用户日记后面。

往里收一层到 <div class="note"> 就解决了,但认不出就退回整段,绝不返回 null—— 手上只有 2 篇带这个容器,n=2 推不出一个封闭集合。 这个项目在「从手上的样本推出封闭集合」上已经栽过四次了。

← 更新:又名是搜索里最值钱的一栏 更早:一按「开始抓取」就报错 →