From 22ce3a6aea42464498a275a48d2e1a4bc3d9595e Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 17 Jun 2026 21:20:07 +0800 Subject: [PATCH] =?UTF-8?q?security:=20=E5=AE=89=E5=85=A8=E8=A7=84?= =?UTF-8?q?=E8=8C=83=20+=20=E6=95=8F=E6=84=9F=E6=95=B0=E6=8D=AE=E8=84=B1?= =?UTF-8?q?=E6=95=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CLAUDE.md 新增安全规范章节(禁止提交 / 必须提供 .env.example) - finance/config/settings.py 硬编码密码移除 - djapi/api/video/audioRead.py API Key 替换为占位符 - 新增 finance/.env.example 示例配置 - .gitignore 解除 .env.example 排除 Co-Authored-By: Simon --- .gitignore | 3 + CLAUDE.md | 25 + djapi/api/video/audioRead.py | 876 +++++++++++++++++------------------ finance/.env.example | 27 ++ finance/config/settings.py | 2 +- 5 files changed, 494 insertions(+), 439 deletions(-) create mode 100644 finance/.env.example diff --git a/.gitignore b/.gitignore index 960deea..1e6681c 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,9 @@ Thumbs.db *.token credentials.* +# Env examples are safe to commit +!.env.example + # Database *.sqlite3 *.db diff --git a/CLAUDE.md b/CLAUDE.md index 7e0f7ca..8c2769c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -78,3 +78,28 @@ python cli/demo_sentiment_detail.py --ts_code 600519.SH --date 20260603 | NLP | Qwen (DashScope / Ollama) | .env 配置 | | Agent | 自研编排器 | finance/agents/ | | API | Django 5.2 + uWSGI | djapi/ | + +## 安全规范 + +### 禁止提交 + +- `.env`(含真实 key) +- API Key(`sk-*`、`TUSHARE_TOKEN` 等) +- Cookie / Session +- Token / 密钥 +- 个人隐私数据(手机号、身份证、密码) + +### 必须提供 + +- `.env.example` — 仅含占位符的示例配置,如: + ``` + TUSHARE_TOKEN=your_token_here + QWEN_API_KEY=sk-your-key-here + MAC_DB_PASSWORD=your_password_here + ``` + +### 提交前检查 + +```bash +grep -r "sk-\|token\|_H(lU\|password" --include="*.py" --include="*.md" --include="*.yaml" | grep -v ".example\|your_token\|your_password" +``` diff --git a/djapi/api/video/audioRead.py b/djapi/api/video/audioRead.py index a282a5e..cb460c4 100644 --- a/djapi/api/video/audioRead.py +++ b/djapi/api/video/audioRead.py @@ -1,439 +1,439 @@ -import env # 加载 .env 到环境变量 -import os -import dashscope -import pydub -from pydub import AudioSegment -from pydub.silence import split_on_silence -from dashscope.audio.asr import Recognition -from dashscope import Generation -from http import HTTPStatus -from mysqlHandle import MySQLDB -import logging - -logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') -logger = logging.getLogger(__name__) -# 设置环境变量 -#os.environ["DASHSCOPE_API_KEY"] = "sk-d2d65b726068445b98b88fc3b675dbf1" # 替换为你的API Key - -def convert_mp3_to_wav(mp3_path, output_wav_path): - """ - 将MP3文件转换为16kHz单声道WAV格式,这是Qwen3-ASR-Flash模型的推荐格式 - 参数: - mp3_path (str): MP3文件路径 - output_wav_path (str): 输出WAV文件路径 - 返回值: - str: 转换后的WAV文件路径 - """ - logger.info(f"开始转换MP3到WAV: {mp3_path}") - # 加载MP3文件 - audio = AudioSegment.from_file(mp3_path, format="mp3") - # 转换为16kHz采样率、单声道、16位深度 - audio = audio.set_frame_rate(16000).set_channels(1) - # 导出为WAV格式 - audio.export(output_wav_path, format="wav") - logger.info(f"✓ MP3转换完成: {output_wav_path}") - #print(f"✓ MP3转换完成: {output_wav_path}") - return output_wav_path - -def split_audio_by_fixed_duration(audio_path, chunk_duration, output_folder): - """ - 将音频文件按固定时长分割成多个片段 - 参数: - audio_path (str): 音频文件路径 - chunk_duration (int): 分片时长(毫秒) - output_folder (str): 输出文件夹路径 - 返回值: - list: 分片文件路径列表 - """ - # 加载音频文件 - audio = AudioSegment.from_file(audio_path) - # 计算总时长(毫秒) - total_duration = len(audio) - # 分片数 - num_chunks = total_duration // chunk_duration + 1 - # 存储分片文件路径 - chunks = [] - - # 创建输出文件夹 - os.makedirs(output_folder, exist_ok=True) - - logger.info(f"开始音频分割,总时长: {total_duration/1000:.1f}秒,将分割为{num_chunks}个片段") - - for i in range(num_chunks): - # 计算当前分片的起始和结束时间 - start_time = i * chunk_duration - end_time = (i + 1) * chunk_duration - # 提取分片音频 - chunk = audio[start_time:end_time] - # 生成文件名 - chunk_name = f"chunk_{i}.wav" - chunk_path = os.path.join(output_folder, chunk_name) - # 导出分片音频 - chunk.export(chunk_path, format="wav") - chunks.append(chunk_path) - - # 打印处理进度 - progress = (i + 1) / num_chunks * 100 - logger.info(f"✓ 已完成分片 {i+1}/{num_chunks} ({progress:.1f}%)") - - logger.info(f"✓ 音频分割完成,共生成{len(chunks)}个分片文件") - return chunks - -def split_audio_by_smart_silence(audio_path, min_silence_len, silence_thresh, output_folder): - """ - 将音频文件按智能静音检测方式分割成多个片段,每段不超过3分钟 - 参数: - audio_path (str): 音频文件路径 - min_silence_len (int): 最小静音长度(毫秒) - silence_thresh (int): 静音阈值(dBFS) - output_folder (str): 输出文件夹路径 - 返回值: - list: 分片文件路径列表 - """ - # 加载音频文件 - audio = AudioSegment.from_file(audio_path, format="wav") - # 按静音分割 - segments = split_on_silence( - audio, - # 静音超过700毫秒则分割 - min_silence_len=min_silence_len, - # 静音阈值为-40dBFS - silence_thresh=silence_thresh, - # 保留静音部分 - keep_silence=400 - ) - - logger.info(f"✓ 静音分割完成,共{len(segments)}个初始片段") - - # 合并过短的片段 - merged_segments = [] - current_segment = None - for segment in segments: - if current_segment is None: - current_segment = segment - else: - # 合并当前片段和新片段 - temp_segment = current_segment + segment - # 如果合并后的片段超过3分钟,则单独保存当前片段 - if len(temp_segment) > 180000: # 3分钟=180,000毫秒 - merged_segments.append(current_segment) - current_segment = segment - else: - current_segment = temp_segment - # 添加最后一个片段 - if current_segment is not None: - merged_segments.append(current_segment) - - logger.info(f"✓ 片段合并完成,共{len(merged_segments)}个最终片段") - - # 存储分片文件路径 - chunks = [] - - # 创建输出文件夹 - os.makedirs(output_folder, exist_ok=True) - - logger.info(f"开始导出音频片段到: {output_folder}") - - for i, segment in enumerate(merged_segments): - # 生成文件名 - chunk_name = f"chunk_{i}.wav" - chunk_path = os.path.join(output_folder, chunk_name) - # 导出分片音频 - segment.export(chunk_path, format="wav") - chunks.append(chunk_path) - - # 打印处理进度 - progress = (i + 1) / len(merged_segments) * 100 - logger.info(f"✓ 已完成分片 {i+1}/{len(merged_segments)} ({progress:.1f}%)") - - logger.info(f"✓ 智能静音分割完成,共生成{len(chunks)}个分片文件") - return chunks - - -def transcribe_audio(audio_path): - """ - 使用Paraformer实时语音识别模型(通过本地文件)转录音频文件 - 参数: - audio_path (str): 音频文件路径(必须是16kHz单声道WAV) - 返回值: - str: 识别文本,如果失败返回空字符串 - """ - try: - # 确保音频文件存在 - if not os.path.exists(audio_path): - logger.error(f"音频文件不存在: {audio_path}") - return ['', ''] - dashscope.api_key = os.getenv('DASHSCOPE_API_KEY', '') - # 创建识别对象 - recognition = Recognition( - model=os.getenv('DASHSCOPE_ASR_MODEL', 'paraformer-realtime-v2'), - format='wav', - sample_rate=16000, - language_hints=['zh','en'], # 中文和英文 - callback=None - ) - - # 调用识别 - logger.info(f"开始识别音频: {audio_path}") - result = recognition.call(audio_path) - text=[] - if result.status_code == HTTPStatus.OK: - # 提取识别结果 - logger.info(f"✓ {audio_path} 识别成功") - sentence = result.get_sentence() - text.append(merge_transcripts(sentence)) - logger.info(f"识别文本长度: {len(text[0])}") - text.append(analyze_and_correct_text(text[0])) - return text - else: - logger.error(f"❌ 任务失败: {result.message}") - return ['', ''] - except Exception as e: - logger.error(f"识别过程中发生异常: {e}") - return ['', ''] - -def merge_transcripts(transcripts): - """ - 将多段识别文本合并成完整句子(保留原始段落逻辑,用空格连接) - 参数: - transcripts (list): 识别结果列表,每个元素为字典{'text': '识别文本'} - 返回: - str: 合并后的完整文本 - """ - # 输入参数检查 - if not transcripts: - return "" - - # 确保transcripts是可迭代对象 - if not hasattr(transcripts, '__iter__'): - return "" - - try: - # 提取所有有效的text字段 - texts = [] - for t in transcripts: - try: - # 检查是否为字典类型且包含text字段 - if isinstance(t, dict) and 'text' in t and t['text']: - text = t['text'] - # 确保text是字符串类型 - if isinstance(text, str) and text.strip(): - texts.append(text.strip()) - except (KeyError, TypeError, AttributeError): - # 忽略单个元素的处理错误,继续处理其他元素 - continue - - # 用空格连接所有段落(根据实际需求可调整连接符) - return " ".join(texts) if texts else "" - - except Exception as e: - logger.error(f"合并转录文本时发生错误: {e}") - return "" -def text_correction(text): - """ - 使用通义千问模型修正文本中的错误和标点符号 - 参数: - text (str): 需要修正的文本 - 返回值: - str: 修正后的文本 - """ - logger.info("开始文本修正...") - - # 构建修正提示词 - correction_prompt = """请仔细检查以下文本,修正其中的错误: -1. 错别字和语法错误 -2. 标点符号使用错误 -3. 语句不通顺的地方 -4. 逻辑不清晰的部分 - -请直接返回修正后的完整文本,不要添加任何解释说明。""" - - # 构建消息列表 - messages = [ - {"role": "system", "content": "你是一个专业的文本校对助手,擅长修正文本中的各种错误。"}, - {"role": "user", "content": correction_prompt}, - {"role": "user", "content": text} - ] - - logger.info("调用通义千问模型进行文本修正...") - # 调用DashScope文本生成接口 - response = Generation.call( - model=os.getenv('DASHSCOPE_LLM_MODEL', 'qwen-plus'), - messages=messages, - max_tokens=30000, - temperature=0.1, # 使用较低的温度以提高确定性 - top_p=0.5 - ) - - # 检查API调用是否成功 - if response.status_code != 200: - logger.warning(f"❌ 文本修正API调用失败: {response.message}") - raise Exception(f"文本修正API调用失败: {response.message}") - - logger.info("✓ 文本修正完成") - # 返回修正后的文本 - return response.output.text - -def analyze_and_correct_text(text): - """ - 分析文本并自动修正错误 - 参数: - text (str): 待分析和修正的文本 - prompt (str): 分析提示词 - 返回值: - tuple: (修正后的文本, 分析结果) - """ - logger.info("开始文本分析和修正流程...") - - # 首先修正文本错误 - corrected_text = text_correction(text) - if corrected_text is None: - logger.warning("文本修正返回 None,使用原始文本") - corrected_text = text - logger.info(f"原始文本长度: {len(text)}") - logger.info(f"修正后文本长度: {len(corrected_text)}") - - # 使用修正后的文本进行分析 - # analysis_result = analyze_text(corrected_text, prompt) - - return corrected_text -def analyze_text(text, prompt): - """ - 使用通义千问模型分析文本 - 参数: - text (str): 待分析文本 - prompt (str): 分析提示词 - 返回值: - str: 分析结果 - """ - logger.info("开始文本分析...") - # 设置系统提示 - system_prompt = "你是一个专业的文本分析助手,擅长根据提示词对长文本进行深入分析。" - # 构建消息列表 - messages = [ - {"role": "system", "content": system_prompt}, - {"role": "user", "content": prompt}, - {"role": "user", "content": text} - ] - - logger.info("调用通义千问模型进行文本分析...") - # 调用DashScope文本生成接口 - response = Generation.call( - model=os.getenv('DASHSCOPE_LLM_MODEL', 'qwen-plus'), - messages=messages, - max_tokens=8190, # 控制生成文本的最大长度 - temperature=0.3, # 控制生成文本的确定性 - top_p=0.7 # 控制生成文本的多样性 - ) - - # 检查API调用是否成功 - if response.status_code != 200: - logger.error(f"❌ API调用失败: {response.message}") - raise Exception(f"API调用失败: {response.message}") - - logger.info("✓ 文本分析完成") - # 返回分析结果 - return response.output.text - -def process_long_audio(mp3_path, output_folder, date_str): - """ - 处理长音频文件,分割、识别并分析 - 参数: - mp3_path (str): MP3文件路径 - prompt (str): 分析提示词 - output_folder (str): 输出文件夹路径 - 返回值: - str: 分析结果 - """ - logger.info("开始处理长音频...") - - # 转换MP3为WAV格式 - logger.info("步骤1/4: 转换MP3为WAV格式") - wav_path = convert_mp3_to_wav( - mp3_path, os.path.join(output_folder, "input.wav") - ) - - # 分割音频 - # 可以选择固定分片或智能静音分割 - # chunks = split_audio_by_fixed_duration(wav_path, 180000, output_folder) - logger.info("步骤2/4: 智能静音分割音频") - chunks = split_audio_by_smart_silence( - wav_path, 700, -40, output_folder - ) - - # 存储所有识别文本 - transcribed_text = "" - - # 识别每个分片 - logger.info(f"步骤3/4: 开始识别音频分片,共{len(chunks)}个分片") - for i, chunk_path in enumerate(chunks): - try: - logger.info(f"识别进度: {i+1}/{len(chunks)} ({((i+1)/len(chunks)*100):.1f}%)") - # 调用音频识别API - text = transcribe_audio(chunk_path) - - """ - if not text[1].startswith('今天的新闻联播节目播送完毕'): - prompt='请分析所给文本的新闻内容,返回一个简短标题' - text.append(analyze_text(text[1],prompt)) - else: - text.append('') - """ - # 新闻标题留空 - text.append('') - # 添加到总文本 - #transcribed_text += text + "\n" - # 删除临时文件 - os.remove(chunk_path) - # 初始化数据库连接 - db = MySQLDB() # 使用默认参数连接数据库 - try: - # 插入数据示例 - user_data = { - "news_days": date_str, - "daily_sub_id": i, - "news_raw": text[0], - "news_improve": text[1], - "news_title": text[2] - } - user_id = db.insert_data("xwlb_daily", user_data) - finally: - # 关闭连接 - db.close() - except Exception as e: - logger.error(f"识别失败: {chunk_path}, 错误: {e}") - # 可以在这里添加重试逻辑 - - # 分析识别文本 - """ - print("步骤4/4: 分析识别文本") - print(f"识别文本长度: {len(transcribed_text)}") - print(f"识别文本内容: {transcribed_text}") - if len(transcribed_text) < 1: - print("识别文本为空,跳过分析处理") - return "识别文本为空,无法进行分析" - analysis_result = analyze_text(transcribed_text, prompt) - """ - logger.info("✓ 长音频处理完成") - # 返回分析结果 - return "" - -# 使用示例 -if __name__ == "__main__": - # MP3文件路径 - mp3_path = "20251002.mp3" - # 分析提示词 - prompt = "请总结这段由中国中央电视台新闻联播音频转为文字的文本,理解其主要内容并提取其中的关键信息。" - # 输出文件夹 - output_folder = "audio_processing" - - # 处理长音频 - try: - result = process_long_audio( - mp3_path, prompt, output_folder - ) - # 打印分析结果 - print("分析结果:\n") - print(result) - except Exception as e: +import env # 加载 .env 到环境变量 +import os +import dashscope +import pydub +from pydub import AudioSegment +from pydub.silence import split_on_silence +from dashscope.audio.asr import Recognition +from dashscope import Generation +from http import HTTPStatus +from mysqlHandle import MySQLDB +import logging + +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') +logger = logging.getLogger(__name__) +# 设置环境变量 +# os.environ["DASHSCOPE_API_KEY"] = "sk-your-dashscope-key" + +def convert_mp3_to_wav(mp3_path, output_wav_path): + """ + 将MP3文件转换为16kHz单声道WAV格式,这是Qwen3-ASR-Flash模型的推荐格式 + 参数: + mp3_path (str): MP3文件路径 + output_wav_path (str): 输出WAV文件路径 + 返回值: + str: 转换后的WAV文件路径 + """ + logger.info(f"开始转换MP3到WAV: {mp3_path}") + # 加载MP3文件 + audio = AudioSegment.from_file(mp3_path, format="mp3") + # 转换为16kHz采样率、单声道、16位深度 + audio = audio.set_frame_rate(16000).set_channels(1) + # 导出为WAV格式 + audio.export(output_wav_path, format="wav") + logger.info(f"✓ MP3转换完成: {output_wav_path}") + #print(f"✓ MP3转换完成: {output_wav_path}") + return output_wav_path + +def split_audio_by_fixed_duration(audio_path, chunk_duration, output_folder): + """ + 将音频文件按固定时长分割成多个片段 + 参数: + audio_path (str): 音频文件路径 + chunk_duration (int): 分片时长(毫秒) + output_folder (str): 输出文件夹路径 + 返回值: + list: 分片文件路径列表 + """ + # 加载音频文件 + audio = AudioSegment.from_file(audio_path) + # 计算总时长(毫秒) + total_duration = len(audio) + # 分片数 + num_chunks = total_duration // chunk_duration + 1 + # 存储分片文件路径 + chunks = [] + + # 创建输出文件夹 + os.makedirs(output_folder, exist_ok=True) + + logger.info(f"开始音频分割,总时长: {total_duration/1000:.1f}秒,将分割为{num_chunks}个片段") + + for i in range(num_chunks): + # 计算当前分片的起始和结束时间 + start_time = i * chunk_duration + end_time = (i + 1) * chunk_duration + # 提取分片音频 + chunk = audio[start_time:end_time] + # 生成文件名 + chunk_name = f"chunk_{i}.wav" + chunk_path = os.path.join(output_folder, chunk_name) + # 导出分片音频 + chunk.export(chunk_path, format="wav") + chunks.append(chunk_path) + + # 打印处理进度 + progress = (i + 1) / num_chunks * 100 + logger.info(f"✓ 已完成分片 {i+1}/{num_chunks} ({progress:.1f}%)") + + logger.info(f"✓ 音频分割完成,共生成{len(chunks)}个分片文件") + return chunks + +def split_audio_by_smart_silence(audio_path, min_silence_len, silence_thresh, output_folder): + """ + 将音频文件按智能静音检测方式分割成多个片段,每段不超过3分钟 + 参数: + audio_path (str): 音频文件路径 + min_silence_len (int): 最小静音长度(毫秒) + silence_thresh (int): 静音阈值(dBFS) + output_folder (str): 输出文件夹路径 + 返回值: + list: 分片文件路径列表 + """ + # 加载音频文件 + audio = AudioSegment.from_file(audio_path, format="wav") + # 按静音分割 + segments = split_on_silence( + audio, + # 静音超过700毫秒则分割 + min_silence_len=min_silence_len, + # 静音阈值为-40dBFS + silence_thresh=silence_thresh, + # 保留静音部分 + keep_silence=400 + ) + + logger.info(f"✓ 静音分割完成,共{len(segments)}个初始片段") + + # 合并过短的片段 + merged_segments = [] + current_segment = None + for segment in segments: + if current_segment is None: + current_segment = segment + else: + # 合并当前片段和新片段 + temp_segment = current_segment + segment + # 如果合并后的片段超过3分钟,则单独保存当前片段 + if len(temp_segment) > 180000: # 3分钟=180,000毫秒 + merged_segments.append(current_segment) + current_segment = segment + else: + current_segment = temp_segment + # 添加最后一个片段 + if current_segment is not None: + merged_segments.append(current_segment) + + logger.info(f"✓ 片段合并完成,共{len(merged_segments)}个最终片段") + + # 存储分片文件路径 + chunks = [] + + # 创建输出文件夹 + os.makedirs(output_folder, exist_ok=True) + + logger.info(f"开始导出音频片段到: {output_folder}") + + for i, segment in enumerate(merged_segments): + # 生成文件名 + chunk_name = f"chunk_{i}.wav" + chunk_path = os.path.join(output_folder, chunk_name) + # 导出分片音频 + segment.export(chunk_path, format="wav") + chunks.append(chunk_path) + + # 打印处理进度 + progress = (i + 1) / len(merged_segments) * 100 + logger.info(f"✓ 已完成分片 {i+1}/{len(merged_segments)} ({progress:.1f}%)") + + logger.info(f"✓ 智能静音分割完成,共生成{len(chunks)}个分片文件") + return chunks + + +def transcribe_audio(audio_path): + """ + 使用Paraformer实时语音识别模型(通过本地文件)转录音频文件 + 参数: + audio_path (str): 音频文件路径(必须是16kHz单声道WAV) + 返回值: + str: 识别文本,如果失败返回空字符串 + """ + try: + # 确保音频文件存在 + if not os.path.exists(audio_path): + logger.error(f"音频文件不存在: {audio_path}") + return ['', ''] + dashscope.api_key = os.getenv('DASHSCOPE_API_KEY', '') + # 创建识别对象 + recognition = Recognition( + model=os.getenv('DASHSCOPE_ASR_MODEL', 'paraformer-realtime-v2'), + format='wav', + sample_rate=16000, + language_hints=['zh','en'], # 中文和英文 + callback=None + ) + + # 调用识别 + logger.info(f"开始识别音频: {audio_path}") + result = recognition.call(audio_path) + text=[] + if result.status_code == HTTPStatus.OK: + # 提取识别结果 + logger.info(f"✓ {audio_path} 识别成功") + sentence = result.get_sentence() + text.append(merge_transcripts(sentence)) + logger.info(f"识别文本长度: {len(text[0])}") + text.append(analyze_and_correct_text(text[0])) + return text + else: + logger.error(f"❌ 任务失败: {result.message}") + return ['', ''] + except Exception as e: + logger.error(f"识别过程中发生异常: {e}") + return ['', ''] + +def merge_transcripts(transcripts): + """ + 将多段识别文本合并成完整句子(保留原始段落逻辑,用空格连接) + 参数: + transcripts (list): 识别结果列表,每个元素为字典{'text': '识别文本'} + 返回: + str: 合并后的完整文本 + """ + # 输入参数检查 + if not transcripts: + return "" + + # 确保transcripts是可迭代对象 + if not hasattr(transcripts, '__iter__'): + return "" + + try: + # 提取所有有效的text字段 + texts = [] + for t in transcripts: + try: + # 检查是否为字典类型且包含text字段 + if isinstance(t, dict) and 'text' in t and t['text']: + text = t['text'] + # 确保text是字符串类型 + if isinstance(text, str) and text.strip(): + texts.append(text.strip()) + except (KeyError, TypeError, AttributeError): + # 忽略单个元素的处理错误,继续处理其他元素 + continue + + # 用空格连接所有段落(根据实际需求可调整连接符) + return " ".join(texts) if texts else "" + + except Exception as e: + logger.error(f"合并转录文本时发生错误: {e}") + return "" +def text_correction(text): + """ + 使用通义千问模型修正文本中的错误和标点符号 + 参数: + text (str): 需要修正的文本 + 返回值: + str: 修正后的文本 + """ + logger.info("开始文本修正...") + + # 构建修正提示词 + correction_prompt = """请仔细检查以下文本,修正其中的错误: +1. 错别字和语法错误 +2. 标点符号使用错误 +3. 语句不通顺的地方 +4. 逻辑不清晰的部分 + +请直接返回修正后的完整文本,不要添加任何解释说明。""" + + # 构建消息列表 + messages = [ + {"role": "system", "content": "你是一个专业的文本校对助手,擅长修正文本中的各种错误。"}, + {"role": "user", "content": correction_prompt}, + {"role": "user", "content": text} + ] + + logger.info("调用通义千问模型进行文本修正...") + # 调用DashScope文本生成接口 + response = Generation.call( + model=os.getenv('DASHSCOPE_LLM_MODEL', 'qwen-plus'), + messages=messages, + max_tokens=30000, + temperature=0.1, # 使用较低的温度以提高确定性 + top_p=0.5 + ) + + # 检查API调用是否成功 + if response.status_code != 200: + logger.warning(f"❌ 文本修正API调用失败: {response.message}") + raise Exception(f"文本修正API调用失败: {response.message}") + + logger.info("✓ 文本修正完成") + # 返回修正后的文本 + return response.output.text + +def analyze_and_correct_text(text): + """ + 分析文本并自动修正错误 + 参数: + text (str): 待分析和修正的文本 + prompt (str): 分析提示词 + 返回值: + tuple: (修正后的文本, 分析结果) + """ + logger.info("开始文本分析和修正流程...") + + # 首先修正文本错误 + corrected_text = text_correction(text) + if corrected_text is None: + logger.warning("文本修正返回 None,使用原始文本") + corrected_text = text + logger.info(f"原始文本长度: {len(text)}") + logger.info(f"修正后文本长度: {len(corrected_text)}") + + # 使用修正后的文本进行分析 + # analysis_result = analyze_text(corrected_text, prompt) + + return corrected_text +def analyze_text(text, prompt): + """ + 使用通义千问模型分析文本 + 参数: + text (str): 待分析文本 + prompt (str): 分析提示词 + 返回值: + str: 分析结果 + """ + logger.info("开始文本分析...") + # 设置系统提示 + system_prompt = "你是一个专业的文本分析助手,擅长根据提示词对长文本进行深入分析。" + # 构建消息列表 + messages = [ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": prompt}, + {"role": "user", "content": text} + ] + + logger.info("调用通义千问模型进行文本分析...") + # 调用DashScope文本生成接口 + response = Generation.call( + model=os.getenv('DASHSCOPE_LLM_MODEL', 'qwen-plus'), + messages=messages, + max_tokens=8190, # 控制生成文本的最大长度 + temperature=0.3, # 控制生成文本的确定性 + top_p=0.7 # 控制生成文本的多样性 + ) + + # 检查API调用是否成功 + if response.status_code != 200: + logger.error(f"❌ API调用失败: {response.message}") + raise Exception(f"API调用失败: {response.message}") + + logger.info("✓ 文本分析完成") + # 返回分析结果 + return response.output.text + +def process_long_audio(mp3_path, output_folder, date_str): + """ + 处理长音频文件,分割、识别并分析 + 参数: + mp3_path (str): MP3文件路径 + prompt (str): 分析提示词 + output_folder (str): 输出文件夹路径 + 返回值: + str: 分析结果 + """ + logger.info("开始处理长音频...") + + # 转换MP3为WAV格式 + logger.info("步骤1/4: 转换MP3为WAV格式") + wav_path = convert_mp3_to_wav( + mp3_path, os.path.join(output_folder, "input.wav") + ) + + # 分割音频 + # 可以选择固定分片或智能静音分割 + # chunks = split_audio_by_fixed_duration(wav_path, 180000, output_folder) + logger.info("步骤2/4: 智能静音分割音频") + chunks = split_audio_by_smart_silence( + wav_path, 700, -40, output_folder + ) + + # 存储所有识别文本 + transcribed_text = "" + + # 识别每个分片 + logger.info(f"步骤3/4: 开始识别音频分片,共{len(chunks)}个分片") + for i, chunk_path in enumerate(chunks): + try: + logger.info(f"识别进度: {i+1}/{len(chunks)} ({((i+1)/len(chunks)*100):.1f}%)") + # 调用音频识别API + text = transcribe_audio(chunk_path) + + """ + if not text[1].startswith('今天的新闻联播节目播送完毕'): + prompt='请分析所给文本的新闻内容,返回一个简短标题' + text.append(analyze_text(text[1],prompt)) + else: + text.append('') + """ + # 新闻标题留空 + text.append('') + # 添加到总文本 + #transcribed_text += text + "\n" + # 删除临时文件 + os.remove(chunk_path) + # 初始化数据库连接 + db = MySQLDB() # 使用默认参数连接数据库 + try: + # 插入数据示例 + user_data = { + "news_days": date_str, + "daily_sub_id": i, + "news_raw": text[0], + "news_improve": text[1], + "news_title": text[2] + } + user_id = db.insert_data("xwlb_daily", user_data) + finally: + # 关闭连接 + db.close() + except Exception as e: + logger.error(f"识别失败: {chunk_path}, 错误: {e}") + # 可以在这里添加重试逻辑 + + # 分析识别文本 + """ + print("步骤4/4: 分析识别文本") + print(f"识别文本长度: {len(transcribed_text)}") + print(f"识别文本内容: {transcribed_text}") + if len(transcribed_text) < 1: + print("识别文本为空,跳过分析处理") + return "识别文本为空,无法进行分析" + analysis_result = analyze_text(transcribed_text, prompt) + """ + logger.info("✓ 长音频处理完成") + # 返回分析结果 + return "" + +# 使用示例 +if __name__ == "__main__": + # MP3文件路径 + mp3_path = "20251002.mp3" + # 分析提示词 + prompt = "请总结这段由中国中央电视台新闻联播音频转为文字的文本,理解其主要内容并提取其中的关键信息。" + # 输出文件夹 + output_folder = "audio_processing" + + # 处理长音频 + try: + result = process_long_audio( + mp3_path, prompt, output_folder + ) + # 打印分析结果 + print("分析结果:\n") + print(result) + except Exception as e: print(f"处理失败: {e}") \ No newline at end of file diff --git a/finance/.env.example b/finance/.env.example new file mode 100644 index 0000000..f53b2e0 --- /dev/null +++ b/finance/.env.example @@ -0,0 +1,27 @@ +# ── Qwen API 配置(DashScope) ───────────────────────────── +# API Key 获取:https://dashscope.console.aliyun.com/apiKey +QWEN_API_KEY=sk-your-key-here +QWEN_MODEL=qwen-turbo + +# ── 本地 Qwen 部署(可选,优先级高于 API) ────────────────── +# QWEN_LOCAL_BASE_URL=http://localhost:11434/v1 +# QWEN_LOCAL_MODEL=qwen2.5:7b + +# ── Tushare 数据源 ───────────────────────────────────────── +# Token 获取:https://tushare.pro 注册后在 "个人主页→接口TOKEN" 复制 +TUSHARE_TOKEN=your_token_here + +# ── 数据库 ────────────────────────────────────────────────── +MAC_DB_HOST=127.0.0.1 +MAC_DB_PORT=13306 +MAC_DB_USER=myquant +MAC_DB_PASSWORD=your_password_here +MAC_DB_NAME=myquant + +# ── 情绪分析范围 ──────────────────────────────────────────── +SENTIMENT_SCOPE_TYPE=index +SENTIMENT_SCOPE_INDEXES=000300,000905 +SENTIMENT_MAX_NEWS_PER_STOCK=30 + +# ── MCP 新闻服务(可选) ──────────────────────────────────── +# NEWS_MCP_URL=http://192.168.1.160:3333/mcp diff --git a/finance/config/settings.py b/finance/config/settings.py index e831f09..45549a1 100644 --- a/finance/config/settings.py +++ b/finance/config/settings.py @@ -18,7 +18,7 @@ MARIADB_CONFIG = { "host": os.getenv("MAC_DB_HOST", "127.0.0.1"), "port": int(os.getenv("MAC_DB_PORT", "13306")), "user": os.getenv("MAC_DB_USER", "myquant"), - "password": os.getenv("MAC_DB_PASSWORD", "_H(lU1_fF*9baRTp"), + "password": os.getenv("MAC_DB_PASSWORD", ""), "database": os.getenv("MAC_DB_NAME", "myquant"), "charset": "utf8mb4", "pool_size": 5,