从“分数”到“证据链”:开源命令行SEO检查工具全解析
2026/9/16 15:30:03 网站建设 项目流程

从“分数”到“证据链”:我开源了一个把SEO检查依据全部摊开来的命令行工具

先说点实在的。做了这么多年站点,我越来越觉得市面上的SEO检查工具都沾着同一个毛病:给你一个总分,然后列几个红色感叹号,告诉你“标题标签太长”“图片缺少alt”。你看着那个78分,既不知道这分怎么算出来的,也不知道扣分扣在哪一行代码上,更不知道改了之后到底能不能涨分。工具变成了一个黑盒,SEO优化就变成了一场盲人摸象。

所以我花了两周多时间,写了一个开源的网页SEO检查工具。它不追求做一个华丽丽的评分大屏,核心思路就一句:每个扣分项必须附上“证据”——具体是哪个标签、哪段HTML、哪个URL触发了这条规则,以及官方文档或行业惯例里为什么说这是问题。分数和依据并列输出,你要改哪里、为什么改、改完会怎样,一条链路清清楚楚。目前项目已经放到了GitHub上,支持命令行运行,输出Markdown或JSON格式报告,也方便直接接进CI/CD流程里做自动化巡检。

这篇文章就把这个工具从设计思路、检查规则、核心代码到踩坑实录完整拆一遍。如果你正在做独立站、内容站,或者想给团队搭一套廉价的SEO巡检体系,这篇应该能帮你省不少事。哪怕你不写代码,光看检查规则的设计逻辑,也能对“SEO工具到底在测什么”有个更清醒的判断。

1. 项目背景与整体设计思路

1.1 为什么我不想要一个“打分器”

先讲个我自己的真实经历。有一次朋友拉我去看他们公司刚上线的营销站,技术负责人很自豪地说“我们用某专业工具测过,SEO得分92分”。我当场用另一款工具跑了一下,得分只有61。两款差的工具算法不同可以理解,但问题在于:那个92分的报告里,只显示“内容相关性强”“链接结构良好”,没有任何一项能回答“强在哪、良好在哪里”。

这就是SEO工具的经典陷阱:评分本身是个压缩过程,它把几十个维度的诊断结果压成一个数字,丢掉了所有上下文。而SEO恰恰是一个极其依赖上下文的领域。标题长度不是非黑即白的规则,一个新闻页和一个产品详情页对description的要求完全不同;canonical标签缺不缺、nocanonical了没有,要分页面类型判断。把这一切抹平成“+3分、-2分”,只会误导决策。

所以我在设计这个工具时定了三原则:

  • 每条规则必须声明触发条件和判定依据,不能只有结论。
  • 报告必须展示“命中的原文片段”或“关联URL”,而不是只显示规则名。
  • 分数只作为排序和关注的辅助,不作为优化是否完成的唯一标准。

换句话说,我把工具从“打分器”改成了“证据记录仪”。打分仍然有,但真正的交付物是那套可以追溯、可以复核的证据清单。

1.2 技术选型:为什么用Python,为什么不做成浏览器插件

技术选型上我几乎没有犹豫就选了Python。原因有几点:requests加BeautifulSoup的解析链路成熟稳定,生态里关于HTML解析、URL处理的库非常齐全;CLI工具用Python写方便跨平台,团队里做站长的同学即使不是专业程序员也能看懂代码;后续想接异步抓取、接入Playwright做动态渲染,Python都有现成的方案。

有人问我为什么不直接做成Chrome浏览器插件。坦白说,浏览器插件在拿document对象、模拟用户行为方面确实有天然优势,但它的致命限制是“一次只能看一个页面”。你无法脱离Chrome做批量巡检,也无法在服务器上定时跑任务。而做SEO巡检,批量抓取整站和有规律的周更、月更才是硬需求。我倾向于把这个工具定位成“可编程的SEO巡检底座”,而不是一个顺手点一下的小插件。

