Python Flask Web应用,用于定期从CSV文件显示内容,并包含自动更新与数据清理功能。 RSS -> CSV -> Web page 用Nas的Container/docker 自动服务

目的是为了快速浏览新闻

项目:

利用华尔街日报(wsj)的RSS上看到新闻,不需要第三方的应用程序,用网页展示。

把codes 放到 Nas Container/Docker 上跑,就可以随时看内容,还可以定时更新。

工具: laptop windows11, docker,  qnap nas + app container,  python 

后面还想加入其它的,琢磨Twitter去了。

华尔街日报WSJ 的数字分享

https://www.wsj.com/news/rss-news-and-feeds

在Opinion 观点, World News 全球新闻 等上点右鼠标右键获得 links

我的第一个python code rss_to_csv.py 是用来收集rss内容并转到 csv 文件里:
python code rss_to_csv.py 代码:

# 如果这代码对你有用,你需要安装 import 后面这些必要的库

import os   # 用于操作文件和目录
import feedparser #用于解析RSS源
import pandas as pd #用于数据处理和将数据保存为CSV格式
from deep_translator import GoogleTranslator #用于将RSS内容从英文翻译成中文
from datetime import datetime #用于获取当前时间,以便生成带有时间戳的CSV文件名

# 设置 CSV 文件的目录  这些代码的目录位置已经是按照 linux 调整,因为要跑在Docker里
csv_directory = '/usr/src/app/rss_project/csv_files'
os.makedirs(csv_directory, exist_ok=True)

# 初始化翻译器
translator = GoogleTranslator(source='en', target='zh-CN')

# 定义 5个 RSS 源
rss_feeds = [
    "https://feeds.a.dj.com/rss/RSSOpinion.xml",
    "https://feeds.a.dj.com/rss/RSSWorldNews.xml",
    "https://feeds.a.dj.com/rss/WSJcomUSBusiness.xml",
    "https://feeds.a.dj.com/rss/RSSMarketsMain.xml",
    "https://feeds.a.dj.com/rss/RSJD.xml"
]

# 获取当前时间 并将其格式化为“年-月-日-小时分钟”的格式 并生成 CSV 文件名 避免重复,以后定期用程序删除, 我在app.py写的是保留7天
current_time = datetime.now().strftime("%Y-%m-%d-%H%M")
csv_file_name = f"wsj_{current_time}.csv"
csv_file_path = os.path.join(csv_directory, csv_file_name)

# 初始化数据列表 准备一个空列表来存储RSS源中提取的内容。
data = []

# 遍历每个 RSS 源并提取信息 然后从中提取每个entry的信息,如标题、摘要、链接和发布时间
for feed in rss_feeds:
    d = feedparser.parse(feed)
    for entry in d.entries:
        title = entry.title
        summary = entry.summary
        link = entry.link
        published = entry.published
        
        try:
            # 使用 deep_translator 翻译标题和摘要  尝试使用翻译器将标题和摘要从英文翻译成中文。如果翻译过程中发生异常,程序会输出错误信息,并继续使用原始内容。 最开始用 googletrans 开始没问题,后来频繁报错,换成了这个。 也是免费的。 如果要很好的翻译,推荐用AI
            translated_title = translator.translate(title)
            translated_summary = translator.translate(summary)
        except Exception as e:
            print(f"翻译失败: {e}")
            translated_title = title
            translated_summary = summary
        
        # 将翻译后的内容与原始内容合并成一个完整的条目,以换行符分隔。
        full_title = f"{title}\n{translated_title}"
        full_summary = f"{summary}\n{translated_summary}"
        
        # 将每条RSS信息(发布时间、链接、标题和摘要)存储在data列表中
        data.append([published, link, full_title, full_summary])

# 将数据保存到 CSV 文件 文件名为带有时间戳
df = pd.DataFrame(data, columns=["Published", "Link", "Title", "Summary"])
df.to_csv(csv_file_path, index=False, encoding='utf-8')

print(f"RSS内容已保存到 {csv_file_path}") #程序最后输出CSV文件已保存的提示信息

总结凑字:

wsj 提供了多个RSS源,汇总到一个表是目标。 因为是英文,为了便于速读,我把题目Titile与汇总Summary 做了双语显示(原+中)在捕捉内容时,同时翻译,一同存入csv文件。

CSV 文件的快速浏览

牢骚:

代码半天不到就拼完了,并上了windows Docker 都好好的。 没有字体,格式等问题。当转到 QNAP Nas Docker/Container上后,已经用了一天时间才搞定。  现在的代码已经是 linux 调整后的。

主程序 app.py 有Flask Web应用,定期从CSV文件显示内容,自动更新与数据清理功能
下面这些库都要安装

import os
import threading
import time
import pandas as pd
from flask import Flask, render_template_string #web框架,用于构建web应用.从字符串直接渲染HTML模板,不需要单独的HTML文件。
from datetime import datetime, timedelta #处理日期和时间的计算
import subprocess #用于运行程序 rss_to_csv.py 上面解释的程序。

# 初始化 Flask 应用
app = Flask(__name__)

