UI-Anpassung: Feed-Anzeige entfernt, Tabelle verbessert

This commit is contained in:
Oliver 2025-07-05 16:36:29 +02:00
parent fe2191e6c8
commit 2db9544fda
9 changed files with 438 additions and 156 deletions

4
.gitignore vendored
View file

@ -24,8 +24,8 @@ Thumbs.db
.env .env
# JSON-Datenbanken / Zwischenspeicherung # JSON-Datenbanken / Zwischenspeicherung
*.json *.json
!feeds.json # optional entfernbar, wenn du Beispielfeeds mit versionieren willst !feeds.json # optional entfernbar, wenn du Beispielfeeds mit versionieren willst
# LOG Files ausschließen, dienen nur zum Debuggen # LOG Files ausschließen, dienen nur zum Debuggen
# *.log # *.log

View file

@ -1,5 +1,25 @@
# CHANGELOG.md # CHANGELOG.md
## [1.4.1] 2025-07-03
### Hinzugefügt
- Logging für `process_articles()`, damit nachvollziehbar ist, welche Feeds verarbeitet wurden
- Rückmeldung in der App bei Klick auf „Alle Feeds neu laden“
### Geändert
- `main.py`: Inhalte aus `content`, `summary` oder `description` werden vollständig geladen und mit `BeautifulSoup` bereinigt
- Sicherstellung, dass `fetch_and_process_feed()` alle relevanten Artikelinformationen vollständig speichert
### Fehlerbehebungen
- Problem behoben, bei dem Artikeltexte nicht vollständig übernommen wurden
## [1.3.1] 2025-07-03
### Added
- Tabellenansicht mit Checkbox, Titel, Datum, Zusammenfassung, Wortanzahl, Tags, Status
- Direktes Bearbeiten des Status über Dropdown-Menü
- Massenbearbeitung von Artikeln per Checkbox
- Rewrite-Button für alle Artikel mit Status 'Rewrite'
## [1.2.0] - 2025-07-04 ## [1.2.0] - 2025-07-04
### Hinzugefügt ### Hinzugefügt
- Automatische Bilderkennung beim Einlesen von Artikeln - Automatische Bilderkennung beim Einlesen von Artikeln

View file

@ -1,5 +0,0 @@
2025-07-04 09:29:50,207 - INFO - Status von 1 Artikel(n) auf 'Rewrite' gesetzt.
2025-07-04 09:30:17,000 - INFO - HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
2025-07-04 09:30:17,010 - INFO - ✅ Artikel umgeschrieben: Das weltweit größte Caravaning-Erlebnis
2025-07-04 09:46:03,001 - INFO - Status von 1 Artikel(n) auf 'Online' gesetzt.
2025-07-04 19:36:22,449 - INFO - 1 neue Artikel geladen.

159
app.py
View file

