D:\tools\python\python.exe D:\历史项目留存2\诺褀2025\python加工浦发模型模拟\py搭建\pythonProject1\KS指标计算\KS01读取数据.py 成功读取 160 条记录 SCORE BAD_FLAG 0 530.0 0 1 585.0 0 2 560.0 0 3 602.0 0 4 650.0 0 KS值计算结果: 最大KS值: 0.2423 KS值对应的分数点: 573.0 D:\tools\pycharm\PyCharm 2024.2\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:82: UserWarning: Glyph 32047 (\N{CJK UNIFIED IDEOGRAPH-7D2F}) missing from font(s) DejaVu Sans. FigureCanvasAgg.draw(self) D:\tools\pycharm\PyCharm 2024.2\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:82: UserWarning: Glyph 35745 (\N{CJK UNIFIED IDEOGRAPH-8BA1}) missing from font(s) DejaVu Sans. FigureCanvasAgg.draw(self) D:\tools\pycharm\PyCharm 2024.2\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:82: UserWarning: Glyph 21344 (\N{CJK UNIFIED IDEOGRAPH-5360}) missing from font(s) DejaVu Sans. FigureCanvasAgg.draw(self) D:\tools\pycharm\PyCharm 2024.2\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:82: UserWarning: Glyph 27604 (\N{CJK UNIFIED IDEOGRAPH-6BD4}) missing from font(s) DejaVu Sans. FigureCanvasAgg.draw(self) D:\tools\pycharm\PyCharm 2024.2\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:82: UserWarning: Glyph 26354 (\N{CJK UNIFIED IDEOGRAPH-66F2}) missing from font(s) DejaVu Sans. FigureCanvasAgg.draw(self) D:\tools\pycharm\PyCharm 2024.2\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:82: UserWarning: Glyph 32447 (\N{CJK UNIFIED IDEOGRAPH-7EBF}) missing from font(s) DejaVu Sans. FigureCanvasAgg.draw(self) D:\tools\pycharm\PyCharm 2024.2\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:82: UserWarning: Glyph 20540 (\N{CJK UNIFIED IDEOGRAPH-503C}) missing from font(s) DejaVu Sans. FigureCanvasAgg.draw(self) D:\tools\pycharm\PyCharm 2024.2\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:82: UserWarning: Glyph 20998 (\N{CJK UNIFIED IDEOGRAPH-5206}) missing from font(s) DejaVu Sans. FigureCanvasAgg.draw(self) D:\tools\pycharm\PyCharm 2024.2\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:82: UserWarning: Glyph 25968 (\N{CJK UNIFIED IDEOGRAPH-6570}) missing from font(s) DejaVu Sans. FigureCanvasAgg.draw(self) D:\tools\pycharm\PyCharm 2024.2\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:82: UserWarning: Glyph 22351 (\N{CJK UNIFIED IDEOGRAPH-574F}) missing from font(s) DejaVu Sans. FigureCanvasAgg.draw(self) D:\tools\pycharm\PyCharm 2024.2\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:82: UserWarning: Glyph 23458 (\N{CJK UNIFIED IDEOGRAPH-5BA2}) missing from font(s) DejaVu Sans. FigureCanvasAgg.draw(self) D:\tools\pycharm\PyCharm 2024.2\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:82: UserWarning: Glyph 25143 (\N{CJK UNIFIED IDEOGRAPH-6237}) missing from font(s) DejaVu Sans. FigureCanvasAgg.draw(self) D:\tools\pycharm\PyCharm 2024.2\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:82: UserWarning: Glyph 22909 (\N{CJK UNIFIED IDEOGRAPH-597D}) missing from font(s) DejaVu Sans. FigureCanvasAgg.draw(self) D:\tools\pycharm\PyCharm 2024.2\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:82: UserWarning: Glyph 28857 (\N{CJK UNIFIED IDEOGRAPH-70B9}) missing from font(s) DejaVu Sans. FigureCanvasAgg.draw(self) D:\tools\pycharm\PyCharm 2024.2\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:85: UserWarning: Glyph 20998 (\N{CJK UNIFIED IDEOGRAPH-5206}) missing from font(s) DejaVu Sans. self.print_png(buf) D:\tools\pycharm\PyCharm 2024.2\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:85: UserWarning: Glyph 25968 (\N{CJK UNIFIED IDEOGRAPH-6570}) missing from font(s) DejaVu Sans. self.print_png(buf) D:\tools\pycharm\PyCharm 2024.2\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:85: UserWarning: Glyph 32047 (\N{CJK UNIFIED IDEOGRAPH-7D2F}) missing from font(s) DejaVu Sans. self.print_png(buf) D:\tools\pycharm\PyCharm 2024.2\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:85: UserWarning: Glyph 35745 (\N{CJK UNIFIED IDEOGRAPH-8BA1}) missing from font(s) DejaVu Sans. self.print_png(buf) D:\tools\pycharm\PyCharm 2024.2\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:85: UserWarning: Glyph 21344 (\N{CJK UNIFIED IDEOGRAPH-5360}) missing from font(s) DejaVu Sans. self.print_png(buf) D:\tools\pycharm\PyCharm 2024.2\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:85: UserWarning: Glyph 27604 (\N{CJK UNIFIED IDEOGRAPH-6BD4}) missing from font(s) DejaVu Sans. self.print_png(buf) D:\tools\pycharm\PyCharm 2024.2\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:85: UserWarning: Glyph 26354 (\N{CJK UNIFIED IDEOGRAPH-66F2}) missing from font(s) DejaVu Sans. self.print_png(buf) D:\tools\pycharm\PyCharm 2024.2\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:85: UserWarning: Glyph 32447 (\N{CJK UNIFIED IDEOGRAPH-7EBF}) missing from font(s) DejaVu Sans. self.print_png(buf) D:\tools\pycharm\PyCharm 2024.2\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:85: UserWarning: Glyph 20540 (\N{CJK UNIFIED IDEOGRAPH-503C}) missing from font(s) DejaVu Sans. self.print_png(buf) D:\tools\pycharm\PyCharm 2024.2\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:85: UserWarning: Glyph 22351 (\N{CJK UNIFIED IDEOGRAPH-574F}) missing from font(s) DejaVu Sans. self.print_png(buf) D:\tools\pycharm\PyCharm 2024.2\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:85: UserWarning: Glyph 23458 (\N{CJK UNIFIED IDEOGRAPH-5BA2}) missing from font(s) DejaVu Sans. self.print_png(buf) D:\tools\pycharm\PyCharm 2024.2\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:85: UserWarning: Glyph 25143 (\N{CJK UNIFIED IDEOGRAPH-6237}) missing from font(s) DejaVu Sans. self.print_png(buf) D:\tools\pycharm\PyCharm 2024.2\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:85: UserWarning: Glyph 22909 (\N{CJK UNIFIED IDEOGRAPH-597D}) missing from font(s) DejaVu Sans. self.print_png(buf) D:\tools\pycharm\PyCharm 2024.2\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:85: UserWarning: Glyph 28857 (\N{CJK UNIFIED IDEOGRAPH-70B9}) missing from font(s) DejaVu Sans. self.print_png(buf) Process finished with exit code 0

