本文目录导读:

- 核心检查清单(脚本需要验证的内容)
- 使用 Bash 脚本(适合简单检查)
- 使用 Node.js 脚本(更健壮,适合复杂逻辑)
- 使用 Python 脚本(适合 Python 项目)
- 如何部署钩子脚本到团队?
- 高级建议
Git 钩子脚本主要用于在代码提交前或推送前自动化执行检查,要检查 SEO 优化,通常需要结合 HTML 解析工具和正则表达式来扫描修改或新增的 HTML/模板文件。
以下是几种常见场景的实现思路和核心检查逻辑:
核心检查清单(脚本需要验证的内容)
在编写脚本前,明确需要检查哪些 SEO 要素:
- Title 标签:是否存在?长度是否合理(50-60 字符)?
- Meta Description:是否存在?长度是否合理(150-160 字符)?
- H1 标签:是否唯一(一个页面只有一个 H1)?
- Alt 属性:图片
<img>标签是否都有alt属性? - Canonical 标签:是否指定了
rel="canonical"? - Robots 元标签:是否配置了
index或noindex? - 死链/相对路径:
<a>标签的href是否为javascript:void(0)或空? - Open Graph 标签:
og:title、og:description、og:image是否存在?
使用 Bash 脚本(适合简单检查)
在 .git/hooks/pre-commit 中(注意:git 钩子脚本默认不提交到仓库,需手动部署):
#!/bin/bash
# 检查暂存区中新增或修改的 .html 文件
CHANGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep '\.html$')
if [ -z "$CHANGED_FILES" ]; then
exit 0
fi
ISSUES_FOUND=0
for FILE in $CHANGED_FILES; do
if [ ! -f "$FILE" ]; then
continue
fi
echo "检查 SEO: $FILE"
# 1. 检查 Title 标签
if ! grep -q '<title>' "$FILE"; then
echo " ⚠️ 错误: 缺少 <title> 标签"
ISSUES_FOUND=$((ISSUES_FOUND + 1))
fi
# 2. 检查 Meta Description
if ! grep -q 'name="description"' "$FILE"; then
echo " ⚠️ 错误: 缺少 meta description"
ISSUES_FOUND=$((ISSUES_FOUND + 1))
fi
# 3. 检查 H1 标签数量(应唯一)
H1_COUNT=$(grep -c '<h1[^>]*>' "$FILE")
if [ "$H1_COUNT" -eq 0 ]; then
echo " ⚠️ 错误: 缺少 <h1> 标签"
ISSUES_FOUND=$((ISSUES_FOUND + 1))
elif [ "$H1_COUNT" -gt 1 ]; then
echo " ⚠️ 错误: 存在多个 <h1> 标签 (共 $H1_COUNT 个)"
ISSUES_FOUND=$((ISSUES_FOUND + 1))
fi
# 4. 检查图片 Alt 属性
IMGS_WITHOUT_ALT=$(grep -oP '<img[^>]*>' "$FILE" | grep -vP 'alt="[^"]*"' | wc -l)
if [ "$IMGS_WITHOUT_ALT" -gt 0 ]; then
echo " ⚠️ 错误: 有 $IMGS_WITHOUT_ALT 个 <img> 缺少 alt 属性"
ISSUES_FOUND=$((ISSUES_FOUND + 1))
fi
# ... 其他检查类似:canonical、og:tags
done
if [ "$ISSUES_FOUND" -gt 0 ]; then
echo ""
echo "❌ SEO 检查发现 $ISSUES_FOUND 个问题,请修正后重新提交。"
exit 1
fi
exit 0
使用 Node.js 脚本(更健壮,适合复杂逻辑)
如果项目使用 Node.js,可以编写 pre-commit.js 脚本,利用 cheerio 解析 DOM(比正则更准确)。
脚本示例 (pre-seo-check.js):
const { execSync } = require('child_process');
const fs = require('fs');
const cheerio = require('cheerio'); // npm install cheerio
// 获取暂存区中修改的 HTML 文件
const output = execSync('git diff --cached --name-only --diff-filter=ACM', { encoding: 'utf8' });
const files = output.split('\n').filter(f => f.endsWith('.html'));
let errors = [];
files.forEach(file => {
if (!fs.existsSync(file)) return;
const html = fs.readFileSync(file, 'utf8');
const $ = cheerio.load(html);
// Title 检查
const title = $('title').text().trim();
if (!title) {
errors.push(`${file}: 缺少 Title 标签`);
} else if (title.length > 60) {
errors.push(`${file}: Title 长度 ${title.length} 字符,建议 ≤60`);
}
// Meta description 检查
const desc = $('meta[name="description"]').attr('content');
if (!desc) {
errors.push(`${file}: 缺少 Meta Description`);
} else if (desc.length > 160) {
errors.push(`${file}: Description 长度 ${desc.length} 字符,建议 ≤160`);
}
// H1 检查
const h1Count = $('h1').length;
if (h1Count === 0) {
errors.push(`${file}: 缺少 H1 标签`);
} else if (h1Count > 1) {
errors.push(`${file}: 存在 ${h1Count} 个 H1 标签,应唯一`);
}
// 图片 Alt 检查
$('img').each((i, el) => {
if (!$(el).attr('alt')) {
errors.push(`${file}: 图片缺少 alt 属性 (src: ${$(el).attr('src')})`);
}
});
// Canonical 检查
const canonical = $('link[rel="canonical"]').attr('href');
if (!canonical) {
errors.push(`${file}: 缺少 canonical 标签`);
}
// Open Graph 基本检查
if (!$('meta[property="og:title"]').length) {
errors.push(`${file}: 缺少 og:title 标签`);
}
});
if (errors.length > 0) {
console.error('❌ SEO 检查失败:\n' + errors.join('\n'));
process.exit(1);
} else {
console.log('✅ SEO 检查通过');
}
使用 Python 脚本(适合 Python 项目)
利用 beautifulsoup4 库:
import subprocess
import sys
from bs4 import BeautifulSoup
# 获取暂存区文件
result = subprocess.run(
['git', 'diff', '--cached', '--name-only', '--diff-filter=ACM'],
capture_output=True, text=True
)
files = [f for f in result.stdout.split('\n') if f.endswith('.html')]
has_error = False
for file in files:
with open(file, 'r', encoding='utf-8') as f:
soup = BeautifulSoup(f.read(), 'html.parser')
# 检查逻辑与 Node.js 类似
if not soup.find('title') or not soup.find('title').get_text(strip=True):
print(f"❌ {file}: 缺少 Title")
has_error = True
# ... 其他检查
if has_error:
sys.exit(1)
如何部署钩子脚本到团队?
由于 .git/hooks/ 目录下的脚本不会被 Git 跟踪,需要手动同步到团队成员的仓库。
推荐两种方式:
- 存储在项目目录:将脚本放在项目根目录的
scripts/git-hooks/文件夹中,然后在package.json中使用husky或pre-commit库自动安装。 - 结合 CI/CD:使用 pre-push 钩子在推送到远程仓库前触发检查,或者在 CI 流水线(GitHub Actions, GitLab CI)中直接运行,这比本地钩子更可靠,因为不会被开发者绕过。
高级建议
- 增量检查:只检查本次修改的行,而不是整个文件(避免旧代码反复报错),使用
git diff --cached --unified=0获取具体变更行。 - 性能考虑:如果项目中 HTML 文件非常多,钩子会变得很慢,可以限制只检查
.html或.ejs/.pug等模板文件。 - 配置化:允许在
seo-config.json中自定义阈值(title 长度上限、是否强制需要 og 标签等)。
Git 钩子能有效在代码入库前拦截明显的 SEO 问题,最佳实践是本地钩子 + CI 双重校验,确保即使开发者跳过了本地钩子,CI 也能强制检查。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播