PDF-Dateien auf Unterwebsite downloaden

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
biofunc
User
Beiträge: 7
Registriert: Sonntag 1. Mai 2022, 15:06

Hallo,
ich benötige als absoluter Neuanfänger Unterstützung der der Umsetzung eines Projektes mit BeautifulSoup.
Ich schaffe es, mit BeautifulSoup alle PDFs einer Seite herunterzuladen, aber wenn sich die PDFs auf Unterseiten befinden, komme ich nicht weiter.

Hier die Aufgabe:

Die Hauptseite ist https://trauer.mittelhessen.de

Über die Suche wird folgende Seite generiert: https://trauer.mittelhessen.de/traueranzeigen-suche/zeitraum-01-01-2010-bis-31-12-2020/seite-1
Es gibt nur ein paar hundert Seiten, die jeweils mit der Adresse: https://trauer.mittelhessen.de/traueranzeigen-suche/zeitraum-01-01-2010-bis-31-12-2020/seite-2, .../seite-3, .../seite-4 aufgerufen werden können.

Pro Seite gibt es 5 Links mit dem Titel "Anzeige(X)", über die man auf jeweils eine Unterseite mit einer oder mehreren Traueranzeigen pro Person kommen, z.B. https://trauer.mittelhessen.de/traueranzeige/heinz-windor/anzeige
<a class="stretched-link pr-2 text-nowrap " href="https://trauer.mittelhessen.de/traueranzeige/heinz-windorf/anzeigen" title="">Anzeigen (1)</a>

Auf diesen Unterseiten gibt es dann einen oder mehrere Link(s) mit dem Tilel "Speichern" über die man die PDFs herunterladen kann.
<a href="https://trauer.mittelhessen.de/MEDIASERVER/content/LH219/obi_new/2022_4/heinz-windorf-traueranzeige-a41279ca-bbd3-40f2-bf99-298e6c8e3930.pdf" rel="noopener" title=" Speichern" data-toggle="tooltip" target="_blank" class="pr-2"><svg class="svg-inline--fa fa-save fa-w-14" aria-hidden="true" data-prefix="fas" data-icon="save" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" data-fa-i2svg=""><path fill="currentColor" d=..."></path></svg><!-- <i class="fas fa-save"></i> --> Speichern</a>

Das Skript sollte über eine Schleife alle Websites automatisch aufrufen, also ../Seite-1, /Seite-2. Den Endpunkt der Schleife (im Beispiel .../Seite-1063) könne ich manuell in das Skript eintragen.
Über die Links Anzeige (1) - wobei auch Anzeige (2,3,4,5 oder 6) als Titel für den Link vorkommen kann - sollen die Unterseiten aufgerufen werden dort die PDF von allen Links mit dem Titel "Speicher" heruntergeladen werden.

Viele Grüße
Willi
Sirius3
User
Beiträge: 17737
Registriert: Sonntag 21. Oktober 2012, 17:20

Und wo hast Du nun konkrete Probleme? Welchen Code hast Du versucht? Was daran funktioniert nicht?
biofunc
User
Beiträge: 7
Registriert: Sonntag 1. Mai 2022, 15:06

Hallo Sirius3,
vielen Dank für Deine Nachfrage. Hier der Code bzw. die Teile, die noch fehlen:

1. Öffne Seite https://trauer.mittelhessen.de/traueran ... 20/seite-1
2. Gehe auf die Links, die im Titel den Text "Anzeigen" enthalten und öffne die entsprechenden Unterseiten
3. Speicher alle PDF auf den jeweiligen Unterseiten: siehe nachfolgenden Code:


# Import libraries
import requests
from bs4 import BeautifulSoup

# URL from which pdfs to be downloaded
url = "https://trauer.mittelhessen.de/traueran ... /anzeigen/"

# Requests URL and get response object
response = requests.get(url)

# Parse text obtained
soup = BeautifulSoup(response.text, 'html.parser')

# Find all hyperlinks present on webpage
links = soup.find_all('a')

i = 0

# From all links check for pdf link and
# if present download file
for link in links:
if ('.pdf' in link.get('href', [])):
i += 1
print("Downloading file: ", i)

# Get response object for link
response = requests.get(link.get('href'))

# Write content in pdf file
pdf = open("pdf"+str(i)+".pdf", 'wb')
pdf.write(response.content)
pdf.close()
print("File ", i, " downloaded")

print("All PDF files downloaded")

