ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

defuddle 如何精准剔除站点 Logo 而不误伤正文——selectors--logo-substring-wrapper 测试样本全解析

defuddle 如何精准剔除站点 Logo 而不误伤正文——selectors--logo-substring-wrapper 测试样本全解析
  • 后端

【免费下载链接】defuddle

Get the main content of any page as Markdown.

项目地址:https://gitcode.com/gh_mirrors/de/defuddle
点击查看免费下载

在网页正文提取场景中,「Logo」是最常见也最容易误判的页面噪声:几乎所有站点都会把站标放在<header>里,而部分站点又习惯用logo作为 class 命名的一部分。当正文容器的 class 恰好也包含logo子串时(例如a-content--logo-end),一个粗糙的子串匹配规则会把整篇正文误删。本文以 defuddle 仓库中的测试样本 selectors--logo-substring-wrapper.html 及其期望输出 selectors--logo-substring-wrapper.md 为切入点,逐行拆解 defuddle 的选择器清理机制:精确选择器与部分选择器如何分工、logo词边界正则如何做到"该删的删、该留的留",以及这一行为如何被 fixture 测试体系固化下来。读完本文,你将掌握 defuddle 选择器规则的判定边界,也能学会如何为自己的站点排查"正文被误删"这类问题。

测试样本要验证什么:一个"名字里藏着 logo"的正文容器

先看这个 fixture 对应的原始 HTML(tests/fixtures/selectors--logo-substring-wrapper.html),它的结构非常典型:

<!DOCTYPE html> <html> <head> <title>City Approves Transit Expansion - Example Daily</title> <meta name="author" content="Jordan Blake"> </head> <body> <header class="site-header"> <div class="logo"><a href="/"><img src="/img/logo.svg" alt="Example Daily"></a></div> </header> <main class="page-main"> <article class="article-body"> <h1>City Approves Transit Expansion</h1> <div class="key-points"> <p>The council approved three new transit lines ...</p> <!-- 若干关键要点段落 --> </div> <div class="a-content a-content--logo-end"> <div class="body-copy"> <p>The vote clears the way for construction to begin ...</p> <!-- 正文主体段落 --> </div> </div> </article> </main> </body> </html>

页面上存在两个敏感目标:

  1. <header class="site-header">内的<div class="logo">——这是真正的站点 Logo 容器,必须被剔除;
  2. <div class="a-content a-content--logo-end">——这是正文的外层包装容器,它的 class 里含有logo子串(a-content--logo-end中的logo),但它承载的是文章主体,绝不能因为子串匹配被误删。

测试文件名selectors--logo-substring-wrapper正是对这一场景的精确概括:"logo-substring"(logo 子串)与 "wrapper"(正文包装层)。期望输出 selectors--logo-substring-wrapper.md 证明了 defuddle 的正确行为:Logo 容器消失,而正文完好无损地保留下来:

{ "title": "City Approves Transit Expansion", "author": "Jordan Blake", "site": "Example Daily", "published": "" }

The council approved three new transit lines on a nine-to-four vote after a hearing that ran past midnight, ending a debate that has occupied the chamber in one form or another for the better part of two decades without ever reaching a binding decision.

Bonds issued by the transit authority will cover roughly two thirds of the projected cost, with the remainder drawn from a state matching fund that lawmakers renewed earlier this year over the objections of the rural caucus, which wanted the money spent on highway resurfacing instead.

Two members who voted against the measure have asked for an independent audit of the ridership projections before any bonds are issued, and a third said she would support the audit even though she voted in favor of the expansion on the merits of the eastern alignment.

Construction on the first line is scheduled to begin in March and to reach the eastern terminus in four years, though the authority has cautioned that the schedule assumes it can acquire the last right-of-way parcels without going to court over the price of two industrial lots.

The county commission still has to ratify the cost-sharing agreement, a step the mayor's office describes as a formality but that at least two commissioners have said they intend to use as leverage for a station stop that the current alignment leaves out entirely.

The vote clears the way for construction to begin next spring on lines that will connect the eastern suburbs to the downtown core for the first time since the original system opened, when the eastern edge of the built-up area stopped several miles short of where it stands today.

Officials estimate that the first of the three lines will carry passengers within four years of groundbreaking, assuming the environmental review now underway concludes on schedule and the authority is able to acquire the last of the right-of-way parcels without resorting to condemnation proceedings.

Opponents argued that the projections rest on optimistic assumptions about population growth in corridors that have seen very little new housing built since the last comprehensive plan, and that the authority has historically overestimated demand on precisely the kind of suburban extension now proposed.

Transit advocates who packed the chamber called the vote long overdue. Earlier proposals collapsed over disputes about which neighborhoods would host maintenance yards and how the costs would be shared between the city and the surrounding county, whose commission has yet to schedule its own vote on the funding agreement.

The authority's chief engineer told the council that early utility relocation work could begin as soon as the environmental review concludes, and that the agency has already begun the procurement process for the rolling stock it expects to need by the time the eastern line opens for revenue service.

A spokesperson for the mayor said the administration considers the funding agreement settled and does not expect the county commission to reopen the cost-sharing formula, which was negotiated over eighteen months and signed by both executives before the council took up the measure this spring.

Construction is expected to begin in March, pending final environmental review.