选型时还考虑过直接拿Lighthouse改,但看了API文档后放弃了。Lighthouse定位是性能与体验审计,它有一套自己的规则体系,扩展自定义SEO检查项的API文档写得不算友好,而且它跑一次要起Chrome,太重了。做纯静态HTML的SEO体检挂这一套东西,严重浪费资源。

整个工具架构拆成三层:

  • 采集层:抓取页面内容、robots.txt、sitemap,处理重定向和超时。
  • 规则引擎层:定义每个检查项的数据结构、触发条件、命中证据提取方式。
  • 报告生成层:把检查结果渲染成Markdown或JSON,输出到终端和文件。

1.3 检查项设计原则:先定规则,再写代码

写代码前,先把检查项清单列了一遍。这个清单是跟几个做搜索优化的朋友反复对过的,最后分为五大类:

  • 基础标签:title、meta description、keywords、lang、charset。
  • 内容结构:H1唯一性、图片alt、内链、外链、内容长度。
  • 可抓取性:robots.txt、sitemap、canonical、noindex、404页面。
  • 结构化数据:JSON-LD有效性、必填字段。
  • 移动与性能基础:viewport、页面体积、响应时间。

每个检查项不是“有就加分、没有就减分”那么粗。我先为每条规则写清楚了“为什么检查它”的依据,比如描述标签我引用了搜索中心对摘要撰写的建议,canonical标签则参考了搜索中心关于规范地址的说明文档。这样后续无论是自己调整规则还是社区提PR,都有据可依,不会凭感觉乱改。

2. 核心功能与检查规则拆解

2.1 基础标签检查:标题、描述、关键词不能只看“有没有”

标题标签和meta description可能是最老生常谈的SEO话题,但真正做到位的站点寥寥无几。工具在这块的检查逻辑不只是“存不存在”,而是拆成多个维度。

标题标签的规则设计是这样:先判断是否唯一,如果全站多个页面共用同一个标题,搜索引擎就会很难判断页面主体。然后看长度,我按60字符来作为截断参考线,因为大多数搜索引擎结果页的标题展示宽度大约在这个范围。接着检查是否出现了关键词,这只是一个参考维度,权重很低,防止有人为了凑分做关键词堆砌。

meta description的部分,我参考了比较常见的建议区间80到160字符。太短说明信息量不够,太长则可能被搜索结果截断。但这里有个值得注意的点,Google在2022年前后开始模糊化描述标签的作用,很多页面它根本不用你写的description,而是从正文里摘录。所以工具会额外判断:如果description缺失,但是正文前160字符里包含核心关键词,提示等级就降为建议,而不是严重。

关键词标签(keywords meta),这个是在很多“SEO工具”里都消失了的老古董。因为Google在2009年就公开声明不把它作为排名因素,百度也没有官方依据说它有效。但既然做开源工具,考虑到可能有国内用户和传统建站用户,我保留了这项检查,但把权重设为零。它纯粹作为信息提示,“写了也不扣分,不写也不扣分”,避免误导站长去堆砌关键词。

下面是描述检查的具体判定逻辑,在代码里是这样的:抓取meta标签列表,找到name属性等于description的那一项,如果不存在就直接记录缺失,并抓取正文前200个可见字符作为“替代摘要证据”。如果存在,就检查长度和是否包含页面主要关键词。

2.2 内容结构检查:H1、alt、链接密度这些“硬指标”其实很软

内容结构这块,最能体现“评分工具容易误伤”的地方。先说H1,很多工具把“每页只有一个H1”列为一票否决项,但我见过不少特殊页面,比如活动页、专题聚合页,它们本身有多个视觉上等价的标题区块,强行合并成一个反而不自然。所以工具的判断是:H1大于1个时,会列出所有H1标签的原文,让用户自己去判断是不是设计需要,而不是直接扣到不及格。

