用Python Streamlit sqlite3 写一个简单博客

图片:

依赖:

streamlit

pandas

matplotlib

代码blog_app.py:

python 复制代码
import streamlit as st
import sqlite3
import pandas as pd
from datetime import datetime
import os

# 确保中文正常显示

# 数据库初始化函数
def init_db():
    conn = sqlite3.connect('blog.db')
    c = conn.cursor()
    # 创建博客文章表
    c.execute('''
    CREATE TABLE IF NOT EXISTS posts (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        title TEXT NOT NULL,
        content TEXT NOT NULL,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        category TEXT,
        tags TEXT
    )
    ''')
    conn.commit()
    conn.close()

# 添加新博客文章
def add_post(title, content, category='未分类', tags=''):
    conn = sqlite3.connect('blog.db')
    c = conn.cursor()
    c.execute('''
    INSERT INTO posts (title, content, category, tags) 
    VALUES (?, ?, ?, ?)
    ''', (title, content, category, tags))
    conn.commit()
    conn.close()

# 获取所有博客文章
def get_all_posts():
    conn = sqlite3.connect('blog.db')
    # 使用pandas读取数据,确保中文正常显示
    posts = pd.read_sql_query('SELECT * FROM posts ORDER BY created_at DESC', conn)
    conn.close()
    return posts

# 获取单篇博客文章
def get_post(post_id):
    conn = sqlite3.connect('blog.db')
    c = conn.cursor()
    c.execute('SELECT * FROM posts WHERE id = ?', (post_id,))
    post = c.fetchone()
    conn.close()
    return post

# 更新博客文章
def update_post(post_id, title, content, category, tags):
    conn = sqlite3.connect('blog.db')
    c = conn.cursor()
    c.execute('''
    UPDATE posts SET title = ?, content = ?, category = ?, tags = ? 
    WHERE id = ?
    ''', (title, content, category, tags, post_id))
    conn.commit()
    conn.close()

# 删除博客文章
def delete_post(post_id):
    conn = sqlite3.connect('blog.db')
    c = conn.cursor()
    c.execute('DELETE FROM posts WHERE id = ?', (post_id,))
    conn.commit()
    conn.close()

# 获取分类统计
def get_category_stats():
    conn = sqlite3.connect('blog.db')
    stats = pd.read_sql_query(
        'SELECT category, COUNT(*) as count FROM posts GROUP BY category', 
        conn
    )
    conn.close()
    return stats