时间: 2025-07-23 14:55:08 AIGC 浏览: 31
<think>问题分析:从错误日志中可以看出,程序在绘制KS曲线时出现了中文字符显示问题。这是因为matplotlib默认使用的字体(DejaVu Sans)不包含中文字符。为了解决这个问题,我们需要设置matplotlib使用支持中文的字体。 解决方案: 1. 指定中文字体:我们可以使用系统自带的字体(如SimHei、Microsoft YaHei等)或者下载第三方中文字体。 2. 设置matplotlib的字体参数:通过`plt.rcParams`来设置字体。 具体步骤: - 在绘制图表前,设置中文字体。 - 注意:不同操作系统可用的中文字体可能不同,需要根据实际情况调整。 修改后的代码示例: ```python # 在绘图前设置中文字体 import matplotlib.pyplot as plt plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签 plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号 # 然后进行绘图 plt.figure(figsize=(10, 6)) plt.plot(df['SCORE'], df['cum_bad_pct'], label='累计坏客户占比') plt.plot(df['SCORE'], df['cum_good_pct'], label='累计好客户占比') plt.axvline(x=ks_point, color='r', linestyle='--', label=f'KS点(分数={ks_point})') plt.title(f'KS曲线 (KS值={ks_value:.4f})') plt.xlabel('分数') plt.ylabel('累计占比') plt.legend() plt.grid(True) plt.show() ``` ### 常见中文字体推荐: - Windows: `SimHei`(黑体)、`Microsoft YaHei`(微软雅黑) - Mac: `Arial Unicode MS`、`PingFang SC`(苹方) - Linux: `WenQuanYi Micro Hei`(文泉驿微米黑) ### 备选方案:使用绝对路径指定字体文件 如果上述字体名称不生效,可以尝试使用绝对路径指定字体文件: ```python from matplotlib.font_manager import FontProperties # 指定字体文件路径(根据实际路径修改) font_path = 'C:/Windows/Fonts/simhei.ttf' # Windows系统下黑体路径 my_font = FontProperties(fname=font_path) # 在绘图时指定字体 plt.title(f'KS曲线 (KS值={ks_value:.4f})', fontproperties=my_font) plt.xlabel('分数', fontproperties=my_font) plt.ylabel('累计占比', fontproperties=my_font) # 对于图例,需要在legend中设置prop plt.legend(prop=my_font) ``` ### 注意事项: 1. 设置全局字体后,如果仍然有部分字符无法显示,可以尝试使用`FontProperties`单独设置。 2. 在Linux服务器上运行时,可能需要安装中文字体包。 ### 调试建议: - 打印可用字体列表,确认是否存在所需中文字体: ```python import matplotlib.font_manager fonts = matplotlib.font_manager.findSystemFonts() chinese_fonts = [f for f in fonts if any(name in f.lower() for name in ['simhei', 'yahei', 'pingfang', 'stheitisc', 'wqy'])] print("可用的中文字体:", chinese_fonts) ``` §§相关问题§§ 1. 在matplotlib中,如何避免中文标签显示为方框或乱码? 2. 除了设置字体,还有哪些方法可以解决matplotlib中文显示问题? 3. 在不同操作系统(Windows/Linux/Mac)下,如何配置matplotlib以支持中文显示? 4. 当使用远程服务器(无图形界面)时,如何保存包含中文的图表?</think>您遇到的错误是由于Matplotlib默认字体不支持中文字符导致的。当尝试绘制包含中文标签(如"累计坏客户占比"、"累计好客户占比"等)的图表时,系统找不到对应的字形,从而产生`UserWarning: Glyph ... missing from font(s)`警告。以下是完整的解决方案: ### 解决方案:设置中文字体支持 ```python import matplotlib as mpl import matplotlib.pyplot as plt import matplotlib.font_manager as fm # 解决方案1:使用系统支持的中文字体 plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'KaiTi', 'SimSun'] # 常用中文字体 plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题 # 解决方案2:指定具体字体文件(更可靠) # 查找系统支持的中文字体 chinese_fonts = [f.name for f in fm.fontManager.ttflist if any('CJK' in s for s in f.get_sfnt())] print("可用的中文字体:", chinese_fonts) # 选择一种可用字体(以微软雅黑为例) if 'Microsoft YaHei' in chinese_fonts: plt.rcParams['font.family'] = 'Microsoft YaHei' elif 'SimHei' in chinese_fonts: plt.rcParams['font.family'] = 'SimHei' else: # 使用字体文件路径(适用于所有系统) font_path = 'C:/Windows/Fonts/msyh.ttc' # Windows微软雅黑路径 # font_path = '/System/Library/Fonts/PingFang.ttc' # Mac苹方字体 prop = fm.FontProperties(fname=font_path) plt.rcParams['font.family'] = prop.get_name() # 绘制KS曲线 plt.figure(figsize=(10, 6)) plt.plot(df['SCORE'], df['cum_bad_pct'], label='累计坏客户占比') plt.plot(df['SCORE'], df['cum_good_pct'], label='累计好客户占比') plt.axvline(x=ks_point, color='r', linestyle='--', label=f'KS点(分数={ks_point})') plt.title(f'KS曲线 (KS值={ks_value:.4f})') plt.xlabel('分数') plt.ylabel('累计占比') plt.legend() plt.grid(True) plt.savefig('ks_curve.png', dpi=300, bbox_inches='tight') # 保存为图片文件 plt.show() ``` ### 关键步骤说明: 1. **字体配置优先级**: - 首选系统自带中文字体(如微软雅黑、黑体) - 次选通过字体文件路径指定 - 最后尝试使用通用CJK字体 2. **跨平台字体解决方案**: ```python # Windows字体路径 win_fonts = [ 'C:/Windows/Fonts/simhei.ttf', # 黑体 'C:/Windows/Fonts/simsun.ttc', # 宋体 'C:/Windows/Fonts/msyh.ttc' # 微软雅黑 ] # MacOS字体路径 mac_fonts = [ '/System/Library/Fonts/PingFang.ttc', # 苹方 '/Library/Fonts/Arial Unicode.ttf' ] # Linux字体路径 linux_fonts = [ '/usr/share/fonts/truetype/wqy/wqy-microhei.ttc' # 文泉驿微米黑 ] ``` 3. **避免中文问题的替代方案**: ```python # 使用英文标签替代 plt.plot(df['SCORE'], df['cum_bad_pct'], label='Cumulative Bad Rate') plt.plot(df['SCORE'], df['cum_good_pct'], label='Cumulative Good Rate') plt.axvline(x=ks_point, color='r', linestyle='--', label=f'KS Point (Score={ks_point})') plt.title(f'KS Curve (KS={ks_value:.4f})') plt.xlabel('Score') plt.ylabel('Cumulative Percentage') ``` ### 调试建议: 1. **检查可用字体**: ```python import matplotlib.font_manager fonts = [f.name for f in matplotlib.font_manager.fontManager.ttflist] chinese_fonts = sorted(set(fonts) & set(['SimHei', 'Microsoft YaHei', 'KaiTi', 'SimSun', 'FangSong'])) print("可用的中文字体:", chinese_fonts) ``` 2. **验证字体支持**: ```python test_text = "中文测试 123 ABC" plt.text(0.5, 0.5, test_text, fontsize=12, ha='center') plt.savefig('font_test.png') ``` 3. **使用Seaborn库(内置更好字体支持)**: ```python import seaborn as sns sns.set(font='SimHei') # 设置中文字体 sns.lineplot(x='SCORE', y='cum_bad_pct', data=df, label='坏客户占比') sns.lineplot(x='SCORE', y='cum_good_pct', data=df, label='好客户占比') plt.axvline(ks_point, color='r', linestyle='--') ```
阅读全文