图片alt检查是最基础也最必要的。搜索引擎无法理解图片内容,alt文本是它的最主要语义来源。工具会找出所有没有alt属性、以及alt属性为空的img标签,并且把src的原样输出。如果一张图片既是懒加载又没有alt,基本上可以确定这个站点的图片SEO没有任何规划。

内链检查这部分有些工具会做链接数量统计,但数字本身没有任何意义。一个写了300个内链的导航页不见得比一个只挂了5个相关内容链接的文章页更好。所以我只做两件事:统计页面internal链接和external链接的数量,然后把external链接的列表完整输出,方便站长度量外链风险。为什么要把外链列出来?因为很多站长的网站被挂了一堆垃圾外链而不自知,外链质量是搜索引擎判断站点的信用依据之一,定期检查很有必要。

处理链接相对地址的时候有一个坑,代码里必须用urljoin来做拼接,否则http://example.com/a/b这个页面里的href="/contact"会被解析成http://example.com/a/contact,实际应该是http://example.com/contact。这种细节错误在后端程序里很常见,但对SEO来说影响是实打实的。

2.3 可抓取性与索引规则:robots、sitemap、canonical、noindex一个都不能少

可抓取性检查是技术上最能体现工具价值的部分,因为这个层面普通站长用浏览器根本看不见,必须靠程序去模拟搜索引擎的爬虫行为。

robots.txt的检查逻辑是这样的:先请求网站的robots.txt,如果返回200但文件内容是空白的,提示“文件存在但没有内容”,这通常说明网站没有主动配置抓取策略。如果返回404,那才是真正的问题,默认情况下爬虫会抓取所有公开页面,包括后台目录和参数URL。如果返回308或者301重定向,就要看目标地址是不是同域的https版本,如果是就视为正常,否则可能带着爬虫跳到别的域名上去了。

工具还会主动检查robots.txt里是否声明了Sitemap路径,这是很多站长容易忽略的。在robots里声明sitemap位置,可以帮搜索引擎更快发现更新的页面。另外,凡是robots里明确Disallow的路径,工具会在抓取阶段就跳过,避免重复抓取已禁止访问的内容,这是对线上站点的一种礼貌。

sitemap.xml检查是另一块。这里我没有用严格的XML Schema校验——太严了容易误报——我只看三个点:返回状态码是否200,URL数量是否为零,以及所有URL是否都在同一域名下。如果sitemap里混入了别的域名URL,搜索引擎大概率会忽略整份文件而不是部分忽略,这是一个很容易踩但改起来极快的坑。

canonical标签的检查我采用了这样的逻辑:如果页面本身没有被noindex,并且没有连向自己URL的canonical标签,就提示“建议添加”。但如果是首页,或者短小的落地页,这个提示降为“建议”。为什么这么做?因为canonical对于内容高度重复的站点和电商聚合页是必需品,但对于一个内容完全孤立的页面,加不加影响有限,不值得为了过工具的检查而加。

2.4 结构化数据与移动端基础:JSON-LD的有效性是硬门槛

结构化数据检查是这套规则里第二个硬核模块。工具会从页面中提取所有application/ld+json类型的script标签,逐个尝试用json.loads解析。解析失败的直接报错,并输出该段脚本的前200个字符作为证据。解析成功后会检查@context和@type是否存在。这里不深究Schema.org的每一个具体类型——不同行业的必填字段千差万别,逐项校验规则太重了——但一个连合法JSON都不是的结构化数据块,Google Search Console里百分之百会给你报错,这个基本门槛还是得守住。

viewport检查对于移动端适配是基础中的基础。没有viewport声明的页面在移动端浏览器会用默认宽度渲染,文字小到需要双指缩放才能看清,这种站点在移动搜索里的体验分基本是负的。这个检查项虽然简单,效果却非常直接。

