简介:这是一套面向小说站长与建站爱好者的2024小说CMS自动建站系统,内置14种采集规则,帮助用户无需深入编程即可快速搭建内容丰富的在线小说平台,解决小说资源抓取、整理与发布效率低的问题。压缩包共16个文件,约7.78MB,以html页面模板为主,涵盖首页、分类、排行、搜索、小说详情与资讯等模块,另有png图标素材、js交互脚本与css样式表,构成完整的前后台页面骨架,并包含default_wap移动端模板,适配手机与平板阅读。目前已有579人学习下载。系统通过多种采集策略自动获取连载章节与分类内容,配合SEO友好的元数据设置与定时更新机制,有助于提升搜索引擎收录与自然流量;同时可结合广告位、会员与数据分析功能,为运营者调整内容策略、提高用户黏性与转化率提供支撑,适合新手站长快速上线并持续扩展站点。
1. 小说 CMS 自动建站:14 种采集规则到底省掉了哪些手工活
如果你手里有一批小说站点要维护,或者想从零搭一个垂直小说站,最耗时的从来不是写页面模板,而是内容从哪来、怎么持续来、来了之后怎么自动变成可访问的页面。2024 年这类小说 CMS 自动建站系统开始把「采集规则」做成内置能力,一套系统自带 14 种规则,覆盖常见小说站的内容结构。这件事的核心价值不是「采集」两个字,而是把「发现书源 → 抓取目录 → 抓取正文 → 清洗入库 → 生成静态页 → 更新站点地图」这条链路从人工操作变成配置驱动。
适合谁看:一是手里有服务器、想低成本跑小说站的个人开发者;二是需要给多个客户快速交付小说类站点的外包团队;三是想研究采集规则引擎怎么设计的后端工程师。这篇不讨论内容版权归属,只讲技术实现路径和参数配置。读完你能判断这套方案值不值得投入,以及自己复现时该从哪一步下手。
2. 14 种采集规则拆开看:规则引擎的字段设计与匹配逻辑
2.1 规则不是 14 个脚本,而是 14 组选择器配置
很多人第一次听到「自带 14 种采集规则」,会以为是 14 个写死的爬虫脚本。实际落地时,常见做法是把规则抽象成一份 JSON 或 YAML 配置,每条规则描述一个目标站点的页面结构。14 种规则对应 14 组预设配置,系统启动时加载进规则引擎,由同一个抓取调度器执行。
一条规则通常包含这些字段:
| 字段 | 作用 | 示例值 |
|---|---|---|
| rule_name | 规则标识 | novel_site_a |
| base_url | 站点根地址 | https://example-novel.test |
| charset | 页面编码 | utf-8 |
| list_selector | 目录页链接选择器 | div.chapter-list a |
| title_selector | 书名选择器 | h1.book-title |
| chapter_selector | 章节正文容器 | div#content |
| next_page | 分页参数模板 | /book/{id}/page/{n} |
| interval | 请求间隔毫秒 | 1200 |
规则引擎的工作流是:调度器按 interval 发起请求,拿到 HTML 后用 charset 解码,再用 list_selector 提取章节链接,逐条进入详情页,用 chapter_selector 取正文,最后交给清洗模块。14 种规则的区别只在选择器和分页模板,执行逻辑完全复用。
2.2 选择器优先级与容错回退
真实站点结构会变,单靠一个选择器容易翻车。我一般会给每条规则配两级回退:主选择器命中失败时,用备用选择器再试一次。配置里可以写成数组:
{ "chapter_selector": ["div#content", "div.read-content", "article.chapter"], "title_selector": ["h1.book-title", "h1.title", "meta[property='og:novel:book_name']"] }逻辑说明:引擎按数组顺序尝试,第一个返回非空结果就停止。参数说明:数组长度建议不超过 3,太多会拖慢单页解析;meta 选择器适合标题在 head 里的站点,取 content 属性而不是文本。
2.3 用 Python 跑通一条规则的最小验证
在把规则写进 CMS 之前,先用独立脚本验证选择器是否命中,能省掉大量调试时间。下面这段代码模拟规则引擎的单页解析:
import requests from parsel import Selector def parse_chapter(url, rule): resp = requests.get(url, timeout=10) resp.encoding = rule.get("charset", "utf-8") sel = Selector(resp.text) # 按优先级尝试章节正文选择器 for css in rule["chapter_selector"]: content = sel.css(css).get() if content: return content return None rule = { "charset": "utf-8", "chapter_selector": ["div#content", "div.read-content"] } html = parse_chapter("https://example-novel.test/book/1/chapter/1", rule) print(len(html) if html else "miss")逻辑说明:requests 负责请求,parsel 负责 CSS 选择。参数说明:timeout 设 10 秒,避免慢站点拖死调度;encoding 必须显式设置,否则中文站点容易乱码。返回 None 说明所有选择器都没命中,需要回站点重新核对 DOM 结构。
2.4 采集频率与并发控制参数
采集规则再准,频率设错也会被封。常见做法是单站点串行、多站点并行。单站点 interval 建议 1000 到 2000 毫秒,并发数不超过 2。系统级参数一般有这几个:
- max_workers:全局并发线程数,建议 4 到 8
- per_domain_delay:单域名请求间隔,单位毫秒
- retry_times:失败重试次数,建议 2
- timeout:单请求超时,建议 10 秒
这些参数在 CMS 后台通常有配置面板,改完不需要重启服务。调参时先看日志里的平均响应时间,再决定 interval 能不能压到 800 毫秒。
3. 自动建站链路:从采集入库到静态页生成
3.1 数据表设计:书籍、章节、采集任务三张核心表
自动建站要跑通,数据库结构必须先定。最小可用模型是三张表:
CREATE TABLE books ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(255) NOT NULL, author VARCHAR(128), cover_url VARCHAR(512), source_rule VARCHAR(64), created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE chapters ( id INT PRIMARY KEY AUTO_INCREMENT, book_id INT NOT NULL, chapter_no INT NOT NULL, title VARCHAR(255), content MEDIUMTEXT, INDEX idx_book_chapter (book_id, chapter_no) ); CREATE TABLE collect_tasks ( id INT PRIMARY KEY AUTO_INCREMENT, rule_name VARCHAR(64), target_url VARCHAR(512), status TINYINT DEFAULT 0, updated_at DATETIME );逻辑说明:books 存书籍元信息,chapters 存正文,collect_tasks 记录采集进度。参数说明:content 用 MEDIUMTEXT 能存约 16MB,单章足够;idx_book_chapter 联合索引保证按书按章查询走索引。status 用 0 未开始、1 进行中、2 完成、3 失败。
3.2 采集任务调度:队列与去重
采集任务不能重复抓同一章。常见做法是在 collect_tasks 里对 target_url 建唯一索引,插入时用 INSERT IGNORE 或 ON DUPLICATE KEY UPDATE。调度器从 status=0 的任务里取一批,标记为 1,抓完写 chapters 并标记为 2。
def fetch_batch(conn, limit=20): cur = conn.cursor() cur.execute( "SELECT id, target_url, rule_name FROM collect_tasks " "WHERE status=0 ORDER BY id LIMIT %s", (limit,) ) rows = cur.fetchall() ids = [r[0] for r in rows] if ids: cur.execute( "UPDATE collect_tasks SET status=1 WHERE id IN (%s)" % ",".join(["%s"] * len(ids)), ids ) conn.commit() return rows逻辑说明:先取再标记,避免多进程重复消费。参数说明:limit 控制单批大小,建议 20 到 50;批量 UPDATE 用占位符拼接,注意 ids 长度和占位符数量一致。
3.3 静态页生成:模板渲染与增量更新
小说站做静态化能扛住高并发。生成逻辑是:书籍详情页按 book_id 渲染一次,章节页按 chapter_id 渲染。增量更新只处理新入库的章节,用 updated_at 或自增 id 做水位线。
from jinja2 import Environment, FileSystemLoader env = Environment(loader=FileSystemLoader("templates")) def render_chapter(chapter, book): tpl = env.get_template("chapter.html") html = tpl.render(chapter=chapter, book=book) path = f"output/{book['id']}/{chapter['chapter_no']}.html" with open(path, "w", encoding="utf-8") as f: f.write(html)逻辑说明:Jinja2 负责模板渲染,输出路径按书 id 分目录。参数说明:模板里章节正文用 safe 过滤器避免转义;输出目录要提前创建,否则写入报错。增量水位线建议存一张 meta 表,记录 last_chapter_id。
3.4 站点地图与伪静态规则
静态页生成后,要更新 sitemap.xml 和伪静态规则。sitemap 按书籍维度生成,每本书一个 urlset。伪静态规则把 /book/1/ 映射到 output/1/index.html,/book/1/chapter/1/ 映射到 output/1/1.html。Nginx 配置片段:
location ~ ^/book/(\d+)/chapter/(\d+)/$ { root /data/novel/output; try_files /$1/$2.html =404; }逻辑说明:正则捕获 book_id 和 chapter_no,try_files 直接命中静态文件。参数说明:root 指向输出根目录;=404 保证未生成的章节返回正确状态码,避免搜索引擎收录空页。
4. 避坑与排查:采集规则跑不通时先看这 5 条
4.1 现象:正文抓到的是导航或广告
原因:chapter_selector 命中了页面里第一个匹配元素,而导航区恰好用了相同 class。解决:把选择器写得更具体,比如 div#content div.text 而不是 div.text;或者用 :not() 排除广告容器。
4.2 现象:中文全部乱码
原因:requests 自动推断编码失败,或者站点声明 charset 与实际不符。解决:显式设置 resp.encoding,优先用规则里的 charset 字段;如果还乱,用 resp.apparent_encoding 兜底,但要在日志里记录,方便后续修正规则。
4.3 现象:采集到一半任务卡在 status=1
原因:进程崩溃或超时后没有回滚状态。解决:加一个定时清理任务,把 updated_at 超过 30 分钟且 status=1 的记录重置为 0;同时给抓取函数加 try/finally,异常时也要更新状态。
4.4 现象:静态页生成了但访问 404
原因:Nginx 的 root 路径和实际输出目录不一致,或者文件名大小写不匹配。解决:用 ls 确认输出文件真实路径,再核对 Nginx root;Linux 文件系统区分大小写,模板里输出的文件名要和 URL 规则完全一致。
4.5 现象:采集频率调低后仍然被限流
原因:并发线程数没降,单域名实际 QPS 还是超了。解决:per_domain_delay 和 max_workers 要一起调,单域名并发建议设为 1;如果站点有 robots 限制,按限制值再留 20% 余量。
5. 进阶技巧:用规则热更新和灰度验证把维护成本压下来
规则会过期,站点改版后选择器失效是常态。我一般会做两件事:规则热更新和灰度验证。
规则热更新是指把 14 种规则存在数据库或配置中心,而不是写死在代码里。CMS 后台提供一个规则编辑页,改完立即生效,不需要重新部署。实现上,规则引擎每次取任务时从缓存读规则,缓存设置 5 分钟过期。这样站点改版后,运维改选择器就能恢复采集。
灰度验证是指新规则先跑 10 个章节,人工抽查正文长度和标题是否正确,确认后再放量。可以加一个 dry_run 参数,只解析不入库,把结果写到日志。
def dry_run(rule, urls): for url in urls[:10]: html = parse_chapter(url, rule) if not html: print(f"[MISS] {url}") continue # 正文长度低于 200 字符视为可疑 flag = "OK" if len(html) > 200 else "SUSPECT" print(f"[{flag}] {url} len={len(html)}")逻辑说明:dry_run 只做解析和长度判断,不写数据库。参数说明:urls 取前 10 条,避免验证阶段产生大量请求;200 字符是经验阈值,短章节可能误判,需要结合站点实际情况调整。
另一个技巧是给每条规则加一个 success_rate 字段,记录最近 100 次采集的成功率。低于 80% 自动告警,提醒运维检查选择器。这个字段用 Redis 的滑动窗口实现,成本很低。
最后说个血泪经验:不要等到站点上线才验证规则。我习惯在本地用 dry_run 把 14 种规则各跑一遍,确认正文长度和标题都正常,再导入生产库。这一步多花半小时,能省掉上线后半夜爬起来修选择器的后悔药。希望帮到你。
本文还有配套的精品资源,点击获取