4. Öffne die Seite: https://trauer.mittelhessen.de/traueran ... 20/seite-2
5. Schleife, bzw. arbeite Nr. 2-4 ab.
Benutzeravatar
__blackjack__
User
Beiträge: 13061
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@biofunc: Das wird alles ein bisschen unhandlich wenn Du das einfach so auf Modulebene als einen grossen unstrukturierten Haufen Code hin schreibst. Das übliche vorgehen ist das Problem auf kleinere Teilprobleme aufzuteilen, und die Teilprobleme dann weiter auf kleinere Teilprobleme, solange bis die einzelnen Teilprobleme so klein sind, das man sie mit einer Funktion mit wenigen Zeilen Code lösen kann. Jede Teillösung testet man dann, ob sie tut was sie soll bevor man mit der nächsten Funktion weiter macht. Dann kann mein die kleinen Teillösungen in Funktionen zusammenfassen die grösste Teillösungen ergeben, bis man am Ende das Gesamtproblem gelöst hat.

Du hast eine Lösung für das Teilproblem alle PDF-Dateien von einer URL herunterzuladen. Die müsste in eine Funktion, die mit der URL parametrisiert wird.

Wenn Du dann eine Funktion schreibst die alle URLs zu Trauerfällen aus einer Suchergebnisseite holt, kannst Du diese Funktion auf jede Suchergebnisseite anwenden. Und dann die Lösung die Du schon hast, mit jeder gefundenen URL aufrufen.

Kommentare sollen dem Leser einen Mehrwert über den Code geben. Faustregel: Kommentare beschreiben nicht *was* der Code macht, denn das steht da bereits als Code, sondern warum er das macht. Sofern das nicht offensichtlich ist. Offensichtlich ist in aller Regel auch was in der Dokumentation von Python und den verwendeten Bibliotheken steht. Also so etwas wie „Hier wird importiert“ vor ``import``-Anweisungen oder ``# Requests URL and get response object`` vor einer Zeile ``response = requests.get(url)`` ist überflüssig. Das vermittelt dem Leser keine Information die da nicht schon als Code stehen würde.

Bei den `Respone`-Objekten sollte man prüfen ob man das bekommen hat was man wollte, denn so wie es jetzt ist, werden Fehlermeldungen behandelt als wenn sie HTML oder PDF-Dateien wären.

Um die Bedingung bei ``if`` gehören keine Klammern.

Eine leere Liste ist ein falscher Defaultwert für "href" weil das immer eine Zeichenkette ist. Da sollte die leere Zeichenkette als Defaultwert angegeben werden. Den Wert würde ich auch nur einmal abfragen und an einen Namen binden. Wobei man auch gleich nur die <a>-Elemente mit einem "href"-Attribut filtern kann, dann braucht man das nicht gesondert behandeln ob es das überhaupt gibt. Und man kann sogar gleich die filtern die mit ".pdf" enden.

Das wäre übrigend der korrekte Test: Ob der "href"-Wert mit ".pdf" *endet* und nicht ob der irgendwo *in* dem Wert enthalten ist.

Wenn man gleich nur über passende <a>-Elemente iteriert, kann man sich das mit dem `i` manuell hochzählen auch gleich sparen und `enumerate()` verwenden.

Dateien sollte man wo möglich mit der ``with``-Anweisung zusammen öffnen. Allerdings haben wir hier einen Sonderfall für den `pathlib.Path` netterweise eine Methode hat die öffnen, schreiben, und schliessen der Datei mit einem Aufruf erledigt.

Das zusammenstückeln von Zeichenketten und Werten mittels ``+`` und `str()` ist eher BASIC als Python. Dafür gibt es die `format()`-Methode auf Zeichenketten und f-Zeichenkettenliterale.

Die überarbeitete Teillösung könnte dann so aussehen, wobei die Fehlerbehandlun vielleicht noch nicht das macht was hier sinnvoll wäre, und die Dateinamen für die lokalen PDFs ungünstig sind, weil die bei jedem Aufruf die vorherigen überschreiben:

Code: Alles auswählen

#!/usr/bin/env python3
from pathlib import Path

import requests
from bs4 import BeautifulSoup


def download_pdfs(url):
    response = requests.get(url)
    response.raise_for_status()
    soup = BeautifulSoup(response.text, "html.parser")
    for i, link in enumerate(
        soup.find_all("a", href=lambda url: url.endswith(".pdf")), 1
    ):
        print("Downloading file: ", i)
        response = requests.get(link.get("href"))
        response.raise_for_status()
        Path(f"pdf{i}.pdf").write_bytes(response.content)
        print("File ", i, " downloaded")

    print("All PDF files downloaded")


def main():
    download_pdfs(
        "https://trauer.mittelhessen.de/traueranzeige/herta-seil/anzeigen/"
    )


if __name__ == "__main__":
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
biofunc
User
Beiträge: 7
Registriert: Sonntag 1. Mai 2022, 15:06

Hallo blackjack,

vielen Dank für Deine Hinweise und den überarbeiteten Code! Ich werde mich wohl doch noch intensiver mit Python und BeautifulSoup beschäftigen müssen.
Antworten