页面体积和响应时间这两项放在一组检查里,它们不直接影响排名,但会影响爬虫抓取效率和用户跳出率。工具会记录HTML源码大小、页面响应时间,以及页面是否启用了gzip压缩。说实话“页面体积超过2MB就扣分”这种一刀切规则我不太认同——一个电商详情页带了大量内联JSON数据,2MB是很正常的。所以这一项只做记录不参与评分,让站长度量自己站点的实际情况。

3. 实操过程与核心代码实现

3.1 环境准备与项目结构

工具在Python 3.9及以上版本运行,依赖只有三个:requests、beautifulsoup4、lxml。没有搞poetry、pipenv那套复杂的东西,就一个requirements.txt,干净省心。安装命令是:

pip install -r requirements.txt

requirements.txt内容如下:

requests>=2.25.0 beautifulsoup4>=4.9.0 lxml>=4.6.0 colorama>=0.4.4

colorama主要是终端输出带颜色用的,严重问题时标红、警告标黄、建议标蓝,看结果的时候视觉层级会更舒服。如果你在CI环境里跑,可以把TERM设置成dumb或者加个--no-color参数禁用颜色输出。

项目目录结构是这样安排的:

seo-checker/ ├── seo_checker.py # 入口文件,CLI解析 ├── core/ │ ├── crawler.py # 采集层,负责抓取页面与资源 │ ├── rules.py # 规则引擎,所有检查项的注册与执行 │ ├── reporter.py # 报告层,输出Markdown/JSON │ └── utils.py # 公共工具,URL解析、HTML清洗 ├── rules/ # 按分类存放的检查项定义 │ ├── base_tags.py │ ├── content.py │ ├── crawlability.py │ ├── structured_data.py │ └── performance.py ├── tests/ # 单元测试与快照测试 ├── requirements.txt └── README.md

入口命令设计得很简单:

python seo_checker.py https://example.com --format markdown --output report.md

不加--format时默认在终端直接打印结果,适合快速预览。加了--output就同时写入文件。

3.2 核心检查模块:一条规则的完整实现

所有检查项都遵循同一种数据结构,这是我做这套工具时最得意的一个设计,因为它在保证灵活性的同时,强制让每条规则都必须写清楚“为什么检查”“命中证据怎么提取”“怎么修复”。用代码说话:

@dataclass class CheckResult: rule_id: str # 规则唯一ID,如 BASE_TAGS_002 name: str # 规则名称,如 "meta description 长度" level: str # 严重程度: error / warning / info passed: bool # 是否通过 evidence: list[str] # 命中证据,原始HTML片段或URL列表 message: str # 检查结论 suggestion: str # 修复建议

以meta description长度检查为例,完整的规则实现如下:

def check_description_length(soup, page_url, keyword_hint): result = CheckResult( rule_id="BASE_TAGS_002", name="meta description 长度与内容", level="warning", passed=True, evidence=[], message="", suggestion="" ) desc_tag = soup.find("meta", attrs={"name": lambda x: x and x.lower() == "description"}) if not desc_tag or not desc_tag.get("content", "").strip(): result.passed = False result.level = "error" result.message = "页面缺失 meta description" result.evidence.append("未检测到 <meta name=\"description\"> 标签") result.suggestion = "建议在 <head> 中添加 description 标签,80-160字符,包含页面核心关键词。" return result content = desc_tag["content"].strip() result.evidence.append(f'<meta name="description" content="{content[:120]}{"..." if len(content) > 120 else ""}">') if len(content) < 80: result.passed = False result.level = "warning" result.message = f"description 长度偏短 ({len(content)} 字符)" result.suggestion = "当前描述信息量过少,建议扩充到80-160字符。" elif len(content) > 160: result.passed = False result.level = "warning" result.message = f"description 长度偏长 ({len(content)} 字符)" result.suggestion = "超长描述在搜索结果中可能被截断,建议精简到160字符以内。" return result

看到没有,关键不是那个长度判断,而是evidence这个字段。不管检查通过还是不通过,我都把页面上真实存在的标签片段原样抓下来。这样用户看报告的时候,不需要再打开浏览器去对照源码,一眼就能看到工具是在对哪一段HTML说话。