@ -1,92 +1,95 @@
# app.py # app.py
import streamlit as st import streamlit as st
import json
import os
from datetime import datetime from datetime import datetime
from dotenv import load_dotenv from main import (
from email.utils import parsedate_to_datetime load_feeds,
from main import load_articles, save_articles, process_articles, fetch_and_process_feed, rewrite_articles save_feeds,
load_articles,
save_articles,
process_articles,
rewrite_articles
)
import os
load_dotenv() st.set_page_config(page_title="📰 RSS Artikel Manager", layout="wide")
st.title("📰 RSS Artikel Manager")
st.set_page_config(layout="wide", page_title="RSS Article Manager") # RSS Feed Verwaltung
st.sidebar.header("📡 RSS Feeds verwalten")
feeds = load_feeds()
new_feed = st.sidebar.text_input("Neuen RSS Feed hinzufügen")
if st.sidebar.button("Feed hinzufügen"):
if new_feed and new_feed not in feeds:
feeds.append({"url": new_feed})
save_feeds(feeds)
st.sidebar.success("Feed hinzugefügt")
# Artikelstatusfilter #if feeds:
status_filter = st.sidebar.selectbox("🔍 Artikelstatus filtern", ["Alle", "New", "Rewrite", "Process", "Online", "On Hold", "Trash"]) # st.sidebar.write("### Aktuelle Feeds:")
# for feed in feeds:
# Neuen Feed hinzufügen # url = feed["url"] if isinstance(feed, dict) else feed
st.sidebar.markdown("---") # st.sidebar.markdown(f"- {url}")
st.sidebar.header(" RSS Feed hinzufügen") #else:
new_feed_url = st.sidebar.text_input("Feed URL") # st.sidebar.info("Noch keine Feeds hinzugefügt.")
if st.sidebar.button("Feed hinzufügen") and new_feed_url:
fetch_and_process_feed(new_feed_url)
st.rerun()
# Alle Feeds neu laden
if st.sidebar.button("Alle Feeds neu laden"):
process_articles()
st.rerun()
# Artikel laden # Artikel laden
try: if st.sidebar.button("🔄 Alle Feeds neu laden"):
articles = load_articles() existing_ids = [a["id"] for a in load_articles()]
except json.decoder.JSONDecodeError: process_articles(existing_ids)
articles = []
# Artikel nach Status filtern
if status_filter != "Alle":
articles = [a for a in articles if a.get("status") == status_filter]
# Artikelübersicht
st.title("📰 RSS Artikel Übersicht")
st.markdown("---")
if not articles:
st.info("Keine Artikel gefunden.")
else:
st.markdown("### 📄 Artikelliste")
selected_ids = []
all_statuses = ["New", "Rewrite", "Process", "Online", "On Hold", "Trash"]
for article in articles:
col1, col2, col3, col4, col5, col6, col7 = st.columns([0.5, 1.2, 2.5, 2, 1, 2, 1.2])
with col1:
if st.checkbox("", key=f"select_{article['id']}"):
selected_ids.append(article['id'])
with col2:
date = parsedate_to_datetime(article['date']).strftime("%d.%m.%y")
st.markdown(date)
with col3:
st.markdown(article['title'])
with col4:
st.markdown(article['summary'][:150] + ("..." if len(article['summary']) > 150 else ""))
with col5:
word_count = len(article['text'].split())
st.markdown(str(word_count))
with col6:
st.markdown(", ".join(article.get("tags", [])))
with col7:
status = st.selectbox("", all_statuses, index=all_statuses.index(article.get("status", "New")), key=f"status_{article['id']}")
if status != article.get("status"):
article["status"] = status
save_articles(articles)
st.rerun() st.rerun()
if selected_ids: if st.sidebar.button("✍️ Artikel umschreiben (Rewrite)"):
new_status = st.selectbox("Status für ausgewählte Artikel setzen", all_statuses)
if st.button("✅ Status aktualisieren"):
for article in articles:
if article['id'] in selected_ids:
article['status'] = new_status
save_articles(articles)
st.success("Status aktualisiert.")
st.rerun()
st.markdown("---")
if st.button("✍️ Artikel mit Status 'Rewrite' umschreiben"):
rewrite_articles() rewrite_articles()
st.rerun() st.rerun()
st.markdown("---") # Artikelübersicht
st.header("📋 Artikelübersicht")
status_filter = st.selectbox("Status filtern", ["Alle", "New", "Rewrite", "Process", "Online", "On Hold", "Trash"])
articles = load_articles()
if status_filter != "Alle":
articles = [a for a in articles if a.get("status") == status_filter]
# Tabelle anzeigen
if articles:
st.markdown("### 📄 Übersichtstabelle")
st.write("**Spaltenübersicht:** Auswahl | Datum | Titel | Zusammenfassung | Wörter | Tags | Status")
for article in articles:
cols = st.columns([0.05, 0.1, 0.2, 0.25, 0.05, 0.2, 0.15])
with cols[0]:
st.checkbox("", key=f"select_{article['id']}")
with cols[1]:
st.markdown(datetime.strptime(article["date"], "%a, %d %b %Y %H:%M:%S %z").strftime("%d.%m.%y") if "GMT" in article["date"] or "+" in article["date"] else article["date"][:10])
with cols[2]:
st.markdown(f"**{article['title']}**")
with cols[3]:
st.markdown(article.get("summary", "")[:150])
with cols[4]:
st.markdown(str(len(article.get("text", "").split())))
with cols[5]:
st.markdown(", ".join(article.get("tags", [])))
with cols[6]:
status_options = ["New", "Rewrite", "Process", "Online", "On Hold", "Trash"]
current_status = article.get("status", "New")
new_status = st.selectbox("", status_options, index=status_options.index(current_status), key=f"status_{article['id']}")
if new_status != current_status:
article["status"] = new_status
save_articles(articles)
st.rerun()
with st.expander(f"🔍 {article['title']}"):
st.markdown("#### ✍️ Artikeltext")
st.code(f"{article['title']}\n\n{article['text']}\n\nQuelle: {article['link']}", language="markdown")
st.markdown("#### 🏷️ Tags")
st.code(", ".join(article.get("tags", [])), language="markdown")
st.markdown("#### 🖼️ Bilder")
for img in article.get("images", []):
st.image(img["url"], caption=img.get("caption", "Kein Titel"), use_column_width=True)
st.caption(f"© {img.get('copyright', 'Unbekannt')} | [Quelle]({img.get('copyright_url', '#')})")
else:
st.info("Keine Artikel für den gewählten Status gefunden.")