相关推荐

import requests from bs4 import BeautifulSoup import re import time import os import random from fake_useragent import UserAgent from urllib.parse import urlparse, urljoin, urlunparse import logging # 配置日志 logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler("novel_downloader.log"), logging.StreamHandler() ] ) # 创建用户代理生成器 ua = UserAgent() # 需要过滤的导航元素列表(已添加笔趣阁专用元素) NAV_ELEMENTS = [ "首页", "关灯", "字体:", "大", "中", "小", "上一页", "返回目录", "下一页", "加入书签", "推荐票", "返回书页", "目录", "设置", "书架", "加入书架", "上一章", "下一章", "书签", "投票", "举报", "本章未完,请点击下一页继续阅读", "本章结束,请点击下一章继续阅读", "返回", "电脑版", "手机版", "APP版", "客户端", "介绍", "足迹", "超大", "进书架", "本章未完 点击下一页继续阅读", "顶部", "底部", "页脚", "页眉", "章节列表", "加书签", "我的书架", "阅读历史", "本章已完", "请收藏本站", "请记住本书首发域名", "天才一秒记住本站地址", "顶点小说", "笔趣阁", "更新最快", "推荐阅读", "上一节", "下一节", "目录页", "返回顶部", "加入书签", "投票推荐", "章节报错","热门推荐", "新书推荐", "最新网址", "蚂蚁文学", "本章结束" "加入书签,方便阅读", # 笔趣阁专用 "本章未完,点击下一页继续阅读", # 笔趣阁专用 "热门推荐", "新书推荐", "推荐阅读", "完本小说", "排行榜单", "小说推荐", "猜你喜欢", "编辑推荐", "小说排行", "精品推荐", "最新小说" ] # 特殊换行保留标签 PRESERVE_LINEBREAK_TAGS = ["br", "p", "div", "pre", "blockquote"] # 常见内容容器选择器(添加笔趣阁专用选择器) CONTENT_SELECTORS = [ ('div', 'Readarea ReadAjax_content'), # 笔趣阁专用 ('div', 'content'), # 通用内容区 ('div', 'novel-content'), # 小说专用 ('div', 'size16 color5 pt-read-text'), ('div', 'pt-read-text'), ('div', 'chapter-content'), ('div', 'novelcontent'), ('div', 'chapter-content'), ('article', None), ('div', 'chapter_body'), ('div', 'read-content'), ('div', 'txt_cont'), ('div', 'content-body'), ('div', 'read-box'), ('div', 'chapter-content-inner'), ('div', 'chapter-text'), ('div', 'content-main'), ('div', 'chapter-container'), ('div', 'chapter-content'), ('div', 'chapter'), ('div', 'main-content'), ('div', 'entry-content'), ('div', 'article-content'), ('div', 'content-body'), ] # 下一页链接选择器(优化后的选择器) NEXT_PAGE_SELECTORS = [ # 优先匹配bottem区域 ('div', 'bottem1', 'a', re.compile(r'下一[页章]|继续阅读')), ('div', 'bottem2', 'a', re.compile(r'下一[页章]|继续阅读')), # 笔趣阁专用选择器 ('a', {'id': 'pt_next'}), # 优先匹配id=pt_next的链接 # 通用选择器 ('a', re.compile(r'下一页|下一章|下一节|继续阅读'), None), ('button', re.compile(r'下一页|下一章|下一节|继续阅读'), 'parent_a'), ('a', None, re.compile(r'next-page|next|next-chapter')), ('a', None, 'next'), ('a', None, 'bt_next'), ('a', None, 'btn-next'), ('a', None, 'nextChapter'), ('a', None, 'next_chapter'), ('a', None, 'chapter-next'), ('a', None, 'btn-next-page'), ('a', {'id': 'nextChapterBtn'}), ('a', {'id': 'next_chapter'}), ('a', {'id': 'next_page'}), ('a', {'id': 'btn_next'}), ('a', {'id': 'nextChapter'}), ('a', {'class': 'btn-info'}), ('a', {'class': 'next-page'}), ('a', {'class': 'next-chapter'}), ('a', {'class': 'next-button'}), ] def normalize_url(url, base_url): """规范化URL,确保正确处理相对路径和绝对路径""" parsed_base = urlparse(base_url) parsed_url = urlparse(url) # 如果URL是绝对路径,直接返回 if parsed_url.scheme and parsed_url.netloc: return url # 处理相对路径 if url.startswith('//'): return f"{parsed_base.scheme}:{url}" # 处理以/开头的路径 if url.startswith('/'): return f"{parsed_base.scheme}://{parsed_base.netloc}{url}" # 处理相对路径 base_path = parsed_base.path.rsplit('/', 1)[0] if '.' in parsed_base.path else parsed_base.path return f"{parsed_base.scheme}://{parsed_base.netloc}{base_path}/{url}" def extract_content_and_next_link(html, base_url): """从HTML中提取内容并找到下一页链接,优化换行处理""" soup = BeautifulSoup(html, 'html.parser') content_div = None # 新增过滤:移除推荐区域 for ad_section in content_div.find_all(class_=re.compile(r"recommend|hot|rank|footer_link|banner")): ad_section.decompose() # 移除特定ID的广告区块 for ad_id in ["hm_t_125039", "banner", "footer_link"]: if content_div.find(id=ad_id): content_div.find(id=ad_id).decompose() # 尝试多种内容容器选择器 for selector in CONTENT_SELECTORS: tag, class_name = selector if class_name: content_div = soup.find(tag, class_=class_name) else: content_div = soup.find(tag) if content_div: logging.info(f"使用选择器: {selector} 找到内容容器") break # 兜底策略 if not content_div: logging.warning("使用body作为内容容器") content_div = soup.find('body') if not content_div: logging.error("无法找到内容容器") return "", None # 1. 移除导航元素 for nav_text in NAV_ELEMENTS: for element in content_div.find_all(string=re.compile(re.escape(nav_text))): element.extract() for ad_class in ["lm", "footer_link", "footer_cont", "reader_mark"]: for element in content_div.find_all(class_=ad_class): element.decompose() # 2. 移除script/style标签 for script in content_div.find_all(['script', 'style']): script.extract() # 3. 清理空白字符 for nbsp in content_div.find_all(string=re.compile(r'\u00a0')): nbsp.replace_with(nbsp.replace('\u00a0', ' ')) # 4. 处理换行标签 for tag in PRESERVE_LINEBREAK_TAGS: for element in content_div.find_all(tag): element.append('\n') # 5. 获取原始文本 raw_text = content_div.get_text(separator='\n', strip=False) # 6. 文本优化处理 cleaned_text = re.sub(r'\n{3,}', '\n\n', raw_text) # 多个连续换行合并 cleaned_text = re.sub(r'(\S)\n(\S)', r'\1 \2', cleaned_text) # 合并单字换行 cleaned_text = re.sub(r'\n\s+\n', '\n\n', cleaned_text) # 清理空白行 cleaned_text = re.sub(r'^\s+', '', cleaned_text, flags=re.MULTILINE) # 删除行首空白 # 在现有的文本清理之后添加 cleaned_text = re.sub(r'热门推荐.+?推荐阅读', '', cleaned_text, flags=re.DOTALL) cleaned_text = re.sub(r'新书推荐.+?加入书签', '', cleaned_text, flags=re.DOTALL) cleaned_text = re.sub(r'本章未完.+?继续阅读', '', cleaned_text) # 7. 添加章节标题 title_tag = soup.find('title') chapter_title = title_tag.text.split('-')[0] if title_tag else "未知章节" cleaned_text = f"## {chapter_title} ##\n\n" + cleaned_text.strip() # 8. 保留诗歌格式 cleaned_text = re.sub(r'(\n {4,}.+?\n)\n+', r'\1\n', cleaned_text) cleaned_text = re.sub(r'\n{2,}', '\n\n', cleaned_text) # 多个空行合并 # 查找下一页链接 - 优化后的逻辑 next_link = None # 1. 查找JavaScript变量(扩展匹配规则) script_tags = soup.find_all('script') for script in script_tags: if script.string: # 扩展正则表达式匹配更多变量名 match = re.search( r'var\s+(?:nexturl|nextUrl|next_page_url|next_page|nextChapterUrl)\s*=\s*["\'](.*?)["\'];', script.string ) if match: next_link = match.group(1) if '<' in next_link: link_soup = BeautifulSoup(next_link, 'html.parser') a_tag = link_soup.find('a') if a_tag and 'href' in a_tag.attrs: next_link = a_tag['href'] next_link = normalize_url(next_link, base_url) logging.info(f"通过JavaScript变量找到下一页: {next_link}") return cleaned_text + "\n\n", next_link # 2. 改进的选择器匹配逻辑 for selector in NEXT_PAGE_SELECTORS: try: # 处理四元素选择器(新增类型) if len(selector) == 4: parent_tag, parent_class, child_tag, text_pattern = selector parent_element = soup.find(parent_tag, class_=parent_class) if parent_element: element = parent_element.find(child_tag, string=text_pattern) if element and 'href' in element.attrs: next_link = element['href'] next_link = normalize_url(next_link, base_url) logging.info(f"通过选择器 {selector} 找到下一页: {next_link}") return cleaned_text + "\n\n", next_link # 处理三元素选择器 elif len(selector) == 3: tag, *params = selector element = None if isinstance(params[0], dict): # 属性选择器 element = soup.find(tag, attrs=params[0]) elif hasattr(params[0], 'match') and isinstance(params[1], str): # 文本和类名选择器 text_pattern, class_name = params element = soup.find(tag, string=text_pattern, class_=class_name) elif hasattr(params[0], 'match'): # 文本匹配 element = soup.find(tag, string=params[0]) elif isinstance(params[1], str): # 类名匹配 element = soup.find(tag, class_=params[1]) if element and 'href' in element.attrs: next_link = element['href'] next_link = normalize_url(next_link, base_url) logging.info(f"通过选择器 {selector} 找到下一页: {next_link}") return cleaned_text + "\n\n", next_link # 处理二元素选择器 elif len(selector) == 2: tag, param = selector element = None if isinstance(param, dict): element = soup.find(tag, attrs=param) elif isinstance(param, str): element = soup.find(tag, class_=param) elif hasattr(param, 'match'): element = soup.find(tag, string=param) if element and 'href' in element.attrs: next_link = element['href'] next_link = normalize_url(next_link, base_url) logging.info(f"通过选择器 {selector} 找到下一页: {next_link}") return cleaned_text + "\n\n", next_link except Exception as e: logging.warning(f"选择器 {selector} 匹配出错: {str(e)}") continue logging.warning("未找到下一页链接") return cleaned_text + "\n\n", None def get_random_headers(): """生成随机的请求头""" return { 'User-Agent': ua.random, 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 'Accept-Language': 'zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3', 'Accept-Encoding': 'gzip, deflate, br', 'Connection': 'keep-alive', 'Upgrade-Insecure-Requests': '1', 'Cache-Control': 'max-age=0', 'DNT': '1', 'Referer': 'https://www.dbxsd.com/' } def detect_encoding(response): """智能检测页面编码""" if 'content-type' in response.headers: content_type = response.headers['content-type'].lower() if 'charset=' in content_type: return content_type.split('charset=')[-1] soup = BeautifulSoup(response.content, 'html.parser', from_encoding='iso-8859-1') meta_tag = soup.find('meta', charset=True) if meta_tag: return meta_tag['charset'] meta_tag = soup.find('meta', {'http-equiv': re.compile(r'content-type', re.I)}) if meta_tag and 'content' in meta_tag.attrs: content = meta_tag['content'].lower() if 'charset=' in content: return content.split('charset=')[-1] return 'utf-8' def download_novel(start_url, output_file="novel.txt", max_retries=5): """下载整本小说""" parsed_url = urlparse(start_url) base_url = f"{parsed_url.scheme}://{parsed_url.netloc}" session = requests.Session() session.headers.update({ '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', 'Connection': 'keep-alive', 'Cache-Control': 'max-age=0', 'DNT': '1', 'Upgrade-Insecure-Requests': '1' }) current_url = start_url chapter_count = 0 retry_count = 0 total_bytes = 0 visited_urls = set() output_dir = os.path.dirname(output_file) if output_dir and not os.path.exists(output_dir): os.makedirs(output_dir) logging.info(f"开始下载小说,起始URL: {start_url}") logging.info(f"基础URL: {base_url}") with open(output_file, 'w', encoding='utf-8') as f: while current_url and retry_count < max_retries: if current_url in visited_urls: logging.warning(f"检测到重复URL: {current_url},跳过") break visited_urls.add(current_url) chapter_count += 1 try: logging.info(f"正在下载第 {chapter_count} 章: {current_url}") delay = random.uniform(1.5, 4.0) time.sleep(delay) headers = get_random_headers() headers['Referer'] = base_url response = session.get(current_url, headers=headers, timeout=15) if response.status_code != 200: logging.error(f"错误: 无法获取页面,状态码: {response.status_code}") retry_count += 1 continue detected_encoding = detect_encoding(response) response.encoding = detected_encoding logging.info(f"检测到编码: {detected_encoding}") content, next_link = extract_content_and_next_link(response.text, current_url) if content and len(content.strip()) > 20: bytes_written = f.write(content) total_bytes += bytes_written f.flush() logging.info(f"成功写入第 {chapter_count} 章内容 ({len(content)} 字符)") retry_count = 0 else: logging.warning(f"未提取到有效内容,可能页面结构变化") debug_file = f"debug_ch{chapter_count}.html" with open(debug_file, 'w', encoding='utf-8') as debug_f: debug_f.write(response.text) logging.info(f"已保存调试文件: {debug_file}") retry_count += 1 continue if next_link and next_link != current_url: current_url = next_link else: current_url = None logging.info("已到达最后一章") except requests.exceptions.RequestException as e: logging.error(f"网络请求出错: {str(e)}") retry_count += 1 time.sleep(5) except Exception as e: logging.error(f"处理章节时出错: {str(e)}") retry_count += 1 time.sleep(5) logging.info(f"已保存到: {output_file}") logging.info(f"总章节数: {chapter_count}") logging.info(f"总字数: {total_bytes} 字节") if chapter_count > 0: logging.info(f"小说下载完成!") else: logging.error("下载失败,未获取到任何章节内容") return chapter_count, total_bytes def main(): """主函数,处理用户输入""" print("=" * 50) print("笔趣阁小说下载器 - 自动翻页版") print("=" * 50) start_url = input("请输入小说起始URL: ").strip() if not start_url: print("错误: 起始URL不能为空!") return output_file = input("请输入输出文件名(默认: novel.txt): ").strip() if not output_file: output_file = "novel.txt" start_time = time.time() chapter_count, total_bytes = download_novel(start_url, output_file) print("\n" + "=" * 50) if chapter_count > 0: print(f"下载完成! 共 {chapter_count} 章,{total_bytes} 字节") else: print("下载失败,请检查日志文件了解详情") print(f"耗时: {time.time() - start_time:.2f} 秒") print(f"文件已保存至: {os.path.abspath(output_file)}") print("=" * 50) if __name__ == "__main__": try: main() except KeyboardInterrupt: print("\n用户中断,程序退出") except Exception as e: print(f"程序发生错误: {str(e)}") logging.exception("程序发生未处理异常") D:\tools\python\python.exe D:\历史项目留存2\诺褀2025\python加工浦发模型模拟\py搭建\pythonProject1\爬虫抖音视频\111.py ================================================== 笔趣阁小说下载器 - 自动翻页版 ================================================== 请输入小说起始URL: https://www.mayiwsk.com/127_127460/53169900.html 请输入输出文件名(默认: novel.txt): 01.txt 2025-09-01 11:04:38,714 - INFO - 开始下载小说,起始URL: https://www.mayiwsk.com/127_127460/53169900.html 2025-09-01 11:04:38,714 - INFO - 基础URL: https://www.mayiwsk.com 2025-09-01 11:04:38,716 - INFO - 正在下载第 1 章: https://www.mayiwsk.com/127_127460/53169900.html 2025-09-01 11:04:42,793 - INFO - 检测到编码: utf-8 2025-09-01 11:04:42,802 - ERROR - 处理章节时出错: 'NoneType' object has no attribute 'find_all' ================================================== 下载完成! 共 1 章,0 字节 耗时: 9.09 秒 文件已保存至: D:\历史项目留存2\诺褀2025\python加工浦发模型模拟\py搭建\pythonProject1\爬虫抖音视频\01.txt ================================================== 2025-09-01 11:04:47,803 - WARNING - 检测到重复URL: https://www.mayiwsk.com/127_127460/53169900.html,跳过 2025-09-01 11:04:47,803 - INFO - 已保存到: 01.txt 2025-09-01 11:04:47,803 - INFO - 总章节数: 1 2025-09-01 11:04:47,803 - INFO - 总字数: 0 字节 2025-09-01 11:04:47,803 - INFO - 小说下载完成!