对比 HTML 与期望输出可以发现三件事:站点 Logo(div.logo)被删除;key-points与a-content a-content--logo-end两层包装被解除(解包而非保留),正文段落以纯 Markdown 段落形式输出;标题、作者、站点名被正确提取到元数据中。

选择器清理在 defuddle 处理管线中的位置

在深入规则细节之前,先明确这段逻辑所处的处理阶段。defuddle 的主流程位于 src/defuddle.ts,其中removeBySelector是内容清理管线中的确定性清除步骤:

  • removeHiddenElements:按计算样式剔除隐藏元素;
  • removeEyebrowLabel:剔除标题上方的分类标签;
  • removeBySelector:按 class/id/属性选择器确定性剔除已知非内容元素(导航、页脚、侧栏、Logo 等),在 src/defuddle.ts 中调用,入口函数定义于 src/removals/selectors.ts;
  • removeLowScoring:基于链接密度等启发式打分剔除低分块,运行在removeBySelector之后,因此选择器阶段清理得越干净,打分阶段看到的 DOM 越纯净。

removeBySelector的调用还受两个开关控制:removeExactSelectors与removePartialSelectors(默认均为 true,定义见 src/types.ts),分别对应下面要讲的精确匹配与部分匹配两条路径。

第一道防线:精确选择器直接命中 Logo 容器

defuddle 将"特征非常明确"的元素写死在EXACT_SELECTORS数组中(src/constants.ts),运行前用join(',')拼成一个巨型选择器字符串EXACT_SELECTORS_JOINED,在 src/removals/selectors.ts 中通过一次querySelectorAll全部取出。

与 Logo 相关的精确规则有三条(src/constants.ts):

// logos '[class="logo" i]', '#logo', '#Logo',

注意第一条用的是完全相等的属性选择器[class="logo" i],即只有当 class 属性值恰好是logo(不区分大小写)时才命中。本 fixture 中的<div class="logo">正是这种情况,因此它在精确匹配阶段就被标记删除,与后面的部分匹配逻辑无关。而a-content--logo-end的 class 值不是logo,不会被这条规则命中。

精确匹配阶段还有若干保护性跳过逻辑,避免误伤正文:

  • 跳过代码块内的元素(el.closest('pre, code')),防止语法高亮 span 被误删;
  • 跳过带响应式显示类(如 Tailwind 的hidden sm:flex)的隐藏元素;
  • 跳过mainContent的祖先元素,防止正文容器本身被拔除。

第二道防线:部分选择器按词边界匹配 logo

仅仅处理class="logo"显然不够,现实中大量站点使用site-logo、logo-wrapper、header-logo这类组合类名。这些靠PARTIAL_SELECTORS数组中的子串模式处理,其中 logo 的规则是整条数组里最"精密"的一条(src/constants.ts):

// Matches 'logo' only at a class-token edge ('site-logo', 'logo-wrapper'). '(?:(?<![\\w-])logo|logo(?![\\w-]))',

这是一个词边界式正则,含义是:logo要么出现在 class token 的开头(后面不能跟\w或-,即后面是 token 结束),要么出现在 token 的结尾(前面不能是\w或-,即前面是 token 开始)。用这个规则验证本 fixture 中的两个候选:

class 值是否匹配原因
logo(已在精确阶段删除)—精确选择器[class="logo" i]命中
site-logo匹配logo在 token 末尾,前面是-,lookaheadlogo(?![\\w-])成功
logo-wrapper匹配logo在 token 开头,lookbehind(?<![\\w-])logo成功
a-content--logo-end不匹配logo前后都是-,两个方向的边界断言均失败

关键就在a-content--logo-end:logo前面是-、后面也是-,既不是 token 开头也不是 token 结尾,于是两条分支((?<![\\w-])logo与logo(?![\\w-]))全部落空,正文容器得以存活。这正是该 fixture 名称中 "substring" 一词的深意——规则并非简单的"包含 logo 就删",而是"logo 作为一个独立词元出现才删",从而在logo作为其他单词组成部分(如catalog、dialogue,以及本场景的 BEM 风格--logo-end修饰符)时保持克制。

如果改成朴素子串匹配logo,那么a-content--logo-end会被命中并整体删除,整篇正文随之丢失——本 fixture 就是用来永久防止这类回归的。

部分匹配的完整判定流程

removeBySelector的部分匹配阶段(src/removals/selectors.ts)并不只检查 class,而是把多个"测试属性"拼接后统一做正则测试。测试属性定义在 src/constants.ts:

export const TEST_ATTRIBUTES = [ 'class', 'id', 'data-component', 'data-test', 'data-testid', 'data-test-id', 'data-qa', 'data-cy' ];

运行时会拼成[class],[id],[data-component],...这样的属性选择器TEST_ATTRIBUTES_SELECTOR(src/constants.ts),一次性取出所有候选元素。对每个候选元素:

  1. 跳过已标记删除的元素、[data-defuddle]提取器输出内的元素,以及 code/pre 及其内部元素——语法高亮、内联代码的类名(如language-name、meta-)很容易命中噪声词,必须先排除;
  2. 非标题元素把class +>
  • 后端

【免费下载链接】defuddle

Get the main content of any page as Markdown.

项目地址:https://gitcode.com/gh_mirrors/de/defuddle
点击查看免费下载

相关推荐

上一篇:5分钟永久备份QQ空间青春回忆:GetQzonehistory使用指南
下一篇:GetQzonehistory:3分钟永久保存你的QQ空间青春回忆

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进