docs: Abschlussarbeit on data ingestion (#500)
Co-authored-by: Philipp Horstenkamp <philipp@horstenkamp.de>
After Width: | Height: | Size: 44 KiB |
After Width: | Height: | Size: 75 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 49 KiB |
After Width: | Height: | Size: 75 KiB |
After Width: | Height: | Size: 365 KiB |
After Width: | Height: | Size: 450 KiB |
After Width: | Height: | Size: 137 KiB |
After Width: | Height: | Size: 65 KiB |
After Width: | Height: | Size: 29 KiB |
After Width: | Height: | Size: 58 KiB |
After Width: | Height: | Size: 7.5 KiB |
@ -0,0 +1,765 @@
|
||||
---
|
||||
author:
|
||||
- |
|
||||
Tristan Nolde\
|
||||
Fachhochschule Südwestfalen\
|
||||
Schriftliche Ausarbeitung im Modul\
|
||||
„Projektgruppe"\
|
||||
im Studiengang\
|
||||
M.Sc. Angewandte Künstliche Intelligenz\
|
||||
eingereicht bei\
|
||||
Prof. Dr.-Ing. Doga Arinir
|
||||
bibliography: literatur.bib
|
||||
title: Automatische Daten Extraktion aus Web Quellen
|
||||
---
|
||||
|
||||
# Automatische Daten Extraktion aus Web Quellen
|
||||
|
||||
## Erklärung
|
||||
|
||||
Ich erkläre hiermit, dass ich die vorliegende Arbeit selbstständig
|
||||
verfasst und dabei keine anderen als die angegebenen Hilfsmittel benutzt
|
||||
habe. Sämtliche Stellen der Arbeit, die im Wortlaut oder dem Sinn nach
|
||||
Werken anderer Autoren entnommen sind, habe ich als solche kenntlich
|
||||
gemacht. Die Arbeit wurde bisher weder gesamt noch in Teilen einer
|
||||
anderen Prüfungsbehörde vorgelegt und auch noch nicht veröffentlicht.
|
||||
|
||||
2023-10-03
|
||||
|
||||
<img src="Abbildungen/unterschrift.PNG" alt="image" />
|
||||
|
||||
Tristan Nolde
|
||||
|
||||
## Einleitung
|
||||
|
||||
Im Projekt *Transparenzregister* steht die Analyse sowie Visualisierung
|
||||
von Unternehmensdaten im Vordergrund. Zu diesen Daten gehören unter
|
||||
anderem *Key Performance Indicators (KPIs)* im
|
||||
Bereich Finanzen (z.B. Verlauf des Umsatzes oder Jahresüberschusses)
|
||||
aber auch grundlegende Stammdaten wie den Sitz des Unternehmens oder die
|
||||
Besetzung des Aufsichtsrates. Um diese Informationen jedoch darstellen
|
||||
zu können, bedarf es zunächst der Beschaffung und Aufbereitung von
|
||||
Daten. In der im Projekt verankerten Architektur bedeutet dies, die
|
||||
Extraktion von Rohdaten aus verschiedenen Quellen, die Stücke der
|
||||
benötigten Informationen beinhalten, un den Transport dieser in einem
|
||||
geeigneten Format in die sog. *Staging DB*. Aus dieser heraus bedienen sich die
|
||||
verschiedenen Analyse-Prozesse und stellen letztendlich einen vollkommen
|
||||
aufbereitenden Datenbestand in der *Production DB* zur Verfügung.
|
||||
|
||||
<img src="Abbildungen/HLD.PNG" height=250 />
|
||||
|
||||
Diese Hausarbeit befasst sich mit der Bereitstellung eben dieser
|
||||
Informationen für die *Staging DB*. Neben der initialen Vermittlung von in
|
||||
der Forschung und Industrie bewährten Methoden, soll primär die
|
||||
praktische Umsetzung der Daten Extraktion für die Projektgruppe im
|
||||
Vordergrund stehen. Dafür werden Verfahren vorgestellt, um die
|
||||
gewünschten Informationen aus verschiedenen Quellen zu beziehen. Zum
|
||||
Ende der Arbeit wird ebenfalls ein erster Blick auf die rechtlichen
|
||||
Rahmenbedingungen dieser Daten-Extraktion sowie ihrer Speicherung
|
||||
geworfen. Da die während der Arbeit entstehenden Applikationen auch
|
||||
betrieben und gewartet werden müssen, wird ein Blick auf [Apache
|
||||
Airflow](https://airflow.apache.org/) als Workflow Orchestrator
|
||||
geworfen.
|
||||
|
||||
Als Quellen für die gewünschte Datenbasis dienen unter anderem die
|
||||
Folgenden:
|
||||
|
||||
- Unternehmensregister
|
||||
|
||||
- Bundesanzeiger
|
||||
|
||||
- Tagesschau
|
||||
|
||||
- Handelsblatt
|
||||
|
||||
### Web Mining
|
||||
|
||||
Das Web Mining als Unterdisziplin des Data Minings - \"\[..\] einer
|
||||
Sammlung von Methoden und Vorgehensweisen, mit denen große Datenmengen
|
||||
effizient analysiert werden können\" [1] - beschäftigt sich
|
||||
vorwiegend mit der Auswertung von Quellen aus dem Internet. Da das
|
||||
Internet zu großen Teilen aus textuellen Informationen in Form von
|
||||
Websites besteht, bildet das Web Mining Schnittmengen zum Text Mining.
|
||||
|
||||
<img src="Abbildungen/Web_Mining.PNG" height=400 />
|
||||
|
||||
Besonders im Text Mining wird Wert auf die Analyse von Dokumenten in
|
||||
Form von HTML oder XML Dateien gelegt. Das Web Mining legt lediglich die
|
||||
Schicht der Beschaffung jener Dokumente dazu. [1]
|
||||
|
||||
### Extract, Transform, Load (ETL)
|
||||
|
||||
Ein grundlegender Prozess, der in allen Data Mining Sub-Formen Anwendung
|
||||
findet ist der des *ETL*. Dieser beschreibt unabhängig von der
|
||||
ursprünglichen Datenquelle oder ihrem Ziel, den Wunsch, Daten von einem
|
||||
System in einer aufbereiteten Form in das Zielsystem zu transportieren.
|
||||
[1]
|
||||
|
||||
<img src="Abbildungen/ETL.PNG" height=200 />
|
||||
|
||||
Da die Ergebnisse dieses Prozesses die Grundlage für Folge-Schritte wie
|
||||
eine weitere Analyse oder bereits Darstellung legt, ist hier ein
|
||||
besonderes Augenmerk auf die Qualität der Rohdaten sowie Ergebnisse zu
|
||||
legen. Ein bekanntes Sprichwort in diesem Kontext ist *\"Garbage in,
|
||||
Garbage out\"*. Darunter wird der Zustand verstanden, dass niederwertige
|
||||
Rohdaten selbst mit komplexer Aufbereitung nicht in qualitativen
|
||||
Ergebnissen münden können. So ist im Schritte der Extraktion besonderes
|
||||
Augenmerk auf die Auswahl der Datenquelle bzw. Dokumente zu legen.
|
||||
[1]
|
||||
|
||||
Die Bedeutung von Qualität findet sich ebenfalls in der Transformation
|
||||
wieder. Hier gilt es nicht nur Daten von einen in das andere Format zu
|
||||
überführen (Harmonisierung), sondern auch ungeeignete Datensätze oder
|
||||
Passagen zu erkennen und nicht weiter zu betrachten (Filterung).
|
||||
[1]
|
||||
|
||||
Zuletzt gilt es die aufbereiteten und selektierten Daten in das
|
||||
Zielsystem - hier die Staging Datenbank - zu überführen. Dieser Prozess
|
||||
kann je nach zeitlicher Ausführung (initiales Laden, Aktualisierung)
|
||||
unterschiedlich ablaufen, da entweder neue Daten hinzugefügt oder
|
||||
Bestehende angepasst werden. [1]
|
||||
|
||||
## Web Scraping & Crawling
|
||||
|
||||
Eine technische Anwendung des Web Minings ist in Form des *Web
|
||||
Scrapings* (auch: Web Crawling) zu finden. Zunächst beschreibt das
|
||||
Scrapen die Extraktion von Daten aus Websites. Dies kann entweder
|
||||
manuell oder automatisch geschehen. Sollte das zur Automatisierung
|
||||
entwickelte Programm nicht nur auf einer Zielseite verweilen, sondern
|
||||
über auf der Seite gefundene Links zu weiteren Seiten vordringen, so ist
|
||||
vom *Crawling* die Rede. Dieses Vorgehen ist unter anderem bei
|
||||
Suchmaschinen wie Google anzutreffen. Da diese Technik unter anderem
|
||||
eingesetzt werden kann, um sensible Daten wie Telefonnummern zu finden,
|
||||
wurden mit der Zeit einige Mechanismen wie die *robots.txt* Datei
|
||||
entwickelt, in jener der Betreiber einer Website festlegt, welche
|
||||
Zugriffe erlaubt sind. [2]
|
||||
|
||||
Allerdings hat dieser Mechanismus eine elementare Schachstelle: Die
|
||||
*robots.txt* Einträge dienen lediglich als Richtlinie für Clients, so
|
||||
dass Crawler in der Lage sind diese schlichtweg zu ignorieren und
|
||||
dennoch relevante Daten von der Seite zu scrapen. [3]
|
||||
|
||||
### Bezug von Stammdaten aus dem Unternehmensregister
|
||||
|
||||
Im Unternehmensregister werden veröffentlichungspflichtige Daten
|
||||
deutscher Unternehmen wie etwa die Firmengründung oder Liquidation in
|
||||
elektronischer Art zur Verfügung gestellt. [@unternehmensregister]
|
||||
|
||||
Besonders relevant, um überhaupt ein Inventar an Unternehmen aufstellen
|
||||
zu können, sind die dort zu findenden Registerinformationen. In diesen
|
||||
werden Daten aus Handels-, Genossenschafts- und Partnerschaftsregistern
|
||||
bereit gestellt. Das Ziel ist es aus diesen grundlegende Informationen
|
||||
wie den Sitz des Unternehmens, seine Gesellschaftsform sowie ggf.
|
||||
verfügbare Relationen zu Personen oder anderen Unternehmen zu beziehen.
|
||||
Das dazugehörige Ziel-Datenmodell ist im Anhang zu finden.
|
||||
|
||||
Zu Beginn der Entwicklung wird zunächst einer erster manueller Download
|
||||
Prozess von Daten gestartet. Die Website des Unternehmensregisters
|
||||
stellt eine schlagwort-basierte Suchmaske bereit über die der Nutzer auf
|
||||
eine Historie der gefundenen Unternehmen geleitet wird. Bereits hier
|
||||
stellt sich heraus, dass die Suche des Unternehmensregisters keine 1:1
|
||||
Abgleich des Unternehmensnamens sondern eine ähnlichkeits-basierte Suche
|
||||
verwendet. So förderte die unten dargestellte Suche nach der \"Bayer
|
||||
AG\" auch Einträge wie die Bayer Gastronomie GmbH zu Tage:
|
||||
|
||||
<img src="Abbildungen/suche_bayer.PNG" height=450 />
|
||||
|
||||
Auch eine Anpassung des Suchbegriffes durch die Verwendung regulärer
|
||||
Ausdrücke (z.B. \^̈Bayer AG\$)̈ kann die Qualität der Ergebnisse nicht
|
||||
verbessern. Da es sich bei diesen ähnlichen Unternehmen jedoch auch
|
||||
tatsächlich um Tochtergesellschaften handeln könnte, die für die
|
||||
Verflechtsungsanalyse besonders interessant sind, werden nicht passende
|
||||
Unternehmensnamen nicht entfernt sondern ebenfalls verarbeitet.
|
||||
|
||||
Ist ein Unternehmen ausgewählt, stellt das Unternehmensregister
|
||||
verschiedene Format zur Verfügung. Für eine automatische Auswertung der
|
||||
Daten eignet sich besonders der *SI - strukturierter Registerinhalt*,
|
||||
welcher ein XML-Dokument im Format der XJustiz beinhaltet. Es wird das
|
||||
Schema *xjustiz_0400_register_3_2.xsd* verwendet.
|
||||
|
||||
Da das Unternehmensregister keine öffentliche *Application Programming Interface (API)* hostet, über die dieses Dokument bezogen
|
||||
werden kann, erfolgt eine Automatisierung des über den Nutzer in der
|
||||
Web-Oberfläche getätigten Aktionsfluss mithilfe von
|
||||
[Selenium](https://www.selenium.dev/).
|
||||
|
||||
<img src="Abbildungen/Selenium.PNG" height=400 />
|
||||
|
||||
Eine Schritt-für-Schritt Beschreibung des Prozesses würde den Rahmen der
|
||||
Hausarbeit sprengen, daher wird ein High-Level Überblick in Form eines
|
||||
Ablaufdiagramms im Anhang bereit gestellt.
|
||||
|
||||
Im nachgelagerten Schritt werden die heruntergeladenen *.xml* Dateien in
|
||||
eine *.json* konvertiert und in das Zielformat überführt. Darauf werden
|
||||
die Daten über einen zentral gelegenen Service, der die Verbindung zur
|
||||
StagingDB (MongoDB) in entsprechende Methoden kapselt, in die Persistenz
|
||||
überführt.
|
||||
|
||||
### Bezug von Finanzdaten aus dem Bundesanzeiger
|
||||
|
||||
Jede Kapitalgesellschaft in Deutschland ist gemäß §242 des *Handelsgesetzbuches (HGB)* dazu
|
||||
verpflichtet zu Gründung der Gesellschaft sowie nach jedem abgeschlossen
|
||||
Geschäftsjahr eine Aufstellung der finanziellen Situation des
|
||||
Unternehmens zu veröffentlichen. Diese besteht mindestens aus der
|
||||
Gewinn- und Verlustrechnung, kann jedoch je nach Gesellschaftsform
|
||||
weitere Details verpflichtend beinhalten. Im Bundesanzeiger, einem
|
||||
Amtsblatt sowie Online Plattform geleitet vom Bundesministerium der
|
||||
Justiz, werden diese Jahresabschlüsse der Allgemeinheit zur Verfügung
|
||||
gestellt.
|
||||
|
||||
Aus Sicht des Transparenzregisters eignet sich der Bundesanzeiger daher
|
||||
als besonders gute Quelle, um Finanzdaten zu bereits bekannten
|
||||
Unternehmen (siehe vorheriges Kapitel) zu beziehen, um den
|
||||
wirtschaftlichen Erfolg einer Kapitalgesellschaft quantifizieren zu
|
||||
können.
|
||||
|
||||
Auf GitHub, einem zentralen Anlaufpunkt für Open-Source Projekte, kann
|
||||
eine Python Bibliothek namens
|
||||
[deutschland](https://github.com/bundesAPI/deutschland) gefunden, welche
|
||||
eine Reihe deutscher Datenquellen kapselt und in einem festen Format zur
|
||||
Verfügung stellt. Unter anderem beinhaltet diese Bibliothek auch ein
|
||||
Sub-Paket namens *bundesanzeiger*, welches ein Interface zur Oberfläche
|
||||
des Bundesanzeigers zur Verfügung stellt. Das Projekt wird unter der
|
||||
[Apache License
|
||||
2.0](https://github.com/bundesAPI/deutschland/blob/main/LICENSE)
|
||||
betrieben und ist folglich auch für den Einsatz im Projekt geeignet.
|
||||
|
||||
Die erste Integration der Bibliothek zeigt jedoch schnell eine große
|
||||
Schwäche auf: Im Zuge des Datenbezugs werden die Rohdaten (HTML) bereits
|
||||
in einfachen Text übersetzt. Dies hat zur Folge, dass wertvolle
|
||||
Informationen, die sich aus der Struktur des Dokumentes ergeben (z.B.
|
||||
der Einsatz von Tabellen, um die Aktiva wie Passiva des
|
||||
Jahresabschlusses aufzulisten). Es wird daher ein [Fork des
|
||||
Repositories](https://github.com/TrisNol/deutschland/tree/feat/bundesanzeiger-raw-report)
|
||||
angelegt und der Quellcode so adaptiert, dass auch der Inhalt in seiner
|
||||
Ursprungsform bereitgestellt wird.
|
||||
|
||||
Der damit verbundene Aufwand, um sich in die Bibliothek einzuarbeiten,
|
||||
gibt hilfreiche Einblicke in die genaue Funktionsweise der
|
||||
Implementierung: Über eine Reihe gezielter HTTP-Anfragen gekoppelt mit
|
||||
einer Auswertung über Web Scraping navigiert die Bibliothek durch die
|
||||
Seiten des Bundesanzeigers. Wird für eine Seite (z.B. Jahresabschlüsse)
|
||||
ein CAPTCHA - ein Authentifizierungs-Mechanismus im
|
||||
Challenge-Response-Verfahren, der den Zugang durch Bots verhindern soll
|
||||
[5] - abgefragt, so wird dieser mittels eines Machine Learning
|
||||
Models des Chaos Computer Club e.V. gelöst.
|
||||
|
||||
Auf Basis dieser Grundlage lassen sich unter anderem Informationen wie
|
||||
die Angabe des Wirtschaftsprüfers eines Jahresabschlusses extrahieren:
|
||||
|
||||
```python
|
||||
def extract_auditor_company(self, report: str) -> str | None:
|
||||
soup = BeautifulSoup(report, features="html.parser")
|
||||
temp = soup.find_all("b")
|
||||
for elem in temp:
|
||||
br = elem.findChildren("br")
|
||||
if len(br) > 0:
|
||||
return elem.text.split("\n")[1].strip()
|
||||
return None
|
||||
|
||||
def extract_auditors(self, report: str) -> list:
|
||||
auditor_company = self.extract_auditor_company(report)
|
||||
auditor_regex = r"[a-z A-Z,.'-]+, Wirtschaftsprüfer"
|
||||
hits = re.findall(auditor_regex, report)
|
||||
return [
|
||||
Auditor(hit.replace(", Wirtschaftsprüfer", "").lstrip(), auditor_company)
|
||||
for hit in hits
|
||||
]
|
||||
```
|
||||
|
||||
Diese Methoden werden auf eine anhand des Dokumententyps gefilterte
|
||||
Liste der bezogenen *Reports* angewandt. Eine Erweiterung auf die
|
||||
Extraktion von Finanzdaten steht aus.
|
||||
|
||||
## Extraktion von News Artikeln
|
||||
|
||||
Auch wenn das Ziel des ETL-Prozesses eine MongoDB - also eine NoSQL
|
||||
Document Datenbank, die in einer Collection Dokumente jeglicher Struktur
|
||||
akzeptiert - ist, wird vor der Entwicklung ein Datenformat definiert.
|
||||
Dies soll gewährleisten, dass Folge-Komponenten wie die
|
||||
Sentiment-Analyse auf eine feste Definition der Daten zurückgreifen
|
||||
können. Letztlich soll so die Codequalität und Stabilität erhalten
|
||||
werden, da ein nicht-typisiertes Format zu Fehlern führt. Vor der
|
||||
Entwicklung der Komponenten, um Daten aktueller News Artikel zu
|
||||
beziehen, wird daher das folgende Zielformat als Python DataClass
|
||||
definiert und in einem zentralen *models/* Sub-Modul des Repos zur
|
||||
Verfügung gestellt:
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class News:
|
||||
id: str
|
||||
title: str
|
||||
date: str
|
||||
text: str
|
||||
source_url: str
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return asdict(self)
|
||||
|
||||
Neben dem Inhalt des Artikels in Form des Titels, sowie dem Text werden
|
||||
wertvolle Meta-Informationen wie das Veröffentlichungsdatum sowie der
|
||||
Link des Artikels mitgeführt. So wird der späteren Analyse die
|
||||
Möglichkeit gegeben, ihre Ergebnisse auch in den zeitlichen Kontext zu
|
||||
legen und im Zuge der Visualisierung Nutzer auf den ursprünglichen
|
||||
Artikel zu navigieren. Des Weiteren wird eine ID als eindeutiger
|
||||
Identifier mitgeführt, um eindeutige Referenzen zu ermöglichen und
|
||||
Duplikate zu verhindern. Die Werte des Feldes orientieren sich an den
|
||||
IDs der jeweiligen Quelle - unter anderem Universally Unique Identifier (UUIDs). Die Anreicherung der Datensätze mit den
|
||||
betroffenen Unternehmen sowie dem Sentiment erfolgt in der
|
||||
nachgelagerten Analyse.
|
||||
|
||||
### RSS Feeds
|
||||
|
||||
Im Zuge der Vorbereitung des Projektes wurden RSS (Rich Site Summary) Feeds als mögliche Quelle für eine
|
||||
regelmäßige Bereitstellung aktueller Nachrichten-Artikel und Events
|
||||
identifiziert. Ein RSS Feed verfolgt das Ziel Informationen
|
||||
regelmäßig über eine Web-Schnittstelle an Kunden zu liefern. Die Inhalte
|
||||
bzw. deren Änderungen werden dabei in einem standardisierten,
|
||||
maschinen-lesbaren Format zur Verfügung gestellt. [6]
|
||||
|
||||
Der genaue Aufbau eines RSS Feeds wird im von der Harvard Law School
|
||||
entwickelten RSS 2.0 Standard beschrieben. Er beschreibt zunächst XML
|
||||
als unterliegendes Dateiformat vor. Die Wurzel eines RSS Dokumentes wird
|
||||
von einem sog. *Channel* gebildet, der in sich mehrere *Items* kapselt.
|
||||
Neben dem Titel, Link des Feeds muss ein *Channel* eine Beschreibung
|
||||
enthalten - weitere Meta-Informationen wie die Sprache oder
|
||||
Informationen zum Urheberrecht sind möglich. Ein *Item* hingegen setzt
|
||||
sich aus mindestens dem Titel, Link, Beschreibung, Autoren, Kategorie
|
||||
\[\...\] zusammen. [7] Ein simpler RSS Feed könnte wie folgt
|
||||
aussehen:
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<rss version="2.0">
|
||||
|
||||
<channel>
|
||||
<title>W3Schools Home Page</title>
|
||||
<link>https://www.w3schools.com</link>
|
||||
<description>Free web building tutorials</description>
|
||||
<item>
|
||||
<title>RSS Tutorial</title>
|
||||
<link>https://www.w3schools.com/xml/xml_rss.asp</link>
|
||||
<description>New RSS tutorial on W3Schools</description>
|
||||
</item>
|
||||
<item>
|
||||
<title>XML Tutorial</title>
|
||||
<link>https://www.w3schools.com/xml</link>
|
||||
<description>New XML tutorial on W3Schools</description>
|
||||
</item>
|
||||
</channel>
|
||||
```
|
||||
|
||||
Bezogen auf die Anbindung an das Transparenzregister fällt auf, dass
|
||||
diese Quelle zunächst nur Basis-Informationen des Artikels und noch
|
||||
nicht den eigentlichen Inhalt, welcher für die Sentiment-Analyse sowie
|
||||
Ermittlung betroffener Unternehmen besonders interessant sein könnte,
|
||||
enthält.
|
||||
|
||||
Als erste Quelle wird das
|
||||
[Handelsblatts](https://www.handelsblatt.com/rss-feeds/) gewählt. Es
|
||||
stellt eine Reihe verschiedener Feeds zur Verfügung, die sich mit
|
||||
unterschiedlichen Themen beschäftigen. Für eine Auswertung von
|
||||
Unternehmens-Daten wird daher anfangs der RSS Feed
|
||||
[Schlagzeilen](https://www.handelsblatt.com/contentexport/feed/schlagzeilen)
|
||||
eingesetzt.
|
||||
|
||||
Eine Abfrage dieser Daten gestaltet sich aufgrund der standardisierten
|
||||
XML Struktur über eine gezielte HTTP-Anfrage und nachträgliche
|
||||
Auswertung des eigentlichen Artikels recht einfach:
|
||||
|
||||
```python
|
||||
import requests
|
||||
import xmltodict
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
|
||||
class HandelsblattRSS:
|
||||
def __init__(self):
|
||||
self.base_url = "https://www.handelsblatt.com/contentexport/feed"
|
||||
|
||||
def get_news_for_category(self, category: str = "unternehmen") -> dict:
|
||||
url = f"{self.base_url}/{category}"
|
||||
result = requests.get(url=url)
|
||||
if result.status_code == 200:
|
||||
return xmltodict.parse(result.text)["rss"]["channel"]["item"]
|
||||
return None
|
||||
|
||||
def get_news_details_text(self, url: str) -> dict:
|
||||
content = requests.get(url)
|
||||
soup = BeautifulSoup(content.text, features="html.parser")
|
||||
|
||||
return " ".join(
|
||||
[elem.text.replace("\n", " ") for elem in soup.find_all("p")][:]
|
||||
)
|
||||
|
||||
Die eigentlichen Inhalte des Artikels weisen kein Standard Format mehr
|
||||
auf, auch wenn diese HTML basiert sind, und müssen daher mit einer
|
||||
Bibliothek wie BeautifulSoup verarbeitet werden. Im obigen Code werden
|
||||
dabei lediglich alle Textinhalte ausgewertet und konkateniert.
|
||||
|
||||
Mit der obigen Python-Klasse können jedoch lediglich Daten extrahiert
|
||||
werden, die Transformation sowie das Laden in die Staging DB erfordern
|
||||
weitere Entwicklung.
|
||||
|
||||
```python
|
||||
handelsblatt = HandelsblattRSS()
|
||||
items = handelsblatt.get_news_for_category()
|
||||
|
||||
from datetime import datetime
|
||||
from tqdm import tqdm
|
||||
import pandas as pd
|
||||
|
||||
|
||||
news = []
|
||||
for news_article in tqdm(items):
|
||||
info = {
|
||||
"id": news_article["guid"],
|
||||
"title": news_article["title"],
|
||||
"date": datetime.strptime(
|
||||
news_article["pubDate"], "%a, %d %b %Y %H:%M:%S %z"
|
||||
).strftime("%Y-%m-%dT%H:%M:%S%z"),
|
||||
"source_url": news_article["link"],
|
||||
"text": handelsblatt.get_news_details_text(news_article["link"])
|
||||
}
|
||||
news.append(info)
|
||||
```
|
||||
|
||||
### Tagesschau REST API
|
||||
|
||||
Als weitere Quelle für News Artikel wird die [Tagesschau
|
||||
API](https://tagesschau.api.bund.dev/) eingesetzt. Diese wird von
|
||||
bund.dev - einer Bemühung Verwaltungsdaten und Informationen über APIs
|
||||
zur Verfügung zu stellen[8], bereitgestellt und ermöglicht
|
||||
Anwendern einen maschinen-lesbaren Zugang zu Artikeln der Tagesschau.
|
||||
|
||||
Obwohl der Zugang zu diesen Daten ebenfalls über HTTP GET Anfragen
|
||||
möglich ist, so entspricht das Datenformat keinem Standard, sondern dem
|
||||
Produkt-eigenen Datenschema, welches vom Betreiber mit
|
||||
[Swagger](https://swagger.io/) dokumentiert wurde, und erfordert daher
|
||||
eine andere Aufbereitungs-Routine. Der Abruf der Daten bleibt jedoch
|
||||
simpel und eine gezielte Suche nach Kategorien ist möglich.
|
||||
|
||||
```python
|
||||
import json
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
|
||||
class TagesschauAPI:
|
||||
def __init__(self):
|
||||
self.base_url = "https://www.tagesschau.de/api2"
|
||||
|
||||
def get_news_for_sector(self, sector: str) -> dict:
|
||||
url = f"{self.base_url}/news/"
|
||||
regions = ",".join([str(i) for i in range(1, 16)])
|
||||
result = requests.get(url=url, params={"regions": regions, "ressort": sector})
|
||||
return result.json()
|
||||
|
||||
def custom_search(self, query: str) -> dict:
|
||||
url = f"{self.base_url}/search/"
|
||||
result = requests.get(url=url, params={"searchText": query})
|
||||
return result.json()
|
||||
|
||||
def get_news_details_text(self, url: str) -> dict:
|
||||
content = requests.get(url)
|
||||
soup = BeautifulSoup(content.text, features="html.parser")
|
||||
|
||||
return " ".join(
|
||||
[elem.text.replace("\n", " ") for elem in soup.find_all("p")][1:]
|
||||
)
|
||||
```
|
||||
|
||||
Auch hier werden erneut Details über den ursprünglichen Artikel bezogen.
|
||||
Dafür wird der HTML-Inhalt der ursprünglichen Seite mit BeautifulSoup
|
||||
geparsed und jeglicher Text des Dokumentes extrahiert.
|
||||
|
||||
## Orchestrierung & Operations
|
||||
|
||||
Sobald die obigen Lösungen das Entwicklungsstadium verlassen und
|
||||
folglich eine Ausführungsumgebung benötigen, stellen sich die folgenden
|
||||
Fragen:
|
||||
|
||||
- Wie kann die Ausführung der Anwendungen geplant und zeitbasiert
|
||||
gestartet werden? (Scheduling)
|
||||
|
||||
- Wie lässt sich ein Monitoring auf die Ausführungsumgebung anwenden?
|
||||
(Transparenz)
|
||||
|
||||
- Wie lässt sich die Ausführungsumgebung um weitere Anwendungen
|
||||
erweitern? (Skalierbarkeit)
|
||||
|
||||
|
||||
Zum einen wird ein Scheduling benötigt, um die Extraktion aktueller News
|
||||
Artikel in festgelegten Intervallen (z.B. täglich) durchzuführen. Des
|
||||
Weiteren muss es möglich sein, umfassende Informationen über diese
|
||||
Ausführung zu erhalten. Da der Nutzer nicht vor dem Monitor sitzt, das
|
||||
Skript manuell startet und seine Ausführung überwacht. Folglich ist es
|
||||
schwierig im Nachhinein nachzuvollziehen, woran die Ausführung
|
||||
gescheitert ist und wann dies geschah. Zuletzt muss die Lösung in der
|
||||
Lage sein, auch weitere Anwendungen aufzunehmen, da auch Prozesse wie
|
||||
die Extraktion der Stammdaten oder der Finanzdaten in Intervallen - wenn
|
||||
auch größer, da Jahresabschlüsse immerhin nur jährlich anfallen -
|
||||
ausgeführt werden können.
|
||||
|
||||
Eine einfache Lösung ist der Einsatz vom in Unix integrierten Cron
|
||||
Dienst. Dieser ermöglicht den zeitbasierten Start von Skripten oder
|
||||
Anwendungen. [9]
|
||||
|
||||
```sh
|
||||
0 8 * * * /var/scripts/daily_cron.sh
|
||||
```
|
||||
|
||||
Problematisch gestaltet sich beim Cron jedoch die Bereitstellung von
|
||||
Fehlermeldungen sowie die Kapselung von Ausführungsumgebungen, welches
|
||||
vor allem bei Python Anwendungen von hoher Bedeutung ist, damit es nicht
|
||||
zu kollidierenden Abhängigkeiten kommt.
|
||||
|
||||
Eine in der Industrie verbreitete Lösung stellt Apache Airflow dar.
|
||||
|
||||
<img src="Abbildungen/Apache_Airflow.PNG" height=450 />
|
||||
|
||||
Wie in der obigen Grafik dargestellt, setzt sich eine Airlflow Instanz
|
||||
aus verschiedenen Sub-Komponenten zusammen. Neben einem Web Server, der
|
||||
eine grafische Bedienoberfläche sowie dazugehörige
|
||||
API-Endpunkte
|
||||
stellt, besteht der Kern aus dem Scheduler sowie Executor und der
|
||||
dazugehörigen Datenbank. Anwender legen in Form eines
|
||||
*Directed Acyclic Graphs (DAG)* einen
|
||||
Anwendungsstrang an, der über den Scheduler aufgerufen und im Executor
|
||||
sowie seinen *Workern* - also dedizierten Ausführungsumgebungen -
|
||||
letztlich gestartet wird. Besonders in einer auf Kubernetes
|
||||
bereitgestellten Instanz, der administrative Rechte über das Cluster
|
||||
gegeben wurden, kann Airflow eigenständig für jede Ausführung einen
|
||||
temporären Pod hochfahren, der somit die jeweiligen
|
||||
DAGs komplett
|
||||
isoliert ausführt und nach Ausführung wieder terminiert, um Ressourcen
|
||||
zu sparen. Zusätzlich besteht die Möglichkeiten den Quellcode der
|
||||
DAGs automatisch
|
||||
über einen *Git-sync* Mechanismus mit einem Git Repo zu verbinden. So
|
||||
kann neuer Code automatisch auf die Instanz übertragen werden, ohne dass
|
||||
dieser manuell auf den Server kopiert werden muss. [10]
|
||||
|
||||
Zunächst wird im Rahmen des Transparenzregisters jedoch ein simples
|
||||
Deployment auf Basis von Docker Compose bereitgestellt. Das
|
||||
Deployment besteht aus den Kern-Komponenten sowie einem einzelnen
|
||||
Worker. So gehen zwar die flexiblen Möglichkeiten des Hochfahrens
|
||||
individueller Pods verloren, jedoch ist die Einrichtung umso einfacher.
|
||||
Um die Ausführung der Extraktion der News Artikel zu automatisieren,
|
||||
wird der Quellcode in das Format eines DAG überführt und an den Airflow Server
|
||||
übertragen.
|
||||
|
||||
Aus Sicherheitsgründen wurden die Zugangsdaten zur StagingDB in die
|
||||
Airflow Variables überführt, so dass die genauen Daten nicht im
|
||||
Quellcode ersichtlich sind sowie zentral verwaltet werden können, um
|
||||
schnell zwischen verschiedenen Ziel-Datenbanken wechseln zu können.
|
||||
Diese werden in der grafischen Oberfläche durch den Admin Account unter
|
||||
dem Menüpunkt *Admin/Variables* bzw. dem Link */variable* verwaltet.
|
||||
|
||||

|
||||
|
||||
Über den Einsatz des *\@task.virtualenv* Decorators wird sichergestellt,
|
||||
dass jede Methode ihr eigenes Virtual Environment nutzt und so keine
|
||||
Kollision von Python Abhängigkeiten auftreten können. Über das Argument
|
||||
*schedule=\
|
||||
\"@daily"* wird der DAG auf eine tägliche Ausführung terminiert.
|
||||
Der Startzeitpunkt (*start_date*) muss vor dem aktuellen Datum liegen,
|
||||
damit der Scheduler sofort agiert. Nach der Definition der einzelnen
|
||||
Schritte, wird die Hauptlogik in Form eines geordneten Aufrufs der eben
|
||||
definierten Methode und Übergabe der nötigen Parameter definiert.
|
||||
|
||||
## Rechtliche Rahmenbedingungen
|
||||
|
||||
Der Einsatz von Web Scraping / Crawling, um die Daten der vorgestellten
|
||||
Quellen zu beziehen, lässt sich auf verschiedene Bereiche geltenden
|
||||
Rechts verweisen, um zu bewerten auf welche weiteren Rahmenbedingungen
|
||||
Rücksicht genommen werden muss.
|
||||
|
||||
Zum einen ist das Urheberrecht der Daten zu berücksichtigen. Da es sich
|
||||
bei dem Projekt um nicht-kommerzielle Forschung handelt, ist ein
|
||||
Scraping grundsätzlich gemäß §60d des Urheberrechtsgestzes (UrhG) erlaubt. Jedoch ist darauf zu achten,
|
||||
dass Mechanismen wie die *robots.txt* sowie weitere technische Hürden -
|
||||
etwa IP-Sperren - nicht umgangen werden. Außerdem darf dem Betreiber der
|
||||
Website durch die erzeugte Last keine Schäden beigefügt
|
||||
werden. [11]
|
||||
|
||||
Des Weiteren ist besonderes Augenmerk auf den Datenschutz zu legen. Da
|
||||
im Transparenzregister eine Reihe von personenbezogenen Daten wie Namen,
|
||||
Geburtsdaten und Wohnort sowie Firmenzugehörigkeiten verarbeitet werden,
|
||||
muss sichergestellt sein, dass Richtlinien der
|
||||
Datenschutzgrundverordnung (DSGVO) eingehalten
|
||||
werden. Auch wenn es sich bei den verarbeiteten Daten um frei
|
||||
zugängliches Material handelt, muss dennoch ein Rahmenwerk aufgebaut
|
||||
werden, welches die Ziele des Projektes klar absteckt, um seinen
|
||||
Forschungscharakter zu wahren, und ggf. entsprechende Maßnahmen wie etwa
|
||||
ein zeitlich begrenzte Speicherdauer und Zugangsbeschränkung
|
||||
implementiert werden. [11]
|
||||
|
||||
## Zusammenfassung
|
||||
|
||||
Im Rahmen der Hausarbeit wurden zunächst die theoretischen Grundlagen
|
||||
bestehend aus dem Web Scraping und Crawling sowie dem
|
||||
ETL-Prozess
|
||||
vermittelt. Darauf folgte die Vertiefung dieser Themen anhand der
|
||||
praktischen Umsetzung in der Data Extraktion für das Transparenzregister
|
||||
aus unterschiedlichen Quellen. Zu diesen zählten unterschiedliche
|
||||
Provider für News Artikel wie etwa der RSS Feed des Handelsblattes sowie die
|
||||
Tagesschau REST API der *bund.dev* Gruppe. Darauf folgend wurden
|
||||
Unternehmens Stammdaten aus dem Unternehmensregister und Bewegungsdaten
|
||||
aus dem Bundesanzeiger bezogen. Zuletzt folgte die Ermittlung einer
|
||||
geeigneten Ausführungsplattform in Form von Apache Airflow sowie das
|
||||
Deployment der ersten DAGs auf dieser. Rechtliche
|
||||
Rahmenbedingungen, die die weitere Entwicklung beeinflussen werden,
|
||||
wurden am Schluss dargestellt.
|
||||
|
||||
### Fazit
|
||||
|
||||
Beim Bezug von Daten aus staatlichen Quellen zeigte sich schnell, dass
|
||||
noch keine vollends in APIs überführte Lösungen vorliegen. So gestaltete
|
||||
sich die Exktaktion von Daten hier im Vergleich zu den News Artikeln
|
||||
deutlich komplizierter und vor allem fehleranfälliger. Sollte sich die
|
||||
Struktur der Website nur im geringsten ändern - zum Beispiel indem
|
||||
Buttons umbenannt oder komplett ersetzt werden - so wird die Anwendung
|
||||
nicht mehr korrekt agieren können. Da es sich ohnehin um öffentliche
|
||||
Daten für den allgemeinen Zugriff handelt, wäre hier eine anständige
|
||||
Lösung auf Seiten der Betreiber wünschenswert.
|
||||
|
||||
Der Einsatz von Apache Airflow stellte sich in dem gegebenen Aufbau als
|
||||
fehleranfällig heraus. Dadurch, dass die Instanz nur über einen einzigen
|
||||
Worker verfügt, der sich jedoch von verschiedenen Anwendungen geteilt
|
||||
wird, die dennoch unterschiedliche Abhängigkeiten einsetzen müssen, war
|
||||
das Aufsetzen der DAGs deutlich komplizierter und auch der
|
||||
Einsatz von Bibliotheken, um Duplikate zu vermeiden, konnte dem Worker
|
||||
nur schwer beigebracht werden. Hier wurde deutlich, dass - trotz
|
||||
komplizierteren initialen Setups - ein auf Kubernetes und dem
|
||||
*KubernetesPodOperator* basiertes Deployment hier eleganter wäre.
|
||||
Dennoch ist das Monitoring von Airflow im Vergleich zu anderen Lösungen
|
||||
zu loben, so dass ein weiterer Einsatz geplant ist.
|
||||
|
||||
### Ausblick
|
||||
|
||||
Nun gilt es die entwickelten Puzzle-Stücke zusammenzusetzen und die
|
||||
StagingDB mit immer mehr Daten zu versorgen. Da News Artikel nun
|
||||
regelmäßig eingespeist werden und solange sich an den APIs der Betreiber
|
||||
nichts ändert auch keine Code Änderungen erforderlich sind, wird der
|
||||
Fokus klar auf dem Beziehen weiteren Stamm- und Bewegungsdaten der
|
||||
Unternehmen liegen, um so die weiteren Prozesse wie die
|
||||
Verflechtugnsanalyse und Datenvisualisierung mit Informationen zu
|
||||
versorgen. Sollten sich im Laufe der Entwicklung weitere News-Quellen
|
||||
als wertvoll erweisen, kann auf der bestehenden Code-Basis schnell eine
|
||||
weitere Integration geschaffen werden.
|
||||
|
||||
Des Weiteren mag der im vorherigen Absatz angesprochene Vorschlag,
|
||||
Airflow auf Kubernetes aufzusetzen, eine sinnvolle Ergänzung sein. In
|
||||
Zukunft werden auch Finanzdaten in regelmäßigen Abständen vom
|
||||
Bundesanzeiger bezogen werden müssen, so dass die Investition in eine
|
||||
besser skalierbare Lösung wertvoll wäre.
|
||||
|
||||
Ein weiterer indirekter Vorteil von Airflow ist, dass Entwickler dazu
|
||||
gezwungen sind, ihren Code möglichst modular zu halten, um so die
|
||||
Ausführungsumgebung zu abstrahieren. Der Einsatz von Airflow und das
|
||||
Verpacken des Codes in einen DAG sollte lediglich bestehende
|
||||
Klassen/Methoden verwenden und in die richtige Reihenfolge bringen bzw.
|
||||
verknüpfen. Der Aufbau einer klaren Interface-Struktur und guten
|
||||
Teststrategie ist daher sehr wichtig. Der damit verbundene Aufwand muss
|
||||
entsprechend eingeplant werden.
|
||||
|
||||
## Anhang
|
||||
|
||||
### Company Datenmodell
|
||||
|
||||

|
||||
|
||||
### Ablauf Web Scraping im Unternehmensregister
|
||||
|
||||

|
||||
|
||||
### Airflow DAG
|
||||
|
||||
```python
|
||||
import datetime
|
||||
|
||||
from airflow.decorators import dag, task
|
||||
|
||||
@dag("handelsblatt_news_dag", start_date=datetime.datetime(2000, 1, 1), schedule="@daily", catchup=False)
|
||||
|
||||
def main():
|
||||
|
||||
@task.virtualenv(system_site_packages=False, requirements=['requests', 'bs4', 'xmltodict'])
|
||||
def fetch_data():
|
||||
from datetime import datetime
|
||||
from transparenzregister.utils.handelsblatt import HandelsblattRSS
|
||||
|
||||
handelsblatt = HandelsblattRSS()
|
||||
|
||||
items = handelsblatt.get_news_for_category()
|
||||
|
||||
data = []
|
||||
for article in items:
|
||||
info = {
|
||||
"id": article["guid"],
|
||||
"title": article["title"],
|
||||
"date": datetime.strptime(
|
||||
article["pubDate"], "%a, %d %b %Y %H:%M:%S %z"
|
||||
).strftime("%Y-%m-%dT%H:%M:%S%z"),
|
||||
"source_url": article["link"],
|
||||
"text": handelsblatt.get_news_details_text(article["link"]),
|
||||
}
|
||||
data.append(info)
|
||||
return data
|
||||
|
||||
@task.virtualenv(requirements=["pymongo"], task_id="Process_data")
|
||||
def process_data(list_news: list) -> list:
|
||||
from airflow.models import Variable
|
||||
from transparenzregister.utils.mongo import MongoConnector, MongoNewsService
|
||||
from transparenzregister.models.news import News
|
||||
|
||||
connector = MongoConnector(
|
||||
hostname=Variable.get("mongodb_host"),
|
||||
database="transparenzregister",
|
||||
username=Variable.get("mongodb_username"),
|
||||
password=Variable.get("mongodb_password"),
|
||||
port=None
|
||||
)
|
||||
service = MongoNewsService(connector)
|
||||
|
||||
num_inserted = 0
|
||||
for info in list_news:
|
||||
article = News(**info)
|
||||
if service.get_by_id(article.id) is None:
|
||||
service.insert(article)
|
||||
num_inserted += 1
|
||||
return num_inserted
|
||||
|
||||
raw_data = fetch_data()
|
||||
inserted_entries = process_data(raw_data)
|
||||
print(f"Number of inserted entries: {inserted_entries}")
|
||||
return inserted_entries
|
||||
|
||||
news_dag = main()
|
||||
|
||||
### Literaturverzeichnis
|
||||
|
||||
- **[1]** Samanpour, A. R. (2016). *Studienunterlagen: Business Intelligence 1* (1. Auflage). Wissenschaftliche Genossenschaft Südwestfalen eG. Iserlohn.
|
||||
|
||||
- **[2]** IONOS. (2020). *Was ist Web Scraping?*. [Link](https://www.ionos.de/digitalguide/websites/web-entwicklung/was-ist-web-scraping/) (Zugriff am: 16. September 2023).
|
||||
|
||||
- **[3]** IONOS. (2016). *Indexierungsmanagement mit der robots.txt*. [Link](https://www.ionos.de/digitalguide/hosting/hosting-technik/indexierungsmanagement-mit-der-robotstxt/) (Zugriff am: 16. September 2023).
|
||||
|
||||
- **[4]** Bundesanzeiger Verlag GmbH. *Unternehmensregister*. [Link](https://www.unternehmensregister.de/) (Zugriff am: 24. September 2023).
|
||||
|
||||
- **[5]** Google. *Was ist CAPTCHA?*. [Link](https://support.google.com/a/answer/1217728?hl=en) (Zugriff am: 3. Oktober 2023).
|
||||
|
||||
- **[6]** Markgraf, D. P. (2018). *RSS-Feed*. Springer Fachmedien Wiesbaden GmbH. [Link](https://wirtschaftslexikon.gabler.de/definition/rss-feed-53722/version-276790) (Zugriff am: 16. September 2023).
|
||||
|
||||
- **[7]** Harvard Law. (2014). *RSS 2.0*. [Link](https://cyber.harvard.edu/rss/) (Zugriff am: 16. September 2023).
|
||||
|
||||
- **[8]** bundDEV. *Wir dokumentieren Deutschland*. [Link](https://bund.dev/) (Zugriff am: 16. September 2023).
|
||||
|
||||
- **[9]** ubuntu Deutschland e.V. (2023). *Cron*. [Link](https://wiki.ubuntuusers.de/Cron/) (Zugriff am: 1. Oktober 2023).
|
||||
|
||||
- **[10]** The Apache Software Foundation. *Apache Airflow*. [Link](https://airflow.apache.org/docs/apache-airflow/2.7.1/index.html) (Zugriff am: 1. Oktober 2023).
|
||||
|
||||
- **[11]** Universität Hamburg - Fakultät für Wirtschafts- und Sozialwissenschaften. (2023). *Handreichung zur rechtskonformen Durchführung von Web-Scraping Projekten in der nicht-kommerziellen wissenschaftlichen Forschung*. [PDF Link](https://www.wiso.uni-hamburg.de/forschung/forschungslabor/downloads/20200130-handreichung-web-scraping.pdf) (Zugriff am: 2. Oktober 2023).
|