Add script, readme and requirements.
This commit is contained in:
parent
e395e76562
commit
0f532bd16a
2
.gitignore
vendored
2
.gitignore
vendored
@ -168,3 +168,5 @@ cython_debug/
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
pdfs/
|
||||
export/
|
36
README.md
36
README.md
@ -1,3 +1,39 @@
|
||||
# moodle_quiz_pdf_export
|
||||
|
||||
Kleines Script, um Moddle Quizzes/Tests von Studierenden automatisiert als PDF zu exportieren.
|
||||
|
||||
# Installation
|
||||
Requirements installieren:
|
||||
```
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
# Konfiguration
|
||||
Eine Textdatei `.env` im Hauptverzeichnis anlegen mit folgenden Inhalten und entsprechend anpassen:
|
||||
```
|
||||
MOODLE_URL="https://elearning.ohmportal.de"
|
||||
QUIZ_ATTEMPTS_URL="URL der Ergebnisübersicht des Tests"
|
||||
USERNAME="Moodle Username"
|
||||
PASSWORD="Moodle Passwort"
|
||||
PDF_OUTPUT_DIR="Gewünschtes Ausgabeverzeichnis"
|
||||
```
|
||||
|
||||
**Hinweis:** `QUIZ_ATTEMPTS_URL` sollte die vollständige URL der Ergebnisübersicht des Moodle Tests sein, ggf. inklusive der gewünschten Filter (z.B. Gruppe) sowie einer Seitengröße, die alle Versuche anzeigt. Das Skript extrahiert die Versuche der Studierenden aus dieser Seite, d.h. es werden genau diejenigen Versuche exportiert, die bei Aufruf der URL in der Tabelle sichtbar sind.
|
||||
|
||||
# Ausführen
|
||||
Das Skript ausführen.
|
||||
```
|
||||
python moodle_quiz_pdf_export.py
|
||||
```
|
||||
|
||||
Es sollte eine Ausgabe ähnlich wie unten erscheinen und die PDF-Dateien im gewünschten Ordner gespeichert werden.
|
||||
|
||||
```
|
||||
Found 53 attempt URLs.
|
||||
[1/53] Downloading: https://elearning.ohmportal.de/mod/quiz/review.php?attempt=241863
|
||||
[2/53] Downloading: https://elearning.ohmportal.de/mod/quiz/review.php?attempt=241865
|
||||
[3/53] Downloading: https://elearning.ohmportal.de/mod/quiz/review.php?attempt=241866
|
||||
...
|
||||
[53/53] Downloading: https://elearning.ohmportal.de/mod/quiz/review.php?attempt=241948
|
||||
✅ All PDFs downloaded.
|
||||
```
|
93
moodle_quiz_pdf_export.py
Normal file
93
moodle_quiz_pdf_export.py
Normal file
@ -0,0 +1,93 @@
|
||||
from selenium import webdriver
|
||||
from selenium.common.exceptions import NoSuchElementException
|
||||
from selenium.webdriver.chrome.options import Options
|
||||
from selenium.webdriver.common.by import By
|
||||
import time
|
||||
import os
|
||||
import base64
|
||||
import dotenv
|
||||
|
||||
# Load configuration variables from .env File
|
||||
dotenv.load_dotenv()
|
||||
MOODLE_URL = os.getenv('MOODLE_URL')
|
||||
QUIZ_ATTEMPTS_URL = os.getenv('QUIZ_ATTEMPTS_URL')
|
||||
USERNAME = os.getenv('USERNAME')
|
||||
PASSWORD = os.getenv('PASSWORD')
|
||||
PDF_OUTPUT_DIR = os.getenv('PDF_OUTPUT_DIR')
|
||||
|
||||
# Ensure output directory exists
|
||||
os.makedirs(PDF_OUTPUT_DIR, exist_ok=True)
|
||||
|
||||
# Chrome options for headless PDF printing
|
||||
chrome_options = Options()
|
||||
chrome_options.add_argument('--headless=new') # use --headless=new for Chrome v112+
|
||||
chrome_options.add_argument('--disable-gpu')
|
||||
chrome_options.add_argument('--kiosk-printing')
|
||||
chrome_prefs = {
|
||||
"printing.print_preview_sticky_settings.appState": '{"recentDestinations":[{"id":"Save as PDF","origin":"local"}],"selectedDestinationId":"Save as PDF","version":2}',
|
||||
"savefile.default_directory": PDF_OUTPUT_DIR
|
||||
}
|
||||
chrome_options.add_experimental_option("prefs", chrome_prefs)
|
||||
chrome_options.add_argument('--disable-popup-blocking')
|
||||
chrome_options.add_argument('--no-sandbox')
|
||||
chrome_options.add_argument('--print-to-pdf-no-header')
|
||||
|
||||
# Start driver
|
||||
driver = webdriver.Chrome(options=chrome_options)
|
||||
|
||||
# --- LOGIN ---
|
||||
driver.get(MOODLE_URL)
|
||||
driver.find_element(By.ID, "username").send_keys(USERNAME)
|
||||
driver.find_element(By.ID, "password").send_keys(PASSWORD)
|
||||
driver.find_element(By.ID, "loginbtn").click()
|
||||
time.sleep(2)
|
||||
|
||||
# --- GO TO QUIZ RESULTS PAGE ---
|
||||
driver.get(QUIZ_ATTEMPTS_URL)
|
||||
time.sleep(3)
|
||||
|
||||
# --- SCRAPE ATTEMPT LINKS ---
|
||||
attempt_links = []
|
||||
elements = driver.find_elements(By.CSS_SELECTOR, "a[href*='review.php?attempt=']")
|
||||
for elem in elements:
|
||||
url = elem.get_attribute("href")
|
||||
if url and url not in attempt_links:
|
||||
attempt_links.append(url)
|
||||
|
||||
print(f"Found {len(attempt_links)} attempt URLs.")
|
||||
|
||||
# --- HELPER: Save PDF from current page ---
|
||||
def save_pdf_from_current_page(filename):
|
||||
# Call DevTools Protocol directly
|
||||
result = driver.execute_cdp_cmd("Page.printToPDF", {
|
||||
"format": "A4",
|
||||
"printBackground": True
|
||||
})
|
||||
pdf_data = base64.b64decode(result['data'])
|
||||
with open(filename, "wb") as f:
|
||||
f.write(pdf_data)
|
||||
|
||||
# --- DOWNLOAD EACH AS PDF ---
|
||||
for idx, url in enumerate(attempt_links, start=1):
|
||||
print(f"[{idx}/{len(attempt_links)}] Downloading: {url}")
|
||||
driver.get(url)
|
||||
time.sleep(2)
|
||||
|
||||
try:
|
||||
# Try to get the student's name near the profile picture
|
||||
student_name_elem = driver.find_element(By.CSS_SELECTOR, "td.cell a[href*='/user/view.php']")
|
||||
student_name = student_name_elem.text.strip()
|
||||
except NoSuchElementException:
|
||||
# Fallback if name is not found
|
||||
student_name = f"student_{idx}"
|
||||
|
||||
# Clean the name for filename use
|
||||
safe_name = "".join(c for c in student_name if c.isalnum() or c in ('_', '-'))
|
||||
|
||||
filename = os.path.join(PDF_OUTPUT_DIR, f"{safe_name}.pdf")
|
||||
|
||||
save_pdf_from_current_page(filename)
|
||||
|
||||
print("✅ All PDFs downloaded.")
|
||||
|
||||
driver.quit()
|
2
requirements.txt
Normal file
2
requirements.txt
Normal file
@ -0,0 +1,2 @@
|
||||
selenium
|
||||
dotenv
|
Loading…
x
Reference in New Issue
Block a user