同一个站群里的15个内容页面,Hotjar的免费版每天只给35个会话额度、15个站分摊下来每个站一天只能录2个用户,Clarity完全免费不限会话但热力图的交互式筛选没Hotjar细,Crazy Egg的"糖果条纹"报告能把一个页面不同来源渠道的点击行为用不同颜色分层展示但起售价29美元一个月,三套工具组合下来,用Clarity做所有站的无限数据采集、用Hotjar在重点页面上深挖用户行为、再用heatmap.js自建一个后台把三个工具的数据拼到一张图里,一个15个站的站群点击图批量生成系统,代码加起来不超过300行
网站点击热力图不是大站的专利。管着一堆内容站的人其实更需要它:哪个页面用户进来就往下滑、哪个按钮摆了三天没人点过、哪个区域的点击集中在导航栏而正文区完全没人碰——这些信息比PV和跳出率直观得多。问题是多数热力图工具按单个站点收费,站群做不起。下面把免费、付费、自建三条路线拆开讲清楚。
四种需求速查
| 你的情况 | 推荐方案 | 月成本 | 核心优势 |
| 5个站以内,流量不大 | Microsoft Clarity | 免费 | 无限会话、热力图+录屏+仪表板 |
| 10-30个站,需深度分析 | Clarity + Hotjar组合 | 免费+$32起 | 全站铺Clarity,重点页上Hotjar |
| 有开发能力,要完全自控 | heatmap.js + 自建采集 | 服务器成本 | 数据私有、批量定制、无限站点 |
| 要批量导出截图/报告 | Puppeteer自动截图 + Canvas标注 | 免费 | 定时批量生成,推送到钉钉/飞书 |
一、七款热力图工具横向对比,先选对的再想怎么批量
| 工具 | 免费额度 | 付费起价 | 热力图类型 | 多站点管理 | 站群适配度 | 一句话评价 |
| Microsoft Clarity | 无限会话、无限站点 | 永久免费 | 点击+滚动+区域 | 一个账号多个项目 | ★★★★★ | 免费中的天花板,站群首选 |
| Hotjar | 35会话/天 | $32/月 | 点击+移动+滚动+区域 | 付费版支持多站点 | ★★★☆☆ | 分析深度最强但免费额度太低 |
| Crazy Egg | 30天免费试用 | $29/月 | 点击+滚动+糖果条纹+叠加 | 按站点计费 | ★★☆☆☆ | 糖果条纹独一份但站群做不起 |
| 百度统计热力图 | 完全免费 | 免费 | 点击热力图 | 一个账号多站点 | ★★★★☆ | 国内站首选,数据在境内 |
| Ptengine | 有免费版 | 联系销售 | 点击+注意力+阅读终点 | 支持 | ★★★☆☆ | 功能全但定价不透明 |
| ClickHeat | 开源免费 | 免费 | 点击热力图 | 需自行部署 | ★★★★☆ | PHP自部署,轻量但功能单一 |
| heatmap.js(自建) | 开源免费 | 免费 | 完全自定义 | 完全自主 | ★★★★★ | 自由度最高但需要开发能力 |
二、站群批量部署 Clarity,一个脚本给所有站装好追踪代码
Clarity 是目前站群热力图最实惠的入口:完全免费、不限会话数、不限站点数。每个站在 Clarity 后台创建一个项目,拿到对应的追踪代码,然后批量注入到所有页面里。如果用的是 CMS 或统一模板,改一次全局 footer 就行。如果是不同框架的站,用脚本批量操作。
批量注入 Clarity 代码的 Python 脚本
import osimport re# 站点配置:域名 -> Clarity项目IDSITES = {'site-a.com': 'clarity-project-id-aaa','site-b.com': 'clarity-project-id-bbb','site-c.com': 'clarity-project-id-ccc',}CLARITY_SNIPPET = '''<script type="text/javascript">(function(c,l,a,r,i,t,y){c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i;y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);})(window, document, "clarity", "script", "{project_id}");</script>'''HTML_DIR = '/var/www/sites' # 站群HTML文件根目录for domain, project_id in SITES.items():site_dir = os.path.join(HTML_DIR, domain)snippet = CLARITY_SNIPPET.replace('{project_id}', project_id)for root, dirs, files in os.walk(site_dir):for file in files:if file.endswith(('.html', '.htm', '.php')):filepath = os.path.join(root, file)with open(filepath, 'r', encoding='utf-8') as f:content = f.read()# 避免重复注入if 'clarity.ms/tag' in content:continue# 在 前插入追踪代码content = content.replace('', f'{snippet}\n')with open(filepath, 'w', encoding='utf-8') as f:f.write(content)print(f'[OK] {filepath}')print('All sites injected.')Clarity 批量部署三个注意点
- 每个站用独立项目ID:Clarity后台一个账号可以创建无限个项目,每个站单独一个项目,数据不会混在一起
- 热力图需要积累数据:刚装上不会立刻出图,一般需要几百到上千次页面浏览后热力图才比较有参考价值
- Clarity的弱项:热力图不能像Hotjar那样按"新用户/老用户""手机/PC"等维度自由筛选,更适合看整体趋势
三、Hotjar 做深度分析,只给重点页面开
Hotjar 免费版每天35个会话确实少,但它有一个Clarity没有的优势:热力图可以按设备类型、浏览器、来源渠道、用户行为(新/老访客)等维度精细化筛选。站群的做法是Clarity全站铺开看大盘,然后把转化率最高的3-5个重点页面在Hotjar里单独建热力图。
Clarity vs Hotjar 分工策略
| 数据层面 | 用 Clarity | 用 Hotjar |
| 覆盖站点数 | 所有站 | 重点站/重点页 |
| 数据量 | 无限会话 | 35会话/天(免费) |
| 筛选维度 | 基础筛选 | 设备/渠道/用户类型等 |
| 会话录屏 | 无限 | 有限 |
| 漏斗分析 | 无 | 有 |
四、自建方案:heatmap.js + 点击采集 + 批量生成
如果不想受制于第三方工具的限制——比如30个站都要看热力图、每天几万次点击需要完整保留、或者数据不想经过外部服务器——自建是最彻底的方案。核心流程分三步:采集点击坐标 → 存到后端 → 用heatmap.js渲染。
第一步:前端埋点,采集点击坐标
// click-tracker.js — 嵌入每个站点的全局脚本(function() {const SITE_ID = document.currentScript?.dataset?.siteId || 'default';const API = 'https://your-server.com/api/click';// 节流:移动端touch和click都会触发,只记一次let lastClick = 0;document.addEventListener('click', function(e) {const now = Date.now();if (now - lastClick < 300) return;lastClick = now;const data = {site: SITE_ID,page: location.pathname,x: e.pageX,y: e.pageY,w: window.innerWidth,h: document.documentElement.scrollHeight,vh: window.innerHeight,scrollY: window.scrollY,selector: getElementPath(e.target),ts: now,};// sendBeacon 不阻塞页面卸载navigator.sendBeacon(API, JSON.stringify(data));});function getElementPath(el) {if (!el || el === document.body) return 'body';let path = el.tagName.toLowerCase();if (el.id) path += '#' + el.id;if (el.className && typeof el.className === 'string') {path += '.' + el.className.split(' ').slice(0, 2).join('.');}return path;}})();第二步:后端接收并存储
# click-server.py — Flask 轻量后端from flask import Flask, request, jsonifyimport sqlite3import jsonimport osapp = Flask(__name__)DB = 'clicks.db'def init_db():conn = sqlite3.connect(DB)conn.execute('''CREATE TABLE IF NOT EXISTS clicks (id INTEGER PRIMARY KEY AUTOINCREMENT,site TEXT, page TEXT, x REAL, y REAL,width REAL, height REAL, vh REAL, scroll_y REAL,selector TEXT, ts INTEGER, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)''')conn.execute('CREATE INDEX IF NOT EXISTS idx_site_page ON clicks(site, page)')conn.commit()conn.close()init_db()@app.route('/api/click', methods=['POST'])def collect():data = request.get_json(force=True)conn = sqlite3.connect(DB)conn.execute('''INSERT INTO clicks (site, page, x, y, width, height, vh, scroll_y, selector, ts)VALUES (?,?,?,?,?,?,?,?,?,?)''',(data['site'], data['page'], data['x'], data['y'],data['w'], data['h'], data['vh'], data['scrollY'],data['selector'], data['ts']))conn.commit()conn.close()return jsonify({'status': 'ok'})# 查询某页面点击数据(用于heatmap.js渲染)@app.route('/api/clicks')def get_clicks():site = request.args.get('site', '')page = request.args.get('page', '')conn = sqlite3.connect(DB)rows = conn.execute('SELECT x, y, width, height, selector FROM clicks WHERE site=? AND page=?',(site, page)).fetchall()conn.close()return jsonify([{'x': r[0], 'y': r[1], 'w': r[2], 'h': r[3], 'sel': r[4]} for r in rows])if __name__ == '__main__':app.run(host='0.0.0.0', port=5000)第三步:后台管理面板,批量查看所有站的热力图
<!-- dashboard.html — 站群热力图统一面板 --><div id="site-selector"><select id="site-select"><option value="">选择站点</option><option value="site-a.com">站点A - 科技资讯</option><option value="site-b.com">站点B - 装修百科</option><option value="site-c.com">站点C - 美食分享</option></select><select id="page-select"><option value="">选择页面</option></select><button onclick="loadHeatmap()">生成热力图</button><button onclick="exportHeatmap()">导出截图</button></div><div id="heatmap-container" style="position:relative;width:100%;min-height:600px;"><div id="heatmap-overlay"></div></div><script src="https://cdn.jsdelivr.net/npm/heatmap.js@2.0.5/build/heatmap.min.js"></script><script>let heatmapInstance;async function loadHeatmap() {const site = document.getElementById('site-select').value;const page = document.getElementById('page-select').value;if (!site || !page) return alert('请选择站点和页面');const resp = await fetch(`/api/clicks?site=${site}&page=${page}`);const clicks = await resp.json();const container = document.getElementById('heatmap-overlay');container.innerHTML = '';container.style.width = '100%';container.style.height = '2000px';heatmapInstance = h337.create({container: container,radius: 25,maxOpacity: 0.6,minOpacity: 0,blur: 0.9,gradient: {'0': 'blue','0.25': 'cyan','0.5': 'lime','0.75': 'yellow','1.0': 'red'}});const points = clicks.map(c => ({x: Math.round(c.x),y: Math.round(c.y),value: 1}));heatmapInstance.setData({ max: Math.max(10, points.length / 5), data: points });}function exportHeatmap() {const canvas = document.querySelector('#heatmap-overlay canvas');if (!canvas) return alert('先生成热力图');const link = document.createElement('a');link.download = `heatmap_${Date.now()}.png`;link.href = canvas.toDataURL();link.click();}</script>自建方案三个关键优势
- 不限制站点数:所有站共用一套采集+存储+渲染系统,加一个新站只需给脚本里配一个新的 SITE_ID
- 数据完全私有:点击坐标存在你自己的 SQLite/MySQL 里,不会被第三方分析或用于训练
- 可以导出任意格式:Canvas 转 PNG 图片、原始 JSON 数据导出做二次分析、定时推送到飞书群,完全自定义
五、自动截图方案:Puppeteer 定时批量生成热力图报告
热力图做好了,如果每天都要手动打开后台挨个站点截图保存,就又回到了手工劳动。用 Puppeteer 可以定时打开热力图面板页面,自动截图保存到指定目录:
// batch-screenshot.js — 批量截取所有站的热力图const puppeteer = require('puppeteer');const fs = require('fs');const SITES = [{ name: 'site-a', pages: ['/', '/article/123', '/product/456'] },{ name: 'site-b', pages: ['/', '/blog/post-1', '/blog/post-2'] },{ name: 'site-c', pages: ['/', '/category/tech', '/about'] },];const DASHBOARD = 'http://your-server.com/dashboard.html';const OUTPUT = './heatmap-reports/';fs.mkdirSync(OUTPUT, { recursive: true });(async () => {const browser = await puppeteer.launch({ headless: 'new' });for (const site of SITES) {for (const page of site.pages) {const tab = await browser.newPage();await tab.setViewport({ width: 1440, height: 900 });// 打开面板并自动加载该站该页的热力图await tab.goto(`${DASHBOARD}?site=${site.name}&page=${encodeURIComponent(page)}`,{ waitUntil: 'networkidle0' });// 等待热力图渲染完成await tab.waitForSelector('#heatmap-overlay canvas', { timeout: 10000 });await new Promise(r => setTimeout(r, 2000));// 截图const filename = `${OUTPUT}${site.name}_${page.replace(/\//g, '_')}_${Date.now()}.png`;await tab.screenshot({ path: filename, fullPage: true });console.log(`[OK] ${filename}`);await tab.close();}}await browser.close();console.log('All screenshots done.');})();配合 crontab 每天凌晨跑一次,所有站的前一天热力图就自动生成好了。
# 每天凌晨2点自动生成前一天的站群热力图报告0 2 * * * cd /opt/heatmap && node batch-screenshot.js >> /var/log/heatmap.log 2>&1
六、三种方案的成本和适用规模
| 方案 | 站点上限 | 月成本 | 开发量 | 数据隐私 | 最适合谁 |
| 纯 Clarity | 无限制 | ¥0 | 粘贴JS代码 | 微软服务器 | 5-30个内容站,预算敏感 |
| Clarity + Hotjar | 30个(Hotjar付费) | ¥0 + $32/月 | 粘贴两段JS代码 | 第三方 | 需要深度分析重点页 |
| heatmap.js 自建 | 无限制 | 服务器成本 | 约300行代码 | 完全私有 | 有开发能力、数据敏感 |
| 百度统计热力图 | 无限制 | ¥0 | 粘贴JS代码 | 百度服务器 | 纯国内站群,百度生态 |
七、热力图怎么看,三个最直接的应用场景
场景一:找出"假热门区域"
有些页面PV很高,但热力图一看,点击全集中在顶部导航栏和侧边栏的"最新文章",正文区域几乎没点击。这说明流量是来了,但内容没留住人。这种页面优先优化正文的排版、加内链、加相关推荐卡片,把点击从导航区拉到内容区。

