标签: Python

  • 在 Fedora 44 上部署 OpenAI Whisper

    OpenAI Whisper 是一个由 OpenAI 开源的、极其强大的语音识别(ASR)大模型,其核心功能是将人类的语音高效、精准地转化为文字。由于其完全开源,你可以将其轻松部署在本地,享受 100% 的隐私安全与零成本的转写体验。

    在 Fedora 44 系统中,配合目前业界最快的 Python 包管理工具 uv,我们可以用极短的时间、极干净的代码链,快速搭建起一套现代化的 OpenAI Whisper 语音识别环境。

    1. 安装 uv

    uv 是目前最快的 Python 包管理工具,我们可以直接通过 dnf 进行全局安装:

    sudo dnf install uv

    安装完成后验证:

    uv --version

    2. 创建并进入项目目录

    打开终端(Terminal),创建一个独立的文件夹用于存放 Whisper 项目

    mkdir openai-whisper
    cd openai-whisper

    3. 创建并激活虚拟环境

    为了保证 Python 环境的隔离与干净,我们使用 uv 来创建并激活虚拟环境

    # 使用 uv 创建虚拟环境(默认目录为 .venv)
    uv venv
    
    # 激活虚拟环境
    source .venv/bin/activate

    4. 安装openai-whisper

    GPU 显卡加速版

    如果你的机器安装了 NVIDIA 显卡与 CUDA,可安装 GPU 版本:

    # 安装 PyTorch CUDA 版本
    pip install torch torchvision --index-url https://download.pytorch.org/whl/cu132
    
    # 安装 OpenAI Whisper
    pip install -U openai-whisper

    CPU 版

    CPU 版本(无独立显卡)

    # 安装 OpenAI Whisper
    pip install -U openai-whisper

    运行

    whisper --help
    
    whisper audio.flac audio.mp3 audio.wav --model turbo
  • 基于LangChain和Ollama的本地文档问答系统

    使用 LangChain 框架和 Ollama 开源大模型搭建一个问答系统,根据提供的文档内容回答用户提出的问题。

    系统主要由以下几个部分组成:

    • 文档加载模块: 负责从指定的路径加载文档。
    • 文档切分模块: 将加载的文档按照指定的大小和重叠度进行切分,以便 LLM 处理。
    • LLM 加载模块: 负责加载指定的 Ollama 模型。
    • 问答链创建模块: 使用 LangChain 表达式语言 (LCEL) 构建问答链,将用户问题和文档内容输入到 LLM 中,并解析 LLM 的输出。
    • 提问模块: 接收用户输入的问题,调用问答链生成答案,并返回给用户。
    • 重试机制: 使用 Tenacity 库,当提问模块出现错误时,自动进行重试,以提高系统的稳定性。

    1. 创建一个虚拟环境,名字为langqa

    conda create -n langqa python=3.12

    2. 激活虚拟环境

    conda activate langqa

    3. 安装依赖

    pip install langchain langchain_community langchain-ollama ollama tenacity

    4. 部署代码

    替换为你自己的文件、模型和地址

    • 文件:tcpdump.1-4.4.0.txt
    • 模型:llama3.1:8b
    • OLLAMA :http://localhost:11434

    配置完成后使用命令行进行运行对话

    python langqa.py

    代码

    """
    Author: Qixinlee
    Description: 基于LangChain和Ollama的本地文档问答系统
    """ 
    
    import os
    from langchain_community.document_loaders import TextLoader
    from langchain.text_splitter import RecursiveCharacterTextSplitter
    from langchain_ollama import OllamaLLM
    from langchain.prompts import PromptTemplate
    import ollama
    from tenacity import retry, stop_after_attempt, wait_random_exponential
    from langchain_core.runnables import chain
    from langchain_core.output_parsers import StrOutputParser
    
    # 常量定义
    DOCUMENT_PATH = os.path.join(os.path.dirname(__file__), "tcpdump.1-4.4.0.txt")
    CHUNK_SIZE = 1000
    CHUNK_OVERLAP = 100
    LLM_MODEL_NAME = "llama3.1:8b"
    OLLAMA_HOST = "http://localhost:11434"
    
    # 1. 设置 Ollama 连接信息
    os.environ["OLLAMA_HOST"] = OLLAMA_HOST
    ollama.DEFAULT_BASE_URL = OLLAMA_HOST
    
    # 2. 加载文档
    def load_document(document_path: str):
        try:
            loader = TextLoader(document_path)
            documents = loader.load()
            print(f"Successfully loaded document from {document_path}")
            return documents
        except Exception as e:
            print(f"Error loading document: {e}")
            exit()
    
    # 3. 切分文档
    def split_document(documents, chunk_size: int, chunk_overlap: int):
        text_splitter = RecursiveCharacterTextSplitter(chunk_size=chunk_size, chunk_overlap=chunk_overlap)
        texts = text_splitter.split_documents(documents)
        return texts
    
    # 4. 加载 LLM
    def load_llm(llm_model_name: str):
        try:
            llm = OllamaLLM(model=llm_model_name)
            print(f"Successfully loaded LLM model: {llm_model_name}")
            return llm
        except Exception as e:
            print(f"Error loading LLM model: {e}")
            exit()
    
    # 5. 创建问答链 (使用 LangChain 表达式语言)
    def create_qa_chain(llm):
        try:
            template = """你是一个问答机器人。请根据以下已知信息,用简洁和专业的中文回答用户的问题。
            如果无法从中得到答案,请清晰地输出 “根据已知信息无法回答该问题”。
    
            已知信息:
            {context}
    
            问题: {question}
            答案:"""
            QA_CHAIN_PROMPT = PromptTemplate(
                input_variables=["context", "question"], template=template
            )
    
            # 使用 Runnable 序列
            qa_chain = QA_CHAIN_PROMPT | llm | StrOutputParser()
    
            print("Successfully created QA chain.")
            return qa_chain
        except Exception as e:
            print(f"Error creating QA chain: {e}")
            exit()
    
    # 6. 提问 (使用 Tenacity 实现重试机制)
    @retry(stop=stop_after_attempt(3), wait=wait_random_exponential(multiplier=1, min=4, max=10))
    def generate_answer(qa_chain, document_content: str, query: str):
        try:
            result = qa_chain.invoke({"context": document_content, "question": query})
            print("Successfully generated answer.")
            return result
        except Exception as e:
            print(f"Error during QA: {e}")
            raise
    
    # 主程序
    if __name__ == "__main__":
    
        # 1. 加载文档
        documents = load_document(DOCUMENT_PATH)
    
        # 2. 切分文档
        texts = split_document(documents, CHUNK_SIZE, CHUNK_OVERLAP)
    
        # 3. 加载 LLM (使用常量)
        llm = load_llm(LLM_MODEL_NAME)
    
        # 4. 将所有文本块连接成一个字符串
        document_content = "\n".join([text.page_content for text in texts])
    
        # 5. 创建问答链
        qa_chain = create_qa_chain(llm)
    
        # 6. 循环提问
        while True:
            query = input("请输入你的问题 (输入 'exit' 退出): ")
            if query.lower() == "exit":
                break
    
            try:
                result = generate_answer(qa_chain, document_content, query)
                print("Question (中文):", query)
                print("Answer (中文):", result)
    
            except Exception as e:
                print(f"Failed to generate answer: {e}")
                exit()
  • Web日志分析工具

    1. Web日志分析工具

    一年前,我写了一个Web日志进行分析并按照分类导出到Excel的工具
    一年后,我在AI的加持帮助下对这个工具代码进行了重构
    通过Web页面的方式进行呈现,支持了更多的日志解析,更灵活的使用

    2. 为什么要重构这个工具

    想法

    1. 日志分析的过程中总会遇到性能瓶颈和查找指定相关数据的瓶颈,最终只需要日志中的部分数据,只需要一个小工具获取想要的结果就可以了,暂时不考虑重量级产品全流量匹配
    2. 会遇到跨平台代码处理的问题,既然都跨平台了直接搞个Web版本更直观
    3. 无论是流量匹配还是日志匹配,归根结底还是通过正则表达式的方式去做,随着AI时代的猛烈迭代,我尝试使用AI去编码看看能否给我一个其它的处理数据的方式,尝试了不同的AI产品,例如LLAMA3,ChatGPT,Gemini等相关AI产品,最终的结论依旧是通过正则表达式的方式匹配
    4. 既然使用正则表达式的方式,我依旧在想能不能搞个自动匹配的方式实现,只需要上传日志脚本自动分析匹配最后输出结果,但事实是其实电脑也同样看不懂猜不出来日志中的每个字段究竟是什么,希望未来的AI可以猜出来人类想要什么,哪怕是给个选项!
    5. 在可以输出结果的同时顺便统计出次数数量方便进行统计工作,开始打算使用图表的方式去做最终没做,无法适配不同的图表不如给出输出的数据自己用其它的工具去做,这样更灵活
    6. 在第一版的Web日志分析工具中,输出的是Excel格式,这次直接被我取消了
      1. 原因1:Excel文件需要依赖Microsoft Excel或其他兼容工具
      2. 原因2:Excel在处理非常大的数据集时可能会变得非常缓慢
      3. 原因3:而如果需要人类易读的格式,并且计划由用户手动操作数据,Excel可能是更方便的选择,但它的处理能力不如CSV和JSON强大
    7. 这次使用的CSV和JSON格式进行输出
      1. CSV格式兼容性更好,几乎所有的数据分析工具、数据库和编程语言都能轻松处理CSV格式。它是一种通用的文本格式,可以在不同平台和工具之间无缝传输
      2. CSV文件结构简单,数据存储在纯文本中,适合快速读取和处理,如果后续有其它工具进行调用的话更方便
      3. 为什么增加了JSON呢,因为简单易懂:JSON采用键值对的形式,结构清晰,容易理解。即使没有专业的编程背景的人也能很快理解JSON的数据结构,JSON跨平台兼容性也不错

    实现过程

    参考了几个框架最终选择的streamlit框架,别问为啥,对我来说无论用什么好用就行

    按照思路:
    第一步上传文件能不能上传成功,日志大小问题
    第二步使用正则匹配日志,能不能匹配上选择的日志,反反复复的找了很多日志去测试,确实非常耗时的
    第三步输出日志,能输出结果就好了,别输出报错或者无法下载就行

    代码量虽然不大但中途改了很多次代码,主要改的都是正则表达式用来匹配日志的,中途反反复复的匹配日志,最终这事儿成了

    中途遇到的困难

    1. 困难还是有的,一年前第一版的Web日志分析工具改正则表达式匹配日志确实挺难的,没想打一年后有AI可以帮我干了,在LLAMA3,ChatGPT,Gemini等相关AI工具的帮助下写正则表达式方便多了,每家AI输出的正则表达式大同小异,不行就多试几次比手写快
    2. 中途大胆的做了些尝试,把我的代码全部交给AI例如LLAMA3,ChatGPT,Gemini等相关AI产品,让AI帮我修改全部的代码重构去实现最初的想法只需要上传日志脚本自动分析匹配最后输出结果,但结果是不行,等以后有其它的方式可以做到我在升级V3版本的Web日志分析工具吧,目前确实不太好做
    3. 既然没办法实现全自动的方式,最终把Apache、Nginx、IIS和Tomcat常用的日志格式进行正则表达式进行匹配,不像V1版本一样只处理Nginx日志
    4. 希望未来的AI工具更好用,以后有条件了一定搞个全自动的,因为目前增加新的日志格式的话还是要增加正则的规则,我不想改正则了。

    未来的计划(还没实现)

    1. 上传日志,脚本全自动分析匹配,输出结果
    2. 上传日志,解析全部日志字段,手动打类型标签,输出结果

    3. 功能介绍

    1. 支持多种日志格式:支持Apache、Nginx、IIS和Tomcat日志格式。
    2. 自动识别日志格式:自动识别日志格式,无需手动配置。
    3. 高效分析:快速分析大型日志文件,获取有关网站访问量、用户行为和系统性能的详细信息。
    4. 灵活筛选:支持多种筛选条件,例如日期范围、请求方法、请求URL等。
    5. 详细统计:支持多种统计条件,例如请求方法、请求URL、状态码等。
    6. 导出文件:支持导出CSV和JSON文件,方便进一步分析和处理。

    4. 项目地址

    以下是完成的Python代码

    # Author: liqixin
    # Mail: [email protected]
    # Web: https://www.qixinlee.com
    
    import streamlit as st
    import pandas as pd
    import json
    import logging
    import os
    import re
    from datetime import datetime
    from typing import List, Dict
    
    # 设置日志级别和格式
    logging.basicConfig(filename='log.log', format='%(asctime)s - %(levelname)s - %(message)s', level=logging.INFO)
    
    # 定义日志解析器的基类
    class LogParser:
        def __init__(self, log_file: bytes):
            self.log_file = log_file
    
        def _parse_log(self, log_pattern: re.Pattern) -> List[Dict]:
            parsed_log_entries = []
            for line in self.log_file:
                line = line.decode('utf-8')
                match = log_pattern.findall(line)
                if match:
                    for match_item in match:
                        request = match_item[3]
                        request_parts = request.split(' ', 2)
                        if len(request_parts) == 3:
                            request_method, request_url, http_protocol = request_parts
                        else:
                            request_method = request_parts[0]
                            request_url = ''
                            http_protocol = ''
                        parsed_log_entries.append({
                            'remote_addr': match_item[0],
                            'remote_user': match_item[1],
                            'time_local': match_item[2],
                            'request_method': request_method,
                            'request_url': request_url,
                            'http_protocol': http_protocol,
                            'status': match_item[4],
                            'body_bytes_sent': match_item[5],
                            'http_referer': match_item[6],
                            'http_user_agent': match_item[7]
                        })
            return parsed_log_entries
    
        def parse(self) -> List[Dict]:
            raise NotImplementedError("必须实现解析方法")
    
    # Apache 日志解析器
    class ApacheLogParser(LogParser):
        def parse(self) -> List[Dict]:
            log_pattern = re.compile(r'(\S+) - (\S+) \[(.*?)\] "(.*?)" (\d+) (\d+) "(.*?)" "(.*?)"', re.DOTALL)
            return self._parse_log(log_pattern)
    
    # Nginx 日志解析器
    class NginxLogParser(LogParser):
        def parse(self) -> List[Dict]:
            log_pattern = re.compile(r'(\S+) - - \[(.*?)\] "(.*?)" (\d+) (\d+) "(.*?)" "(.*?)"', re.DOTALL)
            return self._parse_log(log_pattern)
    
    # IIS 日志解析器
    class IISLogParser(LogParser):
        def parse(self) -> List[Dict]:
            log_pattern = re.compile(r'(\S+) (\S+) (\S+) \[(.*?)\] "(.*?)" (\d+) (\d+) "(.*?)" "(.*?)"', re.DOTALL)
            return self._parse_log(log_pattern)
    
    # Tomcat 日志解析器
    class TomcatLogParser(LogParser):
        def parse(self) -> List[Dict]:
            log_pattern = re.compile(r'(\S+) (\S+) \[(.*?)\] "(.*?)" (\d+) (\d+) "(.*?)" "(.*?)"', re.DOTALL)
            return self._parse_log(log_pattern)
    
    # 通用日志解析方法
    class LogParserFactory:
        @staticmethod
        def create_parser(log_type: str, log_file: bytes) -> LogParser:
            if log_type == 'Apache':
                return ApacheLogParser(log_file)
            elif log_type == 'Nginx':
                return NginxLogParser(log_file)
            elif log_type == 'IIS':
                return IISLogParser(log_file)
            elif log_type == 'Tomcat':
                return TomcatLogParser(log_file)
            else:
                raise ValueError(f"不支持的日志类型: {log_type}")
    
    # 定义导出文件函数
    def export_file(df: pd.DataFrame, export_format: str) -> str:
        try:
            current_time = datetime.now().strftime("%Y%m%d_%H%M%S")
            if export_format == 'CSV(逗号分隔值)':
                csv_file_path = f'log_entries_{current_time}.csv'
                df.to_csv(csv_file_path, index=False)
                return csv_file_path
            elif export_format == 'JSON(JavaScript对象表示法)':
                json_file_path = f'log_entries_{current_time}.json'
                df['time_local'] = df['time_local'].apply(lambda x: x.isoformat())
                df['date'] = df['date'].apply(lambda x: x.isoformat())
                with open(json_file_path, 'w', encoding='utf-8') as json_file:
                    json.dump(df.to_dict(orient='records'), json_file, indent=4)
                return json_file_path
        except Exception as e:
            logging.error(f"导出文件失败:{e}")
            raise
    
    # 定义Web应用程序
    def main():
        # 设置主题
        st.set_page_config(layout="wide", page_title="安全运营 - Web日志分析工具")
        st.markdown("<style>body {background-color: #2F4F4F; color: #66D9EF;}</style>", unsafe_allow_html=True)
        st.markdown("<h1 style='text-align: center;'>安全运营 - Web日志分析工具</h1>", unsafe_allow_html=True)
    
        # 上传日志文件
        st.subheader('请选择日志文件')
        log_file = st.file_uploader('请选择日志文件', type=['log'])
        log_type = st.selectbox("请选择日志类型", ['Apache', 'Nginx', 'IIS', 'Tomcat'])
    
        if log_file is not None:
            try:
                # 使用工厂模式创建适当的日志解析器
                log_parser = LogParserFactory.create_parser(log_type, log_file)
                parsed_log_entries = log_parser.parse()
    
                if parsed_log_entries:
                    # 日志解析结果
                    st.subheader('日志解析结果')
                    df = pd.DataFrame(parsed_log_entries)
                    df['time_local'] = df['time_local'].apply(lambda x: datetime.strptime(x, '%d/%b/%Y:%H:%M:%S %z'))
                    df['date'] = df['time_local'].dt.date
                    st.write(df)
    
                    # 选择日期范围
                    st.subheader('请选择日期范围')
                    col1, col2 = st.columns(2)
                    start_date = col1.date_input('请选择开始日期')
                    end_date = col2.date_input('请选择结束日期')
                    filtered_df = df[(df['time_local'].dt.date >= start_date) & 
                                     (df['time_local'].dt.date <= end_date)]
    
                    # 筛选功能
                    st.subheader('筛选功能')
                    st.subheader('请选择筛选条件')
                    columns = filtered_df.columns.tolist()
                    select_columns = st.multiselect('请选择筛选字段', columns)
                    select_values = {}
                    for column in select_columns:
                        if column == 'date':
                            select_values[column] = st.date_input(f'请选择{column}值')
                        else:
                            select_values[column] = st.selectbox(f'请选择{column}值', filtered_df[column].unique())
                    filtered_df = filtered_df
                    for column, value in select_values.items():
                        if column == 'date':
                            filtered_df = filtered_df[filtered_df[column] == value]
                        else:
                            filtered_df = filtered_df[filtered_df[column] == value]
    
                    st.write(filtered_df)
    
                    # 统计功能
                    st.subheader('统计功能')
                    rank_columns = filtered_df.columns.tolist()
                    rank_columns_selected = st.multiselect('请选择统计字段', rank_columns)
    
                    if rank_columns_selected:
                        for i, column in enumerate(rank_columns_selected):
                            rank_df = filtered_df[column].value_counts().reset_index()
                            rank_df.columns = [column, '次数']
                            
                            if i % 2 == 0:
                                col1, col2 = st.columns(2)
                                col1.write(rank_df)
                            else:
                                col2.write(rank_df)
                    else:
                        st.write("请选择至少一个字段")
                    
                    # 导出文件功能
                    st.subheader('请选择导出格式')
                    export_format = st.selectbox('请选择导出格式', ['CSV(逗号分隔值)', 'JSON(JavaScript对象表示法)'])
                    if export_format:
                        file_path = export_file(filtered_df, export_format)
                        with open(file_path, 'rb') as file:
                            if export_format == 'CSV(逗号分隔值)':
                                st.download_button('下载CSV文件', file, file_path.split('/')[-1])
                            elif export_format == 'JSON(JavaScript对象表示法)':
                                st.download_button('下载JSON文件', file, file_path.split('/')[-1])
    
                else:
                    st.error('没有匹配的日志条目')
            except Exception as e:
                st.error(f"错误:{e}")
    
    if __name__ == '__main__':
        main()
  • WOL 主机唤醒工具

    1. 我为什么要写WOL 唤醒工具

    答案:是为了省电(是为了环保)

    2. MarsCode是什么?

    MarsCode对话框官方回答:MarsCode是一个在线的集成开发环境(IDE),它专注于提供一个高效、便捷的编程环境,特别是在教育领域。MarsCode支持多种编程语言,包括Python、Java、C++等,并且提供了实时的代码编辑、调试和运行功能。

    MarsCode的特点包括:

    1. 实时协作:支持多人实时协作编程,适用于团队项目或教学场景。
    2. 代码自动补全:提供智能的代码自动补全功能,帮助开发者提高编码效率。
    3. 语法高亮:支持多种编程语言的语法高亮,使代码更加易读。
    4. 调试工具:内置调试工具,方便开发者进行代码调试。
    5. 版本控制:集成了Git版本控制系统,方便管理代码版本。
    6. 云端存储:所有代码和项目都存储在云端,用户可以随时随地访问和编辑。

    MarsCode的目标是为开发者和学习者提供一个简单、易用且功能强大的编程环境,帮助他们更高效地编写代码。

    3. 为什么要使用这个工具

    写这个代码的起因是前段时间参加字节跳动豆包MarsCode活动
    既然参加了活动,我觉得我还是要体验一下产品,使用产品要做点什么的

    4. 项目需求

    对于使用远程桌面工具经常连接回家的我来说,日常电脑正常开机跑编译或是本地跑AI等情景挺多的,但也有很多闲置的时候

    因此做了一个主机唤醒工具方便在不使用电脑的时候关闭电脑,在需要的时候通过远程唤醒的方式打开电脑让电脑起飞

    在豆包MarsCode的帮助下,提供需求和多次改进终于把代码写好啦

    多了一次使用AI编程的实战

    5. 实现方法

    1. 注册一个MarsCode账号
    2. 进入工作台
    3. 可以在本地IDE中集成,或者直接使用云端网页版
    4. 创建一个项目
    5. 把你的想法告诉MarsCode,开启代码创作之旅
    6. 不停的更改你的需求,让MarsCode输出代码
    7. 在本地搭建好环境运行就好啦
    8. 在多次代码的输出下这事儿成啦,可以啦

    6. 总结

    在使用MarsCode时候,在提需求的时候AI会输出代码(你说啥我输出啥只要快就好)
    不停的进行改改改,对于新手小白来说充满了新奇和在不会编程的同时更容易去实现写代码的功能
    刚开始输出的时候的确给了可以用的功能,但不太满意,需要继续提需求达到满意(再一次想起了我的甲方爸爸),我会在提供可用功能的同时继续提供给MarsCode代码让他继续优化,这样的结果会更完美,功夫不负有心人,终于搞定了

    7. 项目地址

    以下是完成的Python代码

    # Author: liqixin
    # Mail: [email protected]
    # Web: https://www.qixinlee.com
    
    import streamlit as st
    import wakeonlan
    
    # 设置页面标题
    st.title("WOL 唤醒工具")
    
    # 预设的常用主机MAC地址列表
    common_mac_addresses = {
        "Host 1": "00:11:22:33:44:55",
        "Host 2": "AA:BB:CC:DD:EE:FF",
        "Host 3": "11:22:33:44:55:66"
    }
    
    # 在页面上添加下拉框,用于选择目标主机
    selected_host = st.selectbox("请选择要唤醒的主机:", list(common_mac_addresses.keys()))
    
    # 在页面上添加文本输入框,用于输入自定义主机MAC地址
    custom_mac_address = st.text_input("或者输入自定义主机MAC地址:")
    
    # 使用st.empty()创建一个占位符,用于在条件满足时显示警告信息
    warning_placeholder = st.empty()
    
    # 在页面上添加一个按钮,用于发送魔法包
    if st.button("唤醒主机"):
        # 检查 MAC 地址是否为空
        if not custom_mac_address and not selected_host:
            # 在占位符中显示警告信息
            warning_placeholder.warning("请选择要唤醒的主机或输入自定义主机MAC地址。")
        else:
            # 发送魔法包
            if custom_mac_address:
                wakeonlan.send_magic_packet(custom_mac_address)
                st.success(f"已发送魔法包到 {custom_mac_address},尝试唤醒主机。")
            else:
                mac_address = common_mac_addresses[selected_host]
                wakeonlan.send_magic_packet(mac_address)
                st.success(f"已发送魔法包到 {mac_address},尝试唤醒主机。")