大家在看

recommend-type

Onvif查找IPcamera和获取Profile,StreamUri

最近在做ONVIF的开发,但是发现网络上面用C#写的代码太少,有些项目,比如ISPY Connect,Onvif Device Manager,要么就是C++的代码,要么就没有源代码。本人对C++不熟悉,而且ONVIF的C++库的代码量很多。对于我的开发来说,我只需要满足搜索到摄像头,并查找到它的Uri即可,所以决定自己写代码。 程序中主要有两个类:Discovery.cs用来搜索局域网内的IPCamera.OnvifIPCameraOperator.cs 用来获取相应摄像头的信息。
recommend-type

WindFarmer+4.2.21.0.zip

WindFarmer4.2.21.0风资源计算软件,亲测可用,建议采用Windows7兼容模式安装。
recommend-type

USB设备过滤驱动[Src+Bin]

U盘过滤驱动完全实现,包括应用程序和驱动程序
recommend-type

AUTOSAR acceptance test官方标准文档

AUTOSAR acceptance test官方标准文档,最新版本12.2016: R1.2 从官方网站获取 https://www.autosar.org/nc/document-search
recommend-type

Down For Everyone Or Just Me?-crx插件

语言:English (United States) 检查当前站点是否对所有人或仅您而言都是关闭的 想知道网站是否关闭或仅仅是您吗? 安装此扩展程序以查找。 如果您在加载网站时遇到错误,则地址栏中的书签图标旁边会出现一个向下箭头。 单击向下箭头以查看网站是否关闭或仅仅是您自己。 这是http://downforeveryoneorjustme.com的非官方扩展名。