场景二:发现"没人点的按钮"
一个CTA按钮颜色调了几版、文案改了五遍,热力图显示三个月了几乎没人点。这时候不是按钮的问题,可能是按钮出现的位置用户还没产生点击意图。把按钮从文章顶部移到"用户读到这里正好想了解更多"的位置——比如一段痛点描述之后、或者对比表格下方,点击率可能翻几倍。

场景三:验证移动端和PC端的点击模式差异
PC上点击集中在页面中部的内容区,手机上同一页面的点击可能全在顶部的两三屏。如果移动端占比超过60%但页面布局还是按PC设计的,等于大半流量都浪费了。用Clarity或Hotjar按设备筛选热力图,一眼就能看到两端的差异有多大。
最后梳理一下
站群点击图批量生成这件事,选对工具组合比什么都重要:
- 预算零、不想写代码 → Clarity全站铺开 + 百度统计热力图,两个都免费,覆盖国内国外
- 预算有限但重点页面需要深挖 → Clarity做大盘监控 + Hotjar付费版只给3-5个转化页开精细热力图
- 有后端开发能力、数据不想过第三方 → heatmap.js + Flask/SQLite自建,300行代码搞定采集→存储→渲染→导出全链路
- 需要自动生成日报/周报 → Puppeteer定时截图 + cron定时任务,把热力图报告自动推送到飞书/钉钉群
说穿了,点击热力图不是拿来看的,是拿来改的。哪个区域点击多就强化哪个区域的内容密度,哪个按钮没人点就挪到更有意图触发的位置,哪个页面的点击分布和预期完全不一样就重新思考这个页面的信息架构。一套批量生成系统搭好之后,每天花五分钟扫一遍所有站的热力图截图,比盯着PV和跳出率数字猜用户行为,直观太多了。
