用Python写个小说爬虫还不够?我加了个AI标题生成器,结果爽到飞起!
异步爬虫:现在是单线程同步请求,太慢了,可以用aiohttp改成异步的,效率能提升好几倍。本地模型部署:调用API要花钱,而且有网络延迟,可以试试用Ollama部署本地模型,比如Qwen2-7B,效果也不差。排版优化:现在保存的TXT文件排版比较简单,可以进一步处理正文的换行和缩进,或者直接生成EPUB电子书。多网站适配:现在的代码只针对一个网站,可以写个配置文件,适配不同的小说网站结构。
最近在追一本玄幻小说,网站上广告弹得烦,而且章节标题起得太敷衍,比如“第123章 出手”,看了完全提不起劲。作为一个爱折腾的程序员,我干脆自己写了个爬虫,把小说内容扒下来,顺便用AI重新生成标题——没想到效果出奇的好,现在读起来都带感多了。
今天就把整个实现过程分享给大家,从爬虫搭建到标题生成,全是实战干货,踩过的坑也会一一说明。
一、需求拆解:我们要实现什么?
先理清楚思路,整个项目分成两个核心部分:
- 小说内容爬取:从目标网站获取章节标题、正文内容,保存到本地文件。
- 智能标题生成:基于章节正文内容,用AI生成更有吸引力、更符合章节情节的标题。
选目标网站的时候,我特意挑了个结构简单、反爬不太严的小说站(这里就不点名了,大家懂的都懂)。重点是学习技术,千万别用于商业用途,尊重版权哈。
二、爬虫部分:稳扎稳打,避开反爬坑
爬虫这东西,说简单也简单,说难也难——难就难在和反爬机制斗智斗勇。我这次踩了几个小坑,给大家提个醒。
2.1 技术选型
- 请求库:
requests,简单够用,没必要上Scrapy那么重的框架。 - 解析库:
BeautifulSoup4,解析HTML足够灵活。 - 文件操作:直接用Python内置的
open(),保存成TXT文件方便阅读。
2.2 实现步骤
先看目录页的结构,通常目录页里每个章节都有一个<a>标签,包含章节标题和链接。我们的思路是:
- 请求目录页,解析出所有章节的链接和原始标题。
- 遍历每个章节链接,请求正文页,解析出正文内容。
- 将原始标题、正文内容保存到本地,同时把正文传给标题生成模块。
上代码(关键部分):
import requests
from bs4 import BeautifulSoup
import time
import random
# 伪装请求头,这是最基本的反反爬
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
}
def get_chapter_links(catalog_url):
"""获取目录页所有章节链接"""
try:
response = requests.get(catalog_url, headers=headers, timeout=10)
response.encoding = 'utf-8' # 这里要注意网站编码,可能是gbk
soup = BeautifulSoup(response.text, 'html.parser')
# 假设章节链接在class为'chapter-list'的div里的a标签中
chapter_list = soup.select('.chapter-list a')
links = []
for item in chapter_list:
title = item.get_text().strip()
url = item['href']
# 有些网站链接是相对路径,需要拼接
if not url.startswith('http'):
url = 'https://www.example.com' + url
links.append({'title': title, 'url': url})
return links
except Exception as e:
print(f"获取目录失败: {e}")
return []
def get_chapter_content(chapter_url):
"""获取章节正文"""
try:
# 加个随机延时,避免请求太快被封
time.sleep(random.uniform(1, 3))
response = requests.get(chapter_url, headers=headers, timeout=10)
response.encoding = 'utf-8'
soup = BeautifulSoup(response.text, 'html.parser')
# 假设正文在class为'content'的div里
content_div = soup.select_one('.content')
if content_div:
# 把br标签换成换行符,保持排版
for br in content_div.find_all('br'):
br.replace_with('\n')
return content_div.get_text().strip()
else:
return ""
except Exception as e:
print(f"获取正文失败: {e}")
return ""
2.3 踩坑记录
- 编码问题:有些老小说站用的是
gbk编码,response.encoding要改成'gbk',不然保存下来全是乱码。 - 相对路径链接:解析出来的链接可能是
/chapter/123.html,一定要记得拼接上域名。 - 请求频率:一开始我没加延时,结果爬了20章就被封IP了,后来加了1-3秒的随机延时,就稳了。
三、标题生成:让AI帮我们“标题党”一回
爬下来内容只是第一步,重点是怎么让标题变得吸引人。我试了两种方案:一种是用本地的小模型(比如Qwen-7B),另一种是调用OpenAI的API。考虑到大部分人可能没有本地部署的条件,这里就讲调用API的方式(其实原理都一样)。
3.1 Prompt设计
这是最关键的一步。直接让AI“生成个标题”肯定不行,得给它明确的指令。我是这么设计Prompt的:
你是一个资深的玄幻小说编辑。请根据以下章节内容,生成一个不超过20字的标题。
要求:1. 突出本章的核心冲突或关键情节;2. 带有一点悬念感,吸引读者继续阅读;3. 不要使用“第X章”这样的格式。
章节内容:
{content}
3.2 代码实现
这里用openai库(如果用国内的API,比如智谱、通义千问,改一下base_url就行):
from openai import OpenAI
# 初始化客户端,这里以国内某API为例
client = OpenAI(
api_key="your-api-key",
base_url="https://api.example.com/v1"
)
def generate_new_title(content):
"""基于正文生成新标题"""
# 正文太长的话,只取前1000字,避免token超限
truncated_content = content[:1000]
prompt = f"""你是一个资深的玄幻小说编辑。请根据以下章节内容,生成一个不超过20字的标题。
要求:1. 突出本章的核心冲突或关键情节;2. 带有一点悬念感,吸引读者继续阅读;3. 不要使用“第X章”这样的格式。
章节内容:
{truncated_content}"""
try:
response = client.chat.completions.create(
model="gpt-4o-mini", # 选个便宜又好用的模型
messages=[{"role": "user", "content": prompt}],
temperature=0.7 # 温度稍高一点,让标题更灵活
)
new_title = response.choices[0].message.content.strip()
# 去掉可能出现的引号
new_title = new_title.replace('"', '').replace("'", "")
return new_title
except Exception as e:
print(f"生成标题失败: {e}")
return None
3.3 效果对比
给大家看几个我实际生成的例子:
-
原标题:第123章 出手
-
新标题:少年一掌震退强敌,全场震惊
-
原标题:第124章 秘境
-
新标题:神秘洞府现世,暗藏惊天机缘
是不是感觉一下子就有内味儿了?
四、整合与保存:把成果落地
最后把所有模块串起来,把原始标题、新标题、正文都保存到文件里:
def main():
catalog_url = "https://www.example.com/novel/123.html"
chapters = get_chapter_links(catalog_url)
if not chapters:
print("没有获取到章节")
return
with open('novel.txt', 'w', encoding='utf-8-sig') as f:
for i, chapter in enumerate(chapters):
print(f"正在处理第 {i+1}/{len(chapters)} 章: {chapter['title']}")
content = get_chapter_content(chapter['url'])
if not content:
continue
new_title = generate_new_title(content)
if not new_title:
new_title = chapter['title'] # 生成失败就用原标题
# 写入文件
f.write(f"【{new_title}】\n")
f.write(f"(原标题:{chapter['title']})\n\n")
f.write(content)
f.write("\n\n" + "="*50 + "\n\n")
print("全部完成!")
if __name__ == "__main__":
main()
五、总结与优化方向
这次折腾下来,虽然功能实现了,但还有很多可以优化的地方:
- 异步爬虫:现在是单线程同步请求,太慢了,可以用
aiohttp改成异步的,效率能提升好几倍。 - 本地模型部署:调用API要花钱,而且有网络延迟,可以试试用
Ollama部署本地模型,比如Qwen2-7B,效果也不差。 - 排版优化:现在保存的TXT文件排版比较简单,可以进一步处理正文的换行和缩进,或者直接生成EPUB电子书。
- 多网站适配:现在的代码只针对一个网站,可以写个配置文件,适配不同的小说网站结构。
最后再啰嗦一句:爬虫技术是把双刃剑,大家一定要在合法合规的前提下使用,不要用于商业用途,也不要给目标网站造成太大压力。
好了,今天的分享就到这里。你们平时写爬虫都遇到过什么有趣的坑?或者有什么更好的标题生成技巧?欢迎一起交流。
更多推荐


所有评论(0)