# 设置 CSV 文件的目录 存储CSV文件
csv_directory = '/usr/src/app/rss_project/csv_files'
rss_script_path = '/usr/src/app/rss_to_csv.py'

os.makedirs(csv_directory, exist_ok=True)

# 定时运行 rss_to_csv.py
def run_rss_script():
    while True:
        subprocess.run(['python', rss_script_path])
        cleanup_old_files()  # 每次运行完脚本后清理旧文件
        time.sleep(7200)  # 每2小时运行一次

# 清理超过7天的 CSV 文件
def cleanup_old_files():
    now = datetime.now()
    cutoff = now - timedelta(days=7)
    for filename in os.listdir(csv_directory):
        if filename.startswith("wsj_") and filename.endswith(".csv"):
            file_path = os.path.join(csv_directory, filename)
            file_time = datetime.fromtimestamp(os.path.getmtime(file_path))
            if file_time < cutoff:
                os.remove(file_path)
                print(f"删除旧文件: {filename}")

# 在 Flask 应用启动前运行一次 rss_to_csv.py   所以程序第一次运行,可能不成展示内容或最新的内容,要等这个文件创建后 刷新才用
subprocess.run(['python', rss_script_path])

# 启动定时任务
threading.Thread(target=run_rss_script, daemon=True).start()

