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

davenian 2024-09-11 15:03:02 阅读 94

目的是为了快速浏览新闻

项目:

利用华尔街日报(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文件时,不能打开。  需要分配到适当的组。 还有文件读写权限。



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。