# 主应用函数
def main():
    # 初始化数据库
    init_db()
    
    st.title('我的博客')
    
    # 创建侧边栏导航
    menu = ['首页', '写文章', '管理文章', '统计']
    choice = st.sidebar.selectbox('选择功能', menu)
    
    if choice == '首页':
        st.subheader('博客文章')
        
        # 显示分类筛选
        categories = pd.read_sql_query('SELECT DISTINCT category FROM posts', sqlite3.connect('blog.db'))['category'].tolist()
        categories.insert(0, '全部')
        selected_category = st.selectbox('按分类筛选', categories)
        
        # 获取并显示文章列表
        if selected_category == '全部':
            posts = get_all_posts()
        else:
            conn = sqlite3.connect('blog.db')
            posts = pd.read_sql_query(
                'SELECT * FROM posts WHERE category = ? ORDER BY created_at DESC', 
                conn, 
                params=(selected_category,)
            )
            conn.close()
        
        if not posts.empty:
            for _, post in posts.iterrows():
                # 使用expander折叠显示文章内容
                with st.expander(f"{post['title']} ({post['created_at']})"):
                    st.write(f"**分类:** {post['category']}")
                    if post['tags']:
                        st.write(f"**标签:** {post['tags']}")
                    st.write(post['content'])
        else:
            st.info('暂无博客文章')
    
    elif choice == '写文章':
        st.subheader('写新文章')
        
        with st.form(key='post_form'):
            title = st.text_input('标题')
            category = st.text_input('分类', '未分类')
            tags = st.text_input('标签(用逗号分隔)')
            content = st.text_area('内容', height=300)
            submit_button = st.form_submit_button(label='发布文章')
        
        if submit_button:
            if title and content:
                add_post(title, content, category, tags)
                st.success('文章发布成功!')
            else:
                st.error('请填写标题和内容')
    
    elif choice == '管理文章':
        st.subheader('管理文章')
        
        # 显示所有文章进行管理
        posts = get_all_posts()
        
        if not posts.empty:
            # 创建一个下拉框让用户选择要编辑/删除的文章
            post_options = {f"{post['title']} ({post['id']})": post['id'] for _, post in posts.iterrows()}
            selected_post_title = st.selectbox('选择文章', list(post_options.keys()))
            
            if selected_post_title:
                post_id = post_options[selected_post_title]
                post = get_post(post_id)
                
                if post:
                    # 显示文章信息
                    st.write(f"**ID:** {post[0]}")
                    st.write(f"**创建时间:** {post[3]}")
                    
                    # 编辑表单
                    with st.form(key='edit_form'):
                        new_title = st.text_input('标题', post[1])
                        new_category = st.text_input('分类', post[4])
                        new_tags = st.text_input('标签', post[5])
                        new_content = st.text_area('内容', post[2], height=300)
                        
                        col1, col2 = st.columns(2)
                        with col1:
                            update_button = st.form_submit_button(label='更新文章')
                        with col2:
                            delete_button = st.form_submit_button(label='删除文章', type='primary')
                    
                    # 处理表单提交
                    if update_button:
                        if new_title and new_content:
                            update_post(post_id, new_title, new_content, new_category, new_tags)
                            st.success('文章更新成功!')
                            st.experimental_rerun()
                        else:
                            st.error('请填写标题和内容')
                    
                    if delete_button:
                        delete_post(post_id)
                        st.success('文章删除成功!')
                        st.experimental_rerun()
        else:
            st.info('暂无文章可管理')
    
    elif choice == '统计':
        st.subheader('博客统计')
        
        # 显示文章总数
        posts = get_all_posts()
        st.write(f"**文章总数:** {len(posts)}")
        
        # 显示分类统计图表
        try:
            import matplotlib.pyplot as plt
            plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
            plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号
            
            stats = get_category_stats()
            if not stats.empty:
                st.write('**分类统计:**')
                fig, ax = plt.subplots()
                stats.plot.pie(y='count', labels=stats['category'], autopct='%1.1f%%', ax=ax)
                ax.set_ylabel('')
                st.pyplot(fig)
            else:
                st.info('暂无统计数据')
        except Exception as e:
            st.error(f'生成统计图表时出错: {e}')

if __name__ == '__main__':
    main()

运行:

streamlit run blog_app.py

window隐藏 进程

run.bat ---- ANSI 码

powershell -WindowStyle Hidden -Command "streamlit run blog_app.py "

结束这个进程:

  1. 打开一个新的 PowerShell 或 命令提示符窗口

  2. tasklist | findstr "streamlit"

  3. 结束进程

    找到进程后,你会看到进程的 PID 。使用 taskkill 命令结束它。

    复制代码
    taskkill /PID 1234 /F

    /F 参数表示强制结束。

增强版

-添加图片、附件、搜索功能

python 复制代码
import streamlit as st
import sqlite3
import pandas as pd
from datetime import datetime
import os

# 确保中文正常显示

# 数据库初始化函数
def init_db():
    conn = sqlite3.connect('blog.db')
    c = conn.cursor()
    # 创建博客文章表
    c.execute('''
    CREATE TABLE IF NOT EXISTS posts (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        title TEXT NOT NULL,
        content TEXT NOT NULL,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        category TEXT,
        tags TEXT
    )
    ''')
    # 创建文件存储表
    c.execute('''
    CREATE TABLE IF NOT EXISTS post_files (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        post_id INTEGER NOT NULL,
        file_name TEXT NOT NULL,
        file_path TEXT NOT NULL,
        file_type TEXT NOT NULL,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        FOREIGN KEY (post_id) REFERENCES posts (id) ON DELETE CASCADE
    )
    ''')
    conn.commit()
    conn.close()

# 添加新博客文章
def add_post(title, content, category='未分类', tags=''):
    conn = sqlite3.connect('blog.db')
    c = conn.cursor()
    c.execute('''
    INSERT INTO posts (title, content, category, tags) 
    VALUES (?, ?, ?, ?)
    ''', (title, content, category, tags))
    conn.commit()
    conn.close()

# 获取所有博客文章
def get_all_posts():
    conn = sqlite3.connect('blog.db')
    # 使用pandas读取数据,确保中文正常显示
    posts = pd.read_sql_query('SELECT * FROM posts ORDER BY created_at DESC', conn)
    conn.close()
    return posts