规则引擎的设计我借鉴了ESLint的思路:每条规则独立注册、独立运行、互不干扰,新增一条规则只需要在rules目录下建一个文件,实现一个接收soup和页面上下文的小函数,然后在规则注册表里登记一下。这种插件式结构后续交给社区维护起来会非常轻松。

3.3 评分计算:加权平均不是拍脑袋,而是分层次

评分这块我设计了三个层次:基础分、内容分、技术分,每一项满分100分,最后的总分是三个分数的加权平均,权重分别对应0.4、0.3、0.3。基础标签权重最高,是因为它对所有类型的页面都有普适意义。内容结构和可抓取性各占三成,这两块对内容站和技术站的重要性因人而异。

单类内部的计算逻辑非常直接:该类下所有检查项的通过数量除以总检查项数量,再乘以100。比如基础标签类共10个检查项,通过了7个,基础分就是70分。最终总分通过一个简单的函数计算:

def calculate_score(results): base_rules = [r for r in results if r.rule_id.startswith("BASE_")] content_rules = [r for r in results if r.rule_id.startswith("CONTENT_")] tech_rules = [r for r in results if r.rule_id.startswith("TECH_")] def avg_score(rules): if not rules: return 100.0 passed = sum(1 for r in rules if r.passed) return round(passed / len(rules) * 100, 1) base_score = avg_score(base_rules) content_score = avg_score(content_rules) tech_score = avg_score(tech_rules) total = round(base_score * 0.4 + content_score * 0.3 + tech_score * 0.3, 1) return base_score, content_score, tech_score, total

这个权重值目前是代码里写死的。如果你想调整某个分类的权重,直接改这三个系数就行。

需要特别说明的是,info级别的检查项不参与评分,它们更像是工具给你的体检报告里“医生备注”那一栏。比如“页面启用了gzip压缩”这类的记录项,你看着就会比较安心,不必担心不加分就心慌。

3.4 用真实URL跑一次完整扫描

写个真实例子。我本地搭了一个测试站点,包含一个比较标准的文章页,故意埋了几个问题:标题超过了60字符,缺失meta description,有两张图片没有alt,没有声明canonical,JSON-LD里有一段非法JSON。把URL喂给工具后,终端输出大概长这样:

$ python seo_checker.py http://localhost:8000/article.html [ERROR] BASE_TAGS_002: meta description 长度与内容 页面缺失 meta description 证据: 未检测到 <meta name="description"> 标签 建议: 在 <head> 中添加 description 标签,80-160字符。 [ERROR] TECH_INDEX_003: canonical 标签缺失 页面未声明 canonical,如果存在重复内容可能影响索引判断 证据: <link rel="canonical"> 未在 <head> 中找到 [WARNING] BASE_TAGS_001: 标题长度超限 当前标题 76 字符,超过建议 60 字符 证据: <title>这是一篇测试文章标题故意写得很长用来测试标题长度检查功能是否正常工作</title> 建议: 精简标题,保留核心关键词,长度控制在60字符以内。 [WARNING] CONTENT_ALT_001: 图片缺失 alt 属性 共 2 张图片未设置 alt 证据: <img src="/images/banner-1.jpg"> <img src="/images/banner-2.jpg"> [INFO] PERF_SIZE_001: 页面体积 HTML 源码 12.3KB,未超出常见建议值。 基础分 | 内容分 | 技术分 | 总分 60.0 | 75.0 | 66.7 | 66.3

发现没有,最重要的不是那个66.3分,而是每一个错误事项下面都跟着证据和具体建议。这就是我在1.1节里说的“证据链”,排查问题的时候完全不需要再另外打开什么工具,照着建议逐条改就行。

3.5 输出格式说明:对接CI/CD和文档系统

工具默认在终端打印报告,但为了对接CI流程和自动化巡检,我实现了两种结构化输出格式。