最新推荐

recommend-type

perl-Test-Compile-2.2.2-2.el8.tar.gz

# 适用操作系统:Centos8 #Step1、解压 tar -zxvf xxx.el8.tar.gz #Step2、进入解压后的目录,执行安装 sudo rpm -ivh *.rpm
recommend-type

swagger-models-jakarta-2.2.30.jar中文-英文对照文档.zip

1、压缩文件中包含: 中文-英文对照文档、jar包下载地址、Maven依赖、Gradle依赖、源代码下载地址。 2、使用方法: 解压最外层zip,再解压其中的zip包,双击 【index.html】 文件,即可用浏览器打开、进行查看。 3、特殊说明: (1)本文档为人性化翻译,精心制作,请放心使用; (2)只翻译了该翻译的内容,如:注释、说明、描述、用法讲解 等; (3)不该翻译的内容保持原样,如:类名、方法名、包名、类型、关键字、代码 等。 4、温馨提示: (1)为了防止解压后路径太长导致浏览器无法打开,推荐在解压时选择“解压到当前文件夹”(放心,自带文件夹,文件不会散落一地); (2)有时,一套Java组件会有多个jar,所以在下载前,请仔细阅读本篇描述,以确保这就是你需要的文件。 5、本文件关键字: jar中文-英文对照文档.zip,java,jar包,Maven,第三方jar包,组件,开源组件,第三方组件,Gradle,中文API文档,手册,开发手册,使用手册,参考手册。
recommend-type

