06 - HTML 报告
06 - HTML 报告
6.1 概述
GoAccess 生成的 HTML 报告是一个完全自包含的单文件,包含所有 CSS、JavaScript 和数据,无需外部依赖即可在任何现代浏览器中打开。这使得报告的分享和部署极为简便。
HTML 报告的核心特性:
- 📦 自包含:所有资源内嵌在一个 HTML 文件中
- 📊 交互式:支持排序、分页、搜索、面板切换
- 📱 响应式:适配桌面和移动设备
- 🎨 可定制:支持主题、配色、布局调整
- 📤 可分享:通过文件、链接或嵌入方式分发
6.2 生成基本报告
# 最基本的报告生成
goaccess /var/log/nginx/access.log \
--log-format=COMBINED \
-o report.html
# 带标题的报告
goaccess /var/log/nginx/access.log \
--log-format=COMBINED \
-o report.html \
--html-title="网站访问分析报告"
# 指定报告标题
goaccess /var/log/nginx/access.log \
--log-format=COMBINED \
-o report.html \
--html-title="2026年5月报告" \
--html-report-title="详细统计"
6.3 自定义样式与主题
6.3.1 使用内置主题
GoAccess 提供多种内置主题,通过 --html-prefs 参数设置:
# Bright 主题(亮色)
goaccess access.log --log-format=COMBINED -o report.html \
--html-prefs='{"theme":"bright"}'
# Dark 主题(暗色)
goaccess access.log --log-format=COMBINED -o report.html \
--html-prefs='{"theme":"darkGreen"}'
# Mono Green
goaccess access.log --log-format=COMBINED -o report.html \
--html-prefs='{"theme":"monoGreen"}'
# Mono Blue
goaccess access.log --log-format=COMBINED -o report.html \
--html-prefs='{"theme":"monoBlue"}'
# Mono Pink
goaccess access.log --log-format=COMBINED -o report.html \
--html-prefs='{"theme":"monoPink"}'
# Monokai(暗色经典)
goaccess access.log --log-format=COMBINED -o report.html \
--html-prefs='{"theme":"monokai"}'
# Dracula
goaccess access.log --log-format=COMBINED -o report.html \
--html-prefs='{"theme":"dracula"}'
# Classic(经典)
goaccess access.log --log-format=COMBINED -o report.html \
--html-prefs='{"theme":"classic"}'
6.3.2 主题效果对比
| 主题 | 背景色 | 适用场景 |
|---|---|---|
bright | 白色 | 打印、正式报告 |
darkGreen | 深绿 | 长时间阅读、暗色环境 |
monoGreen | 黑底绿字 | 极客风格、终端爱好者 |
monoBlue | 黑底蓝字 | 专业风格 |
monoPink | 黑底粉字 | 个性化 |
monokai | 深灰 | 编辑器用户熟悉 |
dracula | 深紫 | 暗色主题爱好者 |
classic | 浅灰 | 传统风格 |
6.3.3 完整 HTML 偏好设置
{
"theme": "bright",
"perPage": 20,
"layout": "horizontal",
"showIndianMap": false,
"visitors": {
"sortBy": "hits",
"sortOrder": "desc"
},
"requests": {
"sortBy": "hits",
"sortOrder": "desc"
},
"status_codes": {
"sortBy": "hits",
"sortOrder": "desc"
}
}
goaccess access.log --log-format=COMBINED -o report.html \
--html-prefs='{"theme":"darkGreen","perPage":50,"layout":"horizontal"}'
6.3.4 自定义 CSS 样式
由于 HTML 报告是自包含的,你可以在生成后注入自定义 CSS:
#!/bin/bash
# generate_report.sh — 生成带自定义样式的报告
# 生成报告
goaccess /var/log/nginx/access.log \
--log-format=COMBINED \
-o /tmp/goaccess_raw.html
# 注入自定义 CSS
cat > /tmp/custom_style.css << 'CSS'
/* 自定义样式覆盖 */
body {
font-family: 'Noto Sans SC', 'Microsoft YaHei', sans-serif;
}
#dashboard .grid .item {
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
table th {
background-color: #2c3e50 !important;
color: white;
}
CSS
# 合并 CSS 到 HTML
sed -i '/<\/style>/i\
/* Custom CSS */' /tmp/goaccess_raw.html
sed -i "/<\/style>/r /tmp/custom_style.css" /tmp/goaccess_raw.html
# 添加中文字体支持
sed -i 's|<head>|<head><link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;700\&display=swap" rel="stylesheet">|' /tmp/goaccess_raw.html
mv /tmp/goaccess_raw.html /var/www/html/report.html
echo "报告已生成: /var/www/html/report.html"
6.4 图表配置
GoAccess 的 HTML 报告包含多种交互式图表:
6.4.1 内置图表类型
| 图表类型 | 展示内容 | 面板 |
|---|---|---|
| 面积图 (Area Chart) | 访问量随时间变化趋势 | 通用仪表盘 |
| 折线图 (Line Chart) | 访客数、带宽趋势 | 通用仪表盘 |
| 水平柱状图 (Bar Chart) | 文件排行、IP 排行 | 请求文件、主机面板 |
| 饼图 (Pie Chart) | 状态码分布、浏览器分布 | 状态码、浏览器面板 |
| 数据表格 (Table) | 详细数据列表 | 所有面板 |
6.4.2 图表交互
HTML 报告中的图表支持以下交互操作:
- 鼠标悬停:显示详细数据
- 点击图例:切换显示/隐藏数据系列
- 分页浏览:大数据集分页显示
- 排序:点击表头排序
- 搜索:在表格中搜索关键词
6.5 报告自动化生成
6.5.1 使用 Cron 定时生成
# 编辑 crontab
crontab -e
# 每天凌晨 1 点生成前一天的日报
0 1 * * * /usr/local/bin/goaccess /var/log/nginx/access.log --log-format=COMBINED -o /var/www/html/daily/$(date +\%Y-\%m-\%d).html --process-and-exit 2>&1
# 每周一凌晨 2 点生成周报(合并上周日志)
0 2 * * 1 /usr/local/bin/cat /var/log/nginx/access.log.1 | /usr/local/bin/goaccess --log-format=COMBINED -o /var/www/html/weekly/$(date +\%Y-\%W).html --process-and-exit 2>&1
# 每月 1 日凌晨 3 点生成月报
0 3 1 * * /usr/local/bin/zcat /var/log/nginx/access.log.*.gz | /usr/local/bin/goaccess --log-format=COMBINED -o /var/www/html/monthly/$(date +\%Y-\%m).html --process-and-exit 2>&1
6.5.2 完整的自动化脚本
#!/bin/bash
# auto_report.sh — GoAccess 自动化报告生成脚本
set -euo pipefail
# ============ 配置 ============
LOG_DIR="/var/log/nginx"
REPORT_DIR="/var/www/html/stats"
GOACCESS="/usr/local/bin/goaccess"
LOG_FORMAT="COMBINED"
DATE=$(date +%Y-%m-%d)
YESTERDAY=$(date -d "yesterday" +%Y-%m-%d)
# ============ 创建目录 ============
mkdir -p "${REPORT_DIR}/daily"
mkdir -p "${REPORT_DIR}/weekly"
mkdir -p "${REPORT_DIR}/monthly"
# ============ 生成日报 ============
echo "[${DATE}] 生成日报..."
# 过滤昨天的日志
awk -v date="$(date -d 'yesterday' +'%d/%b/%Y')" '$0 ~ date' \
"${LOG_DIR}/access.log" | \
${GOACCESS} --log-format="${LOG_FORMAT}" \
-o "${REPORT_DIR}/daily/${YESTERDAY}.html" \
--html-title="日报 - ${YESTERDAY}" \
--process-and-exit - 2>/dev/null
echo "[${DATE}] 日报已生成: ${REPORT_DIR}/daily/${YESTERDAY}.html"
# ============ 生成周报(每周一) ============
DOW=$(date +%u) # 1=Monday, 7=Sunday
if [ "${DOW}" = "1" ]; then
WEEK=$(date -d "last week" +%Y-W%V)
echo "[${DATE}] 生成周报 ${WEEK}..."
cat "${LOG_DIR}/access.log".1 "${LOG_DIR}/access.log" | \
${GOACCESS} --log-format="${LOG_FORMAT}" \
-o "${REPORT_DIR}/weekly/${WEEK}.html" \
--html-title="周报 - ${WEEK}" \
--process-and-exit - 2>/dev/null
echo "[${DATE}] 周报已生成: ${REPORT_DIR}/weekly/${WEEK}.html"
fi
# ============ 生成月报(每月 1 日) ============
DOM=$(date +%d)
if [ "${DOM}" = "01" ]; then
MONTH=$(date -d "last month" +%Y-%m)
echo "[${DATE}] 生成月报 ${MONTH}..."
zcat "${LOG_DIR}/access.log"*.gz | \
${GOACCESS} --log-format="${LOG_FORMAT}" \
-o "${REPORT_DIR}/monthly/${MONTH}.html" \
--html-title="月报 - ${MONTH}" \
--process-and-exit - 2>/dev/null
echo "[${DATE}] 月报已生成: ${REPORT_DIR}/monthly/${MONTH}.html"
fi
# ============ 清理旧报告 ============
# 保留最近 90 天的日报
find "${REPORT_DIR}/daily" -name "*.html" -mtime +90 -delete 2>/dev/null
echo "[${DATE}] 报告生成完成"
6.5.3 使用 systemd Timer 定时执行
# /etc/systemd/system/goaccess-report.service
[Unit]
Description=GoAccess Daily Report Generator
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/auto_report.sh
User=www-data
Group=www-data
# /etc/systemd/system/goaccess-report.timer
[Unit]
Description=Run GoAccess report daily
[Timer]
OnCalendar=*-*-* 01:00:00
Persistent=true
RandomizedDelaySec=300
[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now goaccess-report.timer
# 查看定时器状态
sudo systemctl list-timers goaccess-report.timer
6.6 报告分享策略
6.6.1 通过 Web 服务器分享
# Nginx 配置 — 统计报告站点
server {
listen 443 ssl;
server_name stats.example.com;
ssl_certificate /etc/ssl/certs/stats.pem;
ssl_certificate_key /etc/ssl/private/stats.key;
root /var/www/html/stats;
index index.html;
# Basic Auth 保护
auth_basic "Statistics";
auth_basic_user_file /etc/nginx/.htpasswd;
# 目录浏览
autoindex on;
autoindex_exact_size off;
autoindex_localtime on;
}
# 创建 htpasswd 文件
sudo htpasswd -c /etc/nginx/.htpasswd admin
6.6.2 通过邮件发送报告
#!/bin/bash
# email_report.sh — 通过邮件发送 GoAccess 报告
REPORT_PATH="/var/www/html/stats/daily/$(date -d yesterday +%Y-%m-%d).html"
RECIPIENT="[email protected]"
SUBJECT="GoAccess 日报 - $(date -d yesterday +%Y-%m-%d)"
if [ -f "${REPORT_PATH}" ]; then
mail -s "${SUBJECT}" \
-a "Content-Type: text/html; charset=UTF-8" \
"${RECIPIENT}" < "${REPORT_PATH}"
echo "报告已发送至 ${RECIPIENT}"
else
echo "报告文件不存在: ${REPORT_PATH}"
exit 1
fi
6.6.3 嵌入到现有页面
<!-- 将 GoAccess 报告嵌入 iframe -->
<iframe
src="/stats/daily/2026-05-09.html"
width="100%"
height="800px"
frameborder="0"
style="border: 1px solid #ddd; border-radius: 4px;">
</iframe>
6.6.4 批量导出历史报告
#!/bin/bash
# batch_export.sh — 批量导出历史日志为 HTML 报告
LOG_DIR="/var/log/nginx"
OUTPUT_DIR="/var/www/html/stats/archive"
mkdir -p "${OUTPUT_DIR}"
# 处理所有压缩的日志文件
for logfile in "${LOG_DIR}"/access.log.*.gz; do
filename=$(basename "${logfile}" .gz)
date_part=$(echo "${filename}" | grep -oP '\d{8}' || echo "unknown")
output="${OUTPUT_DIR}/${date_part}.html"
if [ ! -f "${output}" ]; then
echo "处理: ${logfile} → ${output}"
zcat "${logfile}" | \
goaccess --log-format=COMBINED \
-o "${output}" \
--html-title="历史报告 - ${date_part}" \
--process-and-exit - 2>/dev/null
else
echo "跳过: ${output} 已存在"
fi
done
echo "批量导出完成"
6.7 生成 JSON/CSV 报告
6.7.1 JSON 报告
# 生成 JSON 格式报告
goaccess /var/log/nginx/access.log \
--log-format=COMBINED \
-o report.json
# 输出到标准输出(便于管道处理)
goaccess /var/log/nginx/access.log \
--log-format=COMBINED \
-o - \
--no-global-config | jq '.general'
6.7.2 CSV 报告
# 生成 CSV 报告
goaccess /var/log/nginx/access.log \
--log-format=COMBINED \
-o report.csv
# CSV 报告包含所有面板的数据,用注释分隔各面板
6.7.3 多格式同时输出
# 同时生成 HTML 和 JSON(使用两次调用)
goaccess access.log --log-format=COMBINED -o report.html
goaccess access.log --log-format=COMBINED -o report.json
# 使用持久化避免重复解析
goaccess access.log --log-format=COMBINED -o /tmp/db --persist
goaccess /tmp/db --restore -o report.html
goaccess /tmp/db --restore -o report.json
goaccess /tmp/db --restore -o report.csv
6.8 报告索引页
当有多个报告文件时,可以生成一个索引页方便浏览:
#!/bin/bash
# generate_index.sh — 生成报告索引页
REPORT_DIR="/var/www/html/stats"
INDEX="${REPORT_DIR}/index.html"
cat > "${INDEX}" << 'HTML'
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>网站访问统计报告</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Noto Sans SC', sans-serif; background: #f5f5f5; padding: 20px; }
.container { max-width: 800px; margin: 0 auto; }
h1 { color: #2c3e50; margin-bottom: 30px; text-align: center; }
.section { background: white; border-radius: 8px; padding: 20px; margin-bottom: 20px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.section h2 { color: #34495e; margin-bottom: 15px; border-bottom: 2px solid #3498db; padding-bottom: 10px; }
.report-list { list-style: none; }
.report-list li { padding: 8px 0; border-bottom: 1px solid #eee; }
.report-list li:last-child { border-bottom: none; }
.report-list a { color: #3498db; text-decoration: none; font-size: 15px; }
.report-list a:hover { text-decoration: underline; }
.report-list .date { color: #7f8c8d; font-size: 13px; margin-left: 10px; }
</style>
</head>
<body>
<div class="container">
<h1>📊 网站访问统计报告</h1>
<div class="section">
<h2>📋 月报</h2>
<ul class="report-list" id="monthly">
<!-- 由脚本动态生成 -->
</ul>
</div>
<div class="section">
<h2>📅 周报</h2>
<ul class="report-list" id="weekly">
<!-- 由脚本动态生成 -->
</ul>
</div>
<div class="section">
<h2>📆 日报</h2>
<ul class="report-list" id="daily">
<!-- 由脚本动态生成 -->
</ul>
</div>
</div>
</body>
</html>
HTML
# 生成日报链接
echo '<script>' >> "${INDEX}"
echo 'const daily = [' >> "${INDEX}"
ls -r "${REPORT_DIR}/daily/"*.html 2>/dev/null | while read f; do
name=$(basename "${f}" .html)
echo " { name: '${name}', path: 'daily/${name}.html' }," >> "${INDEX}"
done
echo '];' >> "${INDEX}"
echo 'const weekly = [' >> "${INDEX}"
ls -r "${REPORT_DIR}/weekly/"*.html 2>/dev/null | while read f; do
name=$(basename "${f}" .html)
echo " { name: '${name}', path: 'weekly/${name}.html' }," >> "${INDEX}"
done
echo '];' >> "${INDEX}"
echo 'const monthly = [' >> "${INDEX}"
ls -r "${REPORT_DIR}/monthly/"*.html 2>/dev/null | while read f; do
name=$(basename "${f}" .html)
echo " { name: '${name}', path: 'monthly/${name}.html' }," >> "${INDEX}"
done
echo '];' >> "${INDEX}"
cat >> "${INDEX}" << 'JS'
function render(list, containerId) {
const ul = document.getElementById(containerId);
if (!list.length) {
ul.innerHTML = '<li style="color:#999">暂无数据</li>';
return;
}
ul.innerHTML = list.map(item =>
`<li><a href="${item.path}">${item.name}</a></li>`
).join('');
}
render(daily, 'daily');
render(weekly, 'weekly');
render(monthly, 'monthly');
</script>
</html>
JS
echo "索引页已生成: ${INDEX}"
6.9 报告数据导出与二次分析
6.9.1 导出为 JSON 后用 Python 分析
# 导出 JSON
goaccess /var/log/nginx/access.log --log-format=COMBINED -o report.json
# 用 Python 分析
python3 << 'PYEOF'
import json
with open('report.json', 'r') as f:
data = json.load(f)
# 打印概览
g = data['general']
print(f"总请求数: {g['total_requests']}")
print(f"独立访客: {g['unique_visitors']}")
print(f"带宽消耗: {g['bandwidth'] / 1024 / 1024:.1f} MB")
# Top 10 请求文件
print("\nTop 10 请求文件:")
for item in data['requests']['data'][:10]:
print(f" {item['data']}: {item['hits']['count']} 次")
# 状态码分布
print("\n状态码分布:")
for item in data['status_codes']['data']:
print(f" {item['data']}: {item['hits']['count']} ({item['hits']['percent']}%)")
PYEOF
6.9.2 导入到数据库
# 将 JSON 数据导入 SQLite
goaccess /var/log/nginx/access.log --log-format=COMBINED -o report.json
python3 << 'PYEOF'
import json
import sqlite3
with open('report.json', 'r') as f:
data = json.load(f)
conn = sqlite3.connect('goaccess.db')
c = conn.cursor()
# 创建表
c.execute('''CREATE TABLE IF NOT EXISTS visitors (
ip TEXT, hits INTEGER, visitors INTEGER,
hit_pct REAL, visitor_pct REAL
)''')
c.execute('''CREATE TABLE IF NOT EXISTS requests (
file TEXT, hits INTEGER, visitors INTEGER,
hit_pct REAL, visitor_pct REAL, bandwidth TEXT
)''')
c.execute('''CREATE TABLE IF NOT EXISTS status_codes (
code TEXT, hits INTEGER, percent REAL
)''')
# 插入访客数据
for item in data['visitors']['data']:
c.execute('INSERT INTO visitors VALUES (?,?,?,?,?)',
(item['data'], item['hits']['count'],
item['visitors']['count'],
item['hits']['percent'],
item['visitors']['percent']))
# 插入请求数据
for item in data['requests']['data']:
c.execute('INSERT INTO requests VALUES (?,?,?,?,?,?)',
(item['data'], item['hits']['count'],
item['visitors']['count'],
item['hits']['percent'],
item['visitors']['percent'],
item.get('bandwidth', {}).get('count', '0')))
# 插入状态码
for item in data['status_codes']['data']:
c.execute('INSERT INTO status_codes VALUES (?,?,?)',
(item['data'], item['hits']['count'],
item['hits']['percent']))
conn.commit()
conn.close()
print("数据已导入 SQLite: goaccess.db")
PYEOF
6.10 小结
| 功能 | 命令/方法 |
|---|---|
| 基本报告 | -o report.html |
| 自定义主题 | --html-prefs='{"theme":"bright"}' |
| 自定义标题 | --html-title="标题" |
| 定时生成 | Cron / systemd timer |
| 安全分享 | Nginx + Basic Auth |
| 批量导出 | 遍历日志文件脚本 |
| 二次分析 | JSON 导出 + Python/jq |
下一章
下一章将详细介绍 GoAccess 的过滤与排除功能,包括日期范围过滤、状态码过滤、IP 排除和请求模式排除。