Files
AI/참고/trafilatura-master/tests/comparison_small.py

418 lines
15 KiB
Python
Raw Normal View History

2026-05-12 19:40:31 +09:00
"""
Compare extraction results with other libraries of the same kind.
"""
import logging
import os
import re
import sys
import time
from lxml import html # etree
try:
from cchardet import detect
except ImportError:
from charset_normalizer import detect
# import justext
# from readability import Document
from evaldata import EVAL_PAGES # ADDITIONAL_PAGES as EVAL_PAGES
from trafilatura import extract
try:
from trafilatura import baseline, html2txt
except ImportError:
print("Cannot import baseline, using simple version")
baseline = None
html2txt = None
#from trafilatura.htmlprocessing import prune_html
#from trafilatura.external import ReadabilityDocument, custom_justext, jt_stoplist_init
#from trafilatura.external import try_readability, sanitize_tree, custom_justext, jt_stoplist_init
#from trafilatura.utils import load_html, sanitize
#from trafilatura.xml import xmltotxt
logging.basicConfig(stream=sys.stdout, level=logging.ERROR) # logging.WARNING
TEST_DIR = os.path.abspath(os.path.dirname(__file__))
#JT_STOPLIST = jt_stoplist_init()
def trim(string):
'''Remove unnecessary spaces within a text string'''
if string is not None:
# delete newlines that are not related to punctuation or markup
# string = re.sub(r'(?<![p{P}>])\n', ' ', string)
# proper trimming
string = ' '.join(re.split(r'\s+', string.strip(' \t\n\r'), flags=re.UNICODE|re.MULTILINE))
string = string.strip()
return string
def load_document_binary(filename):
'''load mock page from samples'''
mypath = os.path.join(TEST_DIR, 'cache', filename)
if not os.path.isfile(mypath):
mypath = os.path.join(TEST_DIR, 'eval', filename)
#if not os.path.isfile(mypath):
# mypath = os.path.join(TEST_DIR, 'additional', filename)
with open(mypath, 'rb') as inputf:
htmlstring = inputf.read()
return htmlstring
def load_document_string(filename):
'''load mock page from samples'''
mypath = os.path.join(TEST_DIR, 'cache', filename)
if not os.path.isfile(mypath):
mypath = os.path.join(TEST_DIR, 'eval', filename)
#if not os.path.isfile(mypath):
# mypath = os.path.join(TEST_DIR, 'additional', filename)
try:
with open(mypath, 'r', encoding="utf-8") as inputf:
htmlstring = inputf.read()
# encoding/windows fix for the tests
except UnicodeDecodeError:
# read as binary
with open(mypath, 'rb') as inputf:
htmlbinary = inputf.read()
guessed_encoding = detect(htmlbinary)['encoding']
if guessed_encoding is not None:
try:
htmlstring = htmlbinary.decode(guessed_encoding)
except UnicodeDecodeError:
htmlstring = htmlbinary
else:
print('Encoding error')
return htmlstring
def run_html2txt(htmlstring):
if html2txt is not None:
return html2txt(htmlstring)
return ''
def run_baseline_2(htmlstring):
'''run bare text extraction within lxml'''
# binary/string as input tweak
try:
tree = html.fromstring(htmlstring)
except ValueError:
tree = html.fromstring(htmlstring.encode('utf8'))
result = None
# try json-ld
for elem in tree.xpath('//script[@type="application/ld+json"]'):
if elem.text and '"articleBody":' in elem.text:
mymatch = re.search(r'"articleBody":"(.+?)","', elem.text)
if mymatch:
result = mymatch.group(1)
result = result.replace('\\"', '"')
# result = trim(result)
break
if result is not None:
return result
#results = set()
resultlist = []
# iterate potentially relevant elements
for element in tree.iter('blockquote', 'code', 'p', 'pre', 'q'): # 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'
#if element.tag in ('h1', 'h2', 'h3', 'h4', 'h5', 'h6'):
# if not element.text or len(element.text) < 20:
# continue
# entry = element.text
#else:
entry = element.text_content()
#if entry not in results and len(entry) > 10:
resultlist.append(entry)
#results.add(entry)
# if nothing has been found
#if len(resultlist) < 1:
# for element in tree.iter('b', 'em', 'i', 'strong'):
# entry = element.text_content()
# #if entry not in results: # and len(entry) > 15:
# resultlist.append(entry)
# #results.add(entry)
#if len(resultlist) == 0:
# cleaned_tree = HTML_CLEANER.clean_html(tree)
# for element in tree.iter('div'):
# entry = element.text_content()
#if len(entry) > 15:
# resultlist.append(entry)
# #results.add(entry)
#print(len(resultlist))
result = '\n'.join(resultlist)
# result = sanitize(result)
# print(result)
return result
def run_baseline(htmlstring):
'''run bare text extraction within lxml'''
if baseline is not None:
_, result, _ = baseline(htmlstring)
return result
return run_baseline_2(htmlstring)
def run_trafilatura(htmlstring):
'''run trafilatura (without fallback) on content'''
result = extract(htmlstring, no_fallback=True, include_comments=False, include_tables=True, include_formatting=False) # , deduplicate=False
return result
#def run_justext(htmlstring):
# '''try with the generic algorithm justext'''
# valid = list()
# # paragraphs = justext.justext(htmlstring, stop_words, 50, 200, 0.1, 0.2, 0.5, 200, True) # stop_words
# tree = load_html(htmlstring)
# try:
# paragraphs = custom_justext(tree, JT_STOPLIST)
# for paragraph in [p for p in paragraphs if not p.is_boilerplate]:
# valid.append(paragraph.text)
# except UnicodeDecodeError:
# pass
# return sanitize(' '.join(valid))
def run_trafilatura_fallback(htmlstring):
'''run trafilatura (with fallback) on content'''
result = extract(htmlstring, no_fallback=False, include_comments=False, include_tables=True, include_formatting=False) # , deduplicate=False
return result
def run_trafilatura_precision(htmlstring):
'''run trafilatura with preference for precision'''
result = extract(htmlstring, no_fallback=False, favor_precision=True, include_comments=False, include_tables=True, include_formatting=False) # , deduplicate=False
return result
def run_trafilatura_recall(htmlstring):
'''run trafilatura with preference for recall'''
result = extract(htmlstring, no_fallback=False, favor_recall=True, include_comments=False, include_tables=True, include_formatting=False) # , deduplicate=False
return result
#def run_readability(htmlstring):
# '''try with the Python3 port of readability.js'''
# try:
# #doc = Document(htmlstring)
# cleaned_tree, text, _ = sanitize_tree(try_readability(load_html(htmlstring))
# return text
# #return xmltotxt(cleaned_tree, False, False)
# except Exception as err:
# print('Exception:', err)
# return ''
def evaluate_result(result, item):
'''evaluate result contents'''
true_positives = 0
false_negatives = 0
false_positives = 0
true_negatives = 0
# report if problematic
if len(item['with']) == 0 or len(item['with']) > 6:
print('counter', item)
if len(item['without']) == 0 or len(item['without']) > 6:
print('counter', item)
# internal report
#if result is None:
# print('None', item['file'])
#elif type(result) is not str:
# print('not str', item['file'])
# examine
if result is not None and isinstance(result, str):
# expected output
for to_include in item['with']:
if to_include in result:
true_positives += 1
else:
false_negatives += 1
# unwanted output
for to_exclude in item['without']:
if to_exclude in result:
false_positives += 1
else:
true_negatives += 1
# add up as bulk counts
else:
false_negatives += len(item['with'])
true_negatives += len(item['without'])
return true_positives, false_negatives, false_positives, true_negatives
def calculate_scores(mydict):
'''output weighted result score'''
tp, fn, fp, tn = mydict['true positives'], mydict['false negatives'], mydict['false positives'], mydict['true negatives']
precision = tp/(tp+fp)
recall = tp/(tp+fn)
accuracy = (tp+tn)/(tp+tn+fp+fn)
fscore = (2*tp)/(2*tp + fp + fn) # 2*((precision*recall)/(precision+recall))
return precision, recall, accuracy, fscore
template_dict = {'true positives': 0, 'false positives': 0, 'true negatives': 0, 'false negatives': 0, 'time': 0}
everything, nothing, html2txt_result, baseline_result, trafilatura_result, justext_result, trafilatura_fallback_result, trafilatura_precision, trafilatura_recall, readability_result = {}, {}, {}, {}, {}, {}, {}, {}, {}, {}
nothing.update(template_dict)
everything.update(template_dict)
html2txt_result.update(template_dict)
baseline_result.update(template_dict)
trafilatura_result.update(template_dict)
justext_result.update(template_dict)
trafilatura_fallback_result.update(template_dict)
trafilatura_precision.update(template_dict)
trafilatura_recall.update(template_dict)
readability_result.update(template_dict)
i = 0
for item in EVAL_PAGES:
if len(EVAL_PAGES[item]['file']) == 0:
continue
# print(EVAL_PAGES[item]['file'])
htmlstring = load_document_binary(EVAL_PAGES[item]['file'])
if htmlstring is None:
continue
# null hypotheses
tp, fn, fp, tn = evaluate_result('', EVAL_PAGES[item])
nothing['true positives'] += tp
nothing['false positives'] += fp
nothing['true negatives'] += tn
nothing['false negatives'] += fn
#tp, fn, fp, tn = evaluate_result(htmlstring, EVAL_PAGES[item])
#everything['true positives'] += tp
#everything['false positives'] += fp
#everything['true negatives'] += tn
#everything['false negatives'] += fn
# bare html2txt
#start = time.time()
#result = run_html2txt(htmlstring)
#html2txt_result['time'] += time.time() - start
#tp, fn, fp, tn = evaluate_result(result, EVAL_PAGES[item])
#html2txt_result['true positives'] += tp
#html2txt_result['false positives'] += fp
#html2txt_result['true negatives'] += tn
#html2txt_result['false negatives'] += fn
# bare lxml
# if baseline is not None:
start = time.time()
result = run_baseline(htmlstring)
baseline_result['time'] += time.time() - start
tp, fn, fp, tn = evaluate_result(result, EVAL_PAGES[item])
baseline_result['true positives'] += tp
baseline_result['false positives'] += fp
baseline_result['true negatives'] += tn
baseline_result['false negatives'] += fn
# trafilatura
start = time.time()
result = run_trafilatura(htmlstring)
trafilatura_result['time'] += time.time() - start
tp, fn, fp, tn = evaluate_result(result, EVAL_PAGES[item])
trafilatura_result['true positives'] += tp
trafilatura_result['false positives'] += fp
trafilatura_result['true negatives'] += tn
trafilatura_result['false negatives'] += fn
# justext / jparser
#start = time.time()
#result = run_justext(htmlstring)
#justext_result['time'] += time.time() - start
#tp, fn, fp, tn = evaluate_result(result, EVAL_PAGES[item])
#justext_result['true positives'] += tp
#justext_result['false positives'] += fp
#justext_result['true negatives'] += tn
#justext_result['false negatives'] += fn
# trafilatura + fallback
start = time.time()
result = run_trafilatura_fallback(htmlstring)
trafilatura_fallback_result['time'] += time.time() - start
tp, fn, fp, tn = evaluate_result(result, EVAL_PAGES[item])
trafilatura_fallback_result['true positives'] += tp
trafilatura_fallback_result['false positives'] += fp
trafilatura_fallback_result['true negatives'] += tn
trafilatura_fallback_result['false negatives'] += fn
# trafilatura + precision
#start = time.time()
#result = run_trafilatura_precision(htmlstring)
#trafilatura_precision['time'] += time.time() - start
#tp, fn, fp, tn = evaluate_result(result, EVAL_PAGES[item])
#trafilatura_precision['true positives'] += tp
#trafilatura_precision['false positives'] += fp
#trafilatura_precision['true negatives'] += tn
#trafilatura_precision['false negatives'] += fn
# trafilatura + recall
#start = time.time()
#result = run_trafilatura_recall(htmlstring)
#trafilatura_recall['time'] += time.time() - start
#tp, fn, fp, tn = evaluate_result(result, EVAL_PAGES[item])
#trafilatura_recall['true positives'] += tp
#trafilatura_recall['false positives'] += fp
#trafilatura_recall['true negatives'] += tn
#trafilatura_recall['false negatives'] += fn
# readability
#start = time.time()
#result = run_readability(htmlstring)
#readability_result['time'] += time.time() - start
#tp, fn, fp, tn = evaluate_result(result, EVAL_PAGES[item])
#readability_result['true positives'] += tp
#readability_result['false positives'] += fp
#readability_result['true negatives'] += tn
#readability_result['false negatives'] += fn
i += 1
print('number of documents:', i)
print('nothing')
print(nothing)
# print(calculate_f_score(nothing))
#print('everything')
#print(everything)
# print("precision: %.3f recall: %.3f accuracy: %.3f f-score: %.3f" % (calculate_scores(everything)))
#print('html2txt')
#print(html2txt_result)
#try:
# print("precision: %.3f recall: %.3f accuracy: %.3f f-score: %.3f" % (calculate_scores(html2txt_result)))
#except ZeroDivisionError:
# pass
print('baseline')
print(baseline_result)
try:
print("precision: %.3f recall: %.3f accuracy: %.3f f-score: %.3f" % (calculate_scores(baseline_result)))
except ZeroDivisionError:
pass
print('trafilatura')
print(trafilatura_result)
print("time diff.: %.2f" % (trafilatura_result['time'] / baseline_result['time']))
print("precision: %.3f recall: %.3f accuracy: %.3f f-score: %.3f" % (calculate_scores(trafilatura_result)))
#print('Justext')
#print(justext_result)
#print("time diff.: %.2f" % (justext_result['time'] / baseline_result['time']))
#print("precision: %.3f recall: %.3f accuracy: %.3f f-score: %.3f" % (calculate_scores(justext_result)))
print('trafilatura + fallback')
print(trafilatura_fallback_result)
print("time diff.: %.2f" % (trafilatura_fallback_result['time'] / baseline_result['time']))
print("precision: %.3f recall: %.3f accuracy: %.3f f-score: %.3f" % (calculate_scores(trafilatura_fallback_result)))
#print('trafilatura precision')
#print(trafilatura_precision)
#print("time diff.: %.2f" % (trafilatura_precision['time'] / baseline_result['time']))
#print("precision: %.3f recall: %.3f accuracy: %.3f f-score: %.3f" % (calculate_scores(trafilatura_precision)))
#print('trafilatura recall')
#print(trafilatura_recall)
#print("time diff.: %.2f" % (trafilatura_recall['time'] / baseline_result['time']))
#print("precision: %.3f recall: %.3f accuracy: %.3f f-score: %.3f" % (calculate_scores(trafilatura_recall)))
#print('readability')
#print(readability_result)
#print("time diff.: %.2f" % (readability_result['time'] / baseline_result['time']))
#print("precision: %.3f recall: %.3f accuracy: %.3f f-score: %.3f" % (calculate_scores(readability_result)))