分时顶底(2).tn6

分时顶底(2).tn6
recommend-type

深入理解计算机系统学习笔记与习题解析-计算机系统原理-汇编语言-处理器架构-程序优化-存储器层次-链接机制-异常控制流-虚拟内存-系统IO-网络编程-并发编程-学习资源-知识整理-.zip

tdr深入理解计算机系统学习笔记与习题解析_计算机系统原理_汇编语言_处理器架构_程序优化_存储器层次_链接机制_异常控制流_虚拟内存_系统IO_网络编程_并发编程_学习资源_知识整理_.zip
recommend-type

perl-Text-CSV-2.00-2.el8.tar.gz

# 适用操作系统:Centos8 #Step1、解压 tar -zxvf xxx.el8.tar.gz #Step2、进入解压后的目录,执行安装 sudo rpm -ivh *.rpm
recommend-type

HTML时间格式化工具及测试页面介绍

标题 "BoolStudio.github.io" 暗示这是一个与GitHub相关的在线资源,具体来说是与BoolStudio相关的网页地址。GitHub是一个著名的代码托管平台,它支持Git版本控制系统,允许用户在云端存储和共享代码。BoolStudio可能是GitHub上的一个用户或组织账户名称,而该页面可能是他们托管的项目或个人页面的入口。 描述中的信息包含了HTML元素和JavaScript代码片段。这段描述展示了一个测试页文件的部分代码,涉及到HTML的标题(title)和内嵌框架(iframe)的使用,以及JavaScript中Date对象的扩展功能。 从描述中我们可以分析出以下知识点: 1. HTML标题(Title): 在HTML中,`<title>`标签用于定义网页的标题,它会显示在浏览器的标题栏或页面的标签上。在描述中出现了`<title>现在时间</title>`,这表明网页的标题被设置为了“现在时间”。 2. 微软时间: 这可能指的是在网页中嵌入微软产品的日期和时间显示。尽管这部分内容在描述中被删除了,但微软时间通常与Windows操作系统的日期和时间显示相关联。 3. iframe元素: `<iframe>`标签定义了一个内嵌框架,可以在网页中嵌入另一个文档。在描述中出现的是`<iframe src"></iframe>`,这表示创建了一个空的iframe元素,其src属性为空,实际上没有嵌入任何内容。通常src属性会被设置为另一个HTML文档的URL,用来在当前页面中显示外部页面的内容。 4. JavaScript日期格式化: 描述中包含了一段JavaScript代码,这段代码扩展了Date对象的功能,允许它根据提供的格式字符串(fmt)返回格式化的日期和时间。例如,如果fmt是'y年M月d日 h时m分s秒',则该函数会按照这个格式返回当前日期和时间。 具体到代码实现,以下步骤展示了如何在JavaScript中扩展Date对象并格式化日期: - 首先创建了一个对象o,该对象包含日期和时间的不同部分,例如年(y)、月(M)、日(d)、时(h)、分(m)、秒(s)。 - 使用正则表达式检查格式字符串fmt中是否包含年份的占位符(y+),如果存在则将其替换为四位数的年份,不足部分用0补齐。 - 使用for...in循环遍历对象o中的每一个键值对,并使用正则表达式测试这些键(如年、月、日等)是否在格式字符串中存在。如果存在,则替换为相应的日期时间值。 这个知识点展示了在JavaScript中如何自定义对象的方法,以及如何使用正则表达式进行字符串的搜索和替换。 标签 "HTML" 表明这个文件主要涉及到超文本标记语言(HTML),这是构建网页内容的标准标记语言,它允许开发者使用标签来定义网页的结构和内容。HTML文件通常包含各种元素,如段落、链接、图片、表格和表单等。 压缩包子文件的文件名称列表中的 "BoolStudio.github.io-main" 可能指的是一个包含 "BoolStudio.github.io" 网站主要文件的压缩包。通常,网站项目会包含多个文件和资源,为了便于管理和分发,开发者会将这些文件打包成一个压缩文件。在本例中,"main" 可能表示该压缩包包含了网站的主要文件和代码。 通过对给定文件信息的分析,我们可以了解到网页设计和开发的相关知识点,特别是HTML文档结构、JavaScript对象和函数扩展以及如何在网站项目中组织文件。这些知识对于前端开发者来说是非常重要的,能够帮助他们创建更加动态和功能丰富的网页应用。
recommend-type

