<jupyter_start><jupyter_text>Daten, wir brauchen Daten!Das Presseportal veröffentlicht unter dieser [Seite](https://www.presseportal.de/blaulicht/r/Baden-Baden) Polizeimeldungen für Baden-Baden (unter ähnlichen Links finden sich Meldungen für andere Städte).Die Seite bietet auch einen [RSS Feed](https://www.presseportal.de/rss/polizei/r/Baden-Baden.rss2), also einen maschinenlesbaren Nachrichtenticker, welcher von anderen Programmen abonniert werden kann. Dieser Feed wäre eine vorzügliche Quelle für unser kleines Projekt, schauen wir uns diesen Feed einmal an.<jupyter_code>import requests

feed_url = "https://www.presseportal.de/rss/polizei/r/Baden-Baden.rss2"
feed = requests.get(feed_url)

print(f"Status Code der Anfrage: {feed.status_code}")
print()
print(f"Erste Zeichen der Antwort: {feed.text[:256]}")<jupyter_output>Status Code der Anfrage: 200

Erste Zeichen der Antwort: <?xml version="1.0" encoding="ISO-8859-1"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Presseportal.de - Baden-Baden</title><link>https://www.presseportal.de/st/Baden-<jupyter_text>Der Feed antwortet und das Ergebnis sieht, wie erwartet, nach XML aus, dieses XML können wir parsen.<jupyter_code>from bs4 import BeautifulSoup

feed_soup = BeautifulSoup(feed.text, "html.parser")
feed_items = feed_soup.find_all("item")

print(f"{len(feed_items)} Items im Feed:")

for item in feed_items:
    print(f"\t{item.title.get_text()}")<jupyter_output>15 Items im Feed:
	POL-OG: Baden-Baden - Eingebrochen
	POL-OG: Achern, Baden-Baden, A5 - Verfolgung endet in Handschellen
	POL-OG: Baden-Baden, A5 - Unfall mit Folgen -Nachtragsmeldung-
	POL-OG: Baden-Baden, A5 - Unfall mit Folgen
	HZA-KA: Bargeldschmuggel ist teuer!
	BPOLI-OG: Aggressiver Fahrgast bedroht Reisende und beleidigt Polizeibeamte
	BPOLI-OG: Festnahme am Flughafen
	POL-OG: Rastatt, Winterdorf, A5- Flucht beendet
	POL-OG: Baden-Baden - Nach Uhr gegriffen
	POL-OG: Achern - Mutmaßlicher Einbrecher in Haft / Wer kennt die sichergestellten Gegenstände? / NACHTRAGSMELDUNG
	POL-OG: Baden-Baden, Steinbach - Einbruch
	POL-OG: Bühlertal - Gemeinsame Pressemitteilung der Staatsanwaltschaft Baden-Baden und des Polizeipräsidiums Offenburg - Kriminalpolizei ermittelt -NACHTRAGSMELDUNG-
	POL-OG: Baden-Baden - In Brand geraten
	POL-OG: Loffenau - Metalldiebe festgenommen
	POL-OG: Baden-Baden - 20 Anrufe falscher Polizeibeamte<jupyter_text>Auf der einen Seite funktioniert das Parsing und wir können den Feed auslesen, auf der anderen liefert der Feed aber nur die 15 aktuellsten Meldungen (welche auch anscheinend nicht alle direkt Baden-Baden betreffen), das ist für unsere Zwecke etwas dürftig. Daher verwerfen wir die Idee des RSS Feeds.Wir müssen uns unsere Daten wohl auf anderem Weg beschaffen. Schauen wir uns die Seite [https://www.presseportal.de/blaulicht/r/Baden-Baden](https://www.presseportal.de/blaulicht/r/Baden-Baden) noch einmal an. Auf dieser Seite werden 27 Meldungen gelistet, außerdem gibt es eine Pagination für ältere Meldungen, die zweite Seite hat die Adresse [https://www.presseportal.de/blaulicht/r/Baden-Baden/27](https://www.presseportal.de/blaulicht/r/Baden-Baden/27), die dritte Seite [https://www.presseportal.de/blaulicht/r/Baden-Baden/54](https://www.presseportal.de/blaulicht/r/Baden-Baden/54).Es scheint, ab der zweiten Seite, ein Muster zu geben. Die Zahl in der Adresse wird pro Seite immer um 27 erhöht (und jede Seite liefert 27 Meldungen). Wenn wir manuell diese Zahlen ändern und diese Adressen aufrufen, erkennen wir dass diese Zahl der ersten Meldung auf der Seite entspricht, wenn alle Meldungen **null-basiert** hochgezählt werden. Seite 1 zeigt also die die Meldungen 0-26, Seite 2 zeigt 27-53 usw. Folglich müsste die erste Seite auch unter [https://www.presseportal.de/blaulicht/r/Baden-Baden/0](https://www.presseportal.de/blaulicht/r/Baden-Baden/0) erreichbar sein.Wir probieren es aus und Bingo! Folglich können wir uns die Adressen für alle Seiten recht einfach generieren:<jupyter_code>for i in range(5):
    print(f"https://www.presseportal.de/blaulicht/r/Baden-Baden/{i * 27}")<jupyter_output>https://www.presseportal.de/blaulicht/r/Baden-Baden/0
https://www.presseportal.de/blaulicht/r/Baden-Baden/27
https://www.presseportal.de/blaulicht/r/Baden-Baden/54
https://www.presseportal.de/blaulicht/r/Baden-Baden/81
https://www.presseportal.de/blaulicht/r/Baden-Baden/108<jupyter_text>Jetzt wissen wir, wo wir unsere Information herbekommen, Zeit sich um das Wie zu kümmern. Daher schauen wir eine solche Seite mal genauer an.<jupyter_code>url = "https://www.presseportal.de/blaulicht/r/Baden-Baden/0"
r = requests.get(url)
r.status_code<jupyter_output><empty_output><jupyter_text>Wenn wir im Browser die Entwicklertools öffnen, können wir feststellen, dass jede Einzelmeldung innerhalb eines `` Tag mit der Klasse `news` erscheint. Die eigentliche Schlagzeile befindet sich in einem `` Tag mit der Klasse `news-headline-clamp`. Somit können wir die 27 Schlagzeilen aus der Seite extrahieren:<jupyter_code>soup = BeautifulSoup(r.text, "html.parser")

for i, article in enumerate(soup.find_all("article", class_="news")):
    print(f"{i} -> {article.find('h3', class_='news-headline-clamp').get_text()}")<jupyter_output>0 -> POL-OG: Baden-Baden - Eingebrochen
1 -> POL-OG: Achern, Baden-Baden, A5 - Verfolgung endet in Handschellen
2 -> POL-OG: Baden-Baden, A5 - Unfall mit Folgen -Nachtragsmeldung-
3 -> POL-OG: Baden-Baden, A5 - Unfall mit Folgen
4 -> HZA-KA: Bargeldschmuggel ist teuer!
5 -> BPOLI-OG: Aggressiver Fahrgast bedroht Reisende und beleidigt Polizeibeamte
6 -> BPOLI-OG: Festnahme am Flughafen
7 -> POL-OG: Rastatt, Winterdorf, A5- Flucht beendet
8 -> POL-OG: Baden-Baden - Nach Uhr gegriffen
9 -> POL-OG: Achern - Mutmaßlicher Einbrecher in Haft / Wer kennt die sichergestellten Gegenstände? / NACHTRAGSMELDUNG
10 -> POL-OG: Baden-Baden, Steinbach - Einbruch
11 -> POL-OG: Bühlertal - Gemeinsame Pressemitteilung der Staatsanwaltschaft Baden-Baden und des Polizeipräsidiums Offenburg - Kriminalpolizei ermittelt -NACHTRAGSMELDUNG-
12 -> POL-OG: Baden-Baden - In Brand geraten
13 -> POL-OG: Loffenau - Metalldiebe festgenommen
14 -> POL-OG: Baden-Baden - 20 Anrufe falscher Polizeibeamte
15 -> POL-OG: Bad[...]<jupyter_text>Jeder `` Tag enthält in einem `data-url` Attribut die URL-Slug zum eigentlichen Artikel. Damit haben wir das Rüstzeug, um uns eine größere Anzahl von Artikeln zu generieren:<jupyter_code>articles = []

for i in range(5):
    soup = BeautifulSoup(
        requests.get(f"https://www.presseportal.de/blaulicht/r/Baden-Baden/{i * 27}").text,
        "html.parser"
    )
    for article in soup.find_all("article", class_="news"):
        articles.append({
            "url": article["data-url"],
            "headline": article.find("h3", class_="news-headline-clamp").get_text()
        })

len(articles)<jupyter_output><empty_output><jupyter_text>In der obigen Aufzählung sehen wir auch Artikel, welche nicht direkt Baden-Baden betreffen. In diesen Artikleln ist meist die Staatsanwaltschaft Baden-Baden involviert oder es wurden Verletzte in das Klinikum Baden-Baden gebracht. Diese Meldungen interessieren uns nicht.Der Anfang einer jeden Schlagzeile ist die Dienststelle, gefolgt von einem Doppeltpunkt. Uns interessieren nur die Meldungen des Polizeipräsidiums Offenburg (POL-OG), diese scheinen auch die Mehrheit auszumachen.<jupyter_code>from collections import Counter

Counter(article.get("headline").split(":")[0] for article in articles)<jupyter_output><empty_output><jupyter_text>Von 135 Meldungen bleiben noch 120, da es darunter auch indirekte Nachrichten zu geben scheint, wollen wir zusätzlich auf das Vorhandensein von "Baden-Baden" innerhalb der Schlagzeile ffiltern.<jupyter_code>pol_og_articles = [article for article in articles if article.get("headline").startswith("POL-OG:")]

pol_og_bad_articles = [article for article in pol_og_articles if "Baden-Baden" in article.get("headline")]

len(pol_og_bad_articles)<jupyter_output><empty_output><jupyter_text>Jetzt basteln wir aus allem eine Funktion.<jupyter_code>def get_list_of_articles(number_of_pages):
    articles = []
    for i in range(number_of_pages):
        soup = BeautifulSoup(
            requests.get(f"https://www.presseportal.de/blaulicht/r/Baden-Baden/{i * 27}").text,
            "html.parser"
        )
        for article in soup.find_all("article", class_="news"):
            headline = article.find("h3", class_="news-headline-clamp").get_text()
            if (headline.startswith("POL-OG:") and "Baden-Baden" in headline):
                articles.append({
                    "url": article["data-url"],
                    "headline": headline
                })
    return articles


len(get_list_of_articles(5))<jupyter_output><empty_output><jupyter_text>Nun lagern wir diese Funktion in eine Python Datei aus, damit wir sie auch in anderen Notebooks einfach verweden können.<jupyter_code>from scraping import get_list_of_articles as gloa

len(gloa(5))<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10538461538461538, 'fraction_numerical': 0.013296703296703297, 'mean_word_length': 4.789440203562341, 'pattern_counts': {'":': 5, '<': 43, '<?xml version=': 1, '>': 60, 'https://': 21, 'lorem ipsum': 0, 'www.': 22, 'xml': 3}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '10', 'dir': 'jupyter-structured-clean-dedup', 'id': '487507', 'license': 'MIT', 'n_tokens_mistral': 3400, 'n_tokens_neox': 3365, 'n_words': 829, 'path': 'code/01_daten_wir_brauchen_daten.ipynb', 'repo_name': 'yveso/crime-report', 'size': '9100'}