# 自定义 HTML 模板,设置标题为 "The Wall Street Journal"  4个列的宽度,高亮内容含有 China Chinese 条目
HTML_TEMPLATE = """
<!doctype html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>The Wall Street Journal</title>  <!-- 设置网页标题 -->
    <style>
        body {
            font-family: Arial, sans-serif;
            line-height: 1.5;
            color: #333;
            background-color: #f5f5f5;
            margin: 0;
            padding: 20px;
        }
        .header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 20px;
        }
        .file-info {
            font-weight: bold;
            font-size: 18px;
        }
        .refresh-btn-container {
            display: flex;
            align-items: center;
        }
        .refresh-btn {
            padding: 8px 16px;
            background-color: #4CAF50;
            color: white;
            border: none;
            cursor: pointer;
            border-radius: 4px;
            font-size: 14px;
        }
        .refresh-btn:hover {
            background-color: #45a049;
        }
        .countdown {
            margin-left: 10px;
            font-size: 14px;
            color: #555;
        }
        .current-time {
            font-size: 16px;
            font-weight: bold;
        }
        table {
            width: 100%;
            border-collapse: collapse;
            margin-bottom: 20px;
        }
        th, td {
            padding: 8px 12px;
            border: 1px solid #ddd;
            text-align: left;
            white-space: pre-line;  /* 支持换行显示 */
        }
        th {
            background-color: #f4f4f4;
            font-weight: bold;
        }
        tr:nth-child(even) {
            background-color: #f9f9f9;
        }
        .highlight {
            background-color: yellow;
        }
        .url-cell {
            max-width: 100px;  /* 将 URL 列的最大宽度设置为100px */
            word-wrap: break-word;  /* 强制换行 */
            overflow-wrap: break-word;
        }
        .url-container {
            display: inline-flex;  /* 确保 URL 和按钮在同一行 */
            align-items: center;
        }
        .copy-btn {
            margin-left: 10px;
            padding: 5px 10px;
            font-size: 12px;
            cursor: pointer;
        }
    </style>
</head>
<body>
    <div class="header">
        <div class="file-info">文件名: {{ file_name }}</div>
        <div class="refresh-btn-container">
            <button class="refresh-btn" οnclick="location.reload();">刷新页面</button>
            <div class="countdown">下次刷新倒计时: <span id="countdown">120</span> 秒</div>
        </div>
        <div class="current-time">当前时间: <span id="currentTime"></span></div>
    </div>
    <table>
        <thead>
            <tr>
                <th>发布时间</th>
                <th>URL</th>
                <th>标题</th>
                <th>摘要</th>
            </tr>
        </thead>
        <tbody>
            {% for row in data %}
            <tr class="{% if 'china' in row[2].lower() or 'china' in row[3].lower() or 'chinese' in row[2].lower() or 'chinese' in row[3].lower() %}highlight{% endif %}">
                <td>{{ row[0] }}</td>
                <td class="url-cell">
                    <div class="url-container">
                        <a href="{{ row[1] }}" target="_blank">URL</a>  <!-- 使用 URL 替代完整链接文本 -->
                        <button class="copy-btn" οnclick="copyToClipboard('{{ row[1] }}')">复制</button>
                    </div>
                </td>
                <td>{{ row[2] }}</td>
                <td>{{ row[3] }}</td>
            </tr>
            {% endfor %}
        </tbody>
    </table>

    <script>
        function updateTime() {
            const now = new Date();
            const timeString = now.toLocaleTimeString();
            document.getElementById('currentTime').textContent = timeString;
        }
        setInterval(updateTime, 1000);  // 每秒更新一次时间

        function copyToClipboard(text) {
            const tempInput = document.createElement('input');
            tempInput.value = text;
            document.body.appendChild(tempInput);
            tempInput.select();
            document.execCommand('copy');
            document.body.removeChild(tempInput);
            alert('URL 已复制到剪贴板');
        }

        function startCountdown(duration) {
            let timer = duration, seconds;
            const countdownElement = document.getElementById('countdown');

            setInterval(() => {
                seconds = parseInt(timer, 10);
                countdownElement.textContent = seconds;

                if (--timer < 0) {
                    location.reload();
                }
            }, 1000);
        }

        window.onload = () => {
            startCountdown(120);  // 设定倒计时时间为120秒
        };
    </script>
</body>
</html>

补充:

程序从Windows上移到 Linux Docker上,后都缺少字库,HTML解释的也不同,在最后显示结果差异很大,原本在Windows上格式字体完美,在Linux上会重来。 为此,安装了自库,多次修改HTML内容,花了学习+拼代码的2倍时间。

送到Nas的Docker/Container上

我不想让我的Laptop 24小时转,在NAS上跑是最好的安排

我用的是QNAP 4硬盘 家用NAS, 它上面有 Container Station(下面中间白色背景 app), 功能是半价的Docker 跑小程序没问题。

之前有个从github 复制来的(ghcr.io/imputnet/cobalt),用于下载 youtube视频,后来也移到NAS Container上,都是Web 服务。它给我的启发,也做个浏览器可以使用的程序。

制作Docker配置所需的文件 
Dockerfile 内容:

# 使用官方的 Python 3.12 基础镜像
FROM python:3.12-slim

# 设置工作目录
WORKDIR /usr/src/app

# 复制当前目录的内容到工作目录
COPY . .

# 安装依赖
RUN pip install --no-cache-dir -r requirements.txt

# 启动应用
CMD ["python", "app.py"]
 

requirements.txt 内容:

Flask
pandas
feedparser
googletrans==4.0.0-rc1
python-dateutil
pytz
 

pytz是字体库,发现字体变化后 手机安装的,列到需求清单里

这小段写了这么多,这两文件几乎不用。我用docker 命令操作的,因为nas上的app有bug

现在你需要一个工具,可以连接NAS的SSH 服务的客户端。NAS也需要打开SSH服务,默认是不开的。
Docker

在这个实践中,用不到 docker-compose.yml 

简单介绍一下:Docker与家用NAS里的Container差不多,后者是在家用NAS上实现Docker所有功能的APP

container: 理解为阉割的虚拟机vm,它与主机共享OS内核,所以操作系统的不同版本对这个平台上的程序有很大影响。
Image: 只读的完美计算机(VM)环境,就跟光蝶一样,一切都在里面,移到任何的 Docker/Container这个播放器上,都通用。
Application: Container容器中的具体软件或服务,列如 word,execel,sql 这类独立软件或服务,跑在各自的 container容器中,互相不会干扰。

在我的实践里,让程序在容器中跑就行了。类似于一个批处理命令,循环不退出。

常用的Docker命令:

docker ps      # 查看OS上正在运行的容器
docker kill  #中止运行的容器
docker stop <container_id>  # 停止容器
docker start <container_id>  # 启动容器
docker build -t <container中的名字 我用的是 rss_app_container> . (这里有个 “点”) 
docker rm <container_name> #删除容器
docker exec -it <container_name> /bin/bash 进入容器内部  排错主要靠这行
docker cp 文件名 <container_name>:/容器内的路径

这些命令已经足够了,接着后续操作:

所有的文件放到nas上 rss_to_csv.py  app.py  就两个

用ssh客户端链接NAS,并找到上面两个文件所在的目录

执行命令: 
docker build -t  rss_app_container #创建一个 rss_app_container容器
docker cp rss_to_csv.py rss_app_container:/usr/src/app  #把两个文件分别复制到容器 /usr/src/app 目录, 也可以用通配符来多选,我只是列出来清楚些
docker cp app.py rss_app_container:/usr/src/app

Done: 创建 rss_app_container 容器,复制两个文件到上面目录, 在NAS container Station里能看到它,  如下图:

红框里就是新建的容器 rss_app_container   (我也不知道为什么写这么长的名字,已经在container里的,还要写同名。)

flask框架跑这个轻程序没问题, 也指定了端口 5000, 在启动时,给它一个端口映射如图:

代码已经跑快4天,NAS不关机 只要打开 http://nas:5000 就能快速看WSJ新闻。 NAS重启 Container Center 也会启动,并运行这些 容器: rss_app_container, 还有之前提到的 github image

下面是浏览器中看到的内容: http://192.168.1.8:5000  我的NAS IP,与 这个映射的端口。 我有安装证书,但要有DNS名字,不露怯了。

上面黄色行,代表里面有出发关键词:China  / Chinese

如果想分享出去,点复制按钮就获得了当条的链接。如果需要详细阅读,可以点URL,如下:

推荐这款终端软, 一次性购买 SecureCRT  
想演示进入 docker container, 没人开门算了

补充:注意权限问题,在容器里,你传递进来的文件 权限,往往是 root root  | 用户  用户组
           可能程序在访问 CSV文件时,不能打开。  需要分配到适当的组。 还有文件读写权限。

  • 13
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值