Markdown格式适合直接提交到代码仓库的docs目录或者发送给非技术背景的同事。每个检查项会渲染成一段二级标题加列表的形式,末尾自动生成一份分数汇总表。如果你想在项目的README里放一个“SEO巡检状态”的徽章,只需要在CI里跑一次然后生成这张表就行。

JSON格式是为程序消费准备的,每个检查项的结果都包含完整的rule_id、level、evidence数组和suggestion字段,方便下游任务做智能化处理。比如我后续准备写一个自动化的工单系统——当某条规则连续三次失败时,自动在GitHub Issues里提交一个任务,分配给负责对应页面的同事。这种场景下分数没什么用,但精确到规则ID和证据的JSON就非常有价值了。

{ "url": "http://localhost:8000/article.html", "scores": {"base": 60.0, "content": 75.0, "tech": 66.7, "total": 66.3}, "checks": [ { "rule_id": "BASE_TAGS_001", "level": "warning", "passed": false, "evidence": ["<title>这是一篇测试文章标题故意写得很长用来测试标题长度检查功能是否正常工作</title>"], "suggestion": "精简标题,保留核心关键词,长度控制在60字符以内。" } ] }

4. 常见问题与排查技巧实录

4.1 误报场景:SPA页面分数暴跌怎么办

工具早期版本有一个让我特别头疼的误报来源:单页应用(SPA)。一个用Vue或React开发的页面,首屏HTML里往往只有一个空壳,内容全靠JavaScript渲染。如果工具直接抓取原始HTML,会发现页面没有H1、没有meta描述、正文空洞,直接打出20分。可实际上这个页面在真实浏览器里完美展现了所有内容。

这个问题的根源在于,我的工具定位是“轻量级的快速巡检”,默认不做JavaScript渲染。搜索引擎的爬虫虽然现在能渲染JS,但成本极高,不是所有页面都会被执行完整的浏览器渲染流程。所以,SPA站点本身就是一个SEO风险比较大的存在,这个分数暴跌其实并不完全算误报。

不过为了减少困惑,我在工具里增加了一个检测机制:如果抓取到的HTML中,body标签的可见文本少于200个字符,就自动在报告顶部给出一条警告,提示“页面正文过短,疑似JavaScript渲染页面”,并建议该页面采用预渲染或SSR方案。这样用户看到低分时至少知道原因,而且这个提示本身就是一条有价值的SEO建议。

如果你确实需要对SPA页面做完整检查,可以在外部配合Playwright或Puppeteer生成预渲染HTML再喂给工具。脚本逻辑不复杂,就是启动浏览器、打开URL、等待几秒、把document.documentElement.outerHTML保存成文件,然后跑工具分析这个文件。后续我打算在项目里增加一个--render参数,本质就是内置一条调用Playwright的路径,但这需要引入无头浏览器依赖,目前还在权衡。

4.2 内链判定:urljoin的坑和路径规范化

内链和外链的判定,看着简单,实则暗藏不少坑。早期版本我直接拿href属性是否以http开头来区分内链外链,结果出了个经典bug:站内存在类似https://cdn.example.com/asset.js的资源,被误判为外链。后来改成拿页面URL的域名来比对,确实解决了跨子域的资源误判,但内链URL带不带www、是不是https又会产生新问题。

我最终的方案是做两层判断。第一层,把页面URL解析出netloc(域名加端口),比如example.com:8080。第二层,把链接的netloc拿来和页面netloc做比对,如果完全一致,判为内链;如果不一致,再看去掉www前缀后是否相等。这里没有走同站(site)级别的模糊匹配,因为对于SEO诊断来说,站内和跨子域的链接在权重传递逻辑上是有差异的,混在一起不利于报告判断。这个逻辑在utils.py里封装成了一个函数:

