manaviel85370
commited on
Commit
·
da88570
1
Parent(s):
479a9e7
add pages and all
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .gitignore +1 -0
- .streamlit/config.toml +2 -0
- pages/0_DB_Overview.py +67 -0
- pages/1_Get_Urls.py +131 -0
- pages/2_Get_Event_Data.py +121 -0
- pages/3_Sort_Event_Data.py +67 -0
- pages/4_Control.py +128 -0
- pages/5_Playground.py +217 -0
- pages/6_Pipeline.py +136 -0
- pages/TEST.py +0 -2
- requirements.txt +23 -1
- src/__init__.py +0 -0
- src/configuration/__init__.py +0 -0
- src/configuration/config.py +7 -0
- src/crawler/CrawlerV2.py +215 -0
- src/crawler/__init__.py +0 -0
- src/crawler/crawler_service.py +106 -0
- src/crawler/maps_api.py +23 -0
- src/crawler/serp_maps.py +62 -0
- src/crawler/serp_search.py +29 -0
- src/crawler/utils/keywords.py +45 -0
- src/crawler/utils/maps_types.py +25 -0
- src/crawler/utils/regEx.py +97 -0
- src/nlp/__init__.py +0 -0
- src/nlp/config.cfg +17 -0
- src/nlp/data/ner.json +1 -0
- src/nlp/data/ner/texts.json +0 -0
- src/nlp/data/test.txt +14 -0
- src/nlp/dates_txt +4 -0
- src/nlp/event.jpg +0 -0
- src/nlp/experimental/__init__.py +0 -0
- src/nlp/experimental/annotations.json +241 -0
- src/nlp/experimental/annotations_v1.json +430 -0
- src/nlp/experimental/data/img.png +0 -0
- src/nlp/experimental/data/test.md +34 -0
- src/nlp/experimental/gliner/ner_fine_tuning.py +42 -0
- src/nlp/experimental/gliner/open_information_extraction.py +22 -0
- src/nlp/experimental/gliner/summarization.py +194 -0
- src/nlp/experimental/keyword_extraction.py +33 -0
- src/nlp/experimental/layout_parser.py +23 -0
- src/nlp/experimental/llm/__init__.py +0 -0
- src/nlp/experimental/llm/inference_api_test.py +227 -0
- src/nlp/experimental/llm/llm_image_document_question_answering.py +8 -0
- src/nlp/experimental/llm/llm_ner.py +32 -0
- src/nlp/experimental/ner/__init__.py +0 -0
- src/nlp/experimental/ner/create_spacy_annotations.py +158 -0
- src/nlp/experimental/ner/few_shot_ner.py +52 -0
- src/nlp/experimental/ner/nu_ner.py +33 -0
- src/nlp/experimental/ner/spacy_ner.py +47 -0
- src/nlp/experimental/ner/spacy_ner_rule_based.py +58 -0
.gitignore
CHANGED
@@ -1,2 +1,3 @@
|
|
1 |
.idea
|
2 |
.venv
|
|
|
|
1 |
.idea
|
2 |
.venv
|
3 |
+
.env
|
.streamlit/config.toml
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
[theme]
|
2 |
+
base="light"
|
pages/0_DB_Overview.py
ADDED
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import pandas as pd
|
2 |
+
from src.utils.markdown_processing.md_preprocessing import convert_html_to_md
|
3 |
+
from src.persistence.db import *
|
4 |
+
import streamlit_nested_layout
|
5 |
+
|
6 |
+
|
7 |
+
@st.cache_resource
|
8 |
+
def init_connection():
|
9 |
+
return init_db()
|
10 |
+
|
11 |
+
def render_url_content(element):
|
12 |
+
with st.container(border=True, height=400):
|
13 |
+
md = convert_html_to_md(element["cleaned_html"])
|
14 |
+
st.markdown(md, unsafe_allow_html=True)
|
15 |
+
|
16 |
+
db = init_connection()
|
17 |
+
|
18 |
+
# Titel der App
|
19 |
+
st.title("Übersicht über die Datenbank-Inhalte")
|
20 |
+
st.subheader("Aktuelle Einträge in der DB")
|
21 |
+
st.write("""
|
22 |
+
- **unsorted_urls**: Enthält Daten-Objekte bestehend aus Start-Urls, Url-Typ (z.b. "city", "theater") sowie vom Crawler gefundene Sub-Urls.
|
23 |
+
- **event_urls**: Enthält Daten-Objekte bestehend aus Url, Referenz zur Basis-Url (Start-Url), Klasse (EventDetail / EventOverview) sowie HTML der Seite""")
|
24 |
+
df = pd.DataFrame({
|
25 |
+
"DB-Collection":[
|
26 |
+
CollectionNames.UNSORTED_URLS,
|
27 |
+
CollectionNames.EVENT_URLS],
|
28 |
+
"Anzahl an Einträgen":[
|
29 |
+
db.unsorted_urls.count_documents({}),
|
30 |
+
db.event_urls.count_documents({})],
|
31 |
+
"Bereits verarbeitet":[
|
32 |
+
db.unsorted_urls.count_documents({"crawled": True}),
|
33 |
+
db.event_urls.count_documents({"final":True})
|
34 |
+
]})
|
35 |
+
st.table(df)
|
36 |
+
|
37 |
+
overview_pages = list(db.event_urls.find(filter={"class":"EventOverview", "final":True}, projection={"url":1,"base_url_id":1,"cleaned_html":1}))
|
38 |
+
detail_pages = list(db.event_urls.find(filter={"class":"EventDetail", "final":True}, projection={"url":1,"base_url_id":1,"cleaned_html":1, "data":1}) )
|
39 |
+
|
40 |
+
st.subheader("Fertige Einträge in Event Urls:")
|
41 |
+
st.write("Die fertigen Daten sind mithilfe der gpt-api in markdown übersetzt und nur der Veranstaltungsbereich heraus geschnitten.")
|
42 |
+
data = [el for el in detail_pages if "data" in el]
|
43 |
+
st.write(f"Fertig verarbeitete Urls: {len(data)} von {len(detail_pages)}")
|
44 |
+
|
45 |
+
st.subheader("Einträge in Event Urls")
|
46 |
+
st.write("""
|
47 |
+
Die Übersicht zeigt die finalen Daten aus **event_urls**, sortiert nach ihrer Klasse.""")
|
48 |
+
with st.expander(f"Event-Übersichtsseiten ({len(overview_pages)})"):
|
49 |
+
for el in overview_pages:
|
50 |
+
try:
|
51 |
+
with st.expander(f"{el['url']} - ({db.unsorted_urls.find_one(filter={'_id':el['base_url_id']}, projection={'url_type':1})['url_type']})"):
|
52 |
+
render_url_content(el)
|
53 |
+
except Exception as e:
|
54 |
+
st.write(f"Fehler: {e}")
|
55 |
+
|
56 |
+
|
57 |
+
with st.expander(f"Event-Detailseiten ({len(detail_pages)})"):
|
58 |
+
for el in detail_pages:
|
59 |
+
try:
|
60 |
+
with st.expander(f"{el['url']}"):
|
61 |
+
render_url_content(el)
|
62 |
+
except Exception as e:
|
63 |
+
st.write(f"Fehler bei {el['url']}: {e} ")
|
64 |
+
|
65 |
+
|
66 |
+
|
67 |
+
|
pages/1_Get_Urls.py
ADDED
@@ -0,0 +1,131 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from src.crawler.CrawlerV2 import Crawler
|
2 |
+
from src.crawler.crawler_service import *
|
3 |
+
from src.crawler.utils.maps_types import MAPS_TYPES
|
4 |
+
from src.crawler.maps_api import get_maps_results
|
5 |
+
from src.persistence.db import *
|
6 |
+
import random
|
7 |
+
import streamlit_nested_layout
|
8 |
+
|
9 |
+
|
10 |
+
@st.cache_resource
|
11 |
+
def init_connection():
|
12 |
+
return init_db()
|
13 |
+
|
14 |
+
def crawl(item):
|
15 |
+
results =[]
|
16 |
+
try:
|
17 |
+
st.info(f"Crawle {item['url']}")
|
18 |
+
|
19 |
+
if "overview_pages" not in item:
|
20 |
+
crawler = Crawler(item["url"], item["url_type"], depth=2)
|
21 |
+
results = crawler.crawl()
|
22 |
+
except Exception as e:
|
23 |
+
st.error(f"Fehler beim crawlen: {e}")
|
24 |
+
db.unsorted_urls.delete_one({"_id":item["_id"]})
|
25 |
+
return
|
26 |
+
|
27 |
+
# Übersicht-Seiten erkennen
|
28 |
+
overview_regex = re.compile(
|
29 |
+
r"^https?:\/\/([a-zA-Z0-9.-]*\/)*(?!(advent))(kalender|.*veranstaltungen|veranstaltungskalender|.*events?|.*event-?kalender|([a-zA-Z]*)?programm|gottesdienste|auff(ü|ue)hrungen|termine|spielplan)(\/?|(\/?[a-zA-Z]*)\.[a-zA-Z]*)?$",
|
30 |
+
re.IGNORECASE)
|
31 |
+
overview_pages = set()
|
32 |
+
# URLs sortieren
|
33 |
+
|
34 |
+
sub_urls=[]
|
35 |
+
for url in results:
|
36 |
+
if overview_regex.match(url):
|
37 |
+
overview_pages.add(url)
|
38 |
+
else:
|
39 |
+
sub_urls.append(url)
|
40 |
+
if not overview_pages:
|
41 |
+
overview_regex = re.compile(
|
42 |
+
r"^https?:\/\/([a-zA-Z0-9.-]*\/)*(?!(advent))(kalender|.*veranstaltungen|veranstaltungskalender|.*events?|.*event-?kalender|([a-zA-Z]*)?programm|gottesdienste|auff(ü|ue)hrungen|termine|spielplan)(\/?|(\/?[a-zA-Z]*)\.[a-zA-Z]*)?",
|
43 |
+
re.IGNORECASE)
|
44 |
+
for url in results:
|
45 |
+
match = overview_regex.search(url)
|
46 |
+
if match:
|
47 |
+
overview_pages.add(match.group())
|
48 |
+
overview_pages = {url.casefold() for url in overview_pages}
|
49 |
+
|
50 |
+
with st.expander("Gefundene Suburls"):
|
51 |
+
for url in sub_urls:
|
52 |
+
st.write(url)
|
53 |
+
with st.expander("Gefundene Übersichtsseiten:"):
|
54 |
+
for url in overview_pages:
|
55 |
+
st.write(url)
|
56 |
+
|
57 |
+
# Update DB entry
|
58 |
+
new_values = {"$set": {"crawled": True}}
|
59 |
+
if overview_pages:
|
60 |
+
new_values["$set"]["overview_pages"] = list(overview_pages)
|
61 |
+
if sub_urls:
|
62 |
+
item["sub_urls"] = sub_urls
|
63 |
+
new_values["$set"]["sub_urls"] = sub_urls
|
64 |
+
|
65 |
+
db.unsorted_urls.update_one({"_id":item["_id"]}, new_values)
|
66 |
+
print(db.unsorted_urls.find_one({"_id":item["_id"]}))
|
67 |
+
|
68 |
+
db = init_connection()
|
69 |
+
|
70 |
+
# content
|
71 |
+
st.title("Event-Urls-Suche mit Crawler und Google API")
|
72 |
+
st.write("""
|
73 |
+
Wähle aus für wie viele Urls der **Crawler** gestartert werden soll. Diese werden zufällig aus den noch nicht gecrawlten Urls aus der DB ausgewählt.
|
74 |
+
Wenn **"Google Maps Ergebnisse finden"** aktiviert ist, werden bei den Stadtportalen zusätzlich noch neue Veranstaltungsorte gesucht.""")
|
75 |
+
with st.form("Crawler Settings"):
|
76 |
+
count = st.number_input("Wie viele URLs sollen gecrawled werden?", step=1)
|
77 |
+
maps = st.checkbox("Google Maps Ergebnisse finden",disabled=True)
|
78 |
+
st.info("Aktuell können keine neuen Start-URLs generiert werden. Billing für GCP fehlt.")
|
79 |
+
# Every form must have a submit button.
|
80 |
+
submitted = st.form_submit_button("Starte Crawler")
|
81 |
+
if submitted:
|
82 |
+
for i in range(count):
|
83 |
+
item = db.unsorted_urls.find_one({"crawled": None })
|
84 |
+
with st.expander(f"Ergebnisse für {item['url']} in {item['meta']['location']}"):
|
85 |
+
|
86 |
+
if item["url_type"] == "city" and maps:
|
87 |
+
for type_id in random.sample(MAPS_TYPES, 5):
|
88 |
+
print(item)
|
89 |
+
if "maps_searches" not in item or "maps_searches" in item and type_id not in item["maps_searches"]:
|
90 |
+
st.info(f"Suche Maps Ergebnisse für {type_id} in {item['meta']['location']}")
|
91 |
+
maps_results = get_maps_results(type_id, item["meta"]["location"])
|
92 |
+
if maps_results:
|
93 |
+
new_elements = []
|
94 |
+
with st.expander("Maps Ergebnisse"):
|
95 |
+
for result in maps_results:
|
96 |
+
if result.website_uri \
|
97 |
+
and "facebook" not in result.website_uri \
|
98 |
+
and "instagram" not in result.website_uri \
|
99 |
+
and "tiktok" not in result.website_uri \
|
100 |
+
and result.website_uri not in [e["url"] for e in new_elements]:
|
101 |
+
element = {
|
102 |
+
"url_type": type_id,
|
103 |
+
"url": result.website_uri,
|
104 |
+
"meta":{
|
105 |
+
"website_host": result.display_name.text,
|
106 |
+
"location": result.formatted_address.split(", ")[1],
|
107 |
+
"address": result.formatted_address,
|
108 |
+
"maps_types": list(result.types)
|
109 |
+
}}
|
110 |
+
st.write(f"{element['meta']['website_host']} - {element['url']}")
|
111 |
+
new_elements.append(element)
|
112 |
+
if new_elements:
|
113 |
+
db.unsorted_urls.insert_many(new_elements)
|
114 |
+
|
115 |
+
if "maps_searches" in item:
|
116 |
+
maps_searches = item["maps_searches"]
|
117 |
+
maps_searches.append(type_id)
|
118 |
+
item["maps_searches"] = maps_searches
|
119 |
+
else:
|
120 |
+
item["maps_searches"] = [type_id]
|
121 |
+
else:
|
122 |
+
st.success("Maps Ergebnisse bereits in DB")
|
123 |
+
|
124 |
+
crawl(item)
|
125 |
+
|
126 |
+
|
127 |
+
|
128 |
+
|
129 |
+
|
130 |
+
|
131 |
+
|
pages/2_Get_Event_Data.py
ADDED
@@ -0,0 +1,121 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
from src.crawler.CrawlerV2 import *
|
3 |
+
from bs4 import BeautifulSoup
|
4 |
+
from src.utils.apis.gpt_api import classify_text
|
5 |
+
import random
|
6 |
+
|
7 |
+
from src.utils.helpers import clean_html, strip_html_to_text
|
8 |
+
from src.utils.markdown_processing.md_preprocessing import convert_html_to_md
|
9 |
+
from src.persistence.db import init_db
|
10 |
+
from lxml import etree
|
11 |
+
import streamlit_nested_layout
|
12 |
+
|
13 |
+
@st.cache_resource
|
14 |
+
def init_connection():
|
15 |
+
return init_db()
|
16 |
+
|
17 |
+
def get_html(url:str):
|
18 |
+
response = requests.get(url)
|
19 |
+
if response.status_code >= 400:
|
20 |
+
print(f"Skipping {overview_url} with status code {response.status_code}")
|
21 |
+
return None
|
22 |
+
else:
|
23 |
+
return response.content
|
24 |
+
|
25 |
+
def process_url(url:str, el):
|
26 |
+
try:
|
27 |
+
page_content = get_html(url)
|
28 |
+
if page_content:
|
29 |
+
cleaned_html = clean_html(page_content)
|
30 |
+
cleaned_text = strip_html_to_text(cleaned_html)
|
31 |
+
md = convert_html_to_md(cleaned_html)
|
32 |
+
gpt_class = classify_text(md)
|
33 |
+
with st.expander(url):
|
34 |
+
st.write("Bereinigtes HTML:")
|
35 |
+
with st.container(border=True, height=400):
|
36 |
+
st.markdown(md)
|
37 |
+
st.write(f"GPT Klasse: {gpt_class['class']}")
|
38 |
+
|
39 |
+
if gpt_class["class"] != "None":
|
40 |
+
new_element = {
|
41 |
+
"base_url_id": el["_id"],
|
42 |
+
"base_url": el["url"],
|
43 |
+
"url": url,
|
44 |
+
"html": page_content,
|
45 |
+
"cleaned_html": cleaned_html,
|
46 |
+
"cleaned_text": cleaned_text,
|
47 |
+
"class": gpt_class["class"]
|
48 |
+
}
|
49 |
+
db.event_urls.update_one(
|
50 |
+
{"url": new_element["url"]},
|
51 |
+
{"$setOnInsert": new_element},
|
52 |
+
upsert=True
|
53 |
+
)
|
54 |
+
return new_element
|
55 |
+
except Exception as e:
|
56 |
+
st.error(f"Es is ein Fehler aufgetreten, die url {url} wird übersprungen\n Fehlermeldung: {e}")
|
57 |
+
|
58 |
+
|
59 |
+
db = init_connection()
|
60 |
+
|
61 |
+
suburls_count = 10
|
62 |
+
|
63 |
+
# content
|
64 |
+
st.title("Generieren von Event Daten")
|
65 |
+
st.write("""
|
66 |
+
Hier werden die potentiellen Übersichtsseiten aus unsorted_urls sowie alle Links auf dieser Seite mithilfe der GPT API überprüft und den beiden Klassen **"EventDetail"** und **"EventOverview"** zugeordnet. Die Event Daten werden dann als neues Objekt in event_urls gespeichert.""")
|
67 |
+
st.info(
|
68 |
+
f"Es werden immer nur max. {suburls_count} Suburls der Übersichtsseiten angeschaut, damit die Daten ausgeglichen bleiben")
|
69 |
+
counter = st.number_input("Wie viele der Urls sollen insgesamt überprüft werden?", step=1)
|
70 |
+
get_event_data = st.button("Event Daten sammeln")
|
71 |
+
|
72 |
+
if get_event_data:
|
73 |
+
for i in range(counter):
|
74 |
+
el = db.unsorted_urls.find_one({"overview_pages": { "$exists": True, "$ne": [] } , "checked": None})
|
75 |
+
print(el)
|
76 |
+
if el:
|
77 |
+
with st.container(border=True):
|
78 |
+
if counter <= 0:
|
79 |
+
break
|
80 |
+
st.subheader(f"Daten sammeln für {el['url']} mit {len(el['overview_pages'])} Übersichtsseiten")
|
81 |
+
update_element = el
|
82 |
+
|
83 |
+
for overview_url in el["overview_pages"]:
|
84 |
+
|
85 |
+
st.info(f"Überprüfe Übersichtsseite: {overview_url}")
|
86 |
+
|
87 |
+
new_element=process_url(overview_url,el)
|
88 |
+
if new_element:
|
89 |
+
soup = BeautifulSoup(new_element["cleaned_html"],"lxml")
|
90 |
+
links = soup.find_all(["a"])
|
91 |
+
urls = set()
|
92 |
+
with st.expander("Suburls"):
|
93 |
+
try:
|
94 |
+
for link in links:
|
95 |
+
href = link["href"]
|
96 |
+
url = urljoin(overview_url, href)
|
97 |
+
url = urlparse(url)._replace(query="", fragment="").geturl()
|
98 |
+
if overview_url != url and check_regex(url, PATTERNS) and str(urlparse(overview_url).scheme)+str(urlparse(overview_url).netloc) != url:
|
99 |
+
urls.add(url)
|
100 |
+
except:
|
101 |
+
print("Exception while processing links")
|
102 |
+
if len(urls) > suburls_count:
|
103 |
+
urls= set(random.sample(list(urls), 10))
|
104 |
+
for url in urls:
|
105 |
+
new_element = process_url(url, el)
|
106 |
+
if not urls:
|
107 |
+
st.info("Es wurden keine Eventseiten unter der Übersichtsseite gefunden")
|
108 |
+
update_element["overview_pages"].remove(overview_url)
|
109 |
+
|
110 |
+
else:
|
111 |
+
update_element["overview_pages"].remove(overview_url)
|
112 |
+
counter = counter - 1
|
113 |
+
if counter <= 0:
|
114 |
+
break
|
115 |
+
new_values = {"$set": {"overview_pages": update_element["overview_pages"], "checked": True }}
|
116 |
+
db.unsorted_urls.update_one({"_id": update_element["_id"]}, new_values )
|
117 |
+
|
118 |
+
|
119 |
+
|
120 |
+
|
121 |
+
|
pages/3_Sort_Event_Data.py
ADDED
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from src.utils.helpers import clean_html
|
2 |
+
from src.utils.markdown_processing.md_preprocessing import convert_html_to_md
|
3 |
+
from src.persistence.db import *
|
4 |
+
from src.utils.apis.gpt_api import remove_boilerplate
|
5 |
+
|
6 |
+
|
7 |
+
@st.cache_resource
|
8 |
+
def init_connection():
|
9 |
+
return init_db()
|
10 |
+
|
11 |
+
|
12 |
+
def render_url_content(element):
|
13 |
+
cleaned_html = clean_html(element["html"])
|
14 |
+
md = convert_html_to_md(cleaned_html)
|
15 |
+
with st.container(border=True, height=400):
|
16 |
+
st.markdown(md)
|
17 |
+
|
18 |
+
def save_event_url():
|
19 |
+
data = None
|
20 |
+
if current_element["class"] == "EventDetail":
|
21 |
+
md = convert_html_to_md(clean_html(current_element["html"]))
|
22 |
+
data = remove_boilerplate(md)
|
23 |
+
result = db.event_urls.update_one({"_id": current_element["_id"]}, { "$set": { "final": True, "data": data} })
|
24 |
+
|
25 |
+
def remove_url():
|
26 |
+
result = db.event_urls.delete_one({"_id": current_element["_id"]}),
|
27 |
+
|
28 |
+
# Variables
|
29 |
+
db = init_connection()
|
30 |
+
current_element = db.event_urls.find_one(filter={"final": None})
|
31 |
+
|
32 |
+
if current_element:
|
33 |
+
current_url = current_element['url']
|
34 |
+
|
35 |
+
# Page Content
|
36 |
+
st.header("Event Daten Sortieren")
|
37 |
+
st.subheader(f"{db.event_urls.count_documents({'final':None})} URLs sind noch unsortiert")
|
38 |
+
st.write("""
|
39 |
+
Hier wird das Datenset endgültig bereinigt. Wenn die von der GPT API zugeordnete Klasse (EventDetail / EventOverview)
|
40 |
+
falsch ist, muss die URL gelöscht werden. Wenn es korrekt zugeordnet ist können die Daten gespeichert werden. \n
|
41 |
+
**ACHTUNG** Teilweise sind die Daten unvollständig. Das liegt daran, dass das HTML gekürzt wurde,
|
42 |
+
für die Sortierung ist das irrelvant, also auch abgeschnittene Events gehören in die Event-DB.\n
|
43 |
+
**Übersichtsseiten müssen Listen von Events enthalten. Eine Seite mit Kategorien oder anderen Links ist keine Übersichtsseite.**
|
44 |
+
""")
|
45 |
+
st.info("Es sollen nur deutsche Texte verarbeitet werden. Alle anderen Texte müssen gelöscht werden. (Teilweise englisch ist okay)")
|
46 |
+
st.write("")
|
47 |
+
try:
|
48 |
+
st.write(f"""### Aktuelle Seite: \n{current_url} ({db.unsorted_urls.find_one(filter={"_id": current_element["base_url_id"]}, projection={"url_type":1})["url_type"]})""")
|
49 |
+
st.write(f"""#### Predicted Class: {current_element["class"]}""")
|
50 |
+
render_url_content(current_element)
|
51 |
+
except Exception as e:
|
52 |
+
st.write(f"Fehler: {e}")
|
53 |
+
st.write(current_url)
|
54 |
+
# Buttons
|
55 |
+
col1, col2= st.columns([1, 1])
|
56 |
+
|
57 |
+
|
58 |
+
with col1:
|
59 |
+
st.button("Als Event-URL speichern", on_click=save_event_url)
|
60 |
+
with col2:
|
61 |
+
st.button("URL löschen", on_click=remove_url)
|
62 |
+
|
63 |
+
else:
|
64 |
+
st.write("Es sind aktuell keine Daten in der DB zur Berarbeitung vorhanden.")
|
65 |
+
|
66 |
+
|
67 |
+
|
pages/4_Control.py
ADDED
@@ -0,0 +1,128 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import pandas as pd
|
2 |
+
from src.utils.helpers import clean_html
|
3 |
+
from src.utils.markdown_processing.md_preprocessing import convert_html_to_md
|
4 |
+
from src.nlp.playground.pipelines.title_extractor import TitleExtractor
|
5 |
+
from src.utils.helpers import normalize_data
|
6 |
+
from src.persistence.db import *
|
7 |
+
from src.utils.apis.gpt_api import remove_boilerplate
|
8 |
+
import torch
|
9 |
+
|
10 |
+
|
11 |
+
@st.cache_resource
|
12 |
+
def init_connection():
|
13 |
+
return init_db()
|
14 |
+
|
15 |
+
def remove_url():
|
16 |
+
result = db.event_urls.delete_one({"_id": current_element["_id"]})
|
17 |
+
st.session_state.elements = db.event_urls.find({"final":True, "class": "EventDetail"},{"_id":1, "url":1, "data":1, "html":1, "information":1})
|
18 |
+
|
19 |
+
def next():
|
20 |
+
db.event_urls.update_one({"_id": current_element["_id"]}, { "$set": { "information":event_information } })
|
21 |
+
st.session_state.index+=1
|
22 |
+
|
23 |
+
def prev():
|
24 |
+
st.session_state.index-=1
|
25 |
+
|
26 |
+
# Variables
|
27 |
+
db = init_connection()
|
28 |
+
if "index" not in st.session_state:
|
29 |
+
st.session_state.index = 0
|
30 |
+
if "elements" not in st.session_state:
|
31 |
+
elements = db.event_urls.find({"final":True, "class": "EventDetail"},{"_id":1, "url":1, "data":1, "html":1, "information":1})
|
32 |
+
|
33 |
+
# preprocessing of html content: get cleaned markdown
|
34 |
+
for el in elements:
|
35 |
+
if "data" not in el:
|
36 |
+
print(el["url"])
|
37 |
+
md = convert_html_to_md(clean_html(el["html"]))
|
38 |
+
try:
|
39 |
+
st.info("GPT-API Anfrage läuft")
|
40 |
+
gpt_md = remove_boilerplate(md)
|
41 |
+
st.info("Verarbeitung beendet")
|
42 |
+
el["data"] = gpt_md
|
43 |
+
db.event_urls.update_one({"_id": el["_id"]}, { "$set": { 'data': el["data"] } })
|
44 |
+
except Exception as e:
|
45 |
+
st.error(f"Es ist ein Fehler aufgetreten: {e} \n")
|
46 |
+
db.event_urls.delete_one({"_id": el["_id"]})
|
47 |
+
st.session_state.elements = db.event_urls.find({"final":True, "class": "EventDetail"},{"_id":1, "url":1, "data":1, "html":1, "information":1})
|
48 |
+
if "predictions_on" not in st.session_state:
|
49 |
+
st.session_state.predictions_on = False
|
50 |
+
|
51 |
+
|
52 |
+
current_element = st.session_state.elements[st.session_state.index]
|
53 |
+
|
54 |
+
predictions_on = st.toggle("Predictions an (Zeigt Extrahierte Daten an, die Seite lädt dadurch langsamer).")
|
55 |
+
if predictions_on != st.session_state.predictions_on:
|
56 |
+
st.session_state.predictions_on = predictions_on
|
57 |
+
|
58 |
+
if current_element:
|
59 |
+
current_url = current_element['url']
|
60 |
+
|
61 |
+
try:
|
62 |
+
st.write(f"""### Aktuelle Seite: \n{current_url} """)
|
63 |
+
if "data" not in current_element:
|
64 |
+
md = convert_html_to_md(clean_html(current_element["html"]))
|
65 |
+
try:
|
66 |
+
gpt_md = remove_boilerplate(md)
|
67 |
+
current_element["data"] = gpt_md
|
68 |
+
db.event_urls.update_one({"_id": current_element["_id"]}, { "$set": { 'data': current_element["data"] } })
|
69 |
+
except Exception as e:
|
70 |
+
st.error(f"Es ist ein Fehler aufgetreten: {e} \nDer Datenbankeintrag wird gelöscht.")
|
71 |
+
db.event_urls.delete_one({"_id": current_element["_id"]})
|
72 |
+
data = current_element["data"]
|
73 |
+
normalized_text = normalize_data(data)
|
74 |
+
predicted_title = None
|
75 |
+
predicted_date = None
|
76 |
+
if st.session_state.predictions_on:
|
77 |
+
predicted_title = TitleExtractor().extract_title(normalized_text)
|
78 |
+
# predicted_date = extract_entities(normalized_text, ["date", "date_range"])
|
79 |
+
# predicted_date = [ {d["text"],d["label"]} for d in predicted_date ] if predicted_date else None
|
80 |
+
st.subheader("Normalisierte Daten:")
|
81 |
+
with st.container(border=True, height=400):
|
82 |
+
st.markdown(normalized_text)
|
83 |
+
with st.expander("Code ansehen"):
|
84 |
+
with st.container( height=400):
|
85 |
+
st.code(normalized_text)
|
86 |
+
actual_title = st.text_input("Tatsächlicher Titel eingeben:", key="title"+ str(current_element["_id"]),
|
87 |
+
value=current_element.get("information", {}).get("actual", {}).get("title", None))
|
88 |
+
actual_date = None
|
89 |
+
|
90 |
+
event_information = {"actual": {"title":actual_title}}
|
91 |
+
data = {
|
92 |
+
"Information": [
|
93 |
+
"Titel",
|
94 |
+
# "Datum"
|
95 |
+
],
|
96 |
+
"Tatsächlicher Wert":
|
97 |
+
[
|
98 |
+
actual_title,
|
99 |
+
# actual_date
|
100 |
+
],
|
101 |
+
"Predicted Wert": [
|
102 |
+
predicted_title,
|
103 |
+
# predicted_date
|
104 |
+
],
|
105 |
+
}
|
106 |
+
df = pd.DataFrame(data)
|
107 |
+
|
108 |
+
st.subheader("Vergleich der Titel:")
|
109 |
+
st.table(df)
|
110 |
+
|
111 |
+
except Exception as e:
|
112 |
+
st.write(f"Fehler: {e}")
|
113 |
+
st.write(current_url)
|
114 |
+
col1, col2, col3, col4= st.columns([1, 1, 1, 1])
|
115 |
+
|
116 |
+
|
117 |
+
with col1:
|
118 |
+
st.button("Zurück", on_click=prev)
|
119 |
+
with col3:
|
120 |
+
st.button("URL löschen", on_click=remove_url)
|
121 |
+
with col4:
|
122 |
+
st.button("Speichern und Weiter",on_click=next)
|
123 |
+
|
124 |
+
else:
|
125 |
+
st.write("Es sind aktuell keine Daten in der DB zur Bearbeitung vorhanden.")
|
126 |
+
|
127 |
+
|
128 |
+
|
pages/5_Playground.py
ADDED
@@ -0,0 +1,217 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
import logging
|
3 |
+
import os
|
4 |
+
import sys
|
5 |
+
import gc
|
6 |
+
import psutil
|
7 |
+
import streamlit as st
|
8 |
+
import pandas as pd
|
9 |
+
st.info(f"Speicherauslastung vor imports: {psutil.virtual_memory().percent}%. Keys in Cache: {[k for k in st.session_state]}")
|
10 |
+
|
11 |
+
|
12 |
+
from src.configuration.config import SessionStateConfig
|
13 |
+
from src.nlp.playground.textsummarization import SumySummarizer
|
14 |
+
from src.nlp.playground.pipelines.title_extractor import TitleExtractor
|
15 |
+
from src.utils.helpers import normalize_data
|
16 |
+
from src.utils.markdown_processing.CustomMarkdownAnalyzer.MarkdownAnalyzer import MarkdownAnalyzer
|
17 |
+
from src.nlp.playground.llm import QwenLlmHandler
|
18 |
+
from src.nlp.playground.ner import GlinerHandler
|
19 |
+
from src.persistence.db import init_db
|
20 |
+
from src.nlp.playground.textclassification import ZeroShotClassifier, CategoryMode, CustomMode
|
21 |
+
|
22 |
+
entities_schema = [
|
23 |
+
'"title": str | None"',
|
24 |
+
'"organizer": str | None"',
|
25 |
+
'"startDate": str | None"',
|
26 |
+
'"endDate": str | None"',
|
27 |
+
'"startTime": str | None"',
|
28 |
+
'"endTime": str | None"',
|
29 |
+
'"admittanceTime": str | None"',
|
30 |
+
'"locationName": str | None"',
|
31 |
+
'"street": str | None"',
|
32 |
+
'"houseNumber": str | None"',
|
33 |
+
'"postalCode": str | None"',
|
34 |
+
'"city": str | None"',
|
35 |
+
'"price": list[float] | None"',
|
36 |
+
'"currency": str | None"',
|
37 |
+
'"priceFree": bool | None"',
|
38 |
+
'"ticketsRequired": bool | None"',
|
39 |
+
'"categories": list[str] | None"',
|
40 |
+
'"eventDescription": str | None"',
|
41 |
+
'"accesibilityInformation": str | None"',
|
42 |
+
'"keywords": list[str] | None"'
|
43 |
+
]
|
44 |
+
|
45 |
+
|
46 |
+
@st.cache_resource
|
47 |
+
def init_connection():
|
48 |
+
return init_db()
|
49 |
+
|
50 |
+
@st.cache_resource
|
51 |
+
def init_data():
|
52 |
+
return db.event_urls.find(filter={"class":"EventDetail", "final":True}, projection={"url":1,"base_url_id":1,"cleaned_html":1, "data":1})
|
53 |
+
|
54 |
+
def render_md(md):
|
55 |
+
st.subheader("Original Text:")
|
56 |
+
with st.container(border=True, height=400):
|
57 |
+
st.markdown(md)
|
58 |
+
|
59 |
+
def render_table(table_data):
|
60 |
+
st.subheader("Extrahierte Daten:")
|
61 |
+
df = pd.DataFrame(table_data)
|
62 |
+
st.table(df)
|
63 |
+
st.markdown("---")
|
64 |
+
|
65 |
+
def init_session_state(key, value):
|
66 |
+
if key not in st.session_state:
|
67 |
+
clear_st_cache()
|
68 |
+
st.session_state[key] = value
|
69 |
+
|
70 |
+
def clear_st_cache():
|
71 |
+
keys = list(st.session_state.keys())
|
72 |
+
for key in keys:
|
73 |
+
st.session_state.pop(key)
|
74 |
+
|
75 |
+
|
76 |
+
db = init_connection()
|
77 |
+
data = init_data()
|
78 |
+
|
79 |
+
st.info(f"Speicherauslastung: {psutil.virtual_memory().percent}%. Keys in Cache: {[k for k in st.session_state]}")
|
80 |
+
|
81 |
+
|
82 |
+
with st.expander("Large Language Models"):
|
83 |
+
with st.form("Settings LLM"):
|
84 |
+
count = st.number_input("Wie viele Veranstaltungen sollen gestest werden?", step=1)
|
85 |
+
st.write("Welche Informationen sollen extrahiert werden?")
|
86 |
+
options = []
|
87 |
+
for ent in entities_schema:
|
88 |
+
option = st.checkbox(ent ,key=ent)
|
89 |
+
options.append(option)
|
90 |
+
submit_llm = st.form_submit_button("Start")
|
91 |
+
|
92 |
+
if submit_llm:
|
93 |
+
selected_entities = [entity for entity, selected in zip(entities_schema, options) if selected]
|
94 |
+
init_session_state(SessionStateConfig.QWEN_LLM_HANDLER, QwenLlmHandler())
|
95 |
+
qwen_llm_handler = st.session_state[SessionStateConfig.QWEN_LLM_HANDLER]
|
96 |
+
try:
|
97 |
+
for event in data:
|
98 |
+
extracted_data = qwen_llm_handler.extract_data(text=event["data"], entities= ", ".join(selected_entities))
|
99 |
+
table_data = [{"Key": key, "Value": value} for key, value in extracted_data.items()]
|
100 |
+
|
101 |
+
render_md(event["data"])
|
102 |
+
render_table(table_data)
|
103 |
+
|
104 |
+
count -= 1
|
105 |
+
if count == 0:
|
106 |
+
break
|
107 |
+
except Exception as e:
|
108 |
+
st.write(f"Es ist ein Fehler aufgetreten: {e}")
|
109 |
+
|
110 |
+
with st.expander("Named Entity Recognition"):
|
111 |
+
with st.form("Settings NER"):
|
112 |
+
count = st.number_input("Wie viele Veranstaltungen sollen gestest werden?", step=1)
|
113 |
+
label_input = st.text_input("Gebe die Labels der Entitäten getrennt durch Komma an.")
|
114 |
+
submit_ner = st.form_submit_button("Start")
|
115 |
+
|
116 |
+
if submit_ner:
|
117 |
+
init_session_state(SessionStateConfig.GLINER_HANDLER, GlinerHandler())
|
118 |
+
gliner_handler = st.session_state[SessionStateConfig.GLINER_HANDLER]
|
119 |
+
if label_input:
|
120 |
+
labels = label_input.split(",")
|
121 |
+
for event in data:
|
122 |
+
text = normalize_data(event["data"])
|
123 |
+
render_md(text)
|
124 |
+
|
125 |
+
extracted_data = gliner_handler.extract_entities(text, labels)
|
126 |
+
table_data = [{"Key": element["label"], "Value": element["text"] } for element in extracted_data]
|
127 |
+
render_table(table_data)
|
128 |
+
|
129 |
+
count -= 1
|
130 |
+
if count == 0:
|
131 |
+
break
|
132 |
+
|
133 |
+
with st.expander("Textclassification"):
|
134 |
+
with st.form("Settings TextClassification"):
|
135 |
+
mode = st.selectbox("Classification Mode", ["Categories", "Custom"])
|
136 |
+
custom_labels = st.text_input("(Nur bei Custom Mode) Gib die Klassen Labels ein, durch Komma getrennt.", placeholder="Theater,Oper,Film")
|
137 |
+
custom_hypothesis_template = st.text_input("(Nur bei Custom Mode) Gib das Template ein. {} ist dabei der Platzhalter für die Labels", placeholder="Die Art der Veranstaltung ist {}")
|
138 |
+
count = st.number_input("Wie viele Veranstaltungen sollen gestest werden?", step=1)
|
139 |
+
submit_textclass = st.form_submit_button("Start")
|
140 |
+
|
141 |
+
if submit_textclass:
|
142 |
+
init_session_state(SessionStateConfig.ZERO_SHOT_CLASSIFIER, ZeroShotClassifier())
|
143 |
+
classifier = st.session_state[SessionStateConfig.ZERO_SHOT_CLASSIFIER]
|
144 |
+
if mode == "Categories":
|
145 |
+
classifier_mode = CategoryMode()
|
146 |
+
elif custom_labels and custom_hypothesis_template:
|
147 |
+
classifier_mode = CustomMode(labels=custom_labels.split(","), hypothesis_template=custom_hypothesis_template)
|
148 |
+
for event in data:
|
149 |
+
text = normalize_data(event["data"])
|
150 |
+
predictions = classifier.classify(text, classifier_mode)
|
151 |
+
table_data = [{"Kategorie": p.label, "Score": p.score} for p in predictions]
|
152 |
+
|
153 |
+
render_md(text)
|
154 |
+
render_table(table_data)
|
155 |
+
|
156 |
+
count -= 1
|
157 |
+
if count == 0:
|
158 |
+
break
|
159 |
+
|
160 |
+
with st.expander("Titel Extraktion"):
|
161 |
+
with st.form("Settings TitleExtraction"):
|
162 |
+
count = st.number_input("Wie viele Veranstaltungen sollen gestest werden?", step=1)
|
163 |
+
submit_title_extr = st.form_submit_button("Start")
|
164 |
+
|
165 |
+
if submit_title_extr:
|
166 |
+
init_session_state("title_extractor", TitleExtractor())
|
167 |
+
title_extractor = st.session_state.title_extractor
|
168 |
+
st.info(f"Speicherauslastung: {psutil.virtual_memory().percent}%. Keys in Cache: {[k for k in st.session_state]}")
|
169 |
+
|
170 |
+
for event in data:
|
171 |
+
text = normalize_data(event["data"])
|
172 |
+
prediction = title_extractor.extract_title(text)
|
173 |
+
try:
|
174 |
+
pred2 = title_extractor.extract_title_classy_classification(text)
|
175 |
+
except FileNotFoundError as e:
|
176 |
+
pred2 = "ERROR: Train Model before usage"
|
177 |
+
table_data = [{"Label": "Titel (ZeroShot)", "Value": prediction}, {"Label": "Titel (FewShot)", "Value": pred2}]
|
178 |
+
|
179 |
+
render_md(text)
|
180 |
+
render_table(table_data)
|
181 |
+
|
182 |
+
count -= 1
|
183 |
+
if count == 0:
|
184 |
+
break
|
185 |
+
|
186 |
+
with st.expander("Textsummarization"):
|
187 |
+
with st.form("Settings Textsummarization"):
|
188 |
+
count = st.number_input("Wie viele Veranstaltungen sollen gestest werden?", step=1)
|
189 |
+
submit_textsummarization = st.form_submit_button("Start")
|
190 |
+
|
191 |
+
if submit_textsummarization:
|
192 |
+
init_session_state(SessionStateConfig.SUMY_SUMMARIZER, SumySummarizer())
|
193 |
+
sumy_summarizer = st.session_state[SessionStateConfig.SUMY_SUMMARIZER]
|
194 |
+
st.info(f"Speicherauslastung: {psutil.virtual_memory().percent}%. Keys in Cache: {[k for k in st.session_state]}")
|
195 |
+
for event in data:
|
196 |
+
try:
|
197 |
+
md = normalize_data(event["data"])
|
198 |
+
md_analyzer = MarkdownAnalyzer(md).identify_all()["block_elements"]
|
199 |
+
md_analyzer = sorted(md_analyzer, key=lambda el: el.line)
|
200 |
+
text = "\n\n".join([el.text for el in md_analyzer])
|
201 |
+
sumy_summary = sumy_summarizer.summarize(text)
|
202 |
+
summary = []
|
203 |
+
for element in md_analyzer:
|
204 |
+
if any(sentence in element.markdown for sentence in sumy_summary):
|
205 |
+
summary.append(element.markdown)
|
206 |
+
|
207 |
+
render_md(md)
|
208 |
+
st.subheader("Extrahierte Daten:")
|
209 |
+
with st.container(border=True, height=400):
|
210 |
+
st.markdown("\n\n".join(summary))
|
211 |
+
|
212 |
+
except Exception as e:
|
213 |
+
st.error(f"Fehler:{e}")
|
214 |
+
logging.exception("message")
|
215 |
+
count -= 1
|
216 |
+
if count == 0:
|
217 |
+
break
|
pages/6_Pipeline.py
ADDED
@@ -0,0 +1,136 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import streamlit.components.v1 as components
|
3 |
+
|
4 |
+
from src.configuration.config import SessionStateConfig
|
5 |
+
from src.nlp.playground.Event import Event
|
6 |
+
from src.nlp.playground.ner import GlinerHandler
|
7 |
+
from src.nlp.playground.pipelines.description_extractor import DescriptionExtractor
|
8 |
+
from src.nlp.playground.textclassification import ZeroShotClassifier, CategoryMode, CustomMode
|
9 |
+
from src.nlp.playground.pipelines.title_extractor import TitleExtractor
|
10 |
+
from src.persistence.db import init_db
|
11 |
+
from src.utils.apis.gpt_api import remove_boilerplate
|
12 |
+
from src.utils.helpers import normalize_data
|
13 |
+
from src.utils.markdown_processing.CustomMarkdownAnalyzer.MarkdownAnalyzer import MarkdownAnalyzer
|
14 |
+
from src.utils.markdown_processing.md_preprocessing import convert_html_to_md
|
15 |
+
|
16 |
+
@st.cache_resource
|
17 |
+
def init_connection():
|
18 |
+
return init_db()
|
19 |
+
|
20 |
+
@st.cache_resource
|
21 |
+
def init_data():
|
22 |
+
return db.event_urls.find(filter={"class":"EventDetail", "final":True}, projection={"url":1,"base_url_id":1,"cleaned_html":1, "data":1})
|
23 |
+
|
24 |
+
|
25 |
+
if "title_extractor" not in st.session_state:
|
26 |
+
st.session_state["title_extractor"] = TitleExtractor()
|
27 |
+
if "description_extractor" not in st.session_state:
|
28 |
+
st.session_state["description_extractor"] = DescriptionExtractor()
|
29 |
+
if SessionStateConfig.ZERO_SHOT_CLASSIFIER not in st.session_state:
|
30 |
+
st.session_state[SessionStateConfig.ZERO_SHOT_CLASSIFIER] = ZeroShotClassifier()
|
31 |
+
if SessionStateConfig.GLINER_HANDLER not in st.session_state:
|
32 |
+
st.session_state[SessionStateConfig.GLINER_HANDLER] = GlinerHandler()
|
33 |
+
|
34 |
+
db = init_connection()
|
35 |
+
data = init_data()
|
36 |
+
element = next(data,None)
|
37 |
+
|
38 |
+
|
39 |
+
st.subheader("Bereinigtes HTML")
|
40 |
+
st.write(element["url"])
|
41 |
+
st.components.v1.html(element["cleaned_html"], height=500, scrolling=True)
|
42 |
+
|
43 |
+
start_pipeline = st.button("Starte Pipeline")
|
44 |
+
if element:
|
45 |
+
title_extractor = st.session_state.title_extractor
|
46 |
+
description_extractor = st.session_state.description_extractor
|
47 |
+
classifier = st.session_state[SessionStateConfig.ZERO_SHOT_CLASSIFIER]
|
48 |
+
gliner_handler = st.session_state.gliner_handler
|
49 |
+
if start_pipeline:
|
50 |
+
html = element["cleaned_html"]
|
51 |
+
md = convert_html_to_md(html)
|
52 |
+
with st.expander("Markdown"):
|
53 |
+
with st.container(border=True, height=400):
|
54 |
+
st.markdown(md)
|
55 |
+
with st.expander("Markdown Code"):
|
56 |
+
with st.container(height=400):
|
57 |
+
st.code(md)
|
58 |
+
|
59 |
+
cleaned_md = remove_boilerplate(md)
|
60 |
+
st.info("Remove boilerplate with GPT API")
|
61 |
+
with st.expander("Gekürztes Markdown"):
|
62 |
+
with st.container(border=True, height=400):
|
63 |
+
st.markdown(cleaned_md)
|
64 |
+
|
65 |
+
normalized_md = normalize_data(cleaned_md)
|
66 |
+
with st.expander("Normalisiertes Markdown"):
|
67 |
+
with st.container(border=True, height=400):
|
68 |
+
st.markdown(normalized_md)
|
69 |
+
|
70 |
+
|
71 |
+
text = normalized_md
|
72 |
+
analyzer = MarkdownAnalyzer(text)
|
73 |
+
results = analyzer.identify_all()["block_elements"]
|
74 |
+
table_data = [{"Class": r.__class__.__name__, "Markdown": r.markdown} for r in results]
|
75 |
+
with st.expander("Markdown Elemente"):
|
76 |
+
st.table(table_data)
|
77 |
+
|
78 |
+
with st.expander("Markdown Segmente"):
|
79 |
+
segments = analyzer.segmentation()
|
80 |
+
for s in segments:
|
81 |
+
with st.container(border=True):
|
82 |
+
for e in s:
|
83 |
+
st.markdown(e.markdown)
|
84 |
+
|
85 |
+
extracted_event = Event()
|
86 |
+
|
87 |
+
st.info("Extracting title...")
|
88 |
+
extracted_event.title = title_extractor.extract_title(cleaned_md)
|
89 |
+
|
90 |
+
# extracted_event.categories = ZeroShotClassifier().classify(text, CategoryMode())
|
91 |
+
extracted_event.categories = []
|
92 |
+
st.info("Extracting Categories...")
|
93 |
+
family_category = [cat.label for cat in classifier.classify(text,CustomMode(["Kinder_und_Familie","Adults_only"],"Die Veranstaltung ist für {}")) if cat.score >= 0.8]
|
94 |
+
topic_category = [classifier.classify(text,CustomMode(
|
95 |
+
["Kunst","Kultur", "Musik", "Sport", "Bildung", "Tanz", "Wissenschaft", "Unterhaltung", "Gesundheit", "Wellness", "Business", "Politik","Religion"],
|
96 |
+
"In der Veranstaltung geht es um {}"))[0].label]
|
97 |
+
type_category = [classifier.classify(text,CustomMode(
|
98 |
+
["Oper", "Theater", "Konzert", "Gottesdienst", "Ausstellung", "Museum", "Planetarium", "Führung", "Film", "Vortrag", "Show", "Turnier", "Wettkampf", "Markt", "Feier", "Party"],
|
99 |
+
"Die Art der Veranstaltung ist {}"))[0].label]
|
100 |
+
|
101 |
+
extracted_event.categories.extend(family_category)
|
102 |
+
extracted_event.categories.extend(topic_category)
|
103 |
+
extracted_event.categories.extend(type_category)
|
104 |
+
|
105 |
+
st.info("Extracting Organizer and Location...")
|
106 |
+
entities = gliner_handler.extract_entities(text, ["EVENT_ORGANIZER","EVENT_LOCATION_NAME","EVENT_ADDRESS"])
|
107 |
+
extracted_event.organizers = list(set([item["text"] for item in entities if item["label"] == "EVENT_ORGANIZER"]))
|
108 |
+
extracted_event.locations = list(set([item["text"] for item in entities if item["label"] == "EVENT_LOCATION_NAME"]))
|
109 |
+
extracted_event.address = list(set([item["text"] for item in entities if item["label"] == "EVENT_ADDRESS"]))
|
110 |
+
st.info("Extracting Dates and Times...")
|
111 |
+
date_entities = gliner_handler.extract_entities(text, ["SINGLE_DATE", "DATE_RANGE", "START_TIME","END_TIME", "ADMITTANCE_TIME"])
|
112 |
+
st.write(date_entities)
|
113 |
+
st.info("Extracting Price Information...")
|
114 |
+
price_entities = gliner_handler.extract_entities(text, ["KOSTEN"])
|
115 |
+
extracted_event.prices = list(set([item["text"] for item in price_entities if any(char.isdigit() for char in item["text"])]))
|
116 |
+
|
117 |
+
st.info("Extracting Description...")
|
118 |
+
extracted_event.description = description_extractor.extract_description(text, extracted_event.title)
|
119 |
+
|
120 |
+
event_data = []
|
121 |
+
event_data.append({'Field': 'Title', 'Value': extracted_event.title})
|
122 |
+
event_data.append({'Field': 'Categories', 'Value': ", ".join(extracted_event.categories)})
|
123 |
+
event_data.append({'Field': 'Organizers', 'Value': ", ".join(extracted_event.organizers)})
|
124 |
+
event_data.append({'Field': 'Locations', 'Value': ", ".join(extracted_event.locations)})
|
125 |
+
event_data.append({'Field': 'Address', 'Value': ", ".join(extracted_event.address)})
|
126 |
+
event_data.append({'Field': 'Prices', 'Value': ", ".join(extracted_event.prices)})
|
127 |
+
|
128 |
+
|
129 |
+
st.subheader("Extrahierte Daten")
|
130 |
+
st.table(event_data)
|
131 |
+
st.write("Event Description:")
|
132 |
+
with st.container(border=True, height=400):
|
133 |
+
st.markdown(extracted_event.description)
|
134 |
+
|
135 |
+
|
136 |
+
|
pages/TEST.py
DELETED
@@ -1,2 +0,0 @@
|
|
1 |
-
import streamlit as st
|
2 |
-
st.write("Hello world")
|
|
|
|
|
|
requirements.txt
CHANGED
@@ -1 +1,23 @@
|
|
1 |
-
streamlit
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
streamlit
|
2 |
+
streamlit-nested-layout
|
3 |
+
html2text
|
4 |
+
httpx
|
5 |
+
bs4
|
6 |
+
sumy
|
7 |
+
gliner
|
8 |
+
markdownify
|
9 |
+
google-maps-places
|
10 |
+
openai
|
11 |
+
dateparser
|
12 |
+
lxml_html_clean
|
13 |
+
pandas
|
14 |
+
pymongo
|
15 |
+
absl-py
|
16 |
+
dotenv
|
17 |
+
transformers
|
18 |
+
|
19 |
+
|
20 |
+
|
21 |
+
|
22 |
+
|
23 |
+
|
src/__init__.py
ADDED
File without changes
|
src/configuration/__init__.py
ADDED
File without changes
|
src/configuration/config.py
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# streamlit session state model keys
|
2 |
+
|
3 |
+
class SessionStateConfig:
|
4 |
+
ZERO_SHOT_CLASSIFIER = "zero_shot_classifier"
|
5 |
+
SUMY_SUMMARIZER = "sumy_summarizer"
|
6 |
+
GLINER_HANDLER = "gliner_handler"
|
7 |
+
QWEN_LLM_HANDLER = "qwen_llm_handler"
|
src/crawler/CrawlerV2.py
ADDED
@@ -0,0 +1,215 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gzip
|
2 |
+
import sys
|
3 |
+
|
4 |
+
import httpx
|
5 |
+
from urllib.parse import urljoin
|
6 |
+
|
7 |
+
from src.crawler.utils.regEx import PATTERNS
|
8 |
+
from src.crawler.utils.keywords import KEYWORDS
|
9 |
+
from src.crawler.crawler_service import *
|
10 |
+
from urllib.robotparser import RobotFileParser
|
11 |
+
|
12 |
+
URL_KEYWORDS = [
|
13 |
+
"veranstaltung", "event", "kalender", "kunst", "kultur",
|
14 |
+
"freizeit", "termine",
|
15 |
+
"happenings", "ausgehen", "aktivitäten", "aktivitaeten", "programm",
|
16 |
+
"wochenendtipps", "party", "festivals", "konzerte", "musik",
|
17 |
+
"shows", "theater", "veranstaltungskalender", "ausstellungen", "feste", "spielplan", "veranstaltungsplan"
|
18 |
+
]
|
19 |
+
|
20 |
+
|
21 |
+
class Crawler:
|
22 |
+
sys.path.append("..")
|
23 |
+
# filter variables
|
24 |
+
keywords = KEYWORDS
|
25 |
+
url_patterns = PATTERNS
|
26 |
+
|
27 |
+
def __init__(self, url: str, url_type: str, depth: int):
|
28 |
+
|
29 |
+
self.visited_urls = set()
|
30 |
+
self.excluded_urls = set()
|
31 |
+
self.excluded_urls.update(set(get_disallowed_urls(url)))
|
32 |
+
self.url_type = url_type
|
33 |
+
# set tupel of url and max depth to crawl
|
34 |
+
self.queue = [(url, depth)]
|
35 |
+
self.domain = urlparse(url).netloc
|
36 |
+
self.sitemaps_urls = get_sitemaps(url)
|
37 |
+
print(self.url_type)
|
38 |
+
print(f"Crawler startet for {url}")
|
39 |
+
|
40 |
+
|
41 |
+
def crawl(self):
|
42 |
+
# Loop through the URLs in the queue until it is empty
|
43 |
+
if self.sitemaps_urls:
|
44 |
+
print("Sitemaps Crawler startet...")
|
45 |
+
for url in self.sitemaps_urls:
|
46 |
+
if self.include_url(url):
|
47 |
+
print("include URL: ", url)
|
48 |
+
self.visited_urls.add(url)
|
49 |
+
else:
|
50 |
+
print("Crawler startet...")
|
51 |
+
while self.queue:
|
52 |
+
try:
|
53 |
+
# get the next URL to crawl
|
54 |
+
url_tupels = self.queue.pop(0)
|
55 |
+
current_url = url_tupels[0]
|
56 |
+
depth = url_tupels[1]
|
57 |
+
# access = ask_robots(current_url, "*")
|
58 |
+
access = True
|
59 |
+
# make request
|
60 |
+
if access and depth > 0:
|
61 |
+
response = requests.get(current_url)
|
62 |
+
if response.status_code >= 400:
|
63 |
+
print(f"Skipping {current_url} with status code {response.status_code}")
|
64 |
+
continue
|
65 |
+
page_content = response.content
|
66 |
+
|
67 |
+
# Parse the HTML content and extract links to other pages
|
68 |
+
soup = BeautifulSoup(page_content, "lxml")
|
69 |
+
urls_to_crawl = self.find_urls(soup, current_url)
|
70 |
+
urls_to_crawl_tupels = []
|
71 |
+
for url in urls_to_crawl:
|
72 |
+
if self.include_url(url):
|
73 |
+
urls_to_crawl_tupels.append((url, depth - 1))
|
74 |
+
|
75 |
+
# Add the new URLs to the queue and mark the current URL as visited
|
76 |
+
self.queue.extend(urls_to_crawl_tupels)
|
77 |
+
|
78 |
+
|
79 |
+
print(f"Crawled {current_url} and found {len(urls_to_crawl)} new URLs to crawl")
|
80 |
+
else:
|
81 |
+
print("access denied for ",current_url )
|
82 |
+
except Exception as e:
|
83 |
+
print("Exception:", e)
|
84 |
+
|
85 |
+
self.visited_urls.add(current_url)
|
86 |
+
if current_url in self.queue:
|
87 |
+
self.queue.remove(current_url)
|
88 |
+
print("Done. Found ", len(self.visited_urls), " Urls")
|
89 |
+
return self.visited_urls
|
90 |
+
|
91 |
+
def find_urls(self, soup: BeautifulSoup, current_url: str):
|
92 |
+
# get all links from page content
|
93 |
+
links = soup.find_all("a", href=True)
|
94 |
+
urls_to_crawl = set()
|
95 |
+
for link in links:
|
96 |
+
href = link["href"]
|
97 |
+
url = urljoin(current_url, href)
|
98 |
+
url = urlparse(url)._replace(query="", fragment="").geturl()
|
99 |
+
urls_to_crawl.add(url)
|
100 |
+
return urls_to_crawl
|
101 |
+
|
102 |
+
|
103 |
+
def include_url(self,url) -> bool:
|
104 |
+
|
105 |
+
if urlparse(url).netloc.lower() != self.domain.lower() \
|
106 |
+
or url in self.visited_urls \
|
107 |
+
or not check_regex(url, self.url_patterns)\
|
108 |
+
or url in self.queue\
|
109 |
+
or url in self.excluded_urls:
|
110 |
+
return False
|
111 |
+
else:
|
112 |
+
print("Checking ", url)
|
113 |
+
# if self.url_type == "city":
|
114 |
+
if any(keyword in url for keyword in URL_KEYWORDS):
|
115 |
+
print("Found Event URL:", url)
|
116 |
+
return True
|
117 |
+
else:
|
118 |
+
self.excluded_urls.add(url)
|
119 |
+
return False
|
120 |
+
# else:
|
121 |
+
# # if ask_robots(url,"*"):
|
122 |
+
# if True:
|
123 |
+
# response = requests.get(url)
|
124 |
+
# if response.status_code >= 400:
|
125 |
+
# self.excluded_urls.add(url)
|
126 |
+
# print(f"Skipping {url} with status code {response.status_code}")
|
127 |
+
# return False
|
128 |
+
# else:
|
129 |
+
# page_content = response.content
|
130 |
+
# # Parse the HTML content and extract links to other pages
|
131 |
+
# soup = BeautifulSoup(page_content, "html.parser")
|
132 |
+
# # remove navigation elements
|
133 |
+
# for nav in soup.find_all('nav'):
|
134 |
+
# nav.decompose()
|
135 |
+
#
|
136 |
+
# # Step 2: Remove elements with "navigation" or "menu" in the id or class attributes
|
137 |
+
#
|
138 |
+
# nav_elements= []
|
139 |
+
# nav_elements.extend(soup.find_all(id=re.compile(r'.*navigation.*')))
|
140 |
+
# nav_elements.extend(soup.find_all(id=re.compile(r'.*menu.*')))
|
141 |
+
# nav_elements.extend(soup.find_all(class_=re.compile(r'.*navigation.*')))
|
142 |
+
# nav_elements.extend(soup.find_all(class_=re.compile(r'.*menu.*')))
|
143 |
+
#
|
144 |
+
# print(len(nav_elements))
|
145 |
+
# for elem in nav_elements:
|
146 |
+
# if elem:
|
147 |
+
# elem.decompose()
|
148 |
+
#
|
149 |
+
# content = get_page_content(soup)
|
150 |
+
# print("searching content for keywords...")
|
151 |
+
# if check_keywords(content, self.keywords):
|
152 |
+
# print("Found Keyword in ", url)
|
153 |
+
# return True
|
154 |
+
# else:
|
155 |
+
# self.excluded_urls.add(url)
|
156 |
+
# return False
|
157 |
+
|
158 |
+
|
159 |
+
|
160 |
+
def get_sitemaps(url):
|
161 |
+
url_parsed = urlparse(url)
|
162 |
+
url_robots_txt = url_parsed.scheme + '://' + url_parsed.netloc + '/robots.txt'
|
163 |
+
sitemaps = set()
|
164 |
+
all_urls = set()
|
165 |
+
|
166 |
+
robotParse = urllib.robotparser.RobotFileParser()
|
167 |
+
robotParse.set_url(url_robots_txt)
|
168 |
+
try:
|
169 |
+
robotParse.read()
|
170 |
+
robot_sitemaps = robotParse.site_maps()
|
171 |
+
if robot_sitemaps:
|
172 |
+
sitemaps.update(robot_sitemaps)
|
173 |
+
else:
|
174 |
+
sitemaps.add(url_parsed.scheme + "://" + url_parsed.netloc + "/sitemap.xml")
|
175 |
+
sitemaps.add(url_parsed.scheme + "://" + url_parsed.netloc + "/sitemaps.xml")
|
176 |
+
|
177 |
+
|
178 |
+
for sitemap in sitemaps:
|
179 |
+
print("Parsing sitemap:", sitemap)
|
180 |
+
sitemap_urls = get_urls_from_sitemap(sitemap, set())
|
181 |
+
all_urls.update(sitemap_urls)
|
182 |
+
|
183 |
+
print("Total urls collected from sitemaps: ", len(all_urls))
|
184 |
+
except Exception as e:
|
185 |
+
print("Exception while parsing sitemap from ", url, ":", e)
|
186 |
+
return all_urls
|
187 |
+
|
188 |
+
def get_urls_from_sitemap(sitemap, urls):
|
189 |
+
print("Getting URLs from sitemap:", sitemap)
|
190 |
+
try:
|
191 |
+
response = httpx.get(sitemap)
|
192 |
+
if response.status_code == httpx.codes.OK:
|
193 |
+
# Prüfen, ob die Sitemap eine gezippte Datei ist
|
194 |
+
content_type = response.headers.get('Content-Type', '')
|
195 |
+
if 'gzip' in content_type or sitemap.endswith('.gz'):
|
196 |
+
# Falls gezippt, entpacken und lesen
|
197 |
+
decompressed_content = gzip.decompress(response.content)
|
198 |
+
soup = BeautifulSoup(decompressed_content, 'lxml')
|
199 |
+
else:
|
200 |
+
# Falls nicht gezippt, direkt parsen
|
201 |
+
soup = BeautifulSoup(response.content, 'lxml')
|
202 |
+
|
203 |
+
# URLs aus der Sitemap extrahieren
|
204 |
+
locs = soup.find_all("loc")
|
205 |
+
for loc in locs:
|
206 |
+
url = loc.get_text()
|
207 |
+
if "sitemap" in url:
|
208 |
+
# Rekursiv aufrufen, falls es eine Unter-Sitemap ist
|
209 |
+
urls.update(get_urls_from_sitemap(url, urls))
|
210 |
+
else:
|
211 |
+
urls.add(url)
|
212 |
+
|
213 |
+
except Exception as e:
|
214 |
+
print("Exception while resolving sitemap:", sitemap, "-", e)
|
215 |
+
return urls
|
src/crawler/__init__.py
ADDED
File without changes
|
src/crawler/crawler_service.py
ADDED
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import urllib
|
2 |
+
import re
|
3 |
+
import copy
|
4 |
+
from bs4 import BeautifulSoup
|
5 |
+
from urllib.parse import urlparse
|
6 |
+
from urllib.robotparser import RobotFileParser
|
7 |
+
from pathlib import Path
|
8 |
+
import requests
|
9 |
+
|
10 |
+
|
11 |
+
root_path = Path.cwd()
|
12 |
+
html_filter = ['header', 'footer', 'svg', 'img', 'nav', 'script']
|
13 |
+
|
14 |
+
# check if crawler is allowed to crawl url
|
15 |
+
def ask_robots(url: str, useragent="*") -> bool:
|
16 |
+
try:
|
17 |
+
url_parsed = urlparse(url)
|
18 |
+
url_robots_txt = url_parsed.scheme + '://' + url_parsed.netloc + '/robots.txt'
|
19 |
+
print("robots.txt: ", url_robots_txt)
|
20 |
+
robotParse = urllib.robotparser.RobotFileParser()
|
21 |
+
robotParse.set_url(url_robots_txt)
|
22 |
+
robotParse.read()
|
23 |
+
|
24 |
+
print("Ask access to ", url)
|
25 |
+
return robotParse.can_fetch('*', url)
|
26 |
+
except Exception as e:
|
27 |
+
print("Ask Robots :", e)
|
28 |
+
|
29 |
+
|
30 |
+
def get_disallowed_urls(url, user_agent="*"):
|
31 |
+
"""
|
32 |
+
Gibt alle disallowed URLs für den angegebenen User-Agent aus der robots.txt zurück.
|
33 |
+
|
34 |
+
:param robots_url: Die URL zur robots.txt-Datei (z. B. "https://example.com/robots.txt").
|
35 |
+
:param user_agent: Der User-Agent, für den die Regeln geprüft werden (Standard: "*").
|
36 |
+
:return: Eine Liste der disallowed URLs.
|
37 |
+
"""
|
38 |
+
# robots.txt Parser initialisieren
|
39 |
+
url_parsed = urlparse(url)
|
40 |
+
url_robots_txt = url_parsed.scheme + '://' + url_parsed.netloc + '/robots.txt'
|
41 |
+
rp = urllib.robotparser.RobotFileParser()
|
42 |
+
rp.set_url(url_robots_txt)
|
43 |
+
rp.read()
|
44 |
+
|
45 |
+
# Liste der disallowed Pfade initialisieren
|
46 |
+
disallowed_paths = []
|
47 |
+
|
48 |
+
# robots.txt-Datei als Text herunterladen
|
49 |
+
response = requests.get(url_robots_txt)
|
50 |
+
if response.status_code == 200:
|
51 |
+
# Parsen der robots.txt
|
52 |
+
lines = response.text.splitlines()
|
53 |
+
current_user_agent = None
|
54 |
+
for line in lines:
|
55 |
+
# Leerzeichen und Kommentare ignorieren
|
56 |
+
line = line.strip()
|
57 |
+
if not line or line.startswith("#"):
|
58 |
+
continue
|
59 |
+
|
60 |
+
# User-Agent-Zeilen erkennen
|
61 |
+
if line.lower().startswith("user-agent"):
|
62 |
+
current_user_agent = line.split(":")[1].strip()
|
63 |
+
|
64 |
+
# Disallow-Regeln erkennen
|
65 |
+
elif line.lower().startswith("disallow") and current_user_agent == user_agent:
|
66 |
+
disallow_path = line.split(":")[1].strip()
|
67 |
+
if disallow_path:
|
68 |
+
disallowed_paths.append(disallow_path)
|
69 |
+
|
70 |
+
# Basis-URL extrahieren
|
71 |
+
base_url = url_robots_txt.rsplit("/", 1)[0]
|
72 |
+
|
73 |
+
# Vollständige URLs zurückgeben
|
74 |
+
disallowed_urls = [base_url + path for path in disallowed_paths]
|
75 |
+
return disallowed_urls
|
76 |
+
|
77 |
+
|
78 |
+
# check if the url matches the regEx pattern, exclude it from crawler if it does
|
79 |
+
def check_regex(url: str, url_patterns: dict) -> bool:
|
80 |
+
for pattern in url_patterns:
|
81 |
+
if pattern.match(url):
|
82 |
+
return False
|
83 |
+
return True
|
84 |
+
|
85 |
+
|
86 |
+
# exclude url if website contains no keyword
|
87 |
+
def check_keywords(content: str, keywords: dict) -> bool:
|
88 |
+
for word in keywords:
|
89 |
+
if re.search(word, content, flags=re.IGNORECASE):
|
90 |
+
return True
|
91 |
+
return False
|
92 |
+
|
93 |
+
# get only the content of the page without html tags
|
94 |
+
def get_page_content(soup: BeautifulSoup):
|
95 |
+
soup_temp = copy.copy(soup)
|
96 |
+
body = soup_temp.find('body')
|
97 |
+
if body is None:
|
98 |
+
return
|
99 |
+
for tag in html_filter:
|
100 |
+
for el in body.find_all(tag):
|
101 |
+
el.decompose()
|
102 |
+
return prettify_content(body.text)
|
103 |
+
|
104 |
+
|
105 |
+
def prettify_content(content: str):
|
106 |
+
return re.sub(r'\n\s*\n', '\n', content)
|
src/crawler/maps_api.py
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from dataclasses import fields
|
2 |
+
import streamlit as st
|
3 |
+
from google.maps import places_v1
|
4 |
+
import os
|
5 |
+
from dotenv import load_dotenv
|
6 |
+
|
7 |
+
|
8 |
+
def get_maps_results(query, location):
|
9 |
+
# Create a client
|
10 |
+
load_dotenv()
|
11 |
+
client = places_v1.PlacesClient(client_options={"api_key": os.getenv("GOOGLE_MAPS_API_KEY")})
|
12 |
+
|
13 |
+
# Initialize request argument(s)
|
14 |
+
request = places_v1.SearchTextRequest(
|
15 |
+
text_query=f"{query} in {location}",
|
16 |
+
included_type=query
|
17 |
+
)
|
18 |
+
|
19 |
+
fieldMask = "places.displayName,places.websiteUri,places.formattedAddress,places.types"
|
20 |
+
# Make the request
|
21 |
+
response = client.search_text(request=request, metadata=[("x-goog-fieldmask",fieldMask)])
|
22 |
+
return response.places
|
23 |
+
|
src/crawler/serp_maps.py
ADDED
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from src.configuration.config import SERP_API_KEY
|
2 |
+
from serpapi import GoogleSearch
|
3 |
+
|
4 |
+
# maps type ids
|
5 |
+
relevant_locations = [
|
6 |
+
"art_gallery",
|
7 |
+
"auditorium",
|
8 |
+
"museum",
|
9 |
+
"performing_arts_theater",
|
10 |
+
"amphitheatre",
|
11 |
+
"amphitheatre",
|
12 |
+
"amusement_center",
|
13 |
+
"amusement_park",
|
14 |
+
"banquet_hall",
|
15 |
+
"childrens_camp",
|
16 |
+
"comedy_club",
|
17 |
+
"community_center",
|
18 |
+
"concert_hall",
|
19 |
+
"convention_center",
|
20 |
+
"cultural_center",
|
21 |
+
"dance_hall",
|
22 |
+
"event_venue",
|
23 |
+
"karaoke",
|
24 |
+
"night_club",
|
25 |
+
"opera_house",
|
26 |
+
"philharmonic_hall",
|
27 |
+
"planetarium",
|
28 |
+
"library",
|
29 |
+
"church",
|
30 |
+
"hindu_temple",
|
31 |
+
"mosque",
|
32 |
+
"synagogue"
|
33 |
+
]
|
34 |
+
|
35 |
+
|
36 |
+
params = {
|
37 |
+
"engine": "google_maps",
|
38 |
+
"q": "",
|
39 |
+
"type": "search",
|
40 |
+
"api_key": SERP_API_KEY ,
|
41 |
+
# "ll": "@49.4540304,11.101698,14z" # coordinates for Nuremberg with 15 zoom in
|
42 |
+
}
|
43 |
+
|
44 |
+
|
45 |
+
def get_maps_results(search_query ):
|
46 |
+
results = []
|
47 |
+
|
48 |
+
params["q"] = search_query
|
49 |
+
search = GoogleSearch(params)
|
50 |
+
search_dict = search.get_dict()
|
51 |
+
if "local_results" not in search_dict:
|
52 |
+
return results
|
53 |
+
local_results = search_dict["local_results"]
|
54 |
+
|
55 |
+
for location in local_results:
|
56 |
+
if "website" in location and location["website"] not in results:
|
57 |
+
|
58 |
+
results.append(location["website"])
|
59 |
+
return results
|
60 |
+
|
61 |
+
|
62 |
+
# print(get_maps_results("Konzerte Nuernberg"))
|
src/crawler/serp_search.py
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from serpapi import GoogleSearch
|
2 |
+
from urllib.parse import urlparse
|
3 |
+
from src.configuration.config import SERP_API_KEY
|
4 |
+
|
5 |
+
params = {
|
6 |
+
"q": "",
|
7 |
+
"location": "Nuremberg, Bavaria, Germany",
|
8 |
+
"hl": "en",
|
9 |
+
"gl": "us",
|
10 |
+
"google_domain": "google.com",
|
11 |
+
"api_key": SERP_API_KEY
|
12 |
+
}
|
13 |
+
|
14 |
+
def get_search_results(keywords):
|
15 |
+
results = []
|
16 |
+
for keyword in keywords:
|
17 |
+
params["q"] = keyword + " Veranstaltungen"
|
18 |
+
search = GoogleSearch(params)
|
19 |
+
organic_results = search.get_dict().get("organic_results")
|
20 |
+
if organic_results is not None:
|
21 |
+
for result in organic_results:
|
22 |
+
append_url = True
|
23 |
+
base_url = urlparse(result["link"]).netloc
|
24 |
+
for r in results:
|
25 |
+
if base_url in r.url:
|
26 |
+
append_url = False
|
27 |
+
if append_url:
|
28 |
+
results.append( result["link"])
|
29 |
+
return results
|
src/crawler/utils/keywords.py
ADDED
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
KEYWORDS = [
|
2 |
+
"Art Show",
|
3 |
+
"Aufführung",
|
4 |
+
"Ausstellung",
|
5 |
+
"Award Ceremony",
|
6 |
+
"Beginn",
|
7 |
+
"Charity",
|
8 |
+
"Club"
|
9 |
+
"Clubnight",
|
10 |
+
"Concert",
|
11 |
+
"Conference",
|
12 |
+
"Convention",
|
13 |
+
"Einlass",
|
14 |
+
"Eintritt",
|
15 |
+
"Exhibition",
|
16 |
+
"Festival",
|
17 |
+
"Fundraiser",
|
18 |
+
"Gala",
|
19 |
+
"Konferenz",
|
20 |
+
"Konzert",
|
21 |
+
"Kunstausstellung",
|
22 |
+
"Launch",
|
23 |
+
"Lecture",
|
24 |
+
"Meeting",
|
25 |
+
"Messe",
|
26 |
+
"Networking",
|
27 |
+
"Netzwerken",
|
28 |
+
"Party",
|
29 |
+
"Performance",
|
30 |
+
"Präsentation",
|
31 |
+
"Preisverleihung",
|
32 |
+
"Presentation",
|
33 |
+
"Seminar",
|
34 |
+
"Spendenaktion",
|
35 |
+
"Symposium",
|
36 |
+
"Tagung",
|
37 |
+
"Trade Show",
|
38 |
+
"Treffen",
|
39 |
+
"Veranstaltung",
|
40 |
+
"Vortrag",
|
41 |
+
"Webinar",
|
42 |
+
"Wohltätigkeitsveranstaltung",
|
43 |
+
"Workshop",
|
44 |
+
"Event",
|
45 |
+
]
|
src/crawler/utils/maps_types.py
ADDED
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
MAPS_TYPES = [
|
2 |
+
"art_gallery",
|
3 |
+
"auditorium",
|
4 |
+
"museum",
|
5 |
+
"performing_arts_theater",
|
6 |
+
"amphitheatre",
|
7 |
+
"amphitheatre",
|
8 |
+
"childrens_camp",
|
9 |
+
"comedy_club",
|
10 |
+
"community_center",
|
11 |
+
"concert_hall",
|
12 |
+
"convention_center",
|
13 |
+
"cultural_center",
|
14 |
+
"dance_hall",
|
15 |
+
"karaoke",
|
16 |
+
"night_club",
|
17 |
+
"opera_house",
|
18 |
+
"philharmonic_hall",
|
19 |
+
"planetarium",
|
20 |
+
"library",
|
21 |
+
"church",
|
22 |
+
"hindu_temple",
|
23 |
+
"mosque",
|
24 |
+
"synagogue"
|
25 |
+
]
|
src/crawler/utils/regEx.py
ADDED
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import re
|
2 |
+
|
3 |
+
# define patterns to filter urls that should not be crawled
|
4 |
+
|
5 |
+
PATTERNS = [
|
6 |
+
re.compile(r'.*about us*.*', re.IGNORECASE),
|
7 |
+
re.compile(r'.*about*.*us.*', re.IGNORECASE),
|
8 |
+
re.compile(r'.*a(b|n)meld(en|ung)*.*', re.IGNORECASE),
|
9 |
+
re.compile(r'.*about.*', re.IGNORECASE),
|
10 |
+
re.compile(r'.*agb*.*', re.IGNORECASE),
|
11 |
+
re.compile(r'.*archiv*.*', re.IGNORECASE),
|
12 |
+
re.compile(r'.*aussteller*.*', re.IGNORECASE),
|
13 |
+
re.compile(r'.*auszeichnung*.*', re.IGNORECASE),
|
14 |
+
re.compile(r'.*barrierefrei*.*', re.IGNORECASE),
|
15 |
+
re.compile(r'.*bestellverfolgung*.*', re.IGNORECASE),
|
16 |
+
re.compile(r'.*bezahlung*.*',re.IGNORECASE),
|
17 |
+
re.compile(r'.*bilder*.*', re.IGNORECASE),
|
18 |
+
re.compile(r'.*cart*.*', re.IGNORECASE),
|
19 |
+
re.compile(r'.*checkout*.*', re.IGNORECASE),
|
20 |
+
re.compile(r'.*contact*.*', re.IGNORECASE),
|
21 |
+
re.compile(r'.*credit card*.*', re.IGNORECASE),
|
22 |
+
re.compile(r'^https?://(?:www\.)?.*\.(jpg|png|pdf)$'),
|
23 |
+
re.compile(r'.*datenschutz*.*', re.IGNORECASE),
|
24 |
+
re.compile(r'.*debit*.*', re.IGNORECASE),
|
25 |
+
re.compile(r'.*download*.*', re.IGNORECASE),
|
26 |
+
re.compile(r'.*dsgvo.*', re.IGNORECASE),
|
27 |
+
re.compile(r'.*faq*.*', re.IGNORECASE),
|
28 |
+
re.compile(r'.*firmen(feiern|veranstaltungen).*', re.IGNORECASE),
|
29 |
+
re.compile(r'.*f(oe|ö)rder.*', re.IGNORECASE),
|
30 |
+
re.compile(r'.*for-rent*.*', re.IGNORECASE),
|
31 |
+
re.compile(r'.*fotos*.*', re.IGNORECASE),
|
32 |
+
re.compile(r'.*g(ae|ä)steliste*.*', re.IGNORECASE),
|
33 |
+
re.compile(r'.*galerie*.*', re.IGNORECASE),
|
34 |
+
re.compile(r'.*gallery*.*', re.IGNORECASE),
|
35 |
+
re.compile(r'.*geschaeftsbedingungen*.*', re.IGNORECASE),
|
36 |
+
re.compile(r'.*geschäftsbedingungen*.*', re.IGNORECASE),
|
37 |
+
re.compile(r'.*giropay*.*', re.IGNORECASE),
|
38 |
+
re.compile(r'.*guestlist*.*', re.IGNORECASE),
|
39 |
+
re.compile(r'.*hinweise*.*', re.IGNORECASE),
|
40 |
+
re.compile(r'.*impressum*.*', re.IGNORECASE),
|
41 |
+
re.compile(r'.*info.*', re.IGNORECASE),
|
42 |
+
re.compile(r'.*jobs*.*', re.IGNORECASE),
|
43 |
+
re.compile(r'.*karriere*.*', re.IGNORECASE),
|
44 |
+
re.compile(r'.*kasse*.*', re.IGNORECASE),
|
45 |
+
re.compile(r'.*klarna*.*', re.IGNORECASE),
|
46 |
+
re.compile(r'.*kontakt*.*', re.IGNORECASE),
|
47 |
+
re.compile(r'.*konto*.*', re.IGNORECASE),
|
48 |
+
re.compile(r'.*kreditkarte*.*', re.IGNORECASE),
|
49 |
+
re.compile(r'.*lastschrift*.*', re.IGNORECASE),
|
50 |
+
re.compile(r'.*landschaftsprogramm*.*', re.IGNORECASE),
|
51 |
+
re.compile(r'.*lieferung*.*', re.IGNORECASE),
|
52 |
+
re.compile(r'.*location.*', re.IGNORECASE),
|
53 |
+
re.compile(r'.*login*.*', re.IGNORECASE),
|
54 |
+
re.compile(r'.*mein-konto*.*', re.IGNORECASE),
|
55 |
+
re.compile(r'.*meine-bestellungen*.*', re.IGNORECASE),
|
56 |
+
re.compile(r'.*merch*.*', re.IGNORECASE),
|
57 |
+
re.compile(r'.*merkzettel*.*', re.IGNORECASE),
|
58 |
+
re.compile(r'.*mieten*.*', re.IGNORECASE),
|
59 |
+
re.compile(r'.*account*.*', re.IGNORECASE),
|
60 |
+
re.compile(r'.*orders*.*', re.IGNORECASE),
|
61 |
+
re.compile(r'.*news*.*', re.IGNORECASE),
|
62 |
+
re.compile(r'.*newsletter*.*', re.IGNORECASE),
|
63 |
+
re.compile(r'.*pay*.*', re.IGNORECASE),
|
64 |
+
re.compile(r'.*payment*.*', re.IGNORECASE),
|
65 |
+
re.compile(r'.*paypal*.*', re.IGNORECASE),
|
66 |
+
re.compile(r'.*personal*.*', re.IGNORECASE),
|
67 |
+
re.compile(r'.*photos*.*', re.IGNORECASE),
|
68 |
+
re.compile(r'.*pics*.*', re.IGNORECASE),
|
69 |
+
re.compile(r'.*pictures*.*', re.IGNORECASE),
|
70 |
+
re.compile(r'.*policy*.*', re.IGNORECASE),
|
71 |
+
re.compile(r'.*portfolio*.*', re.IGNORECASE),
|
72 |
+
re.compile(r'.*press*.*', re.IGNORECASE),
|
73 |
+
re.compile(r'.*pressemeldung*.*', re.IGNORECASE),
|
74 |
+
re.compile(r'.*pressemitteilungen*.*', re.IGNORECASE),
|
75 |
+
re.compile(r'.*privacy-policy*.*', re.IGNORECASE),
|
76 |
+
re.compile(r'.*produkt*.*', re.IGNORECASE),
|
77 |
+
re.compile(r'.*rathaus*.*', re.IGNORECASE),
|
78 |
+
re.compile(r'.*rechnung*.*', re.IGNORECASE),
|
79 |
+
re.compile(r'.*sepa*.*', re.IGNORECASE),
|
80 |
+
re.compile(r'.*shop*.*', re.IGNORECASE),
|
81 |
+
re.compile(r'.*signup*.*', re.IGNORECASE),
|
82 |
+
re.compile(r'.*sofort*.*', re.IGNORECASE),
|
83 |
+
re.compile(r'.*support*.*', re.IGNORECASE),
|
84 |
+
re.compile(r'.*terms-of-use*.*', re.IGNORECASE),
|
85 |
+
re.compile(r'.*twitter|facebook|instagram|tiktok*.*', re.IGNORECASE),
|
86 |
+
re.compile(r'.*(ue|ü)ber.?uns*.*', re.IGNORECASE),
|
87 |
+
re.compile(r'.*unterricht*.*', re.IGNORECASE),
|
88 |
+
re.compile(r'.*versand*.*', re.IGNORECASE),
|
89 |
+
re.compile(r'.*verbraucherschutz*.*', re.IGNORECASE),
|
90 |
+
re.compile(r'.*warenkorb*.*', re.IGNORECASE),
|
91 |
+
re.compile(r'.*wegbeschreibung*.*', re.IGNORECASE),
|
92 |
+
re.compile(r'.*widerrufsbelehrung*.*', re.IGNORECASE),
|
93 |
+
re.compile(r'.*wishlist*.*', re.IGNORECASE),
|
94 |
+
re.compile(r'.*zahlung*.*', re.IGNORECASE),
|
95 |
+
re.compile(r'.*.jpeg', re.IGNORECASE),
|
96 |
+
]
|
97 |
+
|
src/nlp/__init__.py
ADDED
File without changes
|
src/nlp/config.cfg
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
[nlp]
|
2 |
+
lang = "en"
|
3 |
+
pipeline = ["llm"]
|
4 |
+
|
5 |
+
[components]
|
6 |
+
|
7 |
+
[components.llm]
|
8 |
+
factory = "llm"
|
9 |
+
|
10 |
+
[components.llm.task]
|
11 |
+
@llm_tasks = "spacy.NER.v3"
|
12 |
+
labels = ["PERSON", "ORGANISATION", "LOCATION"]
|
13 |
+
|
14 |
+
[components.llm.model]
|
15 |
+
@llm_models = "spacy.Dolly.v1"
|
16 |
+
# For better performance, use dolly-v2-12b instead
|
17 |
+
name = "dolly-v2-12b"
|
src/nlp/data/ner.json
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
{"classes":["TITLE","STARTTIME","STARTDATE","ENDDATE","ENDTIME","LOCATION"],"annotations":[["Technik-Salon an der TIB: „FLY ROCKET FLY“\r\n==========================================\r\n\r\nÜber den Aufstieg und Fall des Raketenpioniers Lutz Kayser – ein Film- und Gesprächsabend mit dem Dokumentarfilmer Oliver Schwehm\r\n\r\nEin Start-Up aus dem Schwäbischen, 170 Millionen D-Mark Wagniskapital, ein privates Testgelände im afrikanischen Dschungel – so trat Lutz Kayser in den 1970 er-Jahren an, die Raumfahrt zu revolutionieren.\r\n\r\nWarum das gut hätten klappen können und schließlich doch scheiterte, schildert Oliver Schwehm in seiner bildreichen Dokumentation. Im Rahmen der Jubiläumstour zu Ehren von Lutz Kayser („50 Jahre OTRAG – Oribital Transport und Raketen AG“) machen Film und Regisseur am 05.12.2024 im Technik-Salon Station in der TIB in Hannover. Der Eintritt für die Veranstaltung [„FLY ROCKET FLY“](http://www.technik-salon.de/05.12.2024/fly-rocket-fly.html) ist frei (Spendenbox).\r\n\r\nMehr Informationen zum [Technik-Salon](http://www.technik-salon.de)\r\n\r\n**Wann?** 05.12.2024, 19:00-21:00\r\n\r\n**Wo?** Lesesaal im Marstallgebäude, TIB",{"entities":[[0,42,"TITLE"],[980,990,"STARTDATE"],[992,997,"STARTTIME"],[998,1003,"ENDTIME"],[1014,1047,"LOCATION"]]}]]}
|
src/nlp/data/ner/texts.json
ADDED
The diff for this file is too large to render.
See raw diff
|
|
src/nlp/data/test.txt
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Technik-Salon an der TIB: „FLY ROCKET FLY“
|
2 |
+
==========================================
|
3 |
+
|
4 |
+
Über den Aufstieg und Fall des Raketenpioniers Lutz Kayser – ein Film- und Gesprächsabend mit dem Dokumentarfilmer Oliver Schwehm
|
5 |
+
|
6 |
+
Ein Start-Up aus dem Schwäbischen, 170 Millionen D-Mark Wagniskapital, ein privates Testgelände im afrikanischen Dschungel – so trat Lutz Kayser in den 1970 er-Jahren an, die Raumfahrt zu revolutionieren.
|
7 |
+
|
8 |
+
Warum das gut hätten klappen können und schließlich doch scheiterte, schildert Oliver Schwehm in seiner bildreichen Dokumentation. Im Rahmen der Jubiläumstour zu Ehren von Lutz Kayser („50 Jahre OTRAG – Oribital Transport und Raketen AG“) machen Film und Regisseur am 05.12.2024 im Technik-Salon Station in der TIB in Hannover. Der Eintritt für die Veranstaltung [„FLY ROCKET FLY“](http://www.technik-salon.de/05.12.2024/fly-rocket-fly.html) ist frei (Spendenbox).
|
9 |
+
|
10 |
+
Mehr Informationen zum [Technik-Salon](http://www.technik-salon.de)
|
11 |
+
|
12 |
+
**Wann?** 05.12.2024, 19:00-21:00
|
13 |
+
|
14 |
+
**Wo?** Lesesaal im Marstallgebäude, TIB
|
src/nlp/dates_txt
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Die Reservierungen für das diesjährige Mainzer Weihnachtsdorf werden voraussichtlich am 10.01.2024 EVENT_DATE um 08:00 freigeschaltet.
|
2 |
+
Das Interkulturelle Fest wird am 08.09.2025 EVENT_DATE auf dem Domplatz gefeiert.**
|
3 |
+
Vom 17.10.2025 - 03.11.2025 findet das Mainzer Oktoberfest zum 18. Mal statt.
|
4 |
+
Nächster Termin: 13.09.2025 und 14.09.2025
|
src/nlp/event.jpg
ADDED
![]() |
src/nlp/experimental/__init__.py
ADDED
File without changes
|
src/nlp/experimental/annotations.json
ADDED
@@ -0,0 +1,241 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
[
|
2 |
+
[
|
3 |
+
"Im Rahmen der Jubiläumstour zu Ehren von Lutz Kayser (\"50 Jahre OTRAG – Oribital Transport und Raketen AG\") machen Film und Regisseur am 05.12.2024 im Technik-Salon Station in der TIB in Hannover.",
|
4 |
+
{
|
5 |
+
"entities": [
|
6 |
+
[
|
7 |
+
137,
|
8 |
+
147,
|
9 |
+
"EVENT_DATE"
|
10 |
+
]
|
11 |
+
]
|
12 |
+
}
|
13 |
+
],
|
14 |
+
[
|
15 |
+
"Wann? 05.12.2024, 19:00-21:00",
|
16 |
+
{
|
17 |
+
"entities": [
|
18 |
+
[
|
19 |
+
6,
|
20 |
+
16,
|
21 |
+
"EVENT_DATE"
|
22 |
+
]
|
23 |
+
]
|
24 |
+
}
|
25 |
+
],
|
26 |
+
[
|
27 |
+
"Der siebte Workshop Retrodigitalisierung findet am 20.03.2025 und 21.03.2025 bei ZB MED – Informationszentrum Lebenswissenschaften in Köln statt.",
|
28 |
+
{
|
29 |
+
"entities": [
|
30 |
+
[
|
31 |
+
51,
|
32 |
+
61,
|
33 |
+
"EVENT_DATE"
|
34 |
+
],
|
35 |
+
[
|
36 |
+
66,
|
37 |
+
76,
|
38 |
+
"EVENT_DATE"
|
39 |
+
]
|
40 |
+
]
|
41 |
+
}
|
42 |
+
],
|
43 |
+
[
|
44 |
+
"Wann? 20.03.2025 - 21.03.2025",
|
45 |
+
{
|
46 |
+
"entities": [
|
47 |
+
[
|
48 |
+
6,
|
49 |
+
29,
|
50 |
+
"EVENT_DATE_RANGE"
|
51 |
+
]
|
52 |
+
]
|
53 |
+
}
|
54 |
+
],
|
55 |
+
[
|
56 |
+
"Die 18. ACM International Conference on Web Search and Data Mining (WSDM 2025) wird vom 10.03.2025 - 14.03.2025 in Hannover stattfinden.",
|
57 |
+
{
|
58 |
+
"entities": [
|
59 |
+
[
|
60 |
+
88,
|
61 |
+
111,
|
62 |
+
"EVENT_DATE_RANGE"
|
63 |
+
]
|
64 |
+
]
|
65 |
+
}
|
66 |
+
],
|
67 |
+
[
|
68 |
+
"So. 08.12.2024 12:15 - 13:15 CET",
|
69 |
+
{
|
70 |
+
"entities": [
|
71 |
+
[
|
72 |
+
4,
|
73 |
+
14,
|
74 |
+
"EVENT_DATE"
|
75 |
+
]
|
76 |
+
]
|
77 |
+
}
|
78 |
+
],
|
79 |
+
[
|
80 |
+
"24.12.2025 um 16:00",
|
81 |
+
{
|
82 |
+
"entities": [
|
83 |
+
[
|
84 |
+
0,
|
85 |
+
10,
|
86 |
+
"EVENT_DATE"
|
87 |
+
]
|
88 |
+
]
|
89 |
+
}
|
90 |
+
],
|
91 |
+
[
|
92 |
+
"07.01.2025",
|
93 |
+
{
|
94 |
+
"entities": [
|
95 |
+
[
|
96 |
+
0,
|
97 |
+
10,
|
98 |
+
"EVENT_DATE"
|
99 |
+
]
|
100 |
+
]
|
101 |
+
}
|
102 |
+
],
|
103 |
+
[
|
104 |
+
"Am 01.07.2025",
|
105 |
+
{
|
106 |
+
"entities": [
|
107 |
+
[
|
108 |
+
3,
|
109 |
+
13,
|
110 |
+
"EVENT_DATE"
|
111 |
+
]
|
112 |
+
]
|
113 |
+
}
|
114 |
+
],
|
115 |
+
[
|
116 |
+
"Wann? 07.11.2024 - 09.03.2025",
|
117 |
+
{
|
118 |
+
"entities": [
|
119 |
+
[
|
120 |
+
6,
|
121 |
+
29,
|
122 |
+
"EVENT_DATE_RANGE"
|
123 |
+
]
|
124 |
+
]
|
125 |
+
}
|
126 |
+
],
|
127 |
+
[
|
128 |
+
"01.11.2024 - 09.03.2025",
|
129 |
+
{
|
130 |
+
"entities": [
|
131 |
+
[
|
132 |
+
0,
|
133 |
+
23,
|
134 |
+
"EVENT_DATE_RANGE"
|
135 |
+
]
|
136 |
+
]
|
137 |
+
}
|
138 |
+
],
|
139 |
+
[
|
140 |
+
"01.09.2025 - 26.12.2024",
|
141 |
+
{
|
142 |
+
"entities": [
|
143 |
+
[
|
144 |
+
0,
|
145 |
+
23,
|
146 |
+
"EVENT_DATE_RANGE"
|
147 |
+
]
|
148 |
+
]
|
149 |
+
}
|
150 |
+
],
|
151 |
+
[
|
152 |
+
"Premiere am 01.12.2024",
|
153 |
+
{
|
154 |
+
"entities": [
|
155 |
+
[
|
156 |
+
12,
|
157 |
+
22,
|
158 |
+
"EVENT_DATE"
|
159 |
+
]
|
160 |
+
]
|
161 |
+
}
|
162 |
+
],
|
163 |
+
[
|
164 |
+
"01.11.2025 ab 16:00",
|
165 |
+
{
|
166 |
+
"entities": [
|
167 |
+
[
|
168 |
+
0,
|
169 |
+
10,
|
170 |
+
"EVENT_DATE"
|
171 |
+
]
|
172 |
+
]
|
173 |
+
}
|
174 |
+
],
|
175 |
+
[
|
176 |
+
"15.01.2025 ab 18:00",
|
177 |
+
{
|
178 |
+
"entities": [
|
179 |
+
[
|
180 |
+
0,
|
181 |
+
10,
|
182 |
+
"EVENT_DATE"
|
183 |
+
]
|
184 |
+
]
|
185 |
+
}
|
186 |
+
],
|
187 |
+
[
|
188 |
+
"01.12.2025 ab 14:00-15:00",
|
189 |
+
{
|
190 |
+
"entities": [
|
191 |
+
[
|
192 |
+
0,
|
193 |
+
10,
|
194 |
+
"EVENT_DATE"
|
195 |
+
]
|
196 |
+
]
|
197 |
+
}
|
198 |
+
],
|
199 |
+
[
|
200 |
+
"18.01.2025 ab 11:00-18:00",
|
201 |
+
{
|
202 |
+
"entities": [
|
203 |
+
[
|
204 |
+
0,
|
205 |
+
10,
|
206 |
+
"EVENT_DATE"
|
207 |
+
]
|
208 |
+
]
|
209 |
+
}
|
210 |
+
],
|
211 |
+
[
|
212 |
+
"01.12.2024 – 08.12.2024",
|
213 |
+
{
|
214 |
+
"entities": [
|
215 |
+
[
|
216 |
+
0,
|
217 |
+
23,
|
218 |
+
"EVENT_DATE_RANGE"
|
219 |
+
]
|
220 |
+
]
|
221 |
+
}
|
222 |
+
],
|
223 |
+
[
|
224 |
+
"Freitag 16:00-20:00 / Samstag 11:00-20:00 / Sonntag 11:00-18:00",
|
225 |
+
{
|
226 |
+
"entities": []
|
227 |
+
}
|
228 |
+
],
|
229 |
+
[
|
230 |
+
"So, 15.12.2024 Beginn: 15:00 Einlass: 14:30",
|
231 |
+
{
|
232 |
+
"entities": [
|
233 |
+
[
|
234 |
+
4,
|
235 |
+
14,
|
236 |
+
"EVENT_DATE"
|
237 |
+
]
|
238 |
+
]
|
239 |
+
}
|
240 |
+
]
|
241 |
+
]
|
src/nlp/experimental/annotations_v1.json
ADDED
@@ -0,0 +1,430 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
[
|
2 |
+
[
|
3 |
+
"Im Rahmen der Jubiläumstour zu Ehren von Lutz Kayser („50 Jahre OTRAG – Oribital Transport und Raketen AG“) machen Film und Regisseur am 05.12.2024 im Technik-Salon Station in der TIB in Hannover.",
|
4 |
+
{
|
5 |
+
"entities": [
|
6 |
+
[
|
7 |
+
137,
|
8 |
+
147,
|
9 |
+
"DATE"
|
10 |
+
]
|
11 |
+
]
|
12 |
+
}
|
13 |
+
],
|
14 |
+
[
|
15 |
+
"Der siebte Workshop Retrodigitalisierung findet am 20.03.2025 und 21.03.2025 bei ZB MED – Informationszentrum Lebenswissenschaften in Köln statt.",
|
16 |
+
{
|
17 |
+
"entities": [
|
18 |
+
[
|
19 |
+
51,
|
20 |
+
61,
|
21 |
+
"START_DATE"
|
22 |
+
],
|
23 |
+
[
|
24 |
+
66,
|
25 |
+
76,
|
26 |
+
"END_DATE"
|
27 |
+
]
|
28 |
+
]
|
29 |
+
}
|
30 |
+
],
|
31 |
+
[
|
32 |
+
"Die 18. ACM International Conference on Web Search and Data Mining (WSDM 2025) wird vom 10.03.2025 - 14.03.2025 in Hannover stattfinden.",
|
33 |
+
{
|
34 |
+
"entities": [
|
35 |
+
[
|
36 |
+
88,
|
37 |
+
98,
|
38 |
+
"START_DATE"
|
39 |
+
],
|
40 |
+
[
|
41 |
+
101,
|
42 |
+
111,
|
43 |
+
"END_DATE"
|
44 |
+
]
|
45 |
+
]
|
46 |
+
}
|
47 |
+
],
|
48 |
+
[
|
49 |
+
"Die Infoveranstaltung für geistliche Mütter und Väter findet am 08.12.2024 im Christlichen Zentrum Darmstadt statt.",
|
50 |
+
{
|
51 |
+
"entities": [
|
52 |
+
[
|
53 |
+
64,
|
54 |
+
74,
|
55 |
+
"DATE"
|
56 |
+
]
|
57 |
+
]
|
58 |
+
}
|
59 |
+
],
|
60 |
+
[
|
61 |
+
"Erlebe einen besonderen Film-Gottesdienst am 24.12.2025 um 16:00.",
|
62 |
+
{
|
63 |
+
"entities": [
|
64 |
+
[
|
65 |
+
45,
|
66 |
+
55,
|
67 |
+
"DATE"
|
68 |
+
]
|
69 |
+
]
|
70 |
+
}
|
71 |
+
],
|
72 |
+
[
|
73 |
+
"Termin für öffentliche Besichtigung am 07.01.2025.",
|
74 |
+
{
|
75 |
+
"entities": [
|
76 |
+
[
|
77 |
+
39,
|
78 |
+
49,
|
79 |
+
"DATE"
|
80 |
+
]
|
81 |
+
]
|
82 |
+
}
|
83 |
+
],
|
84 |
+
[
|
85 |
+
"Cornelia Poletto Palazzo: Die Dinner-Show im Spiegelpalast findet vom 07.11.2024 bis 09.03.2025 statt.",
|
86 |
+
{
|
87 |
+
"entities": [
|
88 |
+
[
|
89 |
+
70,
|
90 |
+
80,
|
91 |
+
"START_DATE"
|
92 |
+
],
|
93 |
+
[
|
94 |
+
85,
|
95 |
+
95,
|
96 |
+
"END_DATE"
|
97 |
+
]
|
98 |
+
]
|
99 |
+
}
|
100 |
+
],
|
101 |
+
[
|
102 |
+
"Die Ausstellung \"Leonardo da Vinci – uomo universale\" läuft vom 01.09.2025 bis zum 26.12.2024 in Hamburg.",
|
103 |
+
{
|
104 |
+
"entities": [
|
105 |
+
[
|
106 |
+
64,
|
107 |
+
74,
|
108 |
+
"START_DATE"
|
109 |
+
],
|
110 |
+
[
|
111 |
+
83,
|
112 |
+
93,
|
113 |
+
"END_DATE"
|
114 |
+
]
|
115 |
+
]
|
116 |
+
}
|
117 |
+
],
|
118 |
+
[
|
119 |
+
"Liedernachmittag – Lieder von R. Schubert, R. Franz, A. Webern, H. Wolf. Vortrag im Museum August Kestner. Monika Abel, Sopran / Kathrin Isabelle Klein, Klavier. Termine 01.11.2025 ab 16:00.",
|
120 |
+
{
|
121 |
+
"entities": [
|
122 |
+
[
|
123 |
+
170,
|
124 |
+
180,
|
125 |
+
"START_DATE"
|
126 |
+
]
|
127 |
+
]
|
128 |
+
}
|
129 |
+
],
|
130 |
+
[
|
131 |
+
"Die goldene Stadt – Vortrag im Museum August Kestner mit Dr. Martin Hersch (München). Termine 15.01.2025 ab 18:00.",
|
132 |
+
{
|
133 |
+
"entities": [
|
134 |
+
[
|
135 |
+
94,
|
136 |
+
104,
|
137 |
+
"START_DATE"
|
138 |
+
]
|
139 |
+
]
|
140 |
+
}
|
141 |
+
],
|
142 |
+
[
|
143 |
+
"Stadtansichten – Erleben Sie bei einem Besuch der Sonderausstellung Städtetrip. Termine 01.12.2025 ab 14:00-15:00.",
|
144 |
+
{
|
145 |
+
"entities": [
|
146 |
+
[
|
147 |
+
88,
|
148 |
+
98,
|
149 |
+
"START_DATE"
|
150 |
+
]
|
151 |
+
]
|
152 |
+
}
|
153 |
+
],
|
154 |
+
[
|
155 |
+
"Finissage der Sonderausstellung Bartmann, Bier und Tafelzier. Steinzeug in der niederländischen Malerei. Mit Kuratorinnenführung. Termine 18.01.2025 ab 11:00-18:00.",
|
156 |
+
{
|
157 |
+
"entities": [
|
158 |
+
[
|
159 |
+
138,
|
160 |
+
148,
|
161 |
+
"START_DATE"
|
162 |
+
]
|
163 |
+
]
|
164 |
+
}
|
165 |
+
],
|
166 |
+
[
|
167 |
+
"Veranstaltungsinformationen 01.12.2024 – 08.12.2024. Nikolausmarkt im Kulturhof, Kulturzentrum Bottrop.",
|
168 |
+
{
|
169 |
+
"entities": [
|
170 |
+
[
|
171 |
+
28,
|
172 |
+
38,
|
173 |
+
"START_DATE"
|
174 |
+
],
|
175 |
+
[
|
176 |
+
41,
|
177 |
+
51,
|
178 |
+
"END_DATE"
|
179 |
+
]
|
180 |
+
]
|
181 |
+
}
|
182 |
+
],
|
183 |
+
[
|
184 |
+
"Verleihung Erlanger Theaterpreis. So. 8.12. Keine Anmeldung nötig. Theater in der Garage 19:00. Eintritt frei.",
|
185 |
+
{
|
186 |
+
"entities": []
|
187 |
+
}
|
188 |
+
],
|
189 |
+
[
|
190 |
+
"SILVESTER 2024 IM FOODKLUB – Feiern Sie mit uns ins neue Jahr mit einem exquisiten levantinischen Festbuffet! 48€ pro Person. Datum: 2024.",
|
191 |
+
{
|
192 |
+
"entities": []
|
193 |
+
}
|
194 |
+
],
|
195 |
+
[
|
196 |
+
"PIANOKLÄNGE & Herzgeschichten – Stiftung Gemeinsam für Halle. Termine 15.12.2024. Beginn 15:00, Einlass 14:30.",
|
197 |
+
{
|
198 |
+
"entities": [
|
199 |
+
[
|
200 |
+
70,
|
201 |
+
80,
|
202 |
+
"START_DATE"
|
203 |
+
]
|
204 |
+
]
|
205 |
+
}
|
206 |
+
],
|
207 |
+
[
|
208 |
+
"Zwischendrin – Beginn 17:00, Ende 19:00. Veranstaltungsort: Religionspädagogisches Institut im Heinrich-Fries-Haus. Termine 2024-25.",
|
209 |
+
{
|
210 |
+
"entities": []
|
211 |
+
}
|
212 |
+
],
|
213 |
+
[
|
214 |
+
"vom 02.12. – 19.12.2024, Dauer ca. 30 Minuten",
|
215 |
+
{
|
216 |
+
"entities": [
|
217 |
+
[
|
218 |
+
13,
|
219 |
+
23,
|
220 |
+
"END_DATE"
|
221 |
+
]
|
222 |
+
]
|
223 |
+
}
|
224 |
+
],
|
225 |
+
[
|
226 |
+
"01.01.2025*Donnerstag* 15.00 - 17.00 *Uhr*",
|
227 |
+
{
|
228 |
+
"entities": [
|
229 |
+
[
|
230 |
+
0,
|
231 |
+
10,
|
232 |
+
"DATE"
|
233 |
+
]
|
234 |
+
]
|
235 |
+
}
|
236 |
+
],
|
237 |
+
[
|
238 |
+
"Vom 30.11.2024 - 06.07.2025 präsentieren wir in unserer Sonderausstellung das Werk des Berliner Künstlers Alexej Tchernyi.",
|
239 |
+
{
|
240 |
+
"entities": [
|
241 |
+
[
|
242 |
+
4,
|
243 |
+
14,
|
244 |
+
"START_DATE"
|
245 |
+
],
|
246 |
+
[
|
247 |
+
17,
|
248 |
+
27,
|
249 |
+
"END_DATE"
|
250 |
+
]
|
251 |
+
]
|
252 |
+
}
|
253 |
+
],
|
254 |
+
[
|
255 |
+
"01.04.2025 *Montag* 15.00 - 16.00 *Uhr*",
|
256 |
+
{
|
257 |
+
"entities": [
|
258 |
+
[
|
259 |
+
0,
|
260 |
+
10,
|
261 |
+
"DATE"
|
262 |
+
]
|
263 |
+
]
|
264 |
+
}
|
265 |
+
],
|
266 |
+
[
|
267 |
+
"16.12.2024 **Beginn** 20:00-22:30",
|
268 |
+
{
|
269 |
+
"entities": [
|
270 |
+
[
|
271 |
+
0,
|
272 |
+
10,
|
273 |
+
"DATE"
|
274 |
+
]
|
275 |
+
]
|
276 |
+
}
|
277 |
+
],
|
278 |
+
[
|
279 |
+
"29.12.2024 **Beginn** 15:00-17:00",
|
280 |
+
{
|
281 |
+
"entities": [
|
282 |
+
[
|
283 |
+
0,
|
284 |
+
10,
|
285 |
+
"DATE"
|
286 |
+
]
|
287 |
+
]
|
288 |
+
}
|
289 |
+
],
|
290 |
+
[
|
291 |
+
"Die Höhner Weihnacht findet am Freitag, 20.12.2024, im Eurogress Aachen statt.",
|
292 |
+
{
|
293 |
+
"entities": [
|
294 |
+
[
|
295 |
+
40,
|
296 |
+
50,
|
297 |
+
"DATE"
|
298 |
+
]
|
299 |
+
]
|
300 |
+
}
|
301 |
+
],
|
302 |
+
[
|
303 |
+
"Feiere Weihnachten mit uns am 24.12.2024 um 16:00 in der Festhalle Durlach.",
|
304 |
+
{
|
305 |
+
"entities": [
|
306 |
+
[
|
307 |
+
30,
|
308 |
+
40,
|
309 |
+
"DATE"
|
310 |
+
]
|
311 |
+
]
|
312 |
+
}
|
313 |
+
],
|
314 |
+
[
|
315 |
+
"Der Alpha Kurs startet am 13.01.2025 und geht 12 Wochen lang.",
|
316 |
+
{
|
317 |
+
"entities": [
|
318 |
+
[
|
319 |
+
26,
|
320 |
+
36,
|
321 |
+
"DATE"
|
322 |
+
]
|
323 |
+
]
|
324 |
+
}
|
325 |
+
],
|
326 |
+
[
|
327 |
+
"Die Echo Night findet am 05.04.2025 statt.",
|
328 |
+
{
|
329 |
+
"entities": [
|
330 |
+
[
|
331 |
+
25,
|
332 |
+
35,
|
333 |
+
"DATE"
|
334 |
+
]
|
335 |
+
]
|
336 |
+
}
|
337 |
+
],
|
338 |
+
[
|
339 |
+
"Das Nachbarschaftsfest findet am 06.07.2025 und 07.07.2025 statt.",
|
340 |
+
{
|
341 |
+
"entities": [
|
342 |
+
[
|
343 |
+
33,
|
344 |
+
43,
|
345 |
+
"START_DATE"
|
346 |
+
],
|
347 |
+
[
|
348 |
+
48,
|
349 |
+
58,
|
350 |
+
"END_DATE"
|
351 |
+
]
|
352 |
+
]
|
353 |
+
}
|
354 |
+
],
|
355 |
+
[
|
356 |
+
"Die Einführung des neuen Leiters der Jugendkirche findet am 01.12.2024 um 18:00 statt.",
|
357 |
+
{
|
358 |
+
"entities": [
|
359 |
+
[
|
360 |
+
60,
|
361 |
+
70,
|
362 |
+
"DATE"
|
363 |
+
]
|
364 |
+
]
|
365 |
+
}
|
366 |
+
],
|
367 |
+
[
|
368 |
+
"Die Christuskapelle in Grötzingen ist vom 18.12.2023 bis 02.12.2024 täglich von 17:00-19:00 geöffnet.",
|
369 |
+
{
|
370 |
+
"entities": [
|
371 |
+
[
|
372 |
+
42,
|
373 |
+
52,
|
374 |
+
"START_DATE"
|
375 |
+
],
|
376 |
+
[
|
377 |
+
57,
|
378 |
+
67,
|
379 |
+
"END_DATE"
|
380 |
+
]
|
381 |
+
]
|
382 |
+
}
|
383 |
+
],
|
384 |
+
[
|
385 |
+
"Der Adventsmarkt in der Kapelle findet am 30.11.2024 von 14:00-19:00 statt.",
|
386 |
+
{
|
387 |
+
"entities": [
|
388 |
+
[
|
389 |
+
42,
|
390 |
+
52,
|
391 |
+
"DATE"
|
392 |
+
]
|
393 |
+
]
|
394 |
+
}
|
395 |
+
],
|
396 |
+
[
|
397 |
+
"Die 18. Koblenzer Literaturtage finden vom 22.03.2025 bis 04.05.2025 statt.",
|
398 |
+
{
|
399 |
+
"entities": [
|
400 |
+
[
|
401 |
+
43,
|
402 |
+
53,
|
403 |
+
"START_DATE"
|
404 |
+
],
|
405 |
+
[
|
406 |
+
58,
|
407 |
+
68,
|
408 |
+
"END_DATE"
|
409 |
+
]
|
410 |
+
]
|
411 |
+
}
|
412 |
+
],
|
413 |
+
[
|
414 |
+
"Die Kunstausstellung \"Der erweiterte Raum\" ist vom 15.11.2024 bis 01.05.2025 zu sehen.",
|
415 |
+
{
|
416 |
+
"entities": [
|
417 |
+
[
|
418 |
+
51,
|
419 |
+
61,
|
420 |
+
"START_DATE"
|
421 |
+
],
|
422 |
+
[
|
423 |
+
66,
|
424 |
+
76,
|
425 |
+
"END_DATE"
|
426 |
+
]
|
427 |
+
]
|
428 |
+
}
|
429 |
+
]
|
430 |
+
]
|
src/nlp/experimental/data/img.png
ADDED
![]() |
src/nlp/experimental/data/test.md
ADDED
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Infoveranstaltung für geistliche Mütter und Väter
|
2 |
+
=================================================
|
3 |
+
|
4 |
+
**Als Kirche wollen wir uns in die junge Generation investieren und sie fördern. Dazu gebraucht Gott reife geistliche Mütter und Väter.**
|
5 |
+
|
6 |
+
Datum und Uhrzeit
|
7 |
+
-----------------
|
8 |
+
|
9 |
+
So. 08.12.2024 12:15 - 13:15 CET
|
10 |
+
|
11 |
+
Veranstaltungsort
|
12 |
+
-----------------
|
13 |
+
|
14 |
+
Christliches Zentrum Darmstadt
|
15 |
+
|
16 |
+
Röntgenstraße 18 64291 Darmstadt
|
17 |
+
|
18 |
+
Karte anzeigen
|
19 |
+
|
20 |
+
Zu diesem Event
|
21 |
+
---------------
|
22 |
+
|
23 |
+
**Infoveranstaltung für geistliche Mütter und Väter**
|
24 |
+
|
25 |
+
Als Kirche wollen wir uns in die junge Generation investieren und sie fördern. Dazu gebraucht Gott reife geistliche Mütter und Väter. Wenn du im altern von über 55 Jahren bist und einen Unterschied im Leben einer jungen Person machen möchtest, bist du herzlich zu dieser Infoveranstaltung eingeladen.
|
26 |
+
|
27 |
+
Wir wollen uns gemeinsam austauschen was unsere ältere Generation auf ihrer Reise mit Gott benötigt und welches Erbe Gott ihnen gegeben hat. Zudem sprechen wir über das kommende Jahr und welche Schritte wir gehen dürfen, damit die junge Generation fest in Jesus verwurzelt ist.
|
28 |
+
|
29 |
+
Wir starten mit einem kleinen Mittagessen und laden dich herzlich ein.
|
30 |
+
|
31 |
+
Veranstaltet von
|
32 |
+
----------------
|
33 |
+
|
34 |
+
Christliches Zentrum Darmstadt
|
src/nlp/experimental/gliner/ner_fine_tuning.py
ADDED
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from label_studio_sdk import Client
|
2 |
+
|
3 |
+
LABEL_STUDIO_URL = 'http://localhost:8080'
|
4 |
+
API_KEY = 'aad38f54021d443b17395123304a7c01001b55af'
|
5 |
+
ls = Client(url=LABEL_STUDIO_URL, api_key=API_KEY)
|
6 |
+
print(ls.check_connection())
|
7 |
+
|
8 |
+
# Load and preprocess sample data
|
9 |
+
from datasets import load_dataset
|
10 |
+
from tqdm import tqdm
|
11 |
+
|
12 |
+
# We don't need a ton of data, so we'll only look at the training set for now
|
13 |
+
dataset = load_dataset("MultiCoNER/multiconer_v2", "English (EN)")["train"]
|
14 |
+
medical_labels = ["Medication/Vaccine", "MedicalProcedure", "AnatomicalStructure", "Symptom", "Disease"]
|
15 |
+
|
16 |
+
# Filter so we only look at samples with medical tags
|
17 |
+
medical_dataset = []
|
18 |
+
for item in tqdm(dataset):
|
19 |
+
has_medical = any(any(label in tag for label in medical_labels) for tag in item["ner_tags"])
|
20 |
+
if has_medical:
|
21 |
+
# We want the text as a full text and not a list of tokens, so we create that as another key value pair in the item dictionary
|
22 |
+
item["text"] = " ".join(item["tokens"])
|
23 |
+
medical_dataset.append(item)
|
24 |
+
|
25 |
+
project = ls.start_project(
|
26 |
+
title='Medical NER with GLiNER',
|
27 |
+
label_config='''
|
28 |
+
<View>
|
29 |
+
<Labels name="label" toName="text">
|
30 |
+
<Label value="Medication/Vaccine" background="red"/>
|
31 |
+
<Label value="MedicalProcedure" background="blue"/>
|
32 |
+
<Label value="AnatomicalStructure" background="orange"/>
|
33 |
+
<Label value="Symptom" background="green"/>
|
34 |
+
<Label value="Disease" background="purple"/>
|
35 |
+
</Labels>
|
36 |
+
|
37 |
+
<Text name="text" value="$text"/>
|
38 |
+
</View>
|
39 |
+
'''
|
40 |
+
)
|
41 |
+
|
42 |
+
project.import_tasks(medical_dataset)
|
src/nlp/experimental/gliner/open_information_extraction.py
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from gliner import GLiNER
|
2 |
+
from gliner.multitask import GLiNEROpenExtractor
|
3 |
+
|
4 |
+
model_id = 'urchade/gliner_multi-v2.1'
|
5 |
+
model = GLiNER.from_pretrained(model_id)
|
6 |
+
extractor = GLiNEROpenExtractor(model=model, prompt="Extrahiere den Veranstaltungstitel")
|
7 |
+
|
8 |
+
text = """Technik-Salon an der TIB: „FLY ROCKET FLY“
|
9 |
+
Über den Aufstieg und Fall des Raketenpioniers Lutz Kayser – ein Film- und Gesprächsabend mit dem Dokumentarfilmer Oliver Schwehm
|
10 |
+
|
11 |
+
Ein Start-Up aus dem Schwäbischen, 170 Millionen D-Mark Wagniskapital, ein privates Testgelände im afrikanischen Dschungel – so trat Lutz Kayser in den 1970er-Jahren an, die Raumfahrt zu revolutionieren.
|
12 |
+
|
13 |
+
Warum das gut hätten klappen können und schließlich doch scheiterte, schildert Oliver Schwehm in seiner bildreichen Dokumentation. Im Rahmen der Jubiläumstour zu Ehren von Lutz Kayser („50 Jahre OTRAG – Oribital Transport und Raketen AG“) machen Film und Regisseur am 5. Dezember 2024 im Technik-Salon Station in der TIB in Hannover. Der Eintritt für die Veranstaltung „FLY ROCKET FLY“ ist frei (Spendenbox).
|
14 |
+
|
15 |
+
Mehr Informationen zum Technik-Salon
|
16 |
+
|
17 |
+
Wann? 5. Dezember 2024, 19.00-21.00 Uhr
|
18 |
+
|
19 |
+
Wo? Lesesaal im Marstallgebäude, TIB"""
|
20 |
+
labels = ['title']
|
21 |
+
predictions = extractor(text, labels=labels)
|
22 |
+
print(predictions)
|
src/nlp/experimental/gliner/summarization.py
ADDED
@@ -0,0 +1,194 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import json
|
2 |
+
|
3 |
+
from gliner import GLiNER
|
4 |
+
from gliner.multitask import GLiNERSummarizer
|
5 |
+
|
6 |
+
texts = [
|
7 |
+
"""Cornelia Poletto Palazzo: Die Dinner-Show im Spiegelpalast
|
8 |
+
Wann? 7. November 2024 bis 9. März 2025
|
9 |
+
Wo? Spiegelpalast in Hamburg-Altona, Waidmannstraße 26
|
10 |
+
|
11 |
+
Tickets sind hier erhältlich: Tickets buchen*
|
12 |
+
|
13 |
+
Termine
|
14 |
+
November 2024 bis 9. März 2025
|
15 |
+
Erreichbarkeit
|
16 |
+
Anreise mit den öffentlichen Verkehrsmitteln:
|
17 |
+
Über die Bushaltestellen Langenfelderstraße und Diebsteich Ostseite erreichen Sie den Spiegelpalast in wenigen Gehminuten. Bitte nutzen Sie bevorzugt den ÖPNV zur Anreise.
|
18 |
+
|
19 |
+
Anreise mit dem PKW:
|
20 |
+
Eine begrenzte Anzahl an Parkplätzen steht Ihnen in unmittelbarer Nähe zum Spiegelpalast zur Verfügung. Folgen Sie der Beschilderung! Die Anreise mit dem ÖPNV wird empfohlen.""",
|
21 |
+
|
22 |
+
"""Technik-Salon an der TIB: „FLY ROCKET FLY“
|
23 |
+
Über den Aufstieg und Fall des Raketenpioniers Lutz Kayser – ein Film- und Gesprächsabend mit dem Dokumentarfilmer Oliver Schwehm
|
24 |
+
|
25 |
+
Ein Start-Up aus dem Schwäbischen, 170 Millionen D-Mark Wagniskapital, ein privates Testgelände im afrikanischen Dschungel – so trat Lutz Kayser in den 1970er-Jahren an, die Raumfahrt zu revolutionieren.
|
26 |
+
|
27 |
+
Warum das gut hätten klappen können und schließlich doch scheiterte, schildert Oliver Schwehm in seiner bildreichen Dokumentation. Im Rahmen der Jubiläumstour zu Ehren von Lutz Kayser („50 Jahre OTRAG – Oribital Transport und Raketen AG“) machen Film und Regisseur am 5. Dezember 2024 im Technik-Salon Station in der TIB in Hannover. Der Eintritt für die Veranstaltung „FLY ROCKET FLY“ ist frei (Spendenbox).
|
28 |
+
|
29 |
+
Mehr Informationen zum Technik-Salon""",
|
30 |
+
|
31 |
+
"""Workshop Retrodigitalisierung ================================
|
32 |
+
Thema: Digitalisierte Sammlungen präsentieren – Konzeptionierung, Darstellung und Vermittlung
|
33 |
+
|
34 |
+
Der siebte Workshop Retrodigitalisierung findet am 20. und 21. März 2025 bei ZB MED – Informationszentrum Lebenswissenschaften in Köln statt. Er richtet sich an Praktiker:innen, die sich in Bibliotheken mit der Retrodigitalisierung befassen. Wie in den Vorjahren bietet der Workshop ein breites Spektrum an interessanten Vorträgen zur Praxis der Retrodigitalisierung. Dafür brauchen wir Sie und Ihre Einreichungen!
|
35 |
+
|
36 |
+
Im Fokus des nächsten Workshops steht die zeitgemäße Präsentation digitalisierter Sammlungen. Das Programm widmet sich insbesondere den Themen Konzeptionierung, Darstellung und Vermittlung von digitalisierten Sammlungen und Beständen über die Präsentationsplattformen der Einrichtungen rund um die Nutzung von Digitalisaten.
|
37 |
+
|
38 |
+
Der Call for Presentations läuft noch bis zum 18. Oktober 2024. Wir freuen uns auf Ihren Beitrag!
|
39 |
+
|
40 |
+
Der Workshop Retrodigitalisierung wird gemeinsam von den drei deutschen Zentralen Fachbibliotheken TIB – Leibniz-Informationszentrum Technik und Naturwissenschaften, ZB MED – Informationszentrum Lebenswissenschaften und ZBW – Leibniz-Informationszentrum Wirtschaft sowie der Staatsbibliothek zu Berlin – Preußischer Kulturbesitz durchgeführt.
|
41 |
+
|
42 |
+
Wann? 20. März bis 21. März 2025
|
43 |
+
|
44 |
+
Wo? ZB MED in Köln""",
|
45 |
+
|
46 |
+
"""„ACM WSDM 2025“: renommierte Konferenz zu Websuche und Data Mining in Hannover
|
47 |
+
Wissenschaftlicher Austausch und technologische Innovation im Fokus der 18. ACM International Conference on Web Search and Data Mining
|
48 |
+
|
49 |
+
Die 18. ACM International Conference on Web Search and Data Mining (WSDM 2025) wird vom 10.03.2025 - 14.03.2025 in Hannover stattfinden. Die WSDM zählt zu den führenden Konferenzen in den Bereichen Websuche, Data Mining, Maschinelles Lernen und Künstliche Intelligenz. Sie bietet eine Plattform, auf der weltweit führende Wissenschaftler:innen, Fachleute und Praktiker:innen ihre neuesten Forschungsergebnisse präsentieren und zukunftsweisende Ideen austauschen können.
|
50 |
+
|
51 |
+
Die Konferenz wird sich auf ein breites Spektrum aktueller Themen konzentrieren, darunter:
|
52 |
+
|
53 |
+
Web of Things, ortsunabhängige und mobile Datenverarbeitung (Ubiquitous and Mobile Computing)
|
54 |
+
Datenschutz, Fairness, Interpretierbarkeit
|
55 |
+
Soziale Netzwerke
|
56 |
+
Intelligente Assistenten
|
57 |
+
Crowdsourcing und menschliche Datenverarbeitung
|
58 |
+
Zur Konferenz-Website""",
|
59 |
+
|
60 |
+
"""Infoveranstaltung für geistliche Mütter und Väter
|
61 |
+
Als Kirche wollen wir uns in die junge Generation investieren und sie fördern. Dazu gebraucht Gott reife geistliche Mütter und Väter.
|
62 |
+
|
63 |
+
Von Christliches Zentrum DarmstadtFolgenFolgen
|
64 |
+
|
65 |
+
Datum und Uhrzeit
|
66 |
+
So. 8. Dez. 2024 12:15 - 13:15 CET
|
67 |
+
|
68 |
+
Veranstaltungsort
|
69 |
+
Christliches Zentrum Darmstadt
|
70 |
+
|
71 |
+
Röntgenstraße 18 64291 DarmstadtKarte anzeigen
|
72 |
+
|
73 |
+
Zu diesem Event
|
74 |
+
** Infoveranstaltung für geistliche Mütter und Väter **
|
75 |
+
|
76 |
+
Als Kirche wollen wir uns in die junge Generation investieren und sie fördern. Dazu gebraucht Gott reife geistliche Mütter und Väter. Wenn du im altern von über 55 Jahren bist und einen Unterschied im Leben einer jungen Person machen möchtest, bist du herzlich zu dieser Infoveranstaltung eingeladen.
|
77 |
+
|
78 |
+
Wir wollen uns gemeinsam austauschen was unsere ältere Generation auf ihrer Reise mit Gott ben��tigt und welches Erbe Gott ihnen gegeben hat. Zudem sprechen wir über das kommende Jahr und welche Schritte wir gehen dürfen, damit die junge Generation fest in Jesus verwurzelt ist.
|
79 |
+
|
80 |
+
Wir starten mit einem kleinen Mittagessen und laden dich herzlich ein.
|
81 |
+
|
82 |
+
Veranstaltet von
|
83 |
+
Christliches Zentrum Darmstadt""",
|
84 |
+
|
85 |
+
"""MJ – Das Michael Jackson Musical
|
86 |
+
Das Erfolgsmusical über den Ausnahmekünstler Michael Jackson ist seit Dezember 2024 in Hamburg zu sehen! Tickets und Hotel können Sie hier bequem online buchen.
|
87 |
+
|
88 |
+
Tickets buchen
|
89 |
+
Erleben Sie das mehrfach ausgezeichnete Musical MJ – Das Michael Jackson Musical.
|
90 |
+
|
91 |
+
Tickets buchen ab 63,99€*
|
92 |
+
|
93 |
+
Tickets und Hotel buchen
|
94 |
+
Sie möchten Ihren Musical-Besuch mit einer Reise nach Hamburg verbinden? Hier können Sie Ihre Tickets und Hotelübernachtung im Paket buchen:
|
95 |
+
|
96 |
+
Reisepaket: MJ – Das Michael Jackson Musical ab 134,60€*
|
97 |
+
|
98 |
+
Alle Vorteile auf einen Blick:
|
99 |
+
|
100 |
+
Übernachtung im ausgewählten Hotel inkl. Frühstück, Zusatznächte buchbar
|
101 |
+
Musical-Ticket in der gewählten Preiskategorie
|
102 |
+
Hamburg Card (3 Tage) – Ihr Entdeckerticket für freie Fahrt mit Bus und Bahn im Wert von 31,90€
|
103 |
+
Termine für MJ – Das Michael Jackson Musical
|
104 |
+
Anfahrt
|
105 |
+
MJ – Das Michael Jackson Musical | Stage Theater an der Elbe
|
106 |
+
|
107 |
+
Norderelbstraße 8, 20457 Hamburg
|
108 |
+
|
109 |
+
Kontakt speichern
|
110 |
+
|
111 |
+
Adresse auf Karte anzeigen
|
112 |
+
|
113 |
+
[Anfahrt](https://geofox.hvv.de/jsf/home.seam?clear=true&language=de&destination=Norderelbstra%C3%9Fe 8, 20457 Hamburg)
|
114 |
+
|
115 |
+
Termine
|
116 |
+
Premiere am 1. Dezember 2024""",
|
117 |
+
|
118 |
+
"""Liedernachmittag
|
119 |
+
Lieder von R. Schubert, R. Franz, A. Webern, H. Wolf
|
120 |
+
|
121 |
+
Stuhlreihen im Museum August Kestner
|
122 |
+
© MAK/LHH
|
123 |
+
|
124 |
+
Vortrag im Museum August Kestner
|
125 |
+
|
126 |
+
Monika Abel, Sopran / Kathrin Isabelle Klein, Klavier
|
127 |
+
|
128 |
+
Termine
|
129 |
+
|
130 |
+
11.01.2025 ab 16:00 Uhr
|
131 |
+
|
132 |
+
Ort
|
133 |
+
|
134 |
+
Museum August Kestner
|
135 |
+
Platz der Menschenrechte 3
|
136 |
+
30159 Hannover
|
137 |
+
|
138 |
+
Konzertkarten: [email protected] und Tageskasse ab 15.00 Uhr im Museum
|
139 |
+
|
140 |
+
Unter der Schirmherrschaft von Kammersängerin Helen Donath.
|
141 |
+
|
142 |
+
Bis zu viermal im Jahr laden wir zu einem Liedernachmittag im Museum ein.
|
143 |
+
|
144 |
+
In Zusammenarbeit mit: Lohmann-Stiftung für Liedgesang e.V., Hannover; Freundes- und Förderkreis des Museum August Kestner „Antike & Gegenwart e.V.“""",
|
145 |
+
|
146 |
+
"""Die goldene Stadt
|
147 |
+
Vortrag im Museum August Kestner
|
148 |
+
|
149 |
+
Mit Dr. Martin Hersch (München)
|
150 |
+
|
151 |
+
In Kooperation mit dem Freundeskreis Antike und Gegenwart
|
152 |
+
|
153 |
+
Termine
|
154 |
+
15.01.2025 ab 18:00 Uhr
|
155 |
+
|
156 |
+
Ort
|
157 |
+
Museum August Kestner
|
158 |
+
Platz der Menschenrechte 3
|
159 |
+
30159 Hannover
|
160 |
+
|
161 |
+
Eintritt
|
162 |
+
5,00 €
|
163 |
+
|
164 |
+
ermäßigter Eintritt
|
165 |
+
|
166 |
+
4,00 €""",
|
167 |
+
|
168 |
+
"""Stadtansichten
|
169 |
+
Erleben Sie bei einem Besuch der Sonderausstellung Städtetrip vielfältige und spannende Lyrik und Prosa verschiedener Autor*innen, nicht nur zum Thema Reisen. Ausgewählt und vorgetragen von der Literarischen Komponistin und Rezitatorin Marie Dettmer.
|
170 |
+
|
171 |
+
Termine
|
172 |
+
|
173 |
+
12.01.2025 ab 14:00 bis 15:00 Uhr
|
174 |
+
|
175 |
+
Ort
|
176 |
+
|
177 |
+
Landeshauptstadt Hannover
|
178 |
+
|
179 |
+
Trammplatz 2
|
180 |
+
|
181 |
+
30159 Hannover"""
|
182 |
+
]
|
183 |
+
|
184 |
+
model_id = 'urchade/gliner_multi-v2.1'
|
185 |
+
model = GLiNER.from_pretrained(model_id)
|
186 |
+
summarizer = GLiNERSummarizer(model=model)
|
187 |
+
|
188 |
+
|
189 |
+
for text in texts :
|
190 |
+
summary = summarizer(text, threshold=0.001)
|
191 |
+
print("ORIGINAL")
|
192 |
+
print(text)
|
193 |
+
print("SUMMARY")
|
194 |
+
print(summary)
|
src/nlp/experimental/keyword_extraction.py
ADDED
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import spacy
|
2 |
+
from collections import Counter
|
3 |
+
from string import punctuation
|
4 |
+
nlp = spacy.load("de_core_news_lg")
|
5 |
+
def get_hotwords(text):
|
6 |
+
result = []
|
7 |
+
pos_tag = ['PROPN', 'ADJ', 'NOUN']
|
8 |
+
doc = nlp(text.lower())
|
9 |
+
for token in doc:
|
10 |
+
if(token.text in nlp.Defaults.stop_words or token.text in punctuation):
|
11 |
+
continue
|
12 |
+
if(token.pos_ in pos_tag):
|
13 |
+
result.append(token.text)
|
14 |
+
return result
|
15 |
+
new_text = """
|
16 |
+
Technik-Salon an der TIB: „FLY ROCKET FLY“
|
17 |
+
Über den Aufstieg und Fall des Raketenpioniers Lutz Kayser – ein Film- und Gesprächsabend mit dem Dokumentarfilmer Oliver Schwehm
|
18 |
+
|
19 |
+
Ein Start-Up aus dem Schwäbischen, 170 Millionen D-Mark Wagniskapital, ein privates Testgelände im afrikanischen Dschungel – so trat Lutz Kayser in den 1970 er-Jahren an, die Raumfahrt zu revolutionieren.
|
20 |
+
|
21 |
+
Warum das gut hätten klappen können und schließlich doch scheiterte, schildert Oliver Schwehm in seiner bildreichen Dokumentation. Im Rahmen der Jubiläumstour zu Ehren von Lutz Kayser („50 Jahre OTRAG – Oribital Transport und Raketen AG“) machen Film und Regisseur am 05.12.2024 im Technik-Salon Station in der TIB in Hannover. Der Eintritt für die Veranstaltung „FLY ROCKET FLY“ ist frei (Spendenbox).
|
22 |
+
|
23 |
+
Mehr Informationen zum Technik-Salon
|
24 |
+
|
25 |
+
Wann? 05.12.2024, 19:00-21:00
|
26 |
+
|
27 |
+
Wo? Lesesaal im Marstallgebäude, TIB
|
28 |
+
"""
|
29 |
+
output = set(get_hotwords(new_text))
|
30 |
+
print(output)
|
31 |
+
most_common_list = Counter(output).most_common(10)
|
32 |
+
for item in most_common_list:
|
33 |
+
print(item[0])
|
src/nlp/experimental/layout_parser.py
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import layoutparser as lp
|
2 |
+
import cv2
|
3 |
+
|
4 |
+
|
5 |
+
image = cv2.imread("data/img.png")
|
6 |
+
image = image[..., ::-1]
|
7 |
+
# Convert the image from BGR (cv2 default loading style)
|
8 |
+
# to RGB
|
9 |
+
|
10 |
+
|
11 |
+
|
12 |
+
model = lp.Detectron2LayoutModel('lp://PubLayNet/faster_rcnn_R_50_FPN_3x/config',
|
13 |
+
extra_config=["MODEL.ROI_HEADS.SCORE_THRESH_TEST", 0.8],
|
14 |
+
label_map={0: "Text", 1: "Title", 2: "List", 3:"Table", 4:"Figure"})
|
15 |
+
# Load the deep layout model from the layoutparser API
|
16 |
+
# For all the supported model, please check the Model
|
17 |
+
# Zoo Page: https://layout-parser.readthedocs.io/en/latest/notes/modelzoo.html
|
18 |
+
|
19 |
+
layout = model.detect(image)
|
20 |
+
# Detect the layout of the input image
|
21 |
+
|
22 |
+
lp.draw_box(image, layout, box_width=3)
|
23 |
+
# Show the detected layout of the input image
|
src/nlp/experimental/llm/__init__.py
ADDED
File without changes
|
src/nlp/experimental/llm/inference_api_test.py
ADDED
@@ -0,0 +1,227 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from huggingface_hub import InferenceClient
|
2 |
+
import json
|
3 |
+
from src.configuration.config import INFERENCE_API_KEY
|
4 |
+
texts = [
|
5 |
+
"""Cornelia Poletto Palazzo: Die Dinner-Show im Spiegelpalast
|
6 |
+
Wann? 7. November 2024 bis 9. März 2025
|
7 |
+
Wo? Spiegelpalast in Hamburg-Altona, Waidmannstraße 26
|
8 |
+
|
9 |
+
Tickets sind hier erhältlich: Tickets buchen*
|
10 |
+
|
11 |
+
Termine
|
12 |
+
November 2024 bis 9. März 2025
|
13 |
+
Erreichbarkeit
|
14 |
+
Anreise mit den öffentlichen Verkehrsmitteln:
|
15 |
+
Über die Bushaltestellen Langenfelderstraße und Diebsteich Ostseite erreichen Sie den Spiegelpalast in wenigen Gehminuten. Bitte nutzen Sie bevorzugt den ÖPNV zur Anreise.
|
16 |
+
|
17 |
+
Anreise mit dem PKW:
|
18 |
+
Eine begrenzte Anzahl an Parkplätzen steht Ihnen in unmittelbarer Nähe zum Spiegelpalast zur Verfügung. Folgen Sie der Beschilderung! Die Anreise mit dem ÖPNV wird empfohlen.""",
|
19 |
+
|
20 |
+
"""Technik-Salon an der TIB: „FLY ROCKET FLY“
|
21 |
+
Über den Aufstieg und Fall des Raketenpioniers Lutz Kayser – ein Film- und Gesprächsabend mit dem Dokumentarfilmer Oliver Schwehm
|
22 |
+
|
23 |
+
Ein Start-Up aus dem Schwäbischen, 170 Millionen D-Mark Wagniskapital, ein privates Testgelände im afrikanischen Dschungel – so trat Lutz Kayser in den 1970er-Jahren an, die Raumfahrt zu revolutionieren.
|
24 |
+
|
25 |
+
Warum das gut hätten klappen können und schließlich doch scheiterte, schildert Oliver Schwehm in seiner bildreichen Dokumentation. Im Rahmen der Jubiläumstour zu Ehren von Lutz Kayser („50 Jahre OTRAG – Oribital Transport und Raketen AG“) machen Film und Regisseur am 5. Dezember 2024 im Technik-Salon Station in der TIB in Hannover. Der Eintritt für die Veranstaltung „FLY ROCKET FLY“ ist frei (Spendenbox).
|
26 |
+
|
27 |
+
Mehr Informationen zum Technik-Salon""",
|
28 |
+
|
29 |
+
"""Workshop Retrodigitalisierung ================================
|
30 |
+
Thema: Digitalisierte Sammlungen präsentieren – Konzeptionierung, Darstellung und Vermittlung
|
31 |
+
|
32 |
+
Der siebte Workshop Retrodigitalisierung findet am 20. und 21. März 2025 bei ZB MED – Informationszentrum Lebenswissenschaften in Köln statt. Er richtet sich an Praktiker:innen, die sich in Bibliotheken mit der Retrodigitalisierung befassen. Wie in den Vorjahren bietet der Workshop ein breites Spektrum an interessanten Vorträgen zur Praxis der Retrodigitalisierung. Dafür brauchen wir Sie und Ihre Einreichungen!
|
33 |
+
|
34 |
+
Im Fokus des nächsten Workshops steht die zeitgemäße Präsentation digitalisierter Sammlungen. Das Programm widmet sich insbesondere den Themen Konzeptionierung, Darstellung und Vermittlung von digitalisierten Sammlungen und Beständen über die Präsentationsplattformen der Einrichtungen rund um die Nutzung von Digitalisaten.
|
35 |
+
|
36 |
+
Der Call for Presentations läuft noch bis zum 18. Oktober 2024. Wir freuen uns auf Ihren Beitrag!
|
37 |
+
|
38 |
+
Der Workshop Retrodigitalisierung wird gemeinsam von den drei deutschen Zentralen Fachbibliotheken TIB – Leibniz-Informationszentrum Technik und Naturwissenschaften, ZB MED – Informationszentrum Lebenswissenschaften und ZBW – Leibniz-Informationszentrum Wirtschaft sowie der Staatsbibliothek zu Berlin – Preußischer Kulturbesitz durchgeführt.
|
39 |
+
|
40 |
+
Wann? 20. März bis 21. März 2025
|
41 |
+
|
42 |
+
Wo? ZB MED in Köln""",
|
43 |
+
|
44 |
+
"""„ACM WSDM 2025“: renommierte Konferenz zu Websuche und Data Mining in Hannover
|
45 |
+
Wissenschaftlicher Austausch und technologische Innovation im Fokus der 18. ACM International Conference on Web Search and Data Mining
|
46 |
+
|
47 |
+
Die 18. ACM International Conference on Web Search and Data Mining (WSDM 2025) wird vom 10.03.2025 - 14.03.2025 in Hannover stattfinden. Die WSDM zählt zu den führenden Konferenzen in den Bereichen Websuche, Data Mining, Maschinelles Lernen und Künstliche Intelligenz. Sie bietet eine Plattform, auf der weltweit führende Wissenschaftler:innen, Fachleute und Praktiker:innen ihre neuesten Forschungsergebnisse präsentieren und zukunftsweisende Ideen austauschen können.
|
48 |
+
|
49 |
+
Die Konferenz wird sich auf ein breites Spektrum aktueller Themen konzentrieren, darunter:
|
50 |
+
|
51 |
+
Web of Things, ortsunabhängige und mobile Datenverarbeitung (Ubiquitous and Mobile Computing)
|
52 |
+
Datenschutz, Fairness, Interpretierbarkeit
|
53 |
+
Soziale Netzwerke
|
54 |
+
Intelligente Assistenten
|
55 |
+
Crowdsourcing und menschliche Datenverarbeitung
|
56 |
+
Zur Konferenz-Website""",
|
57 |
+
|
58 |
+
"""Infoveranstaltung für geistliche Mütter und Väter
|
59 |
+
Als Kirche wollen wir uns in die junge Generation investieren und sie fördern. Dazu gebraucht Gott reife geistliche Mütter und Väter.
|
60 |
+
|
61 |
+
Von Christliches Zentrum DarmstadtFolgenFolgen
|
62 |
+
|
63 |
+
Datum und Uhrzeit
|
64 |
+
So. 8. Dez. 2024 12:15 - 13:15 CET
|
65 |
+
|
66 |
+
Veranstaltungsort
|
67 |
+
Christliches Zentrum Darmstadt
|
68 |
+
|
69 |
+
Röntgenstraße 18 64291 DarmstadtKarte anzeigen
|
70 |
+
|
71 |
+
Zu diesem Event
|
72 |
+
** Infoveranstaltung für geistliche Mütter und Väter **
|
73 |
+
|
74 |
+
Als Kirche wollen wir uns in die junge Generation investieren und sie fördern. Dazu gebraucht Gott reife geistliche Mütter und Väter. Wenn du im altern von über 55 Jahren bist und einen Unterschied im Leben einer jungen Person machen möchtest, bist du herzlich zu dieser Infoveranstaltung eingeladen.
|
75 |
+
|
76 |
+
Wir wollen uns gemeinsam austauschen was unsere ältere Generation auf ihrer Reise mit Gott benötigt und welches Erbe Gott ihnen gegeben hat. Zudem sprechen wir über das kommende Jahr und welche Schritte wir gehen dürfen, damit die junge Generation fest in Jesus verwurzelt ist.
|
77 |
+
|
78 |
+
Wir starten mit einem kleinen Mittagessen und laden dich herzlich ein.
|
79 |
+
|
80 |
+
Veranstaltet von
|
81 |
+
Christliches Zentrum Darmstadt""",
|
82 |
+
|
83 |
+
"""MJ – Das Michael Jackson Musical
|
84 |
+
Das Erfolgsmusical über den Ausnahmekünstler Michael Jackson ist seit Dezember 2024 in Hamburg zu sehen! Tickets und Hotel können Sie hier bequem online buchen.
|
85 |
+
|
86 |
+
Tickets buchen
|
87 |
+
Erleben Sie das mehrfach ausgezeichnete Musical MJ – Das Michael Jackson Musical.
|
88 |
+
|
89 |
+
Tickets buchen ab 63,99€*
|
90 |
+
|
91 |
+
Tickets und Hotel buchen
|
92 |
+
Sie möchten Ihren Musical-Besuch mit einer Reise nach Hamburg verbinden? Hier können Sie Ihre Tickets und Hotelübernachtung im Paket buchen:
|
93 |
+
|
94 |
+
Reisepaket: MJ – Das Michael Jackson Musical ab 134,60€*
|
95 |
+
|
96 |
+
Alle Vorteile auf einen Blick:
|
97 |
+
|
98 |
+
Übernachtung im ausgewählten Hotel inkl. Frühstück, Zusatznächte buchbar
|
99 |
+
Musical-Ticket in der gewählten Preiskategorie
|
100 |
+
Hamburg Card (3 Tage) – Ihr Entdeckerticket für freie Fahrt mit Bus und Bahn im Wert von 31,90€
|
101 |
+
Termine für MJ – Das Michael Jackson Musical
|
102 |
+
Anfahrt
|
103 |
+
MJ – Das Michael Jackson Musical | Stage Theater an der Elbe
|
104 |
+
|
105 |
+
Norderelbstraße 8, 20457 Hamburg
|
106 |
+
|
107 |
+
Kontakt speichern
|
108 |
+
|
109 |
+
Adresse auf Karte anzeigen
|
110 |
+
|
111 |
+
[Anfahrt](https://geofox.hvv.de/jsf/home.seam?clear=true&language=de&destination=Norderelbstra%C3%9Fe 8, 20457 Hamburg)
|
112 |
+
|
113 |
+
Termine
|
114 |
+
Premiere am 1. Dezember 2024""",
|
115 |
+
|
116 |
+
"""Liedernachmittag
|
117 |
+
Lieder von R. Schubert, R. Franz, A. Webern, H. Wolf
|
118 |
+
|
119 |
+
Stuhlreihen im Museum August Kestner
|
120 |
+
© MAK/LHH
|
121 |
+
|
122 |
+
Vortrag im Museum August Kestner
|
123 |
+
|
124 |
+
Monika Abel, Sopran / Kathrin Isabelle Klein, Klavier
|
125 |
+
|
126 |
+
Termine
|
127 |
+
|
128 |
+
11.01.2025 ab 16:00 Uhr
|
129 |
+
|
130 |
+
Ort
|
131 |
+
|
132 |
+
Museum August Kestner
|
133 |
+
Platz der Menschenrechte 3
|
134 |
+
30159 Hannover
|
135 |
+
|
136 |
+
Konzertkarten: [email protected] und Tageskasse ab 15.00 Uhr im Museum
|
137 |
+
|
138 |
+
Unter der Schirmherrschaft von Kammersängerin Helen Donath.
|
139 |
+
|
140 |
+
Bis zu viermal im Jahr laden wir zu einem Liedernachmittag im Museum ein.
|
141 |
+
|
142 |
+
In Zusammenarbeit mit: Lohmann-Stiftung für Liedgesang e.V., Hannover; Freundes- und Förderkreis des Museum August Kestner „Antike & Gegenwart e.V.“""",
|
143 |
+
|
144 |
+
"""Die goldene Stadt
|
145 |
+
Vortrag im Museum August Kestner
|
146 |
+
|
147 |
+
Mit Dr. Martin Hersch (München)
|
148 |
+
|
149 |
+
In Kooperation mit dem Freundeskreis Antike und Gegenwart
|
150 |
+
|
151 |
+
Termine
|
152 |
+
15.01.2025 ab 18:00 Uhr
|
153 |
+
|
154 |
+
Ort
|
155 |
+
Museum August Kestner
|
156 |
+
Platz der Menschenrechte 3
|
157 |
+
30159 Hannover
|
158 |
+
|
159 |
+
Eintritt
|
160 |
+
5,00 €
|
161 |
+
|
162 |
+
ermäßigter Eintritt
|
163 |
+
|
164 |
+
4,00 €""",
|
165 |
+
|
166 |
+
"""Stadtansichten
|
167 |
+
Erleben Sie bei einem Besuch der Sonderausstellung Städtetrip vielfältige und spannende Lyrik und Prosa verschiedener Autor*innen, nicht nur zum Thema Reisen. Ausgewählt und vorgetragen von der Literarischen Komponistin und Rezitatorin Marie Dettmer.
|
168 |
+
|
169 |
+
Termine
|
170 |
+
|
171 |
+
12.01.2025 ab 14:00 bis 15:00 Uhr
|
172 |
+
|
173 |
+
Ort
|
174 |
+
|
175 |
+
Landeshauptstadt Hannover
|
176 |
+
|
177 |
+
Trammplatz 2
|
178 |
+
|
179 |
+
30159 Hannover"""
|
180 |
+
]
|
181 |
+
|
182 |
+
client = InferenceClient(
|
183 |
+
"Qwen/Qwen2.5-Coder-32B-Instruct",
|
184 |
+
token=INFERENCE_API_KEY,
|
185 |
+
)
|
186 |
+
|
187 |
+
for text in texts:
|
188 |
+
messages = [{
|
189 |
+
"role": "user",
|
190 |
+
"content": """"Du bist ein NER-Model. Gebe die Veranstaltungsinformationen Titel, Startdatum, Enddatum, Startzeit,
|
191 |
+
Endzeit, Einlasszeit, LocationName, Straße, Hausnummer, Postleitzahl, Stadt, Preis usw. aus dem text in JSON Format zurück.
|
192 |
+
Es sollen keine Markdown Elemente enthalten sein, nur das JSON Objekt als string.
|
193 |
+
Gebe Nur das JSON als Antwort zurück. JSON_Schema:
|
194 |
+
{
|
195 |
+
"title": string,
|
196 |
+
"organizer": string,
|
197 |
+
"startDate": string,
|
198 |
+
"endDate": string,
|
199 |
+
"startTime": string,
|
200 |
+
"endTime": string,
|
201 |
+
"admittanceTime": string,
|
202 |
+
"locationName": string,
|
203 |
+
"street": string,
|
204 |
+
"houseNumber": string,
|
205 |
+
"postalCode": string,
|
206 |
+
"city": string,
|
207 |
+
"price": Array<float> | null,
|
208 |
+
"currency": string,
|
209 |
+
"priceFree": bool,
|
210 |
+
"ticketsRequired": bool.
|
211 |
+
"categories": Array<string> | null,
|
212 |
+
"eventDescription": string,
|
213 |
+
"accesibilityInformation": string,
|
214 |
+
"keywords": Array<string> | null,
|
215 |
+
}
|
216 |
+
Text: """ + text}]
|
217 |
+
|
218 |
+
response = client.chat_completion(messages, max_tokens=1000)
|
219 |
+
print(text)
|
220 |
+
print()
|
221 |
+
event = json.loads(response.choices[0].message.content)
|
222 |
+
print(f"TITEL: {event["title"]} - HOST: {event['organizer']}")
|
223 |
+
print(f"KATEGORIEN: {event["categories"]}")
|
224 |
+
print(f"KEYWORDS: {event['keywords']}")
|
225 |
+
print(f"BESCHREIBUNG:\n {event["eventDescription"]}")
|
226 |
+
print("_________________________________________________________________________________________________________________________")
|
227 |
+
|
src/nlp/experimental/llm/llm_image_document_question_answering.py
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from huggingface_hub import InferenceClient
|
2 |
+
from src.configuration.config import INFERENCE_API_KEY
|
3 |
+
client = InferenceClient(
|
4 |
+
"vikhyatk/moondream2",
|
5 |
+
token=INFERENCE_API_KEY,)
|
6 |
+
response = client.image_to_text(
|
7 |
+
image="event.jpg")
|
8 |
+
print(response)
|
src/nlp/experimental/llm/llm_ner.py
ADDED
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
2 |
+
|
3 |
+
model_name = "Qwen/Qwen2.5-Coder-32B-Instruct"
|
4 |
+
|
5 |
+
model = AutoModelForCausalLM.from_pretrained(
|
6 |
+
model_name,
|
7 |
+
torch_dtype="auto",
|
8 |
+
device_map="auto"
|
9 |
+
)
|
10 |
+
tokenizer = AutoTokenizer.from_pretrained(model_name)
|
11 |
+
|
12 |
+
prompt = "write a quick sort algorithm."
|
13 |
+
messages = [
|
14 |
+
{"role": "system", "content": "You are Qwen, created by Alibaba Cloud. You are a helpful assistant."},
|
15 |
+
{"role": "user", "content": prompt}
|
16 |
+
]
|
17 |
+
text = tokenizer.apply_chat_template(
|
18 |
+
messages,
|
19 |
+
tokenize=False,
|
20 |
+
add_generation_prompt=True
|
21 |
+
)
|
22 |
+
model_inputs = tokenizer([text], return_tensors="pt").to(model.device)
|
23 |
+
|
24 |
+
generated_ids = model.generate(
|
25 |
+
**model_inputs,
|
26 |
+
max_new_tokens=512
|
27 |
+
)
|
28 |
+
generated_ids = [
|
29 |
+
output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
|
30 |
+
]
|
31 |
+
|
32 |
+
response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
|
src/nlp/experimental/ner/__init__.py
ADDED
File without changes
|
src/nlp/experimental/ner/create_spacy_annotations.py
ADDED
@@ -0,0 +1,158 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import json
|
2 |
+
|
3 |
+
import spacy
|
4 |
+
|
5 |
+
|
6 |
+
def convert_to_spacy_annotations(data):
|
7 |
+
spacy_annotations = []
|
8 |
+
|
9 |
+
for item in data:
|
10 |
+
text = item["text"]
|
11 |
+
entities = []
|
12 |
+
|
13 |
+
for entity in item["entities"]:
|
14 |
+
start_idx = text.find(entity["data"])
|
15 |
+
if start_idx != -1:
|
16 |
+
end_idx = start_idx + len(entity["data"])
|
17 |
+
entities.append((start_idx, end_idx, entity["label"]))
|
18 |
+
|
19 |
+
spacy_annotations.append((text, {"entities": entities}))
|
20 |
+
|
21 |
+
return spacy_annotations
|
22 |
+
|
23 |
+
|
24 |
+
# Beispiel JSON-Daten
|
25 |
+
data = [
|
26 |
+
{
|
27 |
+
"text": "Im Rahmen der Jubiläumstour zu Ehren von Lutz Kayser (\"50 Jahre OTRAG – Oribital Transport und Raketen AG\") machen Film und Regisseur am 05.12.2024 im Technik-Salon Station in der TIB in Hannover.",
|
28 |
+
"entities": [{ "data": "05.12.2024", "label": "EVENT_DATE" }]
|
29 |
+
},
|
30 |
+
{
|
31 |
+
"text": "Wann? 05.12.2024, 19:00-21:00",
|
32 |
+
"entities": [{ "data": "05.12.2024", "label": "EVENT_DATE" }]
|
33 |
+
},
|
34 |
+
{
|
35 |
+
"text": "Der siebte Workshop Retrodigitalisierung findet am 20.03.2025 und 21.03.2025 bei ZB MED – Informationszentrum Lebenswissenschaften in Köln statt.",
|
36 |
+
"entities": [
|
37 |
+
{ "data": "20.03.2025", "label": "EVENT_DATE" },
|
38 |
+
{ "data": "21.03.2025", "label": "EVENT_DATE" }
|
39 |
+
]
|
40 |
+
},
|
41 |
+
{
|
42 |
+
"text": "Wann? 20.03.2025 - 21.03.2025",
|
43 |
+
"entities": [{ "data": "20.03.2025 - 21.03.2025", "label": "EVENT_DATE_RANGE" }]
|
44 |
+
},
|
45 |
+
{
|
46 |
+
"text": "Die 18. ACM International Conference on Web Search and Data Mining (WSDM 2025) wird vom 10.03.2025 - 14.03.2025 in Hannover stattfinden.",
|
47 |
+
"entities": [{ "data": "10.03.2025 - 14.03.2025", "label": "EVENT_DATE_RANGE" }]
|
48 |
+
},
|
49 |
+
{
|
50 |
+
"text": "So. 08.12.2024 12:15 - 13:15 CET",
|
51 |
+
"entities": [{ "data": "08.12.2024", "label": "EVENT_DATE" }]
|
52 |
+
},
|
53 |
+
{
|
54 |
+
"text": "24.12.2025 um 16:00",
|
55 |
+
"entities": [{ "data": "24.12.2025", "label": "EVENT_DATE" }]
|
56 |
+
},
|
57 |
+
{
|
58 |
+
"text": "07.01.2025",
|
59 |
+
"entities": [{ "data": "07.01.2025", "label": "EVENT_DATE" }]
|
60 |
+
},
|
61 |
+
{
|
62 |
+
"text": "Am 01.07.2025",
|
63 |
+
"entities": [{ "data": "01.07.2025", "label": "EVENT_DATE" }]
|
64 |
+
},
|
65 |
+
{
|
66 |
+
"text": "Wann? 07.11.2024 - 09.03.2025",
|
67 |
+
"entities": [{ "data": "07.11.2024 - 09.03.2025", "label": "EVENT_DATE_RANGE" }]
|
68 |
+
},
|
69 |
+
{
|
70 |
+
"text": "01.11.2024 - 09.03.2025",
|
71 |
+
"entities": [{ "data": "01.11.2024 - 09.03.2025", "label": "EVENT_DATE_RANGE" }]
|
72 |
+
},
|
73 |
+
{
|
74 |
+
"text": "01.09.2025 - 26.12.2024",
|
75 |
+
"entities": [{ "data": "01.09.2025 - 26.12.2024", "label": "EVENT_DATE_RANGE" }]
|
76 |
+
},
|
77 |
+
{
|
78 |
+
"text": "Premiere am 01.12.2024",
|
79 |
+
"entities": [
|
80 |
+
{
|
81 |
+
"data": "01.12.2024",
|
82 |
+
"label": "EVENT_DATE"
|
83 |
+
}
|
84 |
+
]
|
85 |
+
},
|
86 |
+
{
|
87 |
+
"text": "01.11.2025 ab 16:00",
|
88 |
+
"entities": [
|
89 |
+
{
|
90 |
+
"data": "01.11.2025",
|
91 |
+
"label": "EVENT_DATE"
|
92 |
+
}
|
93 |
+
]
|
94 |
+
},
|
95 |
+
{
|
96 |
+
"text": "15.01.2025 ab 18:00",
|
97 |
+
"entities": [
|
98 |
+
{
|
99 |
+
"data": "15.01.2025",
|
100 |
+
"label": "EVENT_DATE"
|
101 |
+
}
|
102 |
+
]
|
103 |
+
},
|
104 |
+
{
|
105 |
+
"text": "01.12.2025 ab 14:00-15:00",
|
106 |
+
"entities": [
|
107 |
+
{
|
108 |
+
"data": "01.12.2025",
|
109 |
+
"label": "EVENT_DATE"
|
110 |
+
}
|
111 |
+
]
|
112 |
+
},
|
113 |
+
{
|
114 |
+
"text": "18.01.2025 ab 11:00-18:00",
|
115 |
+
"entities": [
|
116 |
+
{
|
117 |
+
"data": "18.01.2025",
|
118 |
+
"label": "EVENT_DATE"
|
119 |
+
}
|
120 |
+
]
|
121 |
+
},
|
122 |
+
{
|
123 |
+
"text": "01.12.2024 – 08.12.2024",
|
124 |
+
"entities": [
|
125 |
+
{
|
126 |
+
"data": "01.12.2024 – 08.12.2024",
|
127 |
+
"label": "EVENT_DATE_RANGE"
|
128 |
+
}
|
129 |
+
]
|
130 |
+
},
|
131 |
+
{
|
132 |
+
"text": "Freitag 16:00-20:00 / Samstag 11:00-20:00 / Sonntag 11:00-18:00",
|
133 |
+
"entities": []
|
134 |
+
},
|
135 |
+
{
|
136 |
+
"text": "So, 15.12.2024 Beginn: 15:00 Einlass: 14:30",
|
137 |
+
"entities": [
|
138 |
+
{
|
139 |
+
"data": "15.12.2024",
|
140 |
+
"label": "EVENT_DATE"
|
141 |
+
}
|
142 |
+
]
|
143 |
+
}
|
144 |
+
]
|
145 |
+
|
146 |
+
|
147 |
+
|
148 |
+
|
149 |
+
|
150 |
+
|
151 |
+
# Umwandlung in spaCy-Format
|
152 |
+
annotations = convert_to_spacy_annotations(data)
|
153 |
+
# JSON speichern
|
154 |
+
with open("../annotations.json", "w", encoding="utf-8") as f:
|
155 |
+
json.dump(annotations, f, ensure_ascii=False, indent=4)
|
156 |
+
|
157 |
+
# Ausgabe prüfen
|
158 |
+
print(json.dumps(annotations, ensure_ascii=False, indent=4))
|
src/nlp/experimental/ner/few_shot_ner.py
ADDED
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import spacy
|
2 |
+
from spacy import displacy
|
3 |
+
from spacy.tokenizer import Tokenizer
|
4 |
+
from spacy.util import compile_prefix_regex, compile_suffix_regex, compile_infix_regex
|
5 |
+
|
6 |
+
# Beispieltext mit Datum und Veranstaltung
|
7 |
+
text = """Das Event findet am 01.02.2022 statt. Es wird um 19:00 Uhr beginnen."""
|
8 |
+
|
9 |
+
# Lade ein vortrainiertes deutsches Modell (enthält Dependency Parser!)
|
10 |
+
nlp = spacy.load("de_core_news_md")
|
11 |
+
|
12 |
+
# Punkt als Infix definieren (damit 01.02.2022 getrennt wird)
|
13 |
+
infixes = list(nlp.Defaults.infixes) + [r"(?<=\d)\.(?=\d)"] # Punkt zwischen Zahlen trennen
|
14 |
+
infix_re = compile_infix_regex(infixes)
|
15 |
+
|
16 |
+
# Tokenizer mit neuer Infix-Regel setzen
|
17 |
+
nlp.tokenizer = Tokenizer(nlp.vocab, infix_finditer=infix_re.finditer)
|
18 |
+
|
19 |
+
# Entity Ruler für Datumsangaben hinzufügen
|
20 |
+
ruler = nlp.add_pipe("entity_ruler", before="ner")
|
21 |
+
|
22 |
+
patterns = [
|
23 |
+
{
|
24 |
+
"label": "DATE",
|
25 |
+
"pattern": [
|
26 |
+
{"SHAPE": "dd"}, {"ORTH": "."}, {"SHAPE": "dd"}, {"ORTH": "."}, {"SHAPE": "dddd"}
|
27 |
+
]
|
28 |
+
}
|
29 |
+
]
|
30 |
+
ruler.add_patterns(patterns)
|
31 |
+
|
32 |
+
# Verarbeite den Text
|
33 |
+
doc = nlp(text)
|
34 |
+
|
35 |
+
# Tokenisierung prüfen
|
36 |
+
print("Tokens:", [token.text for token in doc])
|
37 |
+
|
38 |
+
# Extrahiere erkannte Entitäten
|
39 |
+
for ent in doc.ents:
|
40 |
+
print(ent.text, ent.label_ , ent.start_char, ent.end_char)
|
41 |
+
|
42 |
+
# # Überprüfung der Abhängigkeiten zwischen Datum und Veranstaltung
|
43 |
+
# for token in doc:
|
44 |
+
# if token.ent_type_ == "DATE":
|
45 |
+
# # Überprüfe, ob das Datum mit einem Ereigniswort verbunden ist
|
46 |
+
# # Hier suchen wir nach Wörtern wie "findet", "statt", "Event" etc.
|
47 |
+
# for child in token.head.children:
|
48 |
+
# if child.dep_ in ["ROOT", "prep", "attr", "dobj"] and child.lemma_ in ["findet", "statt", "Veranstaltung", "Event"]:
|
49 |
+
# print(f"Das Datum {token.text} bezieht sich auf eine Veranstaltung.")
|
50 |
+
# break
|
51 |
+
|
52 |
+
displacy.serve(doc, style="dep")
|
src/nlp/experimental/ner/nu_ner.py
ADDED
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from gliner import GLiNER
|
2 |
+
|
3 |
+
def merge_entities(entities):
|
4 |
+
if not entities:
|
5 |
+
return []
|
6 |
+
merged = []
|
7 |
+
current = entities[0]
|
8 |
+
for next_entity in entities[1:]:
|
9 |
+
if next_entity['label'] == current['label'] and (next_entity['start'] == current['end'] + 1 or next_entity['start'] == current['end']):
|
10 |
+
current['text'] = text[current['start']: next_entity['end']].strip()
|
11 |
+
current['end'] = next_entity['end']
|
12 |
+
else:
|
13 |
+
merged.append(current)
|
14 |
+
current = next_entity
|
15 |
+
# Append the last entity
|
16 |
+
merged.append(current)
|
17 |
+
return merged
|
18 |
+
|
19 |
+
|
20 |
+
model = GLiNER.from_pretrained("numind/NuNerZero")
|
21 |
+
|
22 |
+
# NuZero requires labels to be lower-cased!
|
23 |
+
labels = ["organization", "initiative", "project"]
|
24 |
+
labels = [l.lower() for l in labels]
|
25 |
+
|
26 |
+
text = "At the annual technology summit, the keynote address was delivered by a senior member of the Association for Computing Machinery Special Interest Group on Algorithms and Computation Theory, which recently launched an expansive initiative titled 'Quantum Computing and Algorithmic Innovations: Shaping the Future of Technology'. This initiative explores the implications of quantum mechanics on next-generation computing and algorithm design and is part of a broader effort that includes the 'Global Computational Science Advancement Project'. The latter focuses on enhancing computational methodologies across scientific disciplines, aiming to set new benchmarks in computational efficiency and accuracy."
|
27 |
+
|
28 |
+
entities = model.predict_entities(text, labels)
|
29 |
+
|
30 |
+
entities = merge_entities(entities)
|
31 |
+
|
32 |
+
for entity in entities:
|
33 |
+
print(entity["text"], "=>", entity["label"])
|
src/nlp/experimental/ner/spacy_ner.py
ADDED
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import json
|
2 |
+
import random
|
3 |
+
|
4 |
+
import spacy
|
5 |
+
from spacy.training.example import Example
|
6 |
+
|
7 |
+
# Lade das deutsche Basismodell
|
8 |
+
nlp = spacy.load("de_core_news_sm")
|
9 |
+
|
10 |
+
with open('../annotations.json', encoding='utf-8') as f:
|
11 |
+
TRAINING_DATA = json.load(f)
|
12 |
+
|
13 |
+
# Wenn das Modell keine benutzerdefinierten Entitäten hat, füge die Entitätenerkennung hinzu
|
14 |
+
if "ner" not in nlp.pipe_names:
|
15 |
+
ner = nlp.create_pipe("ner")
|
16 |
+
nlp.add_pipe("ner", last=True)
|
17 |
+
else:
|
18 |
+
ner = nlp.get_pipe("ner")
|
19 |
+
|
20 |
+
# Füge die Entitäten hinzu
|
21 |
+
ner.add_label("START_DATE")
|
22 |
+
ner.add_label("END_DATE")
|
23 |
+
ner.add_label("DATE")
|
24 |
+
ner.add_label("OTHER")
|
25 |
+
|
26 |
+
# Trainingsdaten in Beispiele umwandeln
|
27 |
+
examples = []
|
28 |
+
for text, annotations in TRAINING_DATA:
|
29 |
+
doc = nlp.make_doc(text)
|
30 |
+
example = Example.from_dict(doc, annotations)
|
31 |
+
examples.append(example)
|
32 |
+
|
33 |
+
# Beginne mit dem Training
|
34 |
+
optimizer = nlp.begin_training()
|
35 |
+
for epoch in range(30): # Anzahl der Epochen
|
36 |
+
print(f"Epoch {epoch + 1}")
|
37 |
+
losses = {}
|
38 |
+
# Shuffle und trainiere das Modell mit den Beispielen
|
39 |
+
random.shuffle(examples)
|
40 |
+
# Trainiere mit den Beispielen
|
41 |
+
for example in examples:
|
42 |
+
nlp.update([example], drop=0.5, losses=losses)
|
43 |
+
print(losses)
|
44 |
+
|
45 |
+
# Speichere das trainierte Modell
|
46 |
+
nlp.to_disk("models/date_model")
|
47 |
+
|
src/nlp/experimental/ner/spacy_ner_rule_based.py
ADDED
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import spacy
|
2 |
+
from spacy.tokenizer import Tokenizer
|
3 |
+
from spacy.util import compile_prefix_regex, compile_suffix_regex, compile_infix_regex
|
4 |
+
from nltk import Tree
|
5 |
+
|
6 |
+
|
7 |
+
# Beispieltext mit Datum
|
8 |
+
text = "Das Event findet am 01.02.2022 statt."
|
9 |
+
|
10 |
+
# Lade ein leeres deutsches Modell
|
11 |
+
nlp = spacy.blank("de")
|
12 |
+
nlp.add_pipe('sentencizer')
|
13 |
+
|
14 |
+
# 1️⃣ Punkt als Suffix & Infix definieren (damit er zwischen Zahlen trennt)
|
15 |
+
suffixes = list(nlp.Defaults.suffixes) + [r"\."] # Punkt als Suffix hinzufügen
|
16 |
+
infixes = list(nlp.Defaults.infixes) + [r"(?<=\d)\.(?=\d)"] # Punkt zwischen Zahlen trennen
|
17 |
+
|
18 |
+
# Regex-Objekte kompilieren
|
19 |
+
suffix_re = compile_suffix_regex(suffixes)
|
20 |
+
infix_re = compile_infix_regex(infixes)
|
21 |
+
|
22 |
+
# Angepasste Tokenizer-Funktion setzen
|
23 |
+
nlp.tokenizer = Tokenizer(nlp.vocab, suffix_search=suffix_re.search, infix_finditer=infix_re.finditer)
|
24 |
+
# 2️⃣ Entity Ruler für Datumsangaben hinzufügen
|
25 |
+
ruler = nlp.add_pipe("entity_ruler")
|
26 |
+
|
27 |
+
patterns = [
|
28 |
+
{
|
29 |
+
"label": "DATE",
|
30 |
+
"pattern": [
|
31 |
+
{"SHAPE": "dd"}, {"ORTH": "."}, {"SHAPE": "dd"}, {"ORTH": "."}, {"SHAPE": "dddd"}
|
32 |
+
]
|
33 |
+
}
|
34 |
+
]
|
35 |
+
|
36 |
+
ruler.add_patterns(patterns)
|
37 |
+
|
38 |
+
# 3️⃣ Verarbeite den Text
|
39 |
+
doc = nlp(text)
|
40 |
+
|
41 |
+
# Prüfe Tokenisierung
|
42 |
+
print("Tokens:", [token.text for token in doc]) # Punkt soll nun getrennt sein
|
43 |
+
|
44 |
+
# Extrahiere erkannte Entitäten
|
45 |
+
for ent in doc.ents:
|
46 |
+
print(ent.text, ent.label_)
|
47 |
+
|
48 |
+
def to_nltk_tree(node):
|
49 |
+
if node.n_lefts + node.n_rights > 0:
|
50 |
+
return Tree(node.orth_, [to_nltk_tree(child) for child in node.children])
|
51 |
+
else:
|
52 |
+
return node.orth_
|
53 |
+
|
54 |
+
|
55 |
+
|
56 |
+
for sent in doc.sents:
|
57 |
+
print(sent.text)
|
58 |
+
print(to_nltk_tree(sent.root))
|