目的是为了快速浏览新闻
项目:
利用华尔街日报(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文件时,不能打开。 需要分配到适当的组。 还有文件读写权限。