154
data/articles.json Normal file
View file

@ -0,0 +1,154 @@
[
{
"id": "Artikel 5926 von camping-news.de",
"title": "Das weltweit gr\u00f6\u00dfte Caravaning-Erlebnis",
"date": "Tue, 13 May 2025 00:00:00 +0200",
"summary": "Vom 29. August (Preview Day) bis zum 7. September 2025 wird Düsseldorf erneut zum Treffpunkt der Caravaning-Welt",
"text": "Der CARAVAN SALON ist die f\u00fchrende Messe f\u00fcr Caravaning weltweit und bietet eine gro\u00dfe Auswahl an Reisemobilen, Caravans und Campervans. Erg\u00e4nzt wird das Angebot durch eine Vielzahl von Zubeh\u00f6r, technischem Equipment und Dienstleistungen im Bereich Caravaning und Camping. Auch Campingpl\u00e4tze und Reisemobilstellpl\u00e4tze werden vorgestellt. Diese Messe ist ein unerl\u00e4sslicher Treffpunkt der Branche, bei dem Kunden die M\u00f6glichkeit haben, Fahrzeuge vor dem Kauf zu erleben und zu vergleichen.\n\nDie Messe pr\u00e4sentiert zudem die Premieren der kommenden Saison, darunter Top-Produkte, Innovationen und Weltneuheiten, die erstmals \u00f6ffentlich zug\u00e4nglich sind. Dank einer neuen Hallenstruktur k\u00f6nnen Besucher schnell und gezielt ihr Interessengebiet erreichen, egal ob sie Camping-Neulinge, Technik-Fans, kreative Selbstausbauer oder Luxus-Liebhaber sind.\n\nCaravaning erfreut sich seit vielen Jahren wachsender Beliebtheit aufgrund der individuellen und flexiblen Reiseform, die ein selbstbestimmter Urlaub im Einklang mit der Natur erm\u00f6glicht. Dieser Trend hat Caravaning zu einer der beliebtesten Reiseformen weltweit gemacht.\n\nAm 29. August findet der Preview Day statt, an dem die Neuheiten der Branche in Ruhe entdeckt werden k\u00f6nnen. Der Ticketverkauf startet am 11. Juni. Weitere Informationen sind auf der offiziellen Website der Messe erh\u00e4ltlich.",
"tags": [
"#Caravan Salon",
"#Camping Messe",
"#Reisemobile"
],
"status": "Online",
"link": "https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5926",
"images": [
{
"url": "https://www.camping-in-deutschland.de/pics2020/logo_weiss.png",
"alt": "Bild aus Originalartikel",
"copyright_text": "Unbekannt",
"copyright_link": "https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5926"
},
{
"url": "https://www.camping-in-deutschland.de/img/news13/caravan24_ct7020.jpg",
"alt": "Bild aus Originalartikel",
"copyright_text": "Unbekannt",
"copyright_link": "https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5926"
},
{
"url": "https://www.camping-in-deutschland.de/pics2013/scrollbox-shadow.png",
"alt": "Bild aus Originalartikel",
"copyright_text": "Unbekannt",
"copyright_link": "https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5926"
},
{
"url": "https://www.camping-in-deutschland.de/pics2013/scrollbox-shadow.png",
"alt": "Bild aus Originalartikel",
"copyright_text": "Unbekannt",
"copyright_link": "https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5926"
},
{
"url": "https://www.camping-in-deutschland.de/pics2013/scrollbox-shadow.png",
"alt": "Bild aus Originalartikel",
"copyright_text": "Unbekannt",
"copyright_link": "https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5926"
},
{
"url": "https://www.camping-in-deutschland.de/pics2013/scrollbox-shadow.png",
"alt": "Bild aus Originalartikel",
"copyright_text": "Unbekannt",
"copyright_link": "https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5926"
},
{
"url": "https://www.hundefreundliche-campingplaetze.de/logos/Familienfreundliche.png",
"alt": "Bild aus Originalartikel",
"copyright_text": "Unbekannt",
"copyright_link": "https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5926"
},
{
"url": "https://www.camping-in-deutschland.de/camp_bilder/2938.345.thb.jpg",
"alt": "Bild aus Originalartikel",
"copyright_text": "Unbekannt",
"copyright_link": "https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5926"
},
{
"url": "https://www.camping-in-deutschland.de/camp_bilder/1839.345.thb.jpg",
"alt": "Bild aus Originalartikel",
"copyright_text": "Unbekannt",
"copyright_link": "https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5926"
},
{
"url": "https://www.camping-in-deutschland.de/camp_bilder/1887.345.thb.jpg",
"alt": "Bild aus Originalartikel",
"copyright_text": "Unbekannt",
"copyright_link": "https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5926"
}
]
},
{
"id": "Artikel 5923 von camping-news.de",
"title": "Komfort und Flexibilit\u00e4t f\u00fcr moderne Camper",
"date": "Wed, 07 May 2025 00:00:00 +0200",
"summary": "dwt-Zelte präsentiert das Luftzelt Scala Air",
"text": "Der Hersteller dwt-Zelte pr\u00e4sentiert mit dem Modell Scala Air ein neues Zelt f\u00fcr Camper, das in zwei Gr\u00f6\u00dfen erh\u00e4ltlich ist und sich durch seinen gro\u00dfz\u00fcgigen Raum und eine durchdachte Bel\u00fcftung auszeichnet. Das Zelt nutzt eine moderne Air-In-Technologie f\u00fcr einen schnellen Aufbau und bietet eine stabile Struktur dank robuster Luftschl\u00e4uche.\n\nDie Ausf\u00fchrungen Scala Air 250 und Scala Air 280 unterscheiden sich in ihrer Tiefe von 250 bzw. 280 cm und eignen sich besonders f\u00fcr Kurzurlaube und wechselfrequente Standorte. Die Zelte sind windstabil und dank zus\u00e4tzlicher Luftschl\u00e4uche und durchdachter Fensterkonstruktionen optimal bel\u00fcftet. \n\nDas Design ist modular, was eine flexible Raumgestaltung erm\u00f6glicht. Entnehmbare W\u00e4nde, hochrollbare T\u00fcr und anpassbare Seitenw\u00e4nde bieten ein individuelles Raumkonzept. Das Zelt ist aus robusten, wetterfesten und pflegeleichten Materialien gefertigt und der Preis bewegt sich je nach Gr\u00f6\u00dfe zwischen 1.995 \u20ac und 2.215 \u20ac.\n\nDie Zelte verf\u00fcgen \u00fcber Sicherheitsgurte f\u00fcr windige Standorte und sind mit H\u00f6henausgleichs-Blocks ausgestattet, um Bodenunebenheiten ausgleichen zu k\u00f6nnen. Damit richten sich die Scala Air Zelte an alle Camper, die Wert auf ein flexibles, robustes und schnell aufzubauendes Zelt legen. Weitere Informationen sind auf der Herstellerseite zu finden.",
"tags": [
"\"Camping\"",
"\"dwt-Zelte\"",
"\"Air-In-Technologie\""
],
"status": "Process",
"link": "https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5923",
"images": [
{
"url": "https://www.camping-in-deutschland.de/pics2020/logo_weiss.png",
"alt": "Bild aus Originalartikel",
"copyright_text": "Unbekannt",
"copyright_link": "https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5923"
},
{
"url": "https://www.camping-in-deutschland.de/img/news13/scala_dia5199.jpg",
"alt": "Bild aus Originalartikel",
"copyright_text": "Unbekannt",
"copyright_link": "https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5923"
},
{
"url": "https://www.camping-in-deutschland.de/pics2013/scrollbox-shadow.png",
"alt": "Bild aus Originalartikel",
"copyright_text": "Unbekannt",
"copyright_link": "https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5923"
},
{
"url": "https://www.camping-in-deutschland.de/pics2013/scrollbox-shadow.png",
"alt": "Bild aus Originalartikel",
"copyright_text": "Unbekannt",
"copyright_link": "https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5923"
},
{
"url": "https://www.camping-in-deutschland.de/pics2013/scrollbox-shadow.png",
"alt": "Bild aus Originalartikel",
"copyright_text": "Unbekannt",
"copyright_link": "https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5923"
},
{
"url": "https://www.camping-in-deutschland.de/pics2013/scrollbox-shadow.png",
"alt": "Bild aus Originalartikel",
"copyright_text": "Unbekannt",
"copyright_link": "https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5923"
},
{
"url": "https://www.hundefreundliche-campingplaetze.de/logos/Familienfreundliche.png",
"alt": "Bild aus Originalartikel",
"copyright_text": "Unbekannt",
"copyright_link": "https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5923"
},
{
"url": "https://www.camping-in-deutschland.de/camp_bilder/2940.345.thb.jpg",
"alt": "Bild aus Originalartikel",
"copyright_text": "Unbekannt",
"copyright_link": "https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5923"
},
{
"url": "https://www.camping-in-deutschland.de/camp_bilder/2433.345.thb.jpg",
"alt": "Bild aus Originalartikel",
"copyright_text": "Unbekannt",
"copyright_link": "https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5923"
},
{
"url": "https://www.camping-in-deutschland.de/camp_bilder/2495.345.thb.jpg",
"alt": "Bild aus Originalartikel",
"copyright_text": "Unbekannt",
"copyright_link": "https://www.camping-in-deutschland.de/index.php?pid=meldung&id=5923"
}
]
}
]

