spaCy entity_ruler, html
spaCy entity_ruler, html#
Demo-Notebook für Studierende der Lehrveranstaltung “Text Mining (dsci-txt)”, SS 2022, HAW Landshut
Autor: Johannes Busse, 2021-07-08, 2022-04-19
Lizenz: public domain / CC 0
import spacy
from spacy import displacy
# conda install -c conda-forge spacy-model-en_core_web_sm
# EN: nlp = spacy.load("en_core_web_sm")
nlp = spacy.load("de_core_news_sm")
Einlesen:
insbesondere Entity Ruler: https://spacy.io/usage/rule-based-matching#entityruler
phrase_matcher_attr = "LEMMA" # LOWER
ruler = nlp.add_pipe( "entity_ruler",
config={"phrase_matcher_attr": phrase_matcher_attr })
patterns = [
{"label": "CT", # irgend etwas, hier: Contolled Term
"pattern": "fettfreie Milch",
"id": "ex:Magermilch"},
{"label": "CT",
"pattern": "skim milk",
"id": "ex:Magermilch"},
{"label": "CT",
"pattern": "skimmed milk",
"id": "ex:Magermilch"},
{"label": "CT",
"pattern": "Milch",
"id": "ex:Milch"},
{"label": "CT",
"pattern": "fettfrei",
"id": "ex:fettfrei"},
{"label": "CT",
"pattern": "Kuriosität",
"id": "ex:Kuriosität"},
]
ruler.add_patterns(patterns)
import copy
def annotate_etree(nlp,
xml_element, # typically body, p, ul etc.
result_element,
elements_deepcopy = [
'a', # notwendig, denn ein a-Element zu analysieren ist sinnlos
'quote', # auch Zitate lassen wir unverändert
'pre' ], # und auch code
elements_analyse = ['p', 'li', 'body'],
lang = 'de'):
if xml_element.tag in elements_deepcopy:
# leave element untouched: simply deepcopy into result tree, no further recursion required
result_xml_element = copy.deepcopy(xml_element)
result_xml_element.tail = xml_element.tail
result_element.append(result_xml_element)
# print(f"deepcopy {xml_element.tag}")
else:
# process xml_element with/out NLP
text = xml_element.text # yields a copy
# allocate new xml_element in result tree
result_xml_element = etree.SubElement(result_element, xml_element.tag, attrib = xml_element.attrib )
if not(xml_element.tag in elements_analyse) or len(text) == 0:
# annotation not in positive list, or no text available
result_xml_element.text = text
else:
# analyse and annotate xml_element
doc = nlp(text)
displacy.render(doc, style="ent", jupyter=True)
ent_position_tuples = [ (e.start_char, e.end_char) for e in doc.ents]
# debug print("ent_position_tuples: ", ent_position_tuples)
# create flat list, c.f. e.g. https://stackoverflow.com/questions/10632839/transform-list-of-tuples-into-a-flat-list-or-a-matrix/35228431
ent_position_flatlist = list(sum (ent_position_tuples, ())) # warum geht das?
#ent_position_flatlist = [item for sublist in ent_position_tuples for item in sublist] # warum geht das?
# add start and end index of total string
ent_positions = [0] + ent_position_flatlist + [len(text)]
# debug print("ent_positions: ", ent_positions)
# string between start of text and first entity
chunk = text[ent_positions[0]:ent_positions[1]]
# debug print(f"\n\ntrailing: {chunk}", end="")
result_xml_element.text = chunk
for i in range(len(doc.ents)):
ent = doc.ents[i]
# debug print(f"\n--- i: {i}, ent: {ent.text} ---")
# entity
#chunk = text[ent_positions[2*i+1]:ent_positions[2*i+2]]
chunk = doc.ents[i].text
# debug print(f"\nenty: {chunk}", end = "")
result_element_tag = etree.SubElement(result_xml_element, "a", attrib = {"href": ent.ent_id_, "label": ent.label_})
result_element_tag.text = chunk
# text immediately following the entity
chunk = text[ent_positions[2*i+2]:ent_positions[2*i+3]]
# debug print(f"\ntail: {chunk}", end = "")
result_element_tag.tail = chunk
for n in xml_element.findall("*"):
annotate_etree(nlp,
n,
result_xml_element,
elements_deepcopy = elements_deepcopy,
elements_analyse = elements_analyse,
lang = lang)
par_1 = "Einige Fakten über fettfreie Milch:"
par_2 = "In England heißt diese Kuriosität skimmed milk, in der USA skim milk."
par_3 = """Zur Einstellung des Fettgehalts wird die Milch zunächst
in Rahm, Magermilch und Nichtmilchbestandteile getrennt."""
source_html_string = f"""<html>
<head>
<title>fettfreie Milch</title>
</head>
<body>(ab hier wollen wir annotieren)
<ul>{par_1}
<li>{par_2}</li>
</ul>
<quote>(In Zitaten bitte keine Annotationen)
<p>{par_3}</p>
</quote>
Das war alles über Milch!
</body>
</html>"""
# https://lxml.de/
from lxml import etree
parser = etree.XMLParser(remove_blank_text=False) # strip e.g. indentation blanks
source_html = etree.fromstring(source_html_string, parser)
source_body = source_html.find(".//body")
print(etree.tostring(source_html, pretty_print=True, encoding="unicode"))
print("elements in source_body:", source_body.text, [(elem.tag, elem.text) for elem in source_body.findall("*") ] )
<html>
<head>
<title>fettfreie Milch</title>
</head>
<body>(ab hier wollen wir annotieren)
<ul>Einige Fakten über fettfreie Milch:
<li>In England heißt diese Kuriosität skimmed milk, in der USA skim milk.</li>
</ul>
<quote>(In Zitaten bitte keine Annotationen)
<p>Zur Einstellung des Fettgehalts wird die Milch zunächst
in Rahm, Magermilch und Nichtmilchbestandteile getrennt.</p>
</quote>
Das war alles über Milch!
</body>
</html>
elements in source_body: (ab hier wollen wir annotieren)
[('ul', 'Einige Fakten über fettfreie Milch:\n '), ('quote', '(In Zitaten bitte keine Annotationen)\n ')]
result_html = etree.fromstring("""
<html>
<head>
<title>neu aufgebautes html</title>
</head>
</html>
""")
annotate_etree(nlp, source_body, result_html)
print(etree.tostring(result_html, pretty_print=True, encoding="unicode"))
/home/dsci/miniconda3/lib/python3.9/site-packages/spacy/displacy/__init__.py:205: UserWarning: [W006] No entities to visualize found in Doc object. If this is surprising to you, make sure the Doc was processed using a model that supports named entity recognition, and check the `doc.ents` property manually if necessary.
warnings.warn(Warnings.W006)
(ab hier wollen wir annotieren)
In
England
LOC
heißt diese
Kuriosität
CT
skimmed milk, in der
USA
LOC
skim milk.
<html>
<head>
<title>neu aufgebautes html</title>
</head>
<body>(ab hier wollen wir annotieren)
<ul>Einige Fakten über fettfreie Milch:
<li>In <a href="" label="LOC">England</a> heißt diese <a href="ex:Kuriosität" label="CT">Kuriosität</a> skimmed milk, in der <a href="" label="LOC">USA</a> skim milk.</li></ul><quote>(In Zitaten bitte keine Annotationen)
<p>Zur Einstellung des Fettgehalts wird die Milch zunächst
in Rahm, Magermilch und Nichtmilchbestandteile getrennt.</p>
</quote>
Das war alles über Milch!
</body></html>