3. Projekt-Setup
Dieses Kapitel setzt das technische Grundgerüst auf. Ziel ist kein fertiger Publisher, sondern ein lauffähiges Projekt mit Python, Playwright, OpenAI-Anbindung, Konfiguration, Verzeichnisstruktur und erstem Smoke-Test.
Die Basis bleibt bewusst schlank:
Python
Playwright
OpenAI SDK
python-dotenv
PyYAML
Playwright installiert die benötigten Browser separat; für Linux-Server kann Playwright Systemabhängigkeiten und Browser mit einem kombinierten Befehl installieren. (playwright.dev)
3.1 Python-Version und virtuelle Umgebung
Empfohlen wird Python 3.11 oder neuer.
Prüfung:
python3 --version
Projektverzeichnis anlegen:
mkdir -p social-publisher
cd social-publisher
Virtuelle Umgebung erstellen:
python3 -m venv .venv
Aktivieren:
source .venv/bin/activate
Pip aktualisieren:
python -m pip install --upgrade pip
Unter Windows wäre der Aktivierungsbefehl:
.\.venv\Scripts\Activate.ps1
Das Handbuch geht im weiteren Verlauf von Linux aus.
3.2 Abhängigkeiten installieren
Für das MVP reichen diese Pakete:
pip install openai playwright python-dotenv pyyaml pytest
Die offizielle OpenAI-Python-Bibliothek wird über das Paket openai installiert; auf PyPI ist sie als offizielles OpenAI-Paket veröffentlicht. (PyPI)
requirements.txt erzeugen:
pip freeze > requirements.txt
Eine bewusst knappe manuelle Variante:
openai
playwright
python-dotenv
pyyaml
pytest
Für reproduzierbare Produktivsysteme werden Versionen später fixiert.
3.3 Playwright installieren
Nach der Python-Paketinstallation müssen die Browser installiert werden.
Für lokalen Betrieb:
python -m playwright install chromium
Für Linux-Server mit fehlenden Systemabhängigkeiten:
sudo .venv/bin/python -m playwright install --with-deps chromium
Playwright dokumentiert playwright install, playwright install-deps und die kombinierte Variante playwright install --with-deps chromium für Browser und Systemabhängigkeiten. (playwright.dev)
Prüfen:
python -m playwright install --list
3.4 OpenAI-Client einrichten
Für das Projekt wird die OpenAI-Anbindung gekapselt. Die restliche Anwendung soll nicht direkt mit dem SDK arbeiten.
Datei:
src/openai_client.py
Inhalt:
from openai import OpenAI
class OpenAiClient:
def __init__(self) -> None:
self.client = OpenAI()
def create_text(self, prompt: str) -> str:
response = self.client.responses.create(
model="gpt-4.1-mini",
input=prompt,
)
output_text = response.output_text
if isinstance(output_text, str) is False:
raise RuntimeError("OpenAI response did not contain text output.")
if output_text.strip() == "":
raise RuntimeError("OpenAI response was empty.")
return output_text
Der Client liest den API-Key standardmäßig aus der Umgebungsvariable OPENAI_API_KEY, sofern diese gesetzt ist. Die Verwendung des offiziellen Python-SDKs bleibt dadurch minimal. (PyPI)
3.5 .env-Konfiguration
Datei:
.env
Inhalt:
OPENAI_API_KEY=sk-...
APP_ENV=dev
APP_TIMEZONE=Europe/Berlin
DRY_RUN=true
Datei:
.env.example
Inhalt:
OPENAI_API_KEY=
APP_ENV=dev
APP_TIMEZONE=Europe/Berlin
DRY_RUN=true
Die .env gehört nicht ins Repository.
.gitignore:
.venv/
.env
__pycache__/
.pytest_cache/
storage/sessions/
storage/screenshots/
storage/logs/
Konfigurationsklasse:
src/config.py
Inhalt:
import os
from dataclasses import dataclass
from pathlib import Path
import yaml
from dotenv import load_dotenv
@dataclass(frozen=True)
class AppConfig:
app_env: str
timezone: str
dry_run: bool
root_dir: Path
config_file: Path
class ConfigLoader:
def __init__(self, root_dir: Path) -> None:
self.root_dir = root_dir
def load(self) -> AppConfig:
load_dotenv(self.root_dir / ".env")
app_env = os.getenv("APP_ENV")
timezone = os.getenv("APP_TIMEZONE")
dry_run_value = os.getenv("DRY_RUN")
if app_env is None:
raise RuntimeError("Missing APP_ENV.")
if timezone is None:
raise RuntimeError("Missing APP_TIMEZONE.")
if dry_run_value is None:
raise RuntimeError("Missing DRY_RUN.")
if dry_run_value == "true":
dry_run = True
elif dry_run_value == "false":
dry_run = False
else:
raise RuntimeError("DRY_RUN must be true or false.")
config_file = self.root_dir / "config.yaml"
if config_file.exists() is False:
raise RuntimeError("Missing config.yaml.")
with config_file.open("r", encoding="utf-8") as file:
yaml.safe_load(file)
return AppConfig(
app_env=app_env,
timezone=timezone,
dry_run=dry_run,
root_dir=self.root_dir,
config_file=config_file,
)
3.6 Verzeichnisstruktur
Anlegen:
mkdir -p data
mkdir -p prompts
mkdir -p src/platforms
mkdir -p storage/sessions
mkdir -p storage/screenshots
mkdir -p storage/images
mkdir -p storage/logs
mkdir -p tests
touch src/__init__.py
touch src/platforms/__init__.py
Zielstruktur:
social-publisher/
├── .env
├── .env.example
├── .gitignore
├── config.yaml
├── requirements.txt
├── data/
│ ├── books.json
│ ├── topics.json
│ ├── drafts.json
│ ├── approved_posts.json
│ └── published_posts.json
├── prompts/
│ └── smoke_test.txt
├── src/
│ ├── __init__.py
│ ├── main.py
│ ├── config.py
│ ├── openai_client.py
│ └── platforms/
│ └── __init__.py
├── storage/
│ ├── sessions/
│ ├── screenshots/
│ ├── images/
│ └── logs/
└── tests/
Basisdateien:
printf "[]\n" > data/books.json
printf "[]\n" > data/topics.json
printf "[]\n" > data/drafts.json
printf "[]\n" > data/approved_posts.json
printf "[]\n" > data/published_posts.json
config.yaml:
platforms:
linkedin:
enabled: true
session_path: "storage/sessions/linkedin.json"
screenshots_path: "storage/screenshots/linkedin"
limits:
max_posts_per_day: 1
max_comments_per_day: 3
max_likes_per_day: 10
Prompt-Datei:
prompts/smoke_test.txt
Inhalt:
Schreibe einen sachlichen Ein-Satz-Testbeitrag für einen kleinen deutschen E-Book-Verlag. Keine Emojis. Keine Hashtags.
3.7 Erster Smoke-Test
Der Smoke-Test prüft drei Dinge:
Konfiguration wird geladen.
OpenAI antwortet.
Playwright kann Chromium starten und einen Screenshot speichern.
Datei:
src/main.py
Inhalt:
from pathlib import Path
from playwright.sync_api import sync_playwright
from src.config import ConfigLoader
from src.openai_client import OpenAiClient
def run_openai_smoke_test(root_dir: Path) -> None:
prompt_file = root_dir / "prompts" / "smoke_test.txt"
if prompt_file.exists() is False:
raise RuntimeError("Missing prompts/smoke_test.txt.")
prompt = prompt_file.read_text(encoding="utf-8")
client = OpenAiClient()
text = client.create_text(prompt)
print("OPENAI_OK")
print(text)
def run_playwright_smoke_test(root_dir: Path) -> None:
screenshot_file = root_dir / "storage" / "screenshots" / "smoke_test.png"
with sync_playwright() as playwright:
browser = playwright.chromium.launch(headless=True)
page = browser.new_page()
page.goto("https://example.com", wait_until="domcontentloaded")
page.screenshot(path=str(screenshot_file), full_page=True)
browser.close()
if screenshot_file.exists() is False:
raise RuntimeError("Playwright screenshot was not created.")
print("PLAYWRIGHT_OK")
print(str(screenshot_file))
def main() -> None:
root_dir = Path(__file__).resolve().parent.parent
config_loader = ConfigLoader(root_dir)
config = config_loader.load()
print("CONFIG_OK")
print(config)
run_openai_smoke_test(root_dir)
run_playwright_smoke_test(root_dir)
if __name__ == "__main__":
main()
Ausführen:
python -m src.main
Erwartete Ausgabe:
CONFIG_OK
AppConfig(...)
OPENAI_OK
...
PLAYWRIGHT_OK
.../storage/screenshots/smoke_test.png
Damit ist das Projekt technisch lauffähig:
Python-Umgebung steht.
Abhängigkeiten sind installiert.
Playwright startet Chromium.
OpenAI-Client erzeugt Text.
.env wird geladen.
Projektstruktur ist vorbereitet.
Smoke-Test erzeugt einen Screenshot.
0 Kommentare