Elixir测试:从用例执行到覆盖率分析

### Elixir 测试:从用例执行到覆盖率分析 #### 1. 测试用例执行 在测试中,以 `Test` 结尾且包含 `use ExUnit` 的模块被视为测试用例。测试用例本质上是设置回调和测试的集合。测试用例的执行方式有两种:并发执行或顺序执行。使用 `async: true` 选项的测试用例将并发执行,其余的则按定义顺序(默认随机)顺序执行。 测试用例的执行流程如下: 1. 执行所有 `setup_all` 回调。 2. 执行所有测试及其特定的设置和拆卸回调。 `setup_all` 回调按定义顺序在同一进程中执行。在运行任何测试之前,测试用例将生成一个进程,在该进程中运行所有
recommend-type

Android Studio 时间延时

<think> 我们参考多个引用,总结在Android Studio中实现时间延时的方法。根据引用[1]和[2],我们知道有几种常见的延时方法,包括使用Handler的postDelayed方法、使用Timer和TimerTask以及使用Thread.sleep。引用[3]和[5]则展示了具体代码示例。 具体方法如下: 1. **使用Handler的postDelayed方法**(推荐在主线程中使用,避免直接操作UI线程的问题): ```java new Handler().postDelayed(new Runnable() { @Override
recommend-type

IMS Open Corpus Workbench:打造高效大型文本语料库管理工具

IMS Open Corpus Workbench(以下简称CWB)是一个强大的开源工具集,它专门用于管理和查询大型的、带有语言注释的文本语料库。这项工具有着广泛的应用领域,包括语言学研究、自然语言处理、人文科学研究等。 ### 标题知识点: #### 大型文本语料库的索引和查询工具 大型文本语料库指的是含有大量文本数据的数据库,其中包含的文本量通常以百万计。这些数据可能是书面文本、口语录音文字转写等形式。对于如此庞大的数据集,索引是必要的,它可以帮助研究者快速定位到感兴趣的片段,而查询工具则提供了从这些大量数据中提取特定信息的能力。 #### 开源 CWB作为一个开源工具,意味着其源代码对所有人开放,并且可以免费使用和修改。开源项目通常是由社区驱动,有着活跃的开发者和用户群体,不断对工具进行改进和拓展。这种模式促进了创新,并且有利于长期维护和升级。 ### 描述知识点: #### 管理和查询带有语言注释的文本 在语料库中,文本数据经常会被加上各种形式的语言注释,比如句法结构、词性标注、语义角色等。CWB支持管理这类富含语言信息的语料库,使其不仅仅保存原始文本信息,还整合了深层的语言知识。此外,CWB提供了多种查询语言注释数据的方式,使得用户可以针对特定的注释信息进行精确查询。 #### 核心组件:CQP(Corpus Query Processor) CQP是CWB中的核心组件,是一个高度灵活和高效的查询处理器。它支持在终端会话中交互式地使用,这为熟悉命令行界面的用户提供了一个强大的工具。同时,CQP也可以嵌入到其他程序中,比如Perl脚本,从而提供编程式的语料库访问方式。这为高级用户提供了一个强大的平台,可以编写复杂的查询,并将查询结果集成到其他程序中。 #### 基于Web的GUI CQPweb 除了命令行界面外,CWB还提供了一个基于Web的图形用户界面CQPweb,使得不熟悉命令行的用户也能够方便地使用CWB的强大功能。CQPweb通常允许用户通过网页直接构建查询,并展示查询结果,极大地降低了使用门槛。 ### 标签知识点: #### 开源软件 CWB作为开源软件,其主要特点和优势包括: - **社区支持**:开放源代码鼓励了全球开发者共同参与,提供错误修正、功能增强、新特性开发等。 - **定制化**:用户可以根据自己的需求对源代码进行修改,从而实现定制化的功能。 - **透明性**:源代码的开放确保了软件工作的透明性,用户可以清楚了解软件的工作原理和数据处理方式。 - **可靠性**:由于代码的公开性,很多用户和开发者可以共同审查代码,提高了软件的可靠性和安全性。 - **成本效益**:开源软件通常不需要支付昂贵的许可费用,对预算有限的个人和机构特别友好。 ### 压缩包子文件的文件名称列表知识点: #### cwb-3.0.0-osx-10.5-universal 这个文件名提供了关于该软件包的重要信息: - **cwb**:表示这是IMS Open Corpus Workbench的软件包。 - **3.0.0**:表示这个包的版本号,了解版本信息对于获取支持、查看更新日志、了解新特性等方面很重要。 - **osx**:表示这个软件包是为Mac OS X操作系统设计的。 - **10.5**:这个数字指明了这个软件包支持的操作系统版本至少是Mac OS X 10.5。 - **universal**:表明这个软件包是为不同架构的处理器(比如32位和64位)设计的通用二进制文件,提高了软件包的兼容性和可移植性。 综上所述,IMS Open Corpus Workbench是一个为处理带有语言注释的大型文本语料库而设计的开源工具集,它以高效且灵活的查询处理器CQP为核心,提供了命令行和基于Web的两种交互方式,极大地促进了语言学和语言技术领域的研究与应用。由于其开源特性,CWB得到了广泛的使用和不断的改进。
recommend-type

基于属性测试的深入解析与策略探讨

### 基于属性测试的深入解析与策略探讨 #### 1. 基于属性测试中的收缩机制 在基于属性的测试中,当测试失败时,像 `stream_data` 这样的框架会执行收缩(Shrinking)操作。收缩的目的是简化导致测试失败的输入,同时确保简化后的输入仍然会使测试失败,这样能更方便地定位问题。 为了说明这一点,我们来看一个简单的排序函数测试示例。我们实现了一个糟糕的排序函数,实际上就是恒等函数,它只是原封不动地返回输入列表: ```elixir defmodule BadSortTest do use ExUnit.Case use ExUnitProperties pro