HTML怎么防止用户复制?

0 阅读6分钟

先告诉你结论:防不住,只能添堵

前端拦不住复制,能做的只是把复制这件事变得更麻烦。麻烦到什么程度取决于你愿意牺牲多少用户体验,以及对方有多想拿到你的内容。

我把市面上能见到的防复制手段做成了一个实验页面,每种技术都是一个开关,你可以亲手打开,然后亲手绕过:

防复制实验室

下面把这些手段从最弱到最强讲一遍,每一种都说清楚怎么写、能拦住谁、怎么破。

添加图片注释,不超过 140 字(可选)


先理解一件事:能看见就能拿到

防复制的根本困境在这里。

浏览器要把内容显示给用户看,就必须把它渲染成屏幕上的像素。用户能看见,就能截图;截图能 OCR,就能转成文字。这条路你堵不住,除非你根本不让用户看见内容——那防复制也就失去意义了。

所以前端防复制要解决的从来都是「拿到要花多大力气」的问题,「能不能拿到」这件事早就有答案了。理解了这一点,下面所有技术你就知道该怎么评价了。


第一档:防君子的 CSS

添加图片注释,不超过 140 字(可选)

user-select: none

最常见的一招。一行 CSS,禁止用户选中文字:

.protected {
  user-select: none;
  -webkit-user-select: none;
}

选不中,自然就 Ctrl+C 不了。

怎么破?打开 F12,在 Elements 面板里把这条规则删掉,或者在 Console 里敲一句:

document.querySelector('.protected').style.userSelect = 'text';

选中恢复,三秒钟的事。

禁用右键菜单

添加图片注释,不超过 140 字(可选)

document.addEventListener('contextmenu', e => e.preventDefault());

用户右键弹不出「复制」菜单。这招的副作用比效果大——用户想用右键翻译、右键查词都不行了,体验直接劝退。

破解更简单,浏览器设置里有「允许网站阻止右键」的开关,关掉就行。

这两招的共同特点:拦得住完全不懂技术的人,懂一点的人当场破。我在实验室里给它们的「绕过难度」都标了 1,意思是小白十秒钟搞定。


第二档:JavaScript 拦事件

拦截 copy 事件

添加图片注释,不超过 140 字(可选)

document.addEventListener('copy', e => {
  e.preventDefault();
});

用户按 Ctrl+C,复制操作被取消,剪贴板里什么都没有。

比 CSS 那招稍微难破一点,但也就稍微。在 Console 里抢先注册一个捕获阶段的监听器,把事件拦截掉就行:

document.addEventListener('copy', e => e.stopImmediatePropagation(), true);

透明遮罩层

在文字上面盖一层透明的 div,用户的鼠标点到的是遮罩,选不中底下的文字:

.overlay {
  position: absolute;
  top: 0; left: 0;
  width: 100%; height: 100%;
  z-index: 999;
}

破法和前面一样,F12 删掉遮罩元素,或者把它的 pointer-events 设成 none。


第三档:换个思路,允许复制但加料

前面那些硬拦的招数,最大的问题是误伤正常用户。有人就想引用你一句话,结果发现连选中都不行,体验极差。

于是主流网站换了个思路:不拦复制,但在你复制的内容里偷偷加东西。

剪贴板内容替换

document.addEventListener('copy', e => {
  e.preventDefault();
  const text = window.getSelection().toString();
  e.clipboardData.setData('text/plain',
    text + '\n\n——来源:xxx,禁止转载');
});

你复制一段话,粘贴出来发现后面多了一行来源声明和原文链接。

知乎、CSDN、纽约时报用的都是这套。它的聪明之处在于:不影响正常阅读和引用,但内容被搬走时能留下出处。从「禁止复制」变成「复制可以,但要带上我的署名」,对内容方反而更有价值。

这招也能破——在 DevTools 里选中文字,右键「Store as global variable」直接拿到原始 DOM 文本,绕过 copy 事件。但对绝大多数转载者来说,那行来源声明已经达到目的了。


第四档:让内容根本不是「文本」

到这一档,思路变了:既然 DOM 里的文本能被抠出来,那就别让内容以文本形式存在。

Canvas 渲染

把文字用 fillText 画到 Canvas 画布上:

const ctx = canvas.getContext('2d');
ctx.font = '16px sans-serif';
ctx.fillText('机密内容', 10, 30);

用户看到的是图片,DOM 里没有任何文本节点,常规爬虫抓下来是一片空白。

代价很大:内容不可被搜索引擎索引(SEO 归零)、屏幕阅读器读不了(无障碍崩溃)、渲染性能差。百度文库的付费文档预览走的就是这条路。

破法:截图加 OCR,或者直接 hook Canvas 的 fillText 调用拿到参数。

字体映射混淆

这是前端能做到的最强招数。自定义一个字体文件,把字符的编码映射打乱——代码里写的是一套编码,字体显示出来是另一套字。

@font-face {
  font-family: "Obfuscated";
  src: url("shuffled.woff2");
}
/* HTML 里写的是 "xk#2m",屏幕上显示的是 "hello" */

页面上看着是正常文字,你复制下来的却是 xk#2m 这样的乱码,因为剪贴板拿到的是真实编码,不是字体渲染后的字形。

起点中文网的付费章节用的就是这招,每个用户、每个章节的字体映射还可能不一样。

破法:分析字体文件的 cmap 表反推出映射关系,或者老办法——OCR。在实验室里我给它的绕过难度标了 5,意思是需要专门的工具和技术,但依然不是不可能。


我把这些招做成了攻防模拟

光说「能破」太抽象,所以我在实验室里加了个攻防模拟:

防复制实验室 - 攻防模拟

添加图片注释,不超过 140 字(可选)

你先在实验台上挑几种防护组合起来,然后选一个攻击者水平——从「只会 Ctrl+C 的普通用户」到「带 OCR 和自动化工具的专业爬虫」——看看每道防线能撑多久。

结论很一致:

  • 普通用户:CSS 那两招就能把他挡在门外
  • 前端开发者:F12 一开,第一、二档全部失效
  • 专业爬虫:配合 OCR,连字体混淆和 Canvas 都能拿下,只是慢一点

这正好对应一个朴素的道理:防护真正的价值在于把绕过成本抬高到攻击者觉得不值得,绝对拦住本来就做不到。对付随手复制的访客和对付铁了心要扒站的爬虫,需要的防护强度完全不同。

说明:实验室里每种技术的「绕过耗时」是基于难度等级和攻击者水平用公式模拟出来的相对数值,用来直观对比强弱,不代表真实世界的精确秒数。


参考来源

  1. MDN Web Docs, “user-select” CSS property developer.mozilla.org/en-US/docs/…
  2. MDN Web Docs, “Element: copy event” developer.mozilla.org/en-US/docs/…
  3. MDN Web Docs, “ClipboardEvent.clipboardData” developer.mozilla.org/en-US/docs/…
  4. W3C, “Clipboard API and events” www.w3.org/TR/clipboar…