[Python] 纯文本查看 复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
小说下载器 (适配醉爱小说网 www.zuiaixs.net)
使用方法:python3 novel_downloader.py
"""
import requests
import re
import time
import os
from concurrent.futures import ThreadPoolExecutor, as_completed
# ==================== 配置区(修改这里下载其他小说) ====================
BASE_URL = "https://www.zuiaixs.net"
BOOK_ID = "2024" # 书籍ID,从目录页URL获取
BOOK_NAME = "不良之年少轻狂" # 书名
AUTHOR = "抚琴的人" # 作者
OUTPUT_FILE = f"{BOOK_NAME}.txt"
MAX_WORKERS = 5 # 并发数(别太大,防封)
DELAY = 1 # 每章启动间隔(秒)
# ======================================================================
HEADERS = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.0',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
'Referer': BASE_URL,
}
session = requests.Session()
session.headers.update(HEADERS)
def get_catalog():
"""
获取章节目录(提取所有 <dd><a> 链接)
返回章节列表,每个元素为 {'title': 标题, 'url': 完整URL, 'chapter_id': 章节ID}
"""
url = f"{BASE_URL}/read/{BOOK_ID}/"
print(f"正在获取目录: {url}")
try:
resp = session.get(url, timeout=30)
resp.encoding = 'utf-8'
html = resp.text
# 匹配所有 <dd><a href="/read/2024/数字.html">标题</a>
pattern = rf'<dd><a href="/read/{BOOK_ID}/(\d+\.html)"[^>]*>([^<]+)</a></dd>'
matches = re.findall(pattern, html)
chapters = []
seen = set()
for chapter_id, title in matches:
if chapter_id not in seen:
seen.add(chapter_id)
chapters.append({
'title': title.strip(),
'url': f"{BASE_URL}/read/{BOOK_ID}/{chapter_id}",
'chapter_id': chapter_id.replace('.html', '')
})
print(f"共找到 {len(chapters)} 个章节")
return chapters
except Exception as e:
print(f"获取目录失败: {e}")
return []
def fetch_page_content(url):
"""获取单页HTML内容"""
try:
resp = session.get(url, timeout=30)
resp.encoding = 'utf-8'
return resp.text
except Exception:
return None
def extract_content_from_page(html):
"""从单页HTML中提取正文(<div id="content"> 中的 <p> 标签)"""
if not html:
return ""
# 定位 id="content"
start = html.find('id="content"')
if start == -1:
return ""
start = html.find('>', start) + 1
end = html.find('</div>', start)
if end == -1:
return ""
content_html = html[start:end]
# 提取所有 <p> 标签中的内容
p_pattern = r'<p[^>]*>(.*?)</p>'
p_matches = re.findall(p_pattern, content_html, re.DOTALL)
content_lines = []
for p_text in p_matches:
clean_text = re.sub(r'<[^>]+>', '', p_text)
clean_text = clean_text.strip()
# 过滤广告
ad_keywords = [
'小主,这个章节后面还有哦', '请点击下一页继续阅读',
'后面更精彩', '本章未完', '点击下一页'
]
if clean_text and not any(ad in clean_text for ad in ad_keywords):
content_lines.append(clean_text)
return '\n\n'.join(content_lines)
def download_chapter(chapter, index, total):
"""下载单章(自动处理分页)"""
title = chapter['title']
chapter_id = chapter['chapter_id']
base_url = f"{BASE_URL}/read/{BOOK_ID}/{chapter_id}"
all_content = []
page_num = 1
while True:
if page_num == 1:
url = f"{base_url}.html"
else:
url = f"{base_url}_{page_num}.html"
html = fetch_page_content(url)
if html is None:
break
content = extract_content_from_page(html)
if not content:
break
all_content.append(content)
page_num += 1
full_content = '\n\n'.join(all_content)
if full_content:
print(f" [{index+1}/{total}] ✓ {title} ({len(full_content)} 字)")
return {'title': title, 'content': full_content, 'success': True}
else:
print(f" [{index+1}/{total}] ✗ {title} (内容为空)")
return {'title': title, 'content': '', 'success': False}
def download_all(chapters):
"""并发下载所有章节"""
results = []
total = len(chapters)
print(f"\n开始下载,共 {total} 个章节,并发数: {MAX_WORKERS},间隔: {DELAY}秒")
print("=" * 60)
with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
future_to_idx = {}
for i, ch in enumerate(chapters):
future = executor.submit(download_chapter, ch, i, total)
future_to_idx[future] = i
time.sleep(DELAY)
for future in as_completed(future_to_idx):
idx = future_to_idx[future]
try:
result = future.result()
results.append((idx, result))
except Exception as e:
print(f" 第{idx+1}个章节下载异常: {e}")
results.sort(key=lambda x: x[0])
return [r[1] for r in results]
def save_to_txt(results, output_file):
"""保存为TXT文件"""
with open(output_file, 'w', encoding='utf-8') as f:
f.write(f"《{BOOK_NAME}》\n")
f.write(f"作者:{AUTHOR}\n")
f.write(f"来源:醉爱小说网 (www.zuiaixs.net)\n")
f.write(f"总章节数:{len(results)} 章\n")
f.write("=" * 50 + "\n\n")
success_count = 0
for result in results:
if result['success']:
f.write(f"\n{result['title']}\n")
f.write("-" * 40 + "\n\n")
f.write(result['content'])
f.write("\n\n")
success_count += 1
f.write("\n\n" + "=" * 50 + "\n")
f.write(f"全部下载完毕\n")
f.write(f"成功下载: {success_count}/{len(results)} 章\n")
return success_count
def main():
print("=" * 60)
print(f" 《{BOOK_NAME}》小说下载器 (适配醉爱小说网)")
print(f" 作者: {AUTHOR}")
print("=" * 60)
chapters = get_catalog()
if not chapters:
print("获取目录失败,请检查网络或网站是否可访问")
return
results = download_all(chapters)
print("\n" + "=" * 60)
print("正在保存文件...")
success_count = save_to_txt(results, OUTPUT_FILE)
if os.path.exists(OUTPUT_FILE):
file_size = os.path.getsize(OUTPUT_FILE) / 1024
print(f"\n✓ 保存完成: {OUTPUT_FILE}")
print(f" 文件大小: {file_size:.1f} KB")
print(f" 成功下载: {success_count}/{len(chapters)} 章")
else:
print("\n✗ 保存失败,请检查写入权限。")
print("=" * 60)
if __name__ == '__main__':
main()