def is_internal_link(link_url, page_url): from urllib.parse import urlparse link_netloc = urlparse(link_url).netloc page_netloc = urlparse(page_url).netloc if not link_netloc: return True if link_netloc == page_netloc: return True if link_netloc.replace("www.", "") == page_netloc.replace("www.", ""): return True return False

这段代码还有一个好处,URL链接不写域名、以斜杠开头的相对路径,会被直接判为内链,因为link_netloc是空的。很多新手写爬虫在这里经常出问题,因为一个页面里的相对链接实在太常见了。

4.3 抓取稳定性:robots.txt 的 302 重定向不要随便跟

robots.txt检查的代码有一个需要特别注意的点,重定向处理不能太激进。举个例子,http://example.com/robots.txt如果返回302跳转到https://example.com/robots.txt,这是正常的HTTP到HTTPS的升级,跟随跳转没问题。但如果跳转到另一个域名,就说明当前站点配置有问题,搜索引擎爬虫很可能抓不到你这个域名的有效robots文件。

但有些工具偷懒,对重定向一律跟随,最后检测的是跳转后的robots内容,从而漏掉配置问题。所以我的实现是:先检查重定向URL的域名是否和原URL一致,一致才跟随,不一致就按缺失处理并给出警告。

另一方面,抓取sitemap.xml时要给足超时时间。很多站点的sitemap是动态生成的,动辄几MB,网络状况差的时候响应很慢。我之前默认设了10秒超时,结果在一台海外VPS上跑国内站点的检查时频繁超时。后来改成允许通过命令行参数--timeout自定义,默认仍为15秒,但用户调大或者调小都方便。

4.4 评分校准:权重设计如何应付“你以为的SEO”和“实际有用的SEO”

我自己的一个原则是:工具的评分,权重不能脱离“搜索引擎官方文档和一线实操经验”来拍脑袋。下面是三个很有代表性的坑。

第一个是keywords标签的权重调整。工具最初版本把keywords标签缺失标为warning,扣分还不少。后来我在详细调研中确认主流搜索引擎不会参考这一项之后,把它的等级下调为info。这就是一个典型的“工具过于传统反而误导用户”的案例。

第二个是canonical缺失要不要作为error。刚写这个检查逻辑时,我几乎对所有内容页都要求有canonical,结果在一批博客站点上跑出了大面积的低分,排查后发现是误伤。博客文章的URL结构本身没有重复内容风险,比如不带跟踪参数、不带排序变量、没有amp版本,这种页面加不加canonical意义很小。我后来加了一个判断条件:在页面没有检测到参数URL、没有分页链接、没有amphtml标记的前提下,canonical缺失只降为suggestion。这个调整让工具的准确率明显提升,不再对独立博客文章页一棍子打死。

第三个经验是关于“内容长度”的检查。我见过一些工具把页面正文少于300字判为内容单薄,扣分很狠。但实际上,一个FAQ页、一个联系方式页面、一个下载链接页,300字可能都是多的。所以工具里内容长度检查只做info记录——输出正文统计字数,不参与评分。这样既给用户提供了量化信息,又不会因为规则和页面类型不匹配而误判。

4.5 实测排错:一次与证书和超时有关的排查记录

分享一次我调工具时遇到的实际排查过程。某个客户的网站配置了CDN,但源站在回源时证书过期了,导致工具每次抓取都报SSL错误。那时候工具还没做证书异常捕获,requests直接抛了SSLError,整个检查流程就中断了。后续修复时我加了一层异常处理,遇到SSL相关的错误会在报告里单列一条“抓取失败”的error,并提示“可能是证书过期或不被信任”。

还有一个场景是CDN限流。有些站点对单个IP的并发请求有限制,工具抓完HTML紧接着抓robots.txt和sitemap,三个请求间隔太短,触发了限流,返回了403。我花了些时间排查,最终在crawler里加了一个每请求之间的短暂延迟,默认0.5秒,可通过--delay参数调整。这个延迟不会让单页面检查变慢太多,但对线上站点比较友好,不至于因为我们的排查把别人的日志搞得乱七八糟。

