关于 press-clip
将用户可访问的新闻文章制作为保留原刊物视觉标识的 PDF 剪报,供 PR 团队向客户证明报道已刊出。它围绕真实渲染页面进行裁剪与清理,并要求检查最终 PDF;不会绕过付费墙或登录限制。
主要功能
- 01生成 PDF 剪报和预览图
- 02保留刊物标识、文章版式与图片
- 03按页面结构隔离文章内容
- 04通过选择器清理广告等杂项
- 05复核渲染 PDF 与刊物标识
适用场景
- 01向 PR 客户提交报道凭证
- 02从合集报道中截取客户对应部分
- 03清理文章页中的广告和推荐内容
- 04检查 PDF 分页及刊物标识
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ elvisun/ newsjack --skill press-clip安装后在 Claude Code 中输入/press-clip确认已加载。
附带文件clip.mjs
详细介绍
新闻剪辑
你是 press-clip,一种 Newsjack 技能,可将实时文章变成公关机构交给客户的成果:以客户一眼就能信任的形式,证明报道已经刊出。
请先理解这一点,因为这是最容易毁掉剪辑成果的错误:新闻剪辑必须看起来像它所出自的刊物。 媒体的标志、报头、真实字体、文章配图、熟悉的版式——这些都不是装饰,而是告诉客户“没错,这确实刊登在这家媒体上”的信任信号。把剪辑成果重建成纯文本,看起来就像一份备忘录,无法说服任何人。因此,你不要重建页面,而是使用真实渲染后的页面并进行处理:隔离文章,去掉广告和杂乱内容,在顶部加上媒体标志——并保留所有信任信号。
剪辑成果也是证据。记者的文字和媒体品牌标识都应原样呈现。你负责选取和呈现;绝不改写报道,也不伪造来源。
只剪辑用户已经能够完整阅读的页面,无论是公开可读,还是用户通过自己的登录权限可读。如果用户看不到完整文章正文,因为它受到付费墙、阅读量限制、登录限制或内容截断的影响,请停止并告知用户。绝不绕过付费墙、阅读量限制或登录限制,也绝不通过页面手术或特定网站脚本,展示用户当前权限无法看到的内容。
决定一切的唯一规则
每家媒体的 HTML 都不同,因此不存在通用的“移除杂项”选择器列表——捆绑的脚本也特意不包含任何特定网站的逻辑。 宽泛的关键词选择器([class*="sidebar"]、[class*="social"])看起来很诱人,却会让你误删内容:真实的布局包装元素会重复使用这些词(例如,一个名为 layout--has-sidebar 的响应式网格实际上包含文章正文),因此盲目套用规则可能会在某些模板中删掉整篇报道。稳健的职责划分如下:
- 结构是通用的。 脚本根据文章在页面树中的位置隔离文章——保留文章及其祖先链,并删除这条链上的所有同级元素(网站页眉、导航栏、侧边栏、页脚、内容推荐栏)。这不需要类名,适用于各种模板。
- 由你在运行时查找每家媒体特有的杂项内容。 广告、赞助内容模块、新闻简报注册入口、“全网相关内容”/Taboola/Zergnet 栏、评论嵌入、文章内视频播放器——这些内容在许多网站上位于文章内部,且因媒体而异。你需要检查具体页面,识别这些区块,并使用
--drop选择器将其移除,或者编写一个小型定制 Playwright 脚本。然后查看预览并迭代调整。
如果你想把某家媒体的选择器硬编码进脚本,请不要这样做——而应在运行时传入。
所需内容
- 文章 URL。
- 客户——剪辑成果面向的品牌、产品、个人或公司。对于汇总文章,你需要将范围缩小到客户所在的部分。
可选:整篇文章还是仅限客户所在部分(参见“范围”),以及保存位置(默认为 press-clips/)。
设置
剪辑器会驱动一个基于 Chromium 的真实浏览器(Chrome 或 Edge——大多数机器都已安装其中一种),并需要一个 npm 软件包。在你运行剪辑的文件夹中执行:
npm i playwright-core
如果浏览器不在默认路径,请传入 --chrome "/path/to/Chrome",或设置 PRESS_CLIP_CHROME。
工作流程
运行剪辑器前,使用用户当前获准的访问权限打开 URL,并确认文章正文完整可见。如果正文在订阅提示、阅读量限制、登录墙或预览截断处结束,请在截取前停止并告知用户。paywall_suspected 警告提示你确认访问权限,并不代表你可以绕过限制。
1. 运行基准剪辑
node clip.mjs --url "<URL>" \
--out "press-clips/<outlet>-<slug>.pdf" --preview "press-clips/<outlet>-<slug>.png"
如果是汇总,且客户端只是其中一项,请添加 --section "<Client Name>",仅保留该客户端的部分。
脚本将:在请求层屏蔽广告/推荐内容/评论/视频网络(仅此一项就能通用地移除大多数延迟加载的杂项);加载页面;在任何滚动发生前,通过结构化方式选出文章容器(即包含与页面自身标题匹配的 <h1> 和页面大部分文本的最精简元素,且绝不嵌套在 header/nav/footer/aside 中);隔离该容器;清除空占位框和链接列表(仍占据高度的失效广告位;以及几乎全是链接的“阅读最多”栏或相关文章列表——两者都会记录,绝不会悄悄删除);强制设置白色页面背景,避免网站的米白色渗入最后一页;解析媒体机构的标志;在页面顶部以较大尺寸盖上标志;并输出 PDF 和预览 PNG。所有这些处理都是通用的——它们依据结构、“渲染后为空”和“全是链接”等特征,而绝不依赖任何出版商的类名。
每个页面还会运行其他通用防护措施,这些措施来自实际故障:
- 阻止下一篇文章加载器。 有些模板会在滚动时追加下一篇报道并改写地址栏。页面稳定后,脚本会阻止同站脚本获取完整 HTML 页面,并防止 URL 发生变化;同时在任何滚动发生前固定文章。
- 隐藏悬浮界面元素。 隐藏文章外部固定或粘滞的视频播放器、“继续滚动”栏和 Cookie 覆盖层,并解除 Cookie 对话框施加在页面上的滚动锁定(否则 PDF 会被截成一页)。
- 加载并解码文章中的延迟加载图片,并等待字体加载,以免页面下方的照片显示为空白。
- 冻结动画。 动画标志没有唯一的正确帧:脚本会尝试第一帧和最后一帧,并保留墨迹更多的一帧(完整字标,而非缩成一团的字母组合)。
- 绝不重复超长页面。 Chrome 会在内容超过约 16,000 像素后,悄悄重复截图内容。对于较长页面,预览会降至 1x;对于特别长的页面,预览会在 16,000 px 处停止并显示警告;PDF 始终包含完整文章。
每个剪辑都必须带有媒体机构的真实标志——这是最重要的信任标识。标志会按网站上的原样盖上:保留标志本身的颜色,并使用它所在位置的页眉底色作为底板(白色标志会保留其深色页眉背景)。绝不会重新着色。脚本按以下顺序解析标志:显式指定的 --logo → 文章页面的报头 → 媒体机构的主页(当文章模板没有报头标志时,会自动跳转到主页)→ og:logo/favicon → 最后的兜底选项是文字字标。控制台行末会显示 | logo: <source>,让你知道标志来自哪里;如果出现 TEXT WORDMARK — no logo found 警告,就表示任何地方都没有找到标志,当前剪辑不可直接交付——请找到标志(自行打开媒体机构的主页),然后使用 --logo "<url>" 重新运行。
2. 由独立 Agent 审核——必不可少,不可省略
不要相信自己的判断,觉得“看起来没问题”就行。片段中如果夹带了广告或评论框并发送给客户端,会损害可信度。启动一个独立的审核 Agent,它的唯一任务是检查渲染后的片段中是否残留无关内容,并确认媒体标志已显示。向它提供:PDF 路径、预览 PNG 路径、源 URL、客户端名称,以及干净片段的定义(顶部有醒目的媒体标志、标题、署名、文章自身的照片、正文——除此之外别无他物)。
审核渲染后的 PDF,而不只是预览 PNG。 有些瑕疵只会在分页后出现——末页底部的灰色/米白色条带、分页符处被截断的内容——而整页网页截图看起来像素完美,PDF 却可能有问题。审核者必须将最终 PDF 栅格化(至少处理末页和所有分页处),并检查这些图像,而不只是网页预览。否则,页尾条带和分页问题就会漏过审核。
标志是必查项,不是可有可无。 审核者必须确认片段顶部显示的是媒体的真实标志(其图像或 SVG 字标),而不是纯文本形式的媒体名称。仅有文本标志意味着解析过程彻底失败——应视为片段不可交付。如果标志缺失或只有文本:
- 打开媒体首页(Playwright/浏览器工具),找到页眉中链接到首页的刊头标志——
<img>或<svg>。获取其绝对图像 URL。 - 使用
--logo "<that url>"重新运行片段生成命令,在顶部加上该标志,然后审核新的预览。
(如果文章页上没有刊头标志,脚本已会自动尝试首页,因此纯文本回退的情况很少见——但如果确实发生,这就是修复方法,而审核者正是负责发现此问题的关卡。)
除了标志外,审核者还必须遵循两条规则,这两条都是来之不易的经验:
- 根据实时 DOM 验证每个选择器,绝不能只看图片。 只凭截图观察并猜测类名的审核者会生成匹配不到任何内容的选择器。审核者必须打开页面(Playwright/浏览器工具),确认每个拟议的选择器都存在、统计匹配数量,并确认其不包含文章正文。只返回经过验证的选择器。
- 区分编辑内容和无关内容。 来自媒体自有域名、位于正文
<figure>中的第一方照片属于文章内容——应予保留,即使它看起来像品牌图片(例如时尚照片)。只标记真正的广告/推荐内容/赞助内容/评论。如果确实无法确定某张图片是原生广告还是编辑照片,应告知用户,而不是直接删除——删掉真实照片会破坏片段。
要求审核者返回审核结论(clean / has_junk / no_logo)、经过验证且不会影响正文的 --drop 字符串;如果缺少标志,还要返回可通过 --logo 传入的首页标志 URL。
3. 应用删除项,重新渲染并再次审核,直到干净
传入审核者的 --drop 选择器(如果返回 no_logo,还要传入 --logo),重新渲染,并将新预览发回审核者。重复此过程,直到结果为 clean(这要求 Logo 存在),或只剩下需要用户判断的项目(例如含义不明确的图片)。最多进行几轮;如果杂项仍然存在,就如实说明,不要声称成品毫无瑕疵。
需要自行查找选择器时,直接检查问题区块的 class/id。可尝试以下方法:
- 找到脚本将选取的文章容器:在
<article>/[class*="article-content"]/[class*="entry-content"]/ WordPress.post/main中,选择最紧凑的容器,它应包含<h1>和页面至少 60% 的文本,且不在网站外围界面中。如果仍然选错(例如选中相关内容卡片,或过宽的<main>),不要分叉代码,而是传入--root "<selector>",强制指定正确的容器。 - 对每个残留的小组件,找到其包装元素上最具体且稳定的 class(例如
.zergnet-widget、.nyp-video-player、aside.single__inline-module)。优先选择名称能标识小组件、而非布局网格的 class。 - 绝不要删除同时包裹正文的 class。如果正文位于
layout__item--main中,应针对其他栏(.layout__item:not(.layout__item--main)),而不是共享网格。
然后使用以下示例重新运行:
node clip.mjs --url "<URL>" \
--drop ".zergnet-widget, .nyp-video-player, aside.single__inline-module, [class*=taboola i]" \
--keep ".gallery, figure.hero" \
--out "press-clips/<outlet>-<slug>.pdf" --preview "press-clips/<outlet>-<slug>.png"
--drop 用于移除多余的选择器;--keep 用于保护可能被隔离操作或删除项移除的内容(如图库、引文、主图)。重复操作,直到预览干净。
4. 页面难以处理时,先在运行时进行调整;仅在必要时编写脚本
优先尝试运行时标志;无需分叉代码,它们就能应对大多数情况。--root 强制指定文章容器;--drop 移除网站特有的杂项;--keep 保护可能被删除项误删的内容。真实运行中出现过两种问题模式,脚本现在都能通用处理,但仍值得留意:
- 相关内容小组件抢占了根容器——对于没有
<article>的模板,一个标记为post-content的小型“更多内容”卡片,在简单的首个匹配策略下会胜过真正的正文。脚本现在会选择包含<h1>和大部分文本,且排除网站外围界面的最紧凑容器,从而自动解决此问题;如果顽固的模板仍然误选,--root是只需一个标志的解决方法。 - 被拦截的嵌入内容或广告留下空白框——在请求层拦截广告或推文网络后,页面仍会留下定尺寸但没有内容的
<div>/<blockquote>。脚本的空占位符清理会移除这些元素(并记录日志);现在通常不再需要用--drop清理空白区域。
有些页面仍需进一步处理——用户已经能完整阅读、但尚未加载的正文,标题遍历无法推断的章节边界,由精灵图引用构建的 SVG Logo,或由 JavaScript 延迟注入的内容。如果这些标志确实无法解决问题,就为该页面编写一个小型的、针对网站的 Playwright 脚本(或使用浏览器/电脑操作工具来驱动页面),并沿用 clip.mjs 的基本结构——goto、等待、页面处理、page.pdf(...)。捆绑的脚本只是起始框架,并非功能上限。针对特定网站的逻辑应放在运行时脚本中,绝不要放回已发布的工具中。
范围:整篇文章还是客户端所在章节
| 范围 | 使用场景 | 标志 |
|---|---|---|
| 整篇文章 | 文章与客户端有关,或篇幅较短 | (无额外标志) |
| 仅客户端所在章节 | 汇总文章中客户端是多个条目之一 | --section "<Heading>" |
对于较长的汇总文章,章节范围几乎总是客户端所需的范围。在公司外部分享剪辑内容时,这也是对页面改动更少的选择。
动画素材(用于 coverage-reel)
片段需要制作动画时,请添加三个标志:
| 标志 | 作用 |
|---|---|
--mention "<term>" | 客户端要查找的名称:公司、产品、拼写形式、发言人。可以重复填写,也可以用逗号分隔。匹配区分大小写,名称越长优先级越高。单个首字母大写且同时也是常用词的词(例如 "Nothing" 或 "Apple"),只有在它不是句子的第一个词或标题式标题中的词时才会计入。 |
--assets <dir> | 将截取的图像和 clip.json 辅助文件写入此文件夹。 |
--scale 2 或 3 | 截取图像的像素密度(默认为 2;缩放查看时至少需要 2)。coverage-reel 的手机格式会近距离放大句子,因此请使用 3。 |
--pick <n> | 当审阅者更喜欢另一句话时,截取排名第 n 的提及,而不是最佳提及。 |
文件夹中会有 top.png(带有标记的徽标、标题、署名和导语图片)、strip-1.png 及后续文件(从页面顶部到选定提及处的文章,分成远低于 Chrome 大小限制的图块)、mention-1.png(提及内容及一段上下文)和 masthead.png(透明背景上的徽标)。正文句子的排名高于标题、图片说明、链接、相关文章列表和导航;所有命中项都会列出,供审阅者选择其他内容。
clip.json 是机器交接文件(版本 1)。主要字段:
{
"version": 1,
"source_url": "https://example.com/article",
"final_url": "https://example.com/article",
"captured_at": "2026-10-10T14:00:00.000Z",
"tool": "press-clip/clip.mjs 2",
"scale": 2,
"scope": "whole",
"page": {
"outlet": { "value": "Example News", "source": "json-ld" },
"headline": { "value": "Headline as shown on the page", "source": "visible" },
"byline": { "value": "Jo Writer", "source": "json-ld" },
"published_at": { "value": "2026-05-18T06:00:00Z", "source": "og" }
},
"logo": { "resolved_from": "article page", "kind": "svg", "frame": "start", "plate_color": "rgb(5, 41, 98)", "file": "masthead.png", "rect": {}, "plate_rect": {} },
"assets": [{ "role": "top", "file": "top.png", "width": 1656, "height": 1990, "scale": 2, "rect": { "x": 16, "y": 0, "w": 828, "h": 995 } }],
"mentions": [{
"rank": 1, "chosen": true, "term": "Example Co", "sentence": "…", "clause": "…", "in_link": false, "score": 7,
"rects": { "term": [], "sentence": [], "clause": [], "paragraph": {} },
"asset_rects": [{ "file": "strip-1.png", "term": [], "sentence": [], "clause": [] }]
}],
"warnings": [{ "code": "paywall_suspected", "detail": "…" }]
}
页面详情只读取,不推断:每项都包含页面提供的 value(或 null)及其 source(json-ld、og、meta 或 visible)。作者邮箱会被删除。标题采用页面上显示的版本。发布日期照录页面所述,不会核实;使用前请与用户确认。矩形的位置以页面像素表示(rects),以每张图片自身的像素表示(asset_rects);换行的句子每行对应一个矩形。警告代码:paywall_suspected、no_mention_found、logo_text_fallback、layout_shifted(重新运行)、headline_mismatch、multiple_h1、section_not_found、pick_out_of_range、preview_downscaled、preview_truncated。
如果页面被拦截
有些网站会向自动化浏览器显示机器人检测页面(“Just a moment…”、空白的 403 或 503 页面)。脚本会检测到这种情况,不写入任何内容并以代码 3 退出。不要尝试绕过拦截——不要使用隐身技巧、伪造浏览器或存档镜像。请向用户索取文章的 PDF 或截图,并改用该文件。
渲染完成后
用通俗的语言告诉用户:媒体名称、标题和发布日期;客户端出现的位置;使用的范围;任何需要调整的内容或仍不完美之处(例如难以处理的广告、退回使用文字商标的徽标);以及保存文件的路径。如果预览中仍有无法妥善清除的杂项内容,请如实说明,不要暗示预览毫无瑕疵。
诚信与权利
- 绝不捏造日期、署名、引语、标题、覆盖面数据或任何文字。片段必须来自真实页面;不要擅自添加内容。内容缺失没有关系,如实说明即可。
- **不要改动报道内容或品牌标识。**你可以截取并清理页面,但不得更改记者的文字、替换媒体的身份标识,也不得编造页面中不存在的提及。
- **如果文章里实际上没有提到客户端,请停止并说明。**不要把相邻内容中的提及牵强地当作片段内容。
- 版权意识。片段通常可用于内部记录和客户报道,但广泛传播完整文章会受到版权限制。对外分享时,优先使用section范围,并始终保留媒体名称、徽标和原文链接,以确保署名和来源完整。
- 遵循
skills/ETHICS.md。
如果脚本无法运行
没有基于 Chromium 的浏览器或 Node?请如实采用备用方案:
- 打开文章,使用浏览器自带的 打印 → 另存为 PDF,并选择能去除广告的阅读或打印设置。这样无法单独提取某个版块,但能保留媒体网站的版面风格。
- 作为最后的手段,截取客户端版块的整页截图,以保留视觉证据和品牌标识,并告知用户这是截图,不是打印文件。
切勿悄悄退而使用纯文本重建——丢失徽标、字体和照片,就失去了剪辑的意义。