目录[-]
一、背景
之前爬取小说网站时,遇到几个痛点:
- 章节列表格式不统一(有的返回列表,有的返回字典)
- 章节标题经常是HTML实体编码(如子)
- 小说内容夹杂大量广告和垃圾字符
- 不同章节的编码格式不一致
所以写了一套完整的爬虫+清理工具,支持:
- API方式获取章节列表和内容
- 自动检测并修复编码问题
- 智能清理广告和垃圾内容
- 并行爬取,断点续传
二、技术架构
|
模块 |
技术栈 |
功能 |
|
爬虫 |
requests + ThreadPoolExecutor |
API调用,并行下载 |
|
内容清理 |
正则表达式 + 自定义规则 |
去除广告、修复编码、清洗文本 |
|
文件管理 |
原生文件操作 |
TXT保存、备份、编码转换 |
整体结构:
novel_crawler/
├── crawler.py # 核心爬虫(API版本)
├── content_utils.py # 内容清理工具
├── run_crawler.py # 启动入口
└── output/ # 输出目录
└── save/ # 清理后的文件
三、核心实现
1. API接口分析
通过抓包发现小说网站的API结构:
- https://apibi.cc/api/book?id={book_id} → 获取小说基本信息(书名、作者、dirid)
- https://apibi.cc/api/booklist?id={dirid} → 获取章节列表
- https://apibi.cc/api/chapter?id={book_id}&chapterid={chapter_id} → 获取章节内容
class ApiNovelCrawler:
def __init__(self):
self.base_url = "https://apibi.cc/api"
self.session = requests.Session()
self.session.headers.update({
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Referer': 'https://www.57389b.sbs/',
})
2. 章节列表解析(兼容多种格式)
不同小说的API返回格式不一样:
- 有的返回字典列表 [{"id": 1, "title": "第一章"}, ...]
- 有的返回字符串列表 ["1:第一章标题", "2:第二章标题", ...]
def _parse_chapter_info(self, chapter_item, index):
if isinstance(chapter_item, dict):
chapter_id = chapter_item.get('id') or chapter_item.get('num') or str(index)
chapter_title = chapter_item.get('title') or chapter_item.get('name') or f'第{chapter_id}章'
elif isinstance(chapter_item, str):
if ':' in chapter_item:
parts = chapter_item.split(':', 1)
chapter_id = parts[0].strip()
chapter_title = parts[1].strip()
elif '章' in chapter_item:
match = re.search(r'第(\d+)章\s*(.*)', chapter_item)
if match:
chapter_id = match.group(1)
chapter_title = match.group(2) or f'第{chapter_id}章'
# ... 其他格式处理
return {'id': chapter_id, 'title': chapter_title, 'index': index}
3. 解码HTML实体
章节标题经常出现 子 这样的HTML实体,需要解码:
def decode_html_entities(text):
import html
import re
decoded = html.unescape(text)
def decode_hex(match):
hex_str = match.group(1)
try:
return chr(int(hex_str, 16))
except:
return match.group(0)
pattern = r'&#x([0-9a-fA-F]+);'
decoded = re.sub(pattern, decode_hex, decoded)
return decoded
4. 内容清理(核心)
小说内容中经常混入大量广告:
- 笔趣阁、www.biquge.com 等网站推广
- 各种乱码符号 ♜♞♛♚♝
- 孤立的英文字母和数字
- 请记住本书首发域名 等垃圾文本
class ContentCleaner:
def __init__(self):
self.ad_keywords = [
'笔趣阁', 'biquge', '请记住本书首发域名', '最新网址',
'『点此报错』', '加入书签', '推荐票', '月票',
]
def clean_content(self, content):
lines = content.split('\n')
cleaned_lines = []
for line in lines:
cleaned_line = self._clean_line_safe(line)
if cleaned_line:
cleaned_lines.append(cleaned_line)
return '\n'.join(cleaned_lines)
def _clean_line_safe(self, line):
# 1. 移除广告关键词
for ad in self.ad_keywords:
line = line.replace(ad, '')
# 2. 清理特殊符号
special_symbols = r'[Θθ⊕⊙⊗¤•·∙▪●◎○◇◆□■△▲▽▼☆★※♜♞♛♚♝♟♔♕♖♗♘♙♠♣♥♦§¶†‡‰‱‽⁈⁉ヽ♀♂]'
line = re.sub(special_symbols, '', line)
# 3. 清理孤立的小写字母和数字
line = re.sub(r'\b[a-z]{1,3}\b', '', line)
line = re.sub(r'\b\d{1,3}\b', '', line)
# 4. 清理多余空格
line = re.sub(r'\s+', ' ', line)
return line.strip()
5. 并行爬取
使用 ThreadPoolExecutor 实现并发下载:
def crawl_novel_parallel(self, book_id, start_chapter=1, max_workers=20):
with ThreadPoolExecutor(max_workers=max_workers) as executor:
future_to_chapter = {}
for chapter in chapters_to_crawl:
future = executor.submit(self._crawl_single_chapter, book_id, chapter, progress)
future_to_chapter[future] = chapter
for future in as_completed(future_to_chapter):
chapter_data = future.result()
results.append(chapter_data)
6. 文件保存与备份
def save_to_txt(self, novel_data, filename, create_backup=True):
# 备份已存在的文件
if create_backup and os.path.exists(txt_path):
backup_filename = f"{filename}_backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt"
shutil.copy2(txt_path, backup_path)
# 写入TXT
with open(txt_path, 'w', encoding='utf-8') as f:
f.write(f"《{title}》\n")
f.write(f"作者:{author}\n")
f.write("=" * 50 + "\n\n")
for chapter in chapters:
f.write(f"{chapter['chaptername']}\n")
f.write(chapter['content'] + "\n\n")
四、踩坑记录
坑1:文件编码问题
- 现象:读取TXT文件时报 UnicodeDecodeError
- 原因:有的文件是GBK编码,有的是UTF-8
- 解决:先用 chardet 检测编码,再读取;失败时遍历常见编码重试
def detect_encoding(self, file_path):
with open(file_path, 'rb') as f:
raw_data = f.read(10000)
result = chardet.detect(raw_data)
return result['encoding'] or 'utf-8'
坑2:章节标题乱码
- 现象:标题显示 子 这样的字符
- 解决:先用 html.unescape 解码,再用正则处理16进制实体
坑3:广告词动态变化
- 现象:每次爬取都会出现新的广告词
- 解决:先用关键词列表过滤,再通过正则识别 字母+数字 的广告模式
坑4:API返回数据格式不统一
- 现象:有的返回 list,有的返回 dict,有的直接是字符串
- 解决:写兼容函数,判断类型后分别处理
坑5:并发导致请求失败
- 现象:并发数过高时出现超时
- 解决:控制 max_workers=10,添加重试机制(最多3次)
五、优化方向
- 断点续传:记录已爬取的章节索引,中断后可继续
- 数据库存储:换成SQLite/MySQL,方便检索和管理
- 分布式爬取:对于超长小说(几千章),可以多机并发
- 增量更新:定期检测新章节,只爬取增量部分
六、使用示例
from crawler import ApiNovelCrawler
crawler = ApiNovelCrawler()
crawler.crawl_by_book_id(
book_id="158231",
start_chapter=1,
max_workers=10
)
爬取完成后,小说会保存到 output/save/ 目录,文件名格式为 《书名》_-作者_完整版_cleaned.txt。
七、总结
这套爬虫的核心价值不在于“爬取”,而在于“清洗”。网上90%的小说爬虫教程只教你如何获取数据,但没人告诉你获取到的数据有多脏。
我的做法是:宁可多花50%的时间在清洗上,也不要让垃圾数据污染你的阅读体验。
原文如下
《诛仙》
作者:萧鼎
下载时间:2025-12-11 11:25:44
==================================================
序章
----------------------------------------
时间:不明,应该在很早、很早以前hxyl8· cc
地点:神州浩土hxyl8· cc
天地不仁,以万物为刍狗!
这世间本是没有什么神仙的,但自太古以来,人类眼见周遭世界,诸般奇异之事,电闪雷鸣,又有天灾人祸,伤亡无数,哀鸿遍野,决非人力所能为,所能抵挡hxyl8· cc遂以为九天之上,有诸般神灵,九幽之下,亦是阴魂归处,阎罗殿堂hxyl8· cc
于是神仙之说,流传于世hxyl8· cc无数人类子民,诚心叩拜,向着自己臆想创造出的各种神明顶礼膜拜,祈福诉苦,香火鼎盛hxyl8· cc
自古以来,凡人无不有一死hxyl8· cc但世人皆恶死爱生,更有地府阎罗之说,平添了几分苦惧,在此之下,遂有长生不死之说hxyl8· cc
相比与其他生灵物种,人类或在体质上处于劣势,但万物灵长,却是绝无虚言hxyl8· cc在追求长生的原动力下,一代代聪明才智之士,前赴后继,投入毕生精力,苦苦钻研hxyl8· cc至今为止,虽然真正意义上的长生不死仍未找到,却有一些修真炼道之士,参透些天地造化,以凡人之身,掌握强横力量,借助各般秘宝法器之力,竟可震撼天地,有雷霆之威hxyl8· cc而一些得到高深的前辈,更传说已活上千年之久而不死hxyl8· cc世上之人以为得道成仙,便有更多人投入修真炼道之路hxyl8· cc
神州浩土,广瀚无边hxyl8· cc唯有中原大地,最是丰美肥沃,天下人口十之八九聚居于此hxyl8· cc而东南西北边荒之地,山险水恶,多凶兽猛禽,多恶瘴毒物,亦多蛮族夷民,虏毛饮血,是以人迹罕至hxyl8· cc而人间自古相传,有洪荒遗种,残存人世,藏于深山密谷,寿逾万年,却是无人得见hxyl8· cc
时至今日,人间修真炼道之人,多如过江之鲫,数不胜数hxyl8· cc又以神州浩土之广阔,人间奇人异士之多,故修炼之法道林林总总,俱不相同hxyl8· cc长生之法还未找到,彼此间却逐渐有了门派之分,正邪之别hxyl8· cc由之而起的门户之见,勾心斗角乃至争伐杀戮,在所多有hxyl8· cc
当长生不死看起来那般遥远而不可捉摸,修炼中所带来的力量,便逐渐成了许多人的目标hxyl8· cc
方今之世,正道大昌,邪魔退避hxyl8· cc中原大地山灵水秀,人气鼎盛,物产丰富,为正派诸家牢牢占据hxyl8· cc其中尤以“青云门”、“天音寺”、和“焚香谷”为三大支柱,是为领袖hxyl8· cc
这个故事,便是从“青云门”开始的hxyl8· cc
————————
清洗后如下,虽然效果并非十分完美,但已经去除了大量广告内容。
《诛仙》
作者:萧鼎。
下载时间:2025-12-11 11:25:44
==================================================
序章。
----------------------------------------
时间:不明,应该在很早、很早以前
地点:神州浩土
天地不仁,以万物为刍狗!
这世间本是没有什么神仙的,但自太古以来,人类眼见周遭世界,诸般奇异之事,电闪雷鸣,又有天灾人祸,伤亡无数,哀鸿遍野,决非人力所能为,所能抵挡 遂以为九天之上,有诸般神灵,九幽之下,亦是阴魂归处,阎罗殿堂
于是神仙之说,流传于世 无数人类子民,诚心叩拜,向着自己臆想创造出的各种神明顶礼膜拜,祈福诉苦,香火鼎盛
自古以来,凡人无不有一死 但世人皆恶死爱生,更有地府阎罗之说,平添了几分苦惧,在此之下,遂有长生不死之说
相比与其他生灵物种,人类或在体质上处于劣势,但万物灵长,却是绝无虚言 在追求长生的原动力下,一代代聪明才智之士,前赴后继,投入毕生精力,苦苦钻研 至今为止,虽然真正意义上的长生不死仍未找到,却有一些修真炼道之士,参透些天地造化,以凡人之身,掌握强横力量,借助各般秘宝法器之力,竟可震撼天地,有雷霆之威 而一些得到高深的前辈,更传说已活上千年之久而不死 世上之人以为得道成仙,便有更多人投入修真炼道之路
神州浩土,广瀚无边 唯有中原大地,最是丰美肥沃,天下人口十之八九聚居于此 而东南西北边荒之地,山险水恶,多凶兽猛禽,多恶瘴毒物,亦多蛮族夷民,虏毛饮血,是以人迹罕至 而人间自古相传,有洪荒遗种,残存人世,藏于深山密谷,寿逾万年,却是无人得见
时至今日,人间修真炼道之人,多如过江之鲫,数不胜数 又以神州浩土之广阔,人间奇人异士之多,故修炼之法道林林总总,俱不相同 长生之法还未找到,彼此间却逐渐有了门派之分,正邪之别 由之而起的门户之见,勾心斗角乃至争伐杀戮,在所多有
当长生不死看起来那般遥远而不可捉摸,修炼中所带来的力量,便逐渐成了许多人的目标
方今之世,正道大昌,邪魔退避 中原大地山灵水秀,人气鼎盛,物产丰富,为正派诸家牢牢占据 其中尤以“青云门”、“天音寺”、和“焚香谷”为三大支柱,是为领袖
这个故事,便是从“青云门”开始的
第一章 青云。