5. 开源发布与后续维护计划

5.1 开源许可证与仓库整理:别让好项目被license劝退

开源项目如果不选许可证,别人是不敢合法使用的。我最后选了MIT License。原因很实际:这是一个工具类项目,不是底层库,我希望大家拿来就用,甚至改进后闭源也没关系。MIT在商业友好度上是最宽松的一档。如果你在做一个对社区影响更大的基础组件,我建议考虑Apache 2.0,它额外包含了明确的专利授权条款,对大公司来说更安心。但SEO检查工具这个场景,MIT就够了。

仓库的README我写得很详细,包括工具定位、安装方式、快速上手、检查项清单、报告示例、常见问题六个部分。尤其检查项清单,我做了个完整的表格,每一个规则都附带文档链接,这样任何用户都能在五分钟内搞明白这个工具能做什么、不能做什么,避免盲目期待。

项目里还给每条规则维护了一份单独的技术文档,写在rules/docs目录下。这份文档不只写给使用者,更是写给未来的贡献者。如果有人想新增一个“检查Hreflang标签是否成对”的规则,照着现有文档的结构填就行。贡献门槛低了,社区活起来的可能性才大。

5.2 规则可配置化:下一阶段的关键任务

目前工具的检查项在代码里写死,想增删必须改Python。这显然不够灵活。我计划把规则配置改成JSON或YAML驱动的形式,这样即便不会写Python的SEO小伙伴,也能通过配置一键启用或关闭某条规则、调整权重、设置阈值。

举个例子,默认规则里description长度设为80到160字符,但某些特殊行业(比如新闻站)的描述天然就短,一条新闻的description可能只有20个字符。有了配置文件,站长可以轻松改成40到120,而不用fork代码去手动改阈值。

这个改动还会让报告的维护性大幅提升。每一条自定义规则都能有自己的evidence字段定义,让报告完美贴合业务场景。虽然这会增加不少开发工作量,但我认为这是工具走向成熟的关键一步。

5.3 持续维护与社区协作:从单一工具到巡检平台

开源最忌讳把代码丢上去就不管了。我给自己设了一个不算严格但有效的工作流:每两周跑一次全量检查,把工具自身官网和几个示例站点作为测试集,发现问题就修复并发布新版本。所有已知问题都放在Issues里,标注“good first issue”标签的留给第一次参与开源贡献的新手。

目前项目里Pull Request的模板也准备好了,要求提交者说明新增规则或修改规则的依据,最好附上参考文档链接。这样每一次规则变动都有迹可循,不会出现“我拍脑袋改了规则”的情况。

再往后,我打算增加两个方向:一是批量抓取单个站点整站做深度巡检,每个页面生成一条记录,汇总成站点级SEO报告;二是增加历史趋势存储——比如用SQLite保存每次检查结果,跑一周、一月之后能看到分数变化曲线,这样SEO优化效果就能被量化追踪了。第二个方向对团队管理尤其有用,SEO不再是“月底拉个报告看看”,而是变成持续集成的一部分。

写在最后

这个工具现在还不够完美,离“开箱即用”还有距离,但它的核心思路已经跑通了:SEO诊断,最重要的从来不是一个漂亮的分数,而是能让线上问题被看见、被理解、被修复的那条证据链。我做开源也有一部分私心,想借社区的力量持续完善这套规则库。你有兴趣的话,可以clone下来跑一下自己的站点,如果发现某个检查项的判断逻辑有问题,或者期待哪些新规则,欢迎提Issue或者直接提PR。这个项目,离了真实站点的检验,是长不大的。

最后说一个小技巧。拿到工具报告后,请不要只盯着总分,先按error级别过滤出一条条过一遍,把每个error对应的evidence和suggestion对照着看。等你把所有error都修掉,再回去看分数,你会感受到那种“每条扣分都心中有数”的踏实感。这是我做这个工具最想传递的东西。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询