阅读时间与可读性估算
输入文本估算阅读时长、词数与可读性等级,辅助内容排版。
在线演示
—估算时长
0中文汉字数
0句子数
0长句数(>40 字)
等待操作:粘贴文本后点击“估算阅读时长”,也可直接点“载入示例文本”体验完整流程。
项目简介
写博客、做 PPT 或给文案定稿前,先估算一下读者要花多久读完。本页提供阅读时长估算(按中文/英文语速区分)与可读性指标(长句比例、生僻度提示),帮助内容创作者控制篇幅与表达复杂度。
代码示例:阅读时长估算
js
function estimateReadingTime(text) {
const trimmed = text.trim();
if (!trimmed) return { minutes: 0, words: 0 };
// 中文字符数 + 英文单词数
const cjk = (trimmed.match(/[\u4e00-\u9fff]/g) || []).length;
const words = (trimmed.replace(/[\u4e00-\u9fff]/g, " ")
.match(/[A-Za-z0-9]+(?:['-][A-Za-z0-9]+)*/g) || []).length;
// 中文 300 字/分、英文 200 词/分(常见阅读速度)
const minutes = cjk / 300 + words / 200;
return { minutes: Math.ceil(minutes * 10) / 10, words: cjk + words };
}代码示例:长句与可读性提示
js
function readabilityTips(text) {
const sentences = text.split(/[。!?.!?\n]+/).filter(s => s.trim());
if (!sentences.length) return [];
const long = sentences.filter(s => s.length > 60).length;
const ratio = long / sentences.length;
const tips = [];
if (ratio > 0.2) {
tips.push("长句比例偏高(" + Math.round(ratio * 100) + "%),建议拆分超过 60 字的句子。");
}
if (/[A-Za-z]{20,}/.test(text)) {
tips.push("检测到超长英文单词,注意专有名词是否可替换为通俗写法。");
}
if ((text.match(/[,、;]/g) || []).length > sentences.length * 2) {
tips.push("逗号使用较密集,适当用句号分段能提升阅读节奏。");
}
return tips;
}操作步骤
- 粘贴正文文本,页面自动统计字数并估算阅读时长(中文按 300 字/分、英文按 200 词/分)。
- 阅读“可读性提示”列表,针对性拆分长句、减少密集逗号。
- 适合用于公众号排版前检查、技术文档篇幅控制与 PPT 讲稿时间预算。
完整可运行工程
可直接运行零第三方依赖Python 3.8+
完整工程(非片段):estimate.py 把「字数统计 → 分速率折算 → 长句判定」串成一条流水线,既可作为 CLI 使用,也可 import estimate 接入你的写作/发布脚本。
依赖清单:仅 Python 标准库(re / unicodedata / argparse / json),无第三方依赖,无需 pip install。
reading-estimator/
├── estimate.py # 分析与 CLI 入口
├── test_estimate.py # 最小用例回归
├── requirements.txt
└── README.md
├── estimate.py # 分析与 CLI 入口
├── test_estimate.py # 最小用例回归
├── requirements.txt
└── README.md
estimate.py
分析核心:analyze / estimate / fmt_duration / report,含 --selftest 自检入口。
python
#!/usr/bin/env python3
"""阅读时长估算与可读性提示(零第三方依赖,仅标准库)
用法:
python estimate.py article.txt
python estimate.py article.txt --cpm 300 --wpm 220 --json
python estimate.py --text "待估算的一句话。" --cpm 200
python estimate.py --selftest
"""
import argparse
import json
import re
import sys
from pathlib import Path
HAN = re.compile(r"[\u4e00-\u9fff]")
WORD = re.compile(r"[A-Za-z][A-Za-z'\-]*")
DIGIT = re.compile(r"\d+(?:\.\d+)?")
SPLIT = re.compile(r"[。!?!?;;]+|\n+")
LONG_HAN = 40 # 单个中文句子超过 40 个汉字视为长句
LONG_WORDS = 25 # 单个英文句子超过 25 个单词视为长句
WORD_WEIGHT = 1.5 # 折算权重:1 个英文单词≈1.5 个汉字
def analyze(text):
sents = []
for raw in SPLIT.split(text.replace("\r", "")):
s = raw.strip()
if not s:
continue
h = len(HAN.findall(s))
w = len(WORD.findall(s))
sents.append({"text": s, "han": h, "words": w, "weight": h + w * WORD_WEIGHT})
weights = [s["weight"] for s in sents]
return {
"han": len(HAN.findall(text)),
"words": len(WORD.findall(text)),
"digits": len(DIGIT.findall(text)),
"paragraphs": len([p for p in re.split(r"\n\s*\n", text) if p.strip()]),
"sentences": len(sents),
"avg_sentence": round(sum(weights) / len(weights), 1) if weights else 0,
"long_sentences": [s for s in sents if s["han"] > LONG_HAN or s["words"] > LONG_WORDS],
"detail": sents,
}
def estimate(text, cpm=300, wpm=220):
"""返回 (总秒数, 中文秒数, 英文秒数)。中英按各自速率累加,不混用一个系数。"""
a = analyze(text)
zh = a["han"] / cpm * 60
en = a["words"] / wpm * 60
return zh + en, zh, en
def fmt_duration(sec):
if sec <= 0:
return "0 秒"
m, s = divmod(int(round(sec)), 60)
if m and s:
return "%d 分 %d 秒" % (m, s)
if m:
return "%d 分钟" % m
return "%d 秒" % s
def report(text, cpm, wpm):
a = analyze(text)
total, zh, en = estimate(text, cpm, wpm)
return {
"duration_seconds": round(total, 1),
"duration_human": fmt_duration(total),
"zh_seconds": round(zh, 1),
"en_seconds": round(en, 1),
"cpm": cpm,
"wpm": wpm,
"han": a["han"],
"words": a["words"],
"sentences": a["sentences"],
"paragraphs": a["paragraphs"],
"avg_sentence": a["avg_sentence"],
"long_sentences": len(a["long_sentences"]),
"advice": ("未发现超长句" if not a["long_sentences"]
else "有 %d 句超过 %d 汉字 / %d 英文单词,建议拆分以降低回看成本"
% (len(a["long_sentences"]), LONG_HAN, LONG_WORDS)),
}
def selftest():
text = "你好世界。" + "a" * 0 + "Hello world this is a test."
a = analyze(text)
assert a["han"] == 4, a["han"]
assert a["words"] == 6, a["words"]
t, zh, en = estimate("中" * 300, cpm=300, wpm=220)
assert abs(t - 60) < 1e-9, t
t2, _, en2 = estimate("word " * 220, cpm=300, wpm=220)
assert abs(t2 - 60) < 1e-9, t2
long_text = "这是一个很长的句子" * 6 + "。" # 60 个汉字
assert len(analyze(long_text)["long_sentences"]) == 1
assert fmt_duration(151) == "2 分 31 秒"
print("selftest OK:字数统计 / 分速率折算 / 长句判定 / 时长格式化 用例全部通过")
def main(argv=None):
ap = argparse.ArgumentParser(description="阅读时长估算与可读性提示")
ap.add_argument("file", nargs="?", help="待分析文本文件(UTF-8)")
ap.add_argument("--text", help="直接传入文本")
ap.add_argument("--cpm", type=int, default=300, help="中文速率(字/分),默认 300")
ap.add_argument("--wpm", type=int, default=220, help="英文速率(词/分),默认 220")
ap.add_argument("--json", action="store_true")
ap.add_argument("--selftest", action="store_true")
a = ap.parse_args(argv)
if a.selftest:
selftest()
return 0
if a.text is not None:
text = a.text
elif a.file:
p = Path(a.file)
if not p.exists():
raise SystemExit("文件不存在:%s" % p)
text = p.read_text(encoding="utf-8", errors="replace")
else:
text = sys.stdin.read()
if not text.strip():
raise SystemExit("没有可分析的文本(可从文件、--text 或标准输入提供)。")
r = report(text, a.cpm, a.wpm)
if a.json:
print(json.dumps(r, ensure_ascii=False, indent=2))
else:
print("估算阅读时长:%s(%.1f 秒)" % (r["duration_human"], r["duration_seconds"]))
print(" 汉字 %d · 英文单词 %d · 句子 %d · 段落 %d · 平均句长 %s 字"
% (r["han"], r["words"], r["sentences"], r["paragraphs"], r["avg_sentence"]))
print(" 耗时拆解:中文 %.1f 秒 + 英文 %.1f 秒(%d 字/分,%d 词/分)" % (r["zh_seconds"], r["en_seconds"], r["cpm"], r["wpm"]))
print(" 可读性:%s" % r["advice"])
return 0
if __name__ == "__main__":
sys.exit(main())
test_estimate.py
unittest 回归:中英计数、双速率折算、长句判定、输出字段完整性。
python
"""estimate.py 最小用例回归(标准库 unittest)"""
import unittest
from estimate import analyze, estimate, fmt_duration, report
class TestEstimate(unittest.TestCase):
def test_han_and_word_count(self):
a = analyze("你好世界。Hello world this is a test.")
self.assertEqual(a["han"], 4)
self.assertEqual(a["words"], 6)
def test_zh_rate(self):
total, zh, _ = estimate("中" * 300, cpm=300, wpm=220)
self.assertAlmostEqual(total, 60.0, places=6)
self.assertAlmostEqual(zh, 60.0, places=6)
def test_en_rate(self):
total, _, en = estimate("word " * 220, cpm=300, wpm=220)
self.assertAlmostEqual(total, 60.0, places=6)
self.assertAlmostEqual(en, 60.0, places=6)
def test_long_sentence_detection(self):
text = "这是一个很长的句子" * 6 + "。"
self.assertEqual(len(analyze(text)["long_sentences"]), 1)
def test_report_fields(self):
r = report("短文一句。", 300, 220)
for k in ("duration_seconds", "duration_human", "han", "words", "long_sentences", "advice"):
self.assertIn(k, r)
def test_fmt_duration(self):
self.assertEqual(fmt_duration(151), "2 分 31 秒")
self.assertEqual(fmt_duration(0), "0 秒")
if __name__ == "__main__":
unittest.main(verbosity=2)
requirements.txt
依赖清单:仅标准库。
text
# 本工程仅使用 Python 标准库(argparse / re / json / pathlib / unittest)。
# 不需要安装任何第三方包;此文件保留是为了统一 `pip install -r requirements.txt` 流程。
README.md
参数说明、阈值常量位置与流水线接入方式。
markdown
# 阅读时长估算(reading-estimator)
中文按汉字数、英文按单词数**分别**用各自速率折算时长,再对句长分布做可读性提示。
不做「一个系数通吃」的估算,是本工具与随手写的字数除法最大的区别。
## 文件说明
| 文件 | 作用 |
|---|---|
| `estimate.py` | 分析核心 + 命令行入口(仅标准库) |
| `test_estimate.py` | unittest 最小用例回归 |
| `requirements.txt` | 依赖清单(本工程无第三方依赖) |
## 运行
```bash
python -m pip install -r requirements.txt # 无第三方依赖,可跳过
python estimate.py article.txt --cpm 300 --wpm 220
python estimate.py article.txt --json
echo "一段中文文本。" | python estimate.py
python estimate.py --selftest
python test_estimate.py
```
## 参数
| 参数 | 含义 | 默认 |
|---|---|---|
| `--cpm` | 中文速率(字/分) | 300(默读) |
| `--wpm` | 英文速率(词/分) | 220 |
| `--json` | 输出结构化结果,便于接入流水线 | 关闭 |
## 判定阈值
- 长句:中文句 > 40 个汉字,或英文句 > 25 个单词;
- 英文单词折算权重 1.5(用于计算加权平均句长)。
阈值写在 `estimate.py` 顶部的 `LONG_HAN` / `LONG_WORDS` / `WORD_WEIGHT` 三个常量,
按你的读者群体调整即可,无需改动其它逻辑。
运行标注
- 可直接运行:保存 4 个文件到同一目录,执行
python estimate.py 你的文章.txt即可得到时长与可读性报告。 - 需按需修改的配置项:中文/英文速率用
--cpm/--wpm传参(默认 300 字/分、220 词/分);长句阈值在estimate.py顶部LONG_HAN(40) /LONG_WORDS(25) 调整。不改也能直接跑。 - 自测:
python estimate.py --selftest(或python test_estimate.py),6 个用例应全部通过。