Spaces:
Build error
Build error
File size: 14,689 Bytes
69e8a15 f4f5cfb 69e8a15 f4f5cfb 69e8a15 f4f5cfb 69e8a15 f4f5cfb 69e8a15 f4f5cfb 69e8a15 f4f5cfb 69e8a15 f4f5cfb 69e8a15 f4f5cfb 69e8a15 f4f5cfb 69e8a15 f4f5cfb 69e8a15 f4f5cfb 69e8a15 f4f5cfb 69e8a15 f4f5cfb 69e8a15 f4f5cfb 69e8a15 f4f5cfb 69e8a15 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 |
import glob
import json
import logging
import os
import pickle
import string
from pathlib import Path
import lxml
import lxml.html
import yaml
from bs4 import BeautifulSoup, Tag
from lxml import etree
from progress.bar import Bar
from transformers import MarkupLMFeatureExtractor
from consts import id2label, label2id
from processor import NewsProcessor
from utils import TextUtils
logging.basicConfig(level=logging.INFO)
class NewsDatasetBuilder:
__processor: NewsProcessor = None
__utils: TextUtils = None
def __init__(self):
self.__processor = NewsProcessor()
self.__utils = TextUtils()
logging.debug('NewsDatasetBuilder Sınıfı oluşturuldu')
def __get_dom_tree(self, html):
"""
Verilen HTML içeriğinden bir DOM ağacı oluşturur.
Args:
html (str): Oluşturulacak DOM ağacının temelini oluşturacak HTML içeriği.
Returns:
ElementTree: Oluşturulan DOM ağacı.
"""
html = self.__processor.encode(html)
x = lxml.html.fromstring(html)
dom_tree = etree.ElementTree(x)
return dom_tree
@staticmethod
def __get_config(config_file_path):
"""
Belirtilen konfigürasyon dosyasını okuyarak bir konfigürasyon nesnesi döndürür.
Args:
config_file_path (str): Okunacak konfigürasyon dosyasının yolunu belirtir.
Returns:
dict: Okunan konfigürasyon verilerini içeren bir sözlük nesnesi.
"""
with open(config_file_path, "r") as yaml_file:
_config = yaml.load(yaml_file, Loader=yaml.FullLoader)
return _config
def __non_ascii_equal(self, value, node_text):
"""
Verilen değer ve düğüm metni arasında benzerlik kontrolü yapar.
Benzerlik için cosine similarity kullanılır. Eğer benzerlik oranı %70'in üzerinde ise bu iki metin benzer kabul edilir.
Args:
value (str): Karşılaştırılacak değer.
node_text (str): Karşılaştırılacak düğüm metni.
Returns:
bool: Değer ve düğüm metni arasında belirli bir benzerlik eşiği üzerinde eşleşme durumunda True, aksi halde False.
"""
value = self.__utils.clean_format_str(value)
# value = re.sub(r"[^a-zA-Z0-9.:]", "", value, 0)
value_nopunct = "".join([char for char in value if char not in string.punctuation])
node_text = self.__utils.clean_format_str(node_text)
# node_text = re.sub(r"[^a-zA-Z0-9.:]", "", node_text, 0)
node_text_nopunct = "".join([char for char in node_text if char not in string.punctuation])
sim = self.__utils.cosine(value_nopunct, node_text_nopunct)
return sim > 0.7 # value.strip() == node_text.strip()
def __get_truth_value(self, site_config, html, label):
"""
Belirtilen site'ya ait konfigürasyondan label parametresi ile gönderilen tarih, başlık, spot (açıklama) ve içerik
alanlarının konfigürasyona göre belirtilen css-query ile bulunup çıkartılır ve döndürülür.
Args:
site_config (dict): Site konfigürasyon verilerini içeren bir sözlük.
html (str): İşlenecek HTML içeriği.
label (str): Etiket adı.
Returns:
list: Etiket adına bağlı doğruluk değerlerini içeren bir liste.
"""
result = []
tree = BeautifulSoup(html, 'html.parser')
qs = site_config["css-queries"][label]
for q in qs:
found = tree.select(q)
if found:
el = found[0]
for c in el:
if type(c) is Tag:
c.decompose()
if el.name == "meta":
text = el.attrs["content"]
else:
text = el.text
if text:
text = self.__utils.clean_format_str(text)
text = text.strip()
result.append(text)
return result
def __annotation(self, html, site_config, feature_extractor):
"""
Verilen HTML içeriği, site konfigürasyonu ve özellik çıkarıcısıyla ilişkili bir etiketleme yapar.
Bu kısımda sitelerin önceden hazırladığımız css-query leri ile ilgili html bölümlerini bulup,
bunu kullanarak otomatik olarak veri işaretlemesi yapılmasını sağlamaktayız.
Args:
html (str): Etiketleme işlemine tabi tutulacak HTML içeriği.
site_config (dict): Site konfigürasyon verilerini içeren bir sözlük.
feature_extractor (function): Özellik çıkarıcısı fonksiyonu.
Returns:
dict or None: Etiketleme sonucunu içeren bir sözlük nesnesi veya None.
"""
annotations = dict()
for _id in id2label:
if _id == -100:
continue
label = id2label[_id]
# Önceden belirlediğimiz tarih (date), başlık (title), spot (description) ve içerik (content),
# alanlarını site konfigürasyonuna göre çıkartıyoruz
annotations[label] = self.__get_truth_value(site_config, html, label)
if len(annotations["content"]) == 0:
return None
# MarkupLMFeatureExtractor ile sayfadaki node text ve xpath'leri çıkarıyoruz.
# MarkupLMFeatureExtractor html içeriğindeki head > meta kısımlarını dikkate almaz
# sadece body elementinin altındaki node'ları ve xpath'leri çıkarır
encoding = feature_extractor(html)
labels = [[]]
nodes = [[]]
xpaths = [[]]
# MarkupLMFeatureExtractor tarafından çıkarılan her bir node'u annotations fonksiyonu ile otomatik olarak
# bulduğumuz bölümleri node'ların textleri ile karşılaştırıp otomatik olarak veri işaretlemesi yapıyoruz.
for idx, node_text in enumerate(encoding['nodes'][0]):
xpath = encoding.data["xpaths"][0][idx]
match = False
for label in annotations:
for mark in annotations[label]:
if self.__non_ascii_equal(mark, node_text):
node_text = self.__utils.clean_format_str(node_text)
labels[0].append(label2id[label])
nodes[0].append(node_text)
xpaths[0].append(xpath)
match = True
if not match:
labels[0].append(label2id["other"])
nodes[0].append(node_text)
xpaths[0].append(xpath)
item = {'nodes': nodes, 'xpaths': xpaths, 'node_labels': labels}
return item
def __transform_file(self, name, file_path, output_path):
"""
Belirtilen dosyayı dönüştürerek temizlenmiş HTML içeriğini yeni bir dosyaya kaydeder.
Args:
name (str): Dosyanın adı.
file_path (str): Dönüştürülecek dosyanın yolunu belirtir.
output_path (str): Temizlenmiş HTML içeriğinin kaydedileceği dizin yolunu belirtir.
Returns:
None
Raises:
IOError: Dosya veya dizin oluşturma hatası durumunda fırlatılır.
"""
with open(file_path, 'r') as html_file:
html = html_file.read()
clean_html = self.__processor.transform(html)
file_dir = f"{output_path}/{name}"
file_name = Path(file_path).name
if not os.path.exists(file_dir):
os.makedirs(file_dir)
file_path = f"{file_dir}/{file_name}"
with open(file_path, 'w', encoding='utf-8') as output:
output.write(clean_html)
def __transform(self, name, raw_html_path, output_path, count):
"""
Belirtilen site için, ham HTML dosyalarının yolunu, çıkış dizin yolunu ve sayımı kullanarak HTML dönüştürme işlemini gerçekleştirir.
Args:
name (str): İşlem yapılacak site adı.
raw_html_path (str): Ham HTML dosyalarının yolunu belirtir.
output_path (str): Dönüştürülmüş HTML dosyalarının kaydedileceği dizin yolunu belirtir.
count (int): İşlem yapılacak dosya sayısı.
Returns:
None
Raises:
IOError: Dosya veya dizin oluşturma hatası durumunda fırlatılır.
"""
files_path = f"{raw_html_path}/{name}"
lfs = glob.glob(f"{files_path}/*.html")
_max = count # len(lfs)
logging.info(f"{name} html transform started.\n")
with Bar(f'{name} Transforming html files', max=_max,
suffix='%(percent).1f%% | %(index)d | %(remaining)d | %(max)d | %(eta)ds') as bar:
i = 0
for lf in lfs:
try:
self.__transform_file(name, lf, output_path)
bar.next()
i = i + 1
if i > count:
break
except Exception as e:
logging.error(f"An exception occurred id: {lf} error: {str(e)}")
bar.finish()
logging.info(f"{name} html transform completed.\n")
def __auto_annotation(self, name, config_path, meta_path, clean_html_path, output_path, count):
"""
Belirtilen site için, yapılandırma dosyası yolunu, meta dosya yolunu, temizlenmiş HTML dosyalarının yolunu,
çıkış dizin yolunu ve işlem yapılacak dosya sayısını kullanarak otomatik etiketleme işlemini gerçekleştirir.
Args:
name (str): İşlem yapılacak site adı.
config_path (str): Yapılandırma dosyasının yolunu belirtir.
meta_path (str): Meta dosyasının yolunu belirtir.
clean_html_path (str): Temizlenmiş HTML dosyalarının yolunu belirtir.
output_path (str): Oluşturulan veri setinin kaydedileceği dizin yolunu belirtir.
count (int): İşlem yapılacak dosya sayısı.
Returns:
None
Raises:
IOError: Dosya veya dizin oluşturma hatası durumunda fırlatılır.
"""
config = self.__get_config(config_path)
annotation_config = config[name]
feature_extractor = MarkupLMFeatureExtractor()
dataset = []
with open(f'{meta_path}/{name}.json', 'r') as json_file:
links = json.load(json_file)
_max = count # len(links)
logging.info(f"{name} auto annotation started.\n")
with Bar(f'{name} Building DataSet', max=_max,
suffix='%(percent).1f%% | %(index)d | %(remaining)d | %(max)d | %(eta)ds') as bar:
i = 0
for link in links:
try:
_id = link["id"]
url = link["url"]
i = i + 1
html_file_path = f"{clean_html_path}/{name}/{_id}.html"
if not os.path.exists(html_file_path):
continue
with open(html_file_path, 'r') as html_file:
html = html_file.read()
item = self.__annotation(html, annotation_config, feature_extractor)
if item:
dataset.append(item)
bar.next()
if len(dataset) >= _max:
break
except Exception as e:
logging.info(f"An exception occurred id: {url} error: {str(e)}")
bar.finish()
pickle_file_path = f'{output_path}/{name}.pickle'
logging.info(f"Writing the dataset for {name}")
with open(pickle_file_path, "wb") as f:
pickle.dump(dataset, f)
json_file_path = f'{output_path}/{name}.json'
with open(json_file_path, 'w', encoding='utf-8') as f:
json.dump(dataset, f, ensure_ascii=False, indent=4)
def run(self, name, config_path, meta_path, raw_html_path, clean_html_path, dataset_path, count):
"""
Belirtilen site için, yapılandırma dosyası yolunu, meta dosya yolunu, ham HTML dosyalarının yolunu,
temizlenmiş HTML dosyalarının yolunu, veri seti dosyasının yolunu ve işlem yapılacak dosya sayısını kullanarak
veri seti oluşturma işlemini gerçekleştirir.
Args:
name (str): İşlem yapılacak site adı.
config_path (str): Yapılandırma dosyasının yolunu belirtir.
meta_path (str): Meta dosyasının yolunu belirtir.
raw_html_path (str): Ham HTML dosyalarının yolunu belirtir.
clean_html_path (str): Temizlenmiş HTML dosyalarının yolunu belirtir.
dataset_path (str): Oluşturulan veri setinin kaydedileceği dizin yolunu belirtir.
count (int): İşlem yapılacak dosya sayısı.
Returns:
None
"""
logging.info(f"{name} build dataset started.")
self.__transform(name=name,
raw_html_path=raw_html_path,
output_path=clean_html_path,
count=count)
self.__auto_annotation(name=name,
config_path=config_path,
meta_path=meta_path,
clean_html_path=clean_html_path,
output_path=dataset_path,
count=count)
logging.info(f"{name} build dataset completed.")
if __name__ == '__main__':
# sites = ["aa", "aksam", "cnnturk", "cumhuriyet", "ensonhaber", "haber7", "haberglobal", "haberler", "haberturk",
# "hurriyet", "milliyet", "ntv", "trthaber"]
sites = ["aa", "aksam", "cnnturk", "cumhuriyet", "ensonhaber", "haber7", "haberglobal", "haberler", "haberturk",
"hurriyet"]
count_per_site = 10
total = count_per_site * len(sites)
builder = NewsDatasetBuilder()
_config_path = "../annotation-config.yaml"
_meta_path = "../data/meta"
_raw_html_path = "../data/html/raw"
_clean_html_path = "../data/html/clean"
_dataset_path = f"../data/dataset/{total}"
for name in sites:
builder.run(name=name,
config_path=_config_path,
meta_path=_meta_path,
raw_html_path=_raw_html_path,
clean_html_path=_clean_html_path,
dataset_path=_dataset_path,
count=count_per_site)
|