# 获取单篇博客文章
def get_post(post_id):
    conn = sqlite3.connect('blog.db')
    c = conn.cursor()
    c.execute('SELECT * FROM posts WHERE id = ?', (post_id,))
    post = c.fetchone()
    conn.close()
    return post

# 更新博客文章
def update_post(post_id, title, content, category, tags):
    conn = sqlite3.connect('blog.db')
    c = conn.cursor()
    c.execute('''
    UPDATE posts SET title = ?, content = ?, category = ?, tags = ? 
    WHERE id = ?
    ''', (title, content, category, tags, post_id))
    conn.commit()
    conn.close()

# 删除博客文章
def delete_post(post_id):
    conn = sqlite3.connect('blog.db')
    c = conn.cursor()
    c.execute('DELETE FROM posts WHERE id = ?', (post_id,))
    conn.commit()
    conn.close()

# 获取分类统计
def get_category_stats():
    conn = sqlite3.connect('blog.db')
    stats = pd.read_sql_query(
        'SELECT category, COUNT(*) as count FROM posts GROUP BY category', 
        conn
    )
    conn.close()
    return stats

# 搜索博客文章
def search_posts(search_term):
    conn = sqlite3.connect('blog.db')
    # 使用LIKE进行模糊搜索,搜索标题、内容、分类和标签
    query = '''
    SELECT * FROM posts 
    WHERE title LIKE ? OR content LIKE ? OR category LIKE ? OR tags LIKE ?
    ORDER BY created_at DESC
    '''
    search_pattern = f'%{search_term}%'
    posts = pd.read_sql_query(query, conn, params=(search_pattern, search_pattern, search_pattern, search_pattern))
    conn.close()
    return posts

# 保存上传的文件
def save_uploaded_file(uploaded_file, post_id):
    # 创建uploads目录
    upload_dir = 'uploads'
    if not os.path.exists(upload_dir):
        os.makedirs(upload_dir)
    
    # 生成唯一文件名
    file_extension = os.path.splitext(uploaded_file.name)[1]
    file_name = f"{post_id}_{datetime.now().strftime('%Y%m%d%H%M%S')}{file_extension}"
    file_path = os.path.join(upload_dir, file_name)
    
    # 保存文件
    with open(file_path, "wb") as f:
        f.write(uploaded_file.getbuffer())
    
    # 判断文件类型
    if file_extension.lower() in ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp']:
        file_type = 'image'
    else:
        file_type = 'attachment'
    
    # 保存文件信息到数据库
    conn = sqlite3.connect('blog.db')
    c = conn.cursor()
    c.execute('''
    INSERT INTO post_files (post_id, file_name, file_path, file_type) 
    VALUES (?, ?, ?, ?)
    ''', (post_id, uploaded_file.name, file_path, file_type))
    conn.commit()
    conn.close()
    
    return file_path

# 获取文章的所有文件
def get_post_files(post_id):
    conn = sqlite3.connect('blog.db')
    files = pd.read_sql_query('SELECT * FROM post_files WHERE post_id = ? ORDER BY created_at', conn, params=(post_id,))
    conn.close()
    return files

# 删除文章的文件
def delete_post_files(post_id):
    # 获取所有文件
    files = get_post_files(post_id)
    
    # 删除物理文件
    for _, file_info in files.iterrows():
        if os.path.exists(file_info['file_path']):
            os.remove(file_info['file_path'])
    
    # 删除数据库记录
    conn = sqlite3.connect('blog.db')
    c = conn.cursor()
    c.execute('DELETE FROM post_files WHERE post_id = ?', (post_id,))
    conn.commit()
    conn.close()