View file

@ -5,3 +5,52 @@ INFO:httpx:HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1
INFO:httpx:HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK" INFO:httpx:HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
INFO:root:✅ Artikel 'Abenteuer für die Kleinen, Entspannung für INFO:root:✅ Artikel 'Abenteuer für die Kleinen, Entspannung für
die Großen' umgeschrieben. die Großen' umgeschrieben.
INFO:root:Abrufen von Feed: https://www.camping-news.de/rss/
INFO:root:Starte Umschreiben von Artikeln mit Status 'Rewrite' ...
INFO:httpx:HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
INFO:httpx:HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
INFO:root:✅ Artikel 'Den eigenen Verbrauchszahlen auf der Spur' umgeschrieben.
INFO:root:Starte Umschreiben von Artikeln mit Status 'Rewrite' ...
INFO:root:Abrufen von Feed: https://www.camping-news.de/rss/
INFO:root:🔄 Starte Verarbeitung von 0 Feeds...
INFO:root:✅ Alle Feeds erfolgreich verarbeitet.
INFO:root:🔄 Starte Verarbeitung von 1 Feeds...
INFO:root:📥 Verarbeite Feed: https://www.camping-news.de/rss/
INFO:root:✅ Alle Feeds erfolgreich verarbeitet.
INFO:root:🔄 Starte Verarbeitung von 1 Feeds...
INFO:root:📥 Verarbeite Feed: https://www.camping-news.de/rss/
INFO:root:✅ Alle Feeds erfolgreich verarbeitet.
2025-07-05 15:53:57,795 - INFO - Lade Feed: https://www.camping-news.de/rss/
2025-07-05 15:54:02,133 - INFO - 10 neue Artikel gefunden in https://www.camping-news.de/rss/
2025-07-05 15:54:02,136 - INFO - 10 neue Artikel gespeichert.
2025-07-05 15:57:50,216 - INFO - Lade Feed: https://www.camping-news.de/rss/
2025-07-05 15:57:54,463 - INFO - 10 neue Artikel gefunden in https://www.camping-news.de/rss/
2025-07-05 15:57:54,465 - INFO - 10 neue Artikel gespeichert.
2025-07-05 16:00:32,416 - INFO - Lade Feed: https://www.camping-news.de/rss/
2025-07-05 16:00:36,108 - INFO - 9 neue Artikel gefunden in https://www.camping-news.de/rss/
2025-07-05 16:00:36,109 - INFO - 9 neue Artikel gespeichert.
2025-07-05 16:06:28,832 - INFO - Lade Feed: https://www.camping-news.de/rss/
2025-07-05 16:06:30,875 - INFO - 4 neue Artikel gefunden in https://www.camping-news.de/rss/
2025-07-05 16:06:30,877 - INFO - 4 neue Artikel gespeichert.
2025-07-05 16:08:14,063 - INFO - Lade Feed: https://www.camping-news.de/rss/
2025-07-05 16:08:15,413 - INFO - 3 neue Artikel gefunden in https://www.camping-news.de/rss/
2025-07-05 16:08:15,414 - INFO - 3 neue Artikel gespeichert.
2025-07-05 16:09:14,943 - INFO - Lade Feed: https://www.camping-news.de/rss/
2025-07-05 16:09:18,883 - INFO - 9 neue Artikel gefunden in https://www.camping-news.de/rss/
2025-07-05 16:09:18,885 - INFO - 9 neue Artikel gespeichert.
2025-07-05 16:17:36,273 - INFO - Lade Feed: https://www.camping-news.de/rss/
2025-07-05 16:17:36,691 - INFO - 0 neue Artikel gefunden in https://www.camping-news.de/rss/
2025-07-05 16:17:36,691 - INFO - Keine neuen Artikel gefunden.
2025-07-05 16:18:45,562 - INFO - ✍️ Umschreiben von: Den eigenen Verbrauchszahlen auf der Spur
2025-07-05 16:19:04,394 - INFO - HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
2025-07-05 16:19:06,129 - INFO - HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
2025-07-05 16:19:06,132 - INFO - ✅ Artikel umgeschrieben: Den eigenen Verbrauchszahlen auf der Spur
2025-07-05 16:19:06,132 - INFO - ✍️ Umschreiben von: Das weltweit größte Caravaning-Erlebnis
2025-07-05 16:19:18,595 - INFO - HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
2025-07-05 16:19:20,334 - INFO - HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
2025-07-05 16:19:20,336 - INFO - ✅ Artikel umgeschrieben: Das weltweit größte Caravaning-Erlebnis
2025-07-05 16:19:20,336 - INFO - ✍️ Umschreiben von: Komfort und Flexibilität für moderne Camper
2025-07-05 16:19:33,242 - INFO - HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
2025-07-05 16:19:34,796 - INFO - HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
2025-07-05 16:19:34,799 - INFO - ✅ Artikel umgeschrieben: Komfort und Flexibilität für moderne Camper
2025-07-05 16:19:34,803 - INFO - Alle Artikel mit Status 'Rewrite' wurden verarbeitet.