# 主应用函数
def main():
    # 初始化数据库
    init_db()
    
    st.title('我的博客')
    
    # 创建侧边栏导航
    menu = ['首页', '写文章', '管理文章', '统计']
    choice = st.sidebar.selectbox('选择功能', menu)
    
    if choice == '首页':
        st.subheader('博客文章')
        
        # 搜索功能
        search_term = st.text_input('🔍 搜索文章(支持标题、内容、分类、标签)', '')
        
        # 显示分类筛选
        categories = pd.read_sql_query('SELECT DISTINCT category FROM posts', sqlite3.connect('blog.db'))['category'].tolist()
        categories.insert(0, '全部')
        selected_category = st.selectbox('按分类筛选', categories)
        
        # 获取并显示文章列表
        if search_term:
            # 如果用户输入了搜索词,优先使用搜索功能
            posts = search_posts(search_term)
            if not posts.empty:
                st.success(f'找到 {len(posts)} 篇相关文章')
        elif selected_category == '全部':
            posts = get_all_posts()
        else:
            conn = sqlite3.connect('blog.db')
            posts = pd.read_sql_query(
                'SELECT * FROM posts WHERE category = ? ORDER BY created_at DESC', 
                conn, 
                params=(selected_category,)
            )
            conn.close()
        
        if not posts.empty:
            for _, post in posts.iterrows():
                # 使用expander折叠显示文章内容
                with st.expander(f"{post['title']} ({post['created_at']})"):
                    st.write(f"**分类:** {post['category']}")
                    if post['tags']:
                        st.write(f"**标签:** {post['tags']}")
                    
                    # 显示文章内容
                    st.write(post['content'])
                    
                    # 显示文章的文件
                    files = get_post_files(post['id'])
                    if not files.empty:
                        st.subheader('相关文件')
                        for _, file_info in files.iterrows():
                            if file_info['file_type'] == 'image':
                                st.image(file_info['file_path'], caption=file_info['file_name'], width=400)
                            else:
                                st.write(f"📎 附件: {file_info['file_name']}")
                                # 显示文件下载链接
                                if os.path.exists(file_info['file_path']):
                                    with open(file_info['file_path'], "rb") as file:
                                        st.download_button(
                                            label=f"下载 {file_info['file_name']}",
                                            data=file,
                                            file_name=file_info['file_name'],
                                            mime="application/octet-stream"
                                        )
        else:
            st.info('暂无博客文章')
    
    elif choice == '写文章':
        st.subheader('写新文章')
        
        with st.form(key='post_form'):
            title = st.text_input('标题')
            category = st.text_input('分类', '未分类')
            tags = st.text_input('标签(用逗号分隔)')
            content = st.text_area('内容', height=300)
            
            # 文件上传功能
            st.subheader('上传文件')
            uploaded_images = st.file_uploader(
                "上传图片", 
                type=['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'], 
                accept_multiple_files=True,
                help="支持JPG、PNG、GIF、BMP、WebP格式"
            )
            uploaded_attachments = st.file_uploader(
                "上传附件", 
                accept_multiple_files=True,
                help="支持任意文件格式"
            )
            
            submit_button = st.form_submit_button(label='发布文章')
        
        if submit_button:
            if title and content:
                # 添加文章
                add_post(title, content, category, tags)
                
                # 获取最新文章的ID
                conn = sqlite3.connect('blog.db')
                c = conn.cursor()
                c.execute('SELECT id FROM posts ORDER BY id DESC LIMIT 1')
                post_id = c.fetchone()[0]
                conn.close()
                
                # 保存上传的文件
                if uploaded_images:
                    for uploaded_file in uploaded_images:
                        save_uploaded_file(uploaded_file, post_id)
                
                if uploaded_attachments:
                    for uploaded_file in uploaded_attachments:
                        save_uploaded_file(uploaded_file, post_id)
                
                st.success('文章发布成功!')
            else:
                st.error('请填写标题和内容')
    
    elif choice == '管理文章':
        st.subheader('管理文章')
        
        # 显示所有文章进行管理
        posts = get_all_posts()
        
        if not posts.empty:
            # 创建一个下拉框让用户选择要编辑/删除的文章
            post_options = {f"{post['title']} ({post['id']})": post['id'] for _, post in posts.iterrows()}
            selected_post_title = st.selectbox('选择文章', list(post_options.keys()))
            
            if selected_post_title:
                post_id = post_options[selected_post_title]
                post = get_post(post_id)
                
                if post:
                    # 显示文章信息
                    st.write(f"**ID:** {post[0]}")
                    st.write(f"**创建时间:** {post[3]}")
                    
                    # 编辑表单
                    with st.form(key='edit_form'):
                        new_title = st.text_input('标题', post[1])
                        new_category = st.text_input('分类', post[4])
                        new_tags = st.text_input('标签', post[5])
                        new_content = st.text_area('内容', post[2], height=300)
                        
                        col1, col2 = st.columns(2)
                        with col1:
                            update_button = st.form_submit_button(label='更新文章')
                        with col2:
                            delete_button = st.form_submit_button(label='删除文章', type='primary')
                    
                    # 显示当前文件
                    st.subheader('当前文件')
                    files = get_post_files(post_id)
                    if not files.empty:
                        for _, file_info in files.iterrows():
                            col1, col2 = st.columns([3, 1])
                            with col1:
                                if file_info['file_type'] == 'image':
                                    st.image(file_info['file_path'], caption=file_info['file_name'], width=300)
                                else:
                                    st.write(f"📎 {file_info['file_name']}")
                            with col2:
                                if st.button('删除', key=f"delete_{file_info['id']}"):
                                    if os.path.exists(file_info['file_path']):
                                        os.remove(file_info['file_path'])
                                    conn = sqlite3.connect('blog.db')
                                    c = conn.cursor()
                                    c.execute('DELETE FROM post_files WHERE id = ?', (file_info['id'],))
                                    conn.commit()
                                    conn.close()
                                    st.success('文件删除成功!')
                                    st.experimental_rerun()
                    else:
                        st.info('暂无文件')
                    
                    # 文件上传功能
                    st.subheader('上传新文件')
                    uploaded_images = st.file_uploader(
                        "上传图片", 
                        type=['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'], 
                        accept_multiple_files=True,
                        key=f"images_{post_id}",
                        help="支持JPG、PNG、GIF、BMP、WebP格式"
                    )
                    uploaded_attachments = st.file_uploader(
                        "上传附件", 
                        accept_multiple_files=True,
                        key=f"attachments_{post_id}",
                        help="支持任意文件格式"
                    )
                    
                    # 处理表单提交
                    if update_button:
                        if new_title and new_content:
                            update_post(post_id, new_title, new_content, new_category, new_tags)
                            
                            # 保存上传的文件
                            if uploaded_images:
                                for uploaded_file in uploaded_images:
                                    save_uploaded_file(uploaded_file, post_id)
                            
                            if uploaded_attachments:
                                for uploaded_file in uploaded_attachments:
                                    save_uploaded_file(uploaded_file, post_id)
                            
                            st.success('文章更新成功!')
                            st.experimental_rerun()
                        else:
                            st.error('请填写标题和内容')
                    
                    if delete_button:
                        # 删除文章前先删除相关文件
                        delete_post_files(post_id)
                        delete_post(post_id)
                        st.success('文章删除成功!')
                        st.experimental_rerun()
        else:
            st.info('暂无文章可管理')
    
    elif choice == '统计':
        st.subheader('博客统计')
        
        # 显示文章总数
        posts = get_all_posts()
        st.write(f"**文章总数:** {len(posts)}")
        
        # 显示分类统计图表
        try:
            import matplotlib.pyplot as plt
            plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
            plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号
            
            stats = get_category_stats()
            if not stats.empty:
                st.write('**分类统计:**')
                fig, ax = plt.subplots()
                stats.plot.pie(y='count', labels=stats['category'], autopct='%1.1f%%', ax=ax)
                ax.set_ylabel('')
                st.pyplot(fig)
            else:
                st.info('暂无统计数据')
        except Exception as e:
            st.error(f'生成统计图表时出错: {e}')

if __name__ == '__main__':
    main()
相关推荐
啊森要自信5 小时前
【MySQL 数据库】使用C语言操作MySQL
linux·c语言·开发语言·数据库·mysql
新子y5 小时前
【小白笔记】关于 Python 类、初始化以及 PyTorch 数据处理的问题
pytorch·笔记·python
程序员小远5 小时前
如何编写自动化测试用例?
自动化测试·软件测试·python·测试工具·职场和发展·测试用例·接口测试
Hello.Reader5 小时前
在 Flink 中用好 Java 8 Lambda类型推断、`.returns(...)` 与常见坑位
java·python·flink
滑水滑成滑头5 小时前
**发散创新:模拟计算的高级应用与实现**随着科技的飞速发展,模拟计算已经成为了众多领域的核心工
java·服务器·python·科技
程序猿John5 小时前
python深度学习之爬虫篇
开发语言·爬虫·python
你想考研啊5 小时前
二、redis集群部署(3主3从)
数据库·redis·缓存
冉冰学姐6 小时前
SSM考试管理z2zvx(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
数据库·ssm 框架·学生信息管理
友友马6 小时前
『 QT 』信号-槽 补充: Qt信号槽断开连接与Lambda槽技巧
开发语言·数据库·qt