199
main.py
View file

@ -1,108 +1,169 @@
# main.py # main.py
import feedparser
import json import json
import os import os
from bs4 import BeautifulSoup
from datetime import datetime from datetime import datetime
import feedparser
from utils.image_extractor import extract_images_with_metadata
from openai import OpenAI
from dotenv import load_dotenv from dotenv import load_dotenv
import logging import logging
from utils.image_extractor import extract_images_with_metadata
import openai
load_dotenv() load_dotenv()
# Log-Verzeichnis sicherstellen # Logging konfigurieren
os.makedirs("logs", exist_ok=True) log_dir = "logs"
os.makedirs(log_dir, exist_ok=True)
logging.basicConfig(
filename=os.path.join(log_dir, "rss_tool.log"),
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s"
)
openai.api_key = os.getenv("OPENAI_API_KEY")
client = OpenAI() ARTICLES_FILE = "data/articles.json"
# 📝 Logging konfigurieren FEEDS_FILE = "data/feeds.json"
logging.basicConfig(filename='logs/rss_tool.log', level=logging.INFO)
ARTICLES_FILE = "processed_articles.json" VALID_STATUSES = ["New", "Rewrite", "Process", "Online", "On Hold", "Trash"]
FEEDS_FILE = "feeds.json"
def load_articles():
if os.path.exists(ARTICLES_FILE):
with open(ARTICLES_FILE, "r") as f:
return json.load(f)
return []
def save_articles(articles):
with open(ARTICLES_FILE, "w") as f:
json.dump(articles, f, indent=2)
def load_feeds(): def load_feeds():
if os.path.exists(FEEDS_FILE): if not os.path.exists(FEEDS_FILE):
return []
with open(FEEDS_FILE, "r") as f: with open(FEEDS_FILE, "r") as f:
return json.load(f) return json.load(f)
return []
def save_feeds(feeds): def save_feeds(feeds):
with open(FEEDS_FILE, "w") as f: with open(FEEDS_FILE, "w") as f:
json.dump(feeds, f, indent=2) json.dump(feeds, f, indent=2)
def fetch_and_process_feed(url): def load_articles():
logging.info(f"Abrufen von Feed: {url}") if not os.path.exists(ARTICLES_FILE):
feed = feedparser.parse(url) return []
articles = load_articles() with open(ARTICLES_FILE, "r") as f:
for entry in feed.entries: articles = json.load(f)
if any(a["link"] == entry.link for a in articles):
continue
try:
images = extract_images_with_metadata(entry.link)
except Exception as e:
logging.warning(f"Fehler beim Bildextrakt: {e}")
images = []
article = {
"id": f"{entry.link}",
"title": entry.title,
"summary": entry.summary,
"link": entry.link,
"date": entry.get("published", datetime.now().isoformat()),
"text": entry.summary,
"status": "New",
"images": images,
"tags": []
}
articles.append(article)
save_articles(articles)
def process_articles(): # Sicherstellen, dass jeder Artikel einen gültigen Status hat
for article in articles:
if article.get("status") not in VALID_STATUSES:
article["status"] = "New"
return articles
def save_articles(articles):
with open(ARTICLES_FILE, "w") as f:
json.dump(articles, f, indent=2)
def fetch_and_process_feed(feed_url, existing_ids):
feed = feedparser.parse(feed_url)
new_articles = []
for entry in feed.entries:
article_id = entry.get("id") or entry.get("link")
if not article_id or article_id in existing_ids:
continue
title = entry.get("title", "Kein Titel")
date = entry.get("published", datetime.now().isoformat())
summary = entry.get("summary", "")
content = entry.get("content", [{}])[0].get("value") or entry.get("description", "")
soup = BeautifulSoup(content, "html.parser")
clean_text = soup.get_text(" ", strip=True)
images = extract_images_with_metadata(entry.link)
new_articles.append({
"id": article_id,
"title": title,
"date": date,
"summary": summary,
"text": clean_text,
"tags": [],
"status": "New",
"link": entry.get("link", ""),
"images": images
})
return new_articles
def process_articles(existing_ids):
feeds = load_feeds() feeds = load_feeds()
for url in feeds: all_articles = load_articles()
fetch_and_process_feed(url) new_entries = []
for feed in feeds:
if isinstance(feed, dict):
url = feed.get("url")
else:
url = feed
if not url:
continue
try:
logging.info(f"Lade Feed: {url}")
entries = fetch_and_process_feed(url, existing_ids)
new_entries.extend(entries)
logging.info(f"{len(entries)} neue Artikel gefunden in {url}")
except Exception as e:
logging.error(f"Fehler beim Verarbeiten von {url}: {e}")
# Nur neue Artikel speichern, deren ID noch nicht vorhanden ist
existing_article_ids = set(article["id"] for article in all_articles)
unique_new_entries = [a for a in new_entries if a["id"] not in existing_article_ids]
if unique_new_entries:
all_articles.extend(unique_new_entries)
save_articles(all_articles)
logging.info(f"{len(unique_new_entries)} neue Artikel gespeichert.")
else:
logging.info("Keine neuen Artikel gefunden.")
def rewrite_articles(): def rewrite_articles():
logging.info("Starte Umschreiben von Artikeln mit Status 'Rewrite' ...")
articles = load_articles() articles = load_articles()
updated = False
for article in articles: for article in articles:
if article["status"] != "Rewrite": if article.get("status") == "Rewrite":
continue
try: try:
prompt = f"Fasse diesen Text neu und interessant zusammen:\n\n{article['summary']}" logging.info(f"✍️ Umschreiben von: {article['title']}")
response = client.chat.completions.create( prompt = f"Schreibe folgenden Artikel um und fasse ihn verständlich zusammen:\n\n{article['text']}"
response = openai.chat.completions.create(
model="gpt-4", model="gpt-4",
messages=[{"role": "user", "content": prompt}] messages=[
{"role": "system", "content": "Du bist ein professioneller Redakteur."},
{"role": "user", "content": prompt}
]
) )
rewritten = response.choices[0].message.content.strip() new_text = response.choices[0].message.content.strip()
article["text"] = rewritten article["text"] = f"{article['title']}\n\n{new_text}"
article["status"] = "Done" article["status"] = "Process"
# Tags generieren tag_prompt = f"Erstelle 3 passende, kurze Stichwörter (Tags) für diesen Artikel:\n\n{new_text}"
tag_prompt = f"Erstelle passende 3-5 Tags für diesen Text:\n\n{rewritten}" tag_response = openai.chat.completions.create(
tag_response = client.chat.completions.create(
model="gpt-4", model="gpt-4",
messages=[{"role": "user", "content": tag_prompt}] messages=[
{"role": "system", "content": "Du bist ein Blog-Tag-Generator."},
{"role": "user", "content": tag_prompt}
]
) )
tags = [tag.strip() for tag in tag_response.choices[0].message.content.split(",")] tags_raw = tag_response.choices[0].message.content.strip()
tags = [tag.strip(" ,") for tag in tags_raw.replace("\n", ",").split(",") if tag.strip()]
article["tags"] = tags article["tags"] = tags
updated = True # Sicherstellen, dass Bildmetadaten vollständig sind
logging.info(f"✅ Artikel '{article['title']}' umgeschrieben.") for img in article.get("images", []):
if "caption" not in img:
img["caption"] = "Kein Bildtitel vorhanden"
if "copyright" not in img:
img["copyright"] = "Unbekannt"
if "copyright_url" not in img:
img["copyright_url"] = "#"
logging.info(f"✅ Artikel umgeschrieben: {article['title']}")
except Exception as e: except Exception as e:
logging.error(f"❌ Fehler beim Umschreiben von '{article['title']}':\n{e}") logging.error(f"❌ Fehler beim Umschreiben von '{article['title']}': {e}")
if updated:
save_articles(articles) save_articles(articles)
logging.info("Alle Artikel mit Status 'Rewrite' wurden verarbeitet.")