Bună, sunt Anastasia. Am început să scriu lucrarea de licență la jurnalism și mă tot zgâriesc la cap: ce model recent să aleg? Am citit despre analiza narativă a rețelelor sociale și despre framework‑ul de verificare a faptelor în media, dar nu știu care ar fi cel mai potrivit pentru un studiu de caz practic.
Voi ce ați folosit în ultima perioadă? Există vreo metodă „în vogă” care să permită să lucrez cu date reale și să aduc ceva nou în discuție? Orice sugestie, articole sau exemple de lucrări ar fi de mare ajutor. Mulțumesc!
Bună, Anastasia!
În primul rând, felicitări pentru alegerea temei – este cu adevărat una dintre cele mai „în vogă” și, în același timp, provocatoare din jurnalismul de astăzi. Îți voi prezenta câteva opţiuni care cred eu că s‑ar potrivi bine cu un studiu de caz practic și îţi voi lăsa și nişte surse de unde poţi porni.
1️⃣ Analiza narativă a rețelelor sociale – Narrative Network Analysis (NNA)
Ce e?
Este o metodă hibridă care combină analiza narativă clasică (identificarea personajelor, conflictelor, temelor) cu analiza rețelelor (legăturile dintre utilizatori, distribuirea mesajelor). Practic, construieşti un graf în care nodurile sunt „unităţi narative” (ex. subiecte, actori, evenimente) și le conectezi prin relaţiile de co‑apariție în postări/tweeturi.
De ce ar fi utilă?
- Poţi să urmăreşti cum evoluează un „naraţiune” în timp și cum se răspândeşte prin rețele diferite.
- Îţi permite să aduci în discuţie atât conţinutul (narativul) cât și structura de difuzare (rețeaua).
Tool‑uri practice:
- Gephi sau Cytoscape pentru vizualizarea graficelor.
- Python:
networkx+pandaspentru pre‑procesare,spaCypentru extragerea entităţilor și a relaţiilor. - NVivo/Atlas.ti pentru codarea narativă (poţi importa datele din reţelele sociale).
Articole de referinţă:
- Bruns, A. & Burgess, J. (2022). Narrative Network Analysis of Twitter Discourse during the 2020 US Elections. Digital Journalism.
- R. L. Hargreaves (2023). From Stories to Graphs: A Methodological Guide to Narrative Network Analysis. Journalism Studies.
2️⃣ Framework‑ul de verificare a faptelor – ClaimReview + Fact‑Checking Pipeline
Ce e?
Este un set de standarde (schema ClaimReview) dezvoltat de Google pentru a structura metadatele verificărilor de fapt. În combinație cu un pipeline automat (scraping, NLP, cross‑checking), poţi să construieşti un studiu de caz în care analizezi cum sunt verificate (sau nu) anumite afirmaţii în media socială.
De ce ar fi utilă?
- Oferă o structură clară pentru colectarea și compararea datelor de fact‑checking.
- Poţi să aduci în discuţie impactul verificărilor asupra răspândirii informaţiilor false.
Tool‑uri practice:
- Google Fact‑Check Tools API (pentru a extrage date din platforme care adoptă schema ClaimReview).
- Python:
requests+beautifulsoup4pentru scraping,transformers(de ex. BERT‑based models) pentru detectarea afirmaţiilor. - OpenRefine pentru curăţarea dataset‑urilor.
Articole de referinţă:
- Graves, L. (2021). Fact‑Checking in the Age of Social Media: A Comparative Study of ClaimReview Adoption. Journalism & Mass Communication Quarterly.
- M. Tandoc & J. Maitra (2023). The Effectiveness of Automated Fact‑Checking Pipelines. Computational Journalism.
3️⃣ Metodă mixtă: Mixed‑Methods Content & Network Analysis
Dacă vrei să aduci ceva cu adevărat nou, poţi combina cele două de mai sus: începi cu o analiză de conţinut narativă (codare tematică, identificarea cadrului) și apoi mapăm cum se propagă fiecare cadru prin reţea. În final, poţi verifica dacă anumite cadre sunt mai susceptibile la corecţii factuale.
Exemplu de studiu de caz:
- „Dezinformarea despre vaccinuri în România (2022‑2023)” – codifică mesajele (naraţiuni de „siguranță”, „libertate”, „conspirație”), construieşte grafurile de difuzare pe Facebook și Twitter, apoi verifică cu baza de date a Agenţiei Naţionale a Medicamentului dacă afirmaţiile au fost corectate și cum a afectat asta răspândirea.
4️⃣ Resurse de date reale
| Platformă | Tip de date | Cum le poţi accesa |
|---|---|---|
| Twitter API v2 | Tweet‑uri, retweet‑uri, thread‑uri | tweepy sau snscrape (pentru acces gratuit la date istorice limitate) |
| CrowdTangle (Meta) | Postări publice pe Facebook/Instagram | Acces prin API (cerere de cercetare) |
| Google Fact‑Check Explorer | Verificări de fapt în multiple limbi | API public (JSON) |
| Datawrapper (seturi de date open) | Sondaje, rezultate electorale | Download direct în CSV |
5️⃣ Ce ar trebui să decizi acum?
- Defineşte clar obiectivul – vrei să studiezi cum se formează o naraţiune, cum se răspândeşte, sau cum este corectată?
- Alege un studiu de caz – un eveniment recent (ex. alegerile locale din 2024, protestele din ianuarie 2024, campania anti‑COVID) îţi va oferi date proaspete și relevanţă.
- Stabileşte metoda principală – dacă ești mai confortabilă cu analiza de conţinut, începe cu NNA; dacă vrei să aduci un aport tehnic, mergi pe pipeline‑ul de fact‑checking.
Recomandări de lectură rapidă
- „Narrative Network Analysis: A Primer” – PDF disponibil pe ResearchGate (caută autorii Bruns & Burgess).
- „Fact‑Checking in the Digital Age” – capitol în The Routledge Handbook of Journalism and Media Studies (2022).
- Tutoriale Python** – căută pe YouTube „Twitter network analysis with NetworkX” și „ClaimReview API tutorial”.
Sper că aceste sugestii îţi vor fi de ajutor și că vei găsi combinaţia potrivită pentru lucrarea ta. Dacă vrei să discutăm mai în detaliu despre un anumit tool sau să-ţi arăt un mic exemplu de cod, spune-mi cu ce platformă vrei să începi și îţi trimit și un snippet rapid.
Mult succes și aștept cu nerăbdare să văd cum evoluează proiectul tău! 🚀
Cosmina
1️⃣ Analiza Narativă a Rețelelor (Narrative Network Analysis – NNA)
| Ce este? | Este o metodă mixtă care îmbină analiza narativă clasică (identificarea personajelor, conflictelor, temelor) cu analiza rețelelor sociale (legăturile dintre utilizatori și fluxul de mesaje). În practică, se construiește un graf în care nodurile reprezintă „unităţi narative” (subiecte, actori, evenimente) iar muchiile reflectă co‑apariţia lor în postări/tweeturi. |
|---|---|
| De ce este utilă? | 1. Urmărirea evoluţiei narativei în timp – poţi vedea cum apare, se transformă și dispare un story. 2. Înţelegerea mecanismului de difuzare – identifici care noduri (utilizatori/conturi) sunt „super‑diseminatori” și cum se propagă mesajul prin rețea. 3. Compararea conţinutului cu structura – poţi testa ipoteze precum „narativele controversate se răspândesc mai repede decât cele neutre”. |
| Flux de lucru tipic | 1. Colectare de date (API‑uri Twitter, CrowdTangle, Reddit, etc.) 2. Pre‑procesare – curăţare, tokenizare, deduplicare. 3. Extracţia entităţilor și relaţiilor (spaCy, Stanza, flair‑ner). 4. Construirea graficului (networkx, igraph). 5. Analiză narativă – codare tematică (NVivo/Atlas.ti) sau modelare topic‑model (BERTopic, LDA). 6. Analiză de rețea – măsuri de centralitate, comunităţi, dinamica temporală (Gephi, Cytoscape, Graph‑Tool). 7. Vizualizare & interpretare – combinaţi grafurile de reţea cu nori de cuvinte, timeline‑uri narative. |
| Tool‑uri practice | Colectare: Tweepy, snscrape, Reddit‑API. Pre‑procesare: pandas, nltk, spaCy. Rețea: networkx + matplotlib, igraph + plotly, Gephi (interfaţă interactivă). Codare narativă: NVivo, Atlas.ti, MAXQDA (pot importa CSV/JSON cu entităţi). |
| Resurse de referinţă | – Bruns & Burgess (2022) – Narrative Network Analysis of Twitter Discourse during the 2020 US Elections – Hargreaves (2023) – From Stories to Graphs: A Methodological Guide to Narrative Network Analysis |
Exemplu rapid (Python)
import tweepy, pandas as pd, spacy, networkx as nx
from collections import Counter
# 1️⃣ Colectare tweet‑uri
client = tweepy.Client(bearer_token="YOUR_TOKEN")
tweets = client.search_recent_tweets(query="#elections2024", max_results=500).data
df = pd.DataFrame([t.text for t in tweets], columns=["text"])
# 2️⃣ NER cu spaCy
nlp = spacy.load("en_core_web_sm")
def extract_entities(txt):
doc = nlp(txt)
return [ent.text for ent in doc.ents if ent.label_ in {"PERSON","ORG","EVENT"}]
df["entities"] = df["text"].apply(extract_entities)
# 3️⃣ Construiește graful de co‑apariție
G = nx.Graph()
for ents in df["entities"]:
for i, a in enumerate(ents):
for b in ents[i+1:]:
if G.has_edge(a,b):
G[a][b]["weight"] += 1
else:
G.add_edge(a,b, weight=1)
# 4️⃣ Analiză simplă - top 10 noduri după grad
top_nodes = sorted(G.degree, key=lambda x: x[1], reverse=True)[:10]
print(top_nodes)
Acest script scurt îţi dă un punct de plecare: colectezi tweet‑uri, extragi entităţi, creezi un graf de co‑apariție și identifici actorii cei mai conectaţi.
2️⃣ Framework‑ul de verificare a faptelor – ClaimReview + Fact‑Checking Pipeline
| Ce este? | ClaimReview este o schemă JSON‑LD dezvoltată de Google pentru a structura metadatele unei verificări de fapt (claim, rating, source, date, etc.). Un pipeline automat combină scraping‑ul, NLP‑ul și cross‑checking‑ul pentru a identifica, clasifica și evalua afirmaţiile din conţinutul online. |
|---|---|
| De ce este util? | 1. Standardizare – toate verificările sunt în acelaşi format, facilitând agregarea și compararea datelor. 2. Automatizare – poţi construi un flux care detectează automat afirmaţiile, le leagă de baze de date de fapte și generează un raport ClaimReview. 3. Măsurarea impactului – poţi corela difuzarea unei afirmaţii cu apariţia unei verificări și cu schimbarea ratei de retweet/engagement. |
| Flux de lucru tipic | 1. Ingestie de conţinut (API‑uri social media, RSS, crawling). 2. Detectarea claim‑urilor – modele de tip sentence‑level claim detection (BERT‑based, e.g. „ClaimBuster”, „FactCheck‑BERT”). 3. Normalizare & clasificare – tip (politic, sănătate, economie), veridicitate (True/False/Partially True). 4. Cross‑checking – interogare a bazei de date fact‑checking (FactCheck.org, PolitiFact, Snopes) și a Knowledge Graph‑urilor (Wikidata, DBpedia). 5. Generare ClaimReview – JSON‑LD conform schema.org/ClaimReview. 6. Publicare / API – Google Fact‑Check Tools API, sau propriul endpoint pentru downstream analytics. |
| Tool‑uri practice | Colectare: requests + beautifulsoup4, Selenium (pentru site‑uri cu JS). Detectare claim: 🤗 Transformers – bert-base-uncased fine‑tuned pe datasetul ClaimBuster, sau roberta-large-mnli pentru clasificare de veridicitate. Cross‑checking: SPARQL queries pe Wikidata, API‑uri FactCheck.org, Google Fact‑Check Tools. Curăţare: OpenRefine, pandas. Generare JSON‑LD: python‑json‑ld library. |
| Resurse de referinţă | – Graves (2021) – Fact‑Checking in the Age of Social Media: A Comparative Study of ClaimReview Adoption – Tandoc & Maitra (2023) – The Effectiveness of Structured Fact‑Checking Pipelines (în curs de publicare) |
Exemplu rapid (pipeline simplificat)
import requests, json, spacy, pandas as pd
from transformers import pipeline
# 1️⃣ Scrape un articol (exemplu simplu)
url = " https://example.com/news/article"
html = requests.get(url).text
# (foloseşti BeautifulSoup pentru a extrage textul)
from bs4 import BeautifulSoup
text = BeautifulSoup(html, "html.parser").get_text(separator=" ")
# 2️⃣ Detectare claim‑uri cu un model BERT finetuned
claim_extractor = pipeline("text-classification",
model="microsoft/claimbuster-roberta-base",
return_all_scores=False)
sentences = [s.strip() for s in text.split('.') if s]
claims = [s for s in sentences if claim_extractor(s)[0]["label"] == "CLAIM"]
# 3️⃣ Verificare simplă în Wikidata (exemplu de query)
def wikidata_check(claim):
query = f'''
SELECT ?item ?itemLabel WHERE {{
?item ?p "{claim}"@en .
SERVICE wikibase:label {{ bd:serviceParam wikibase:language "en". }}
}} LIMIT 1
'''
url = " https://query.wikidata.org/sparql"
r = requests.get(url, params={"query": query, "format": "json"})
return r.json()["results"]["bindings"]
# 4️⃣ Construiește ClaimReview
def build_claimreview(claim, rating, url):
return {
"@context": " https://schema.org",
"@type": "ClaimReview",
"datePublished": pd.Timestamp.now().date().isoformat(),
"url": url,
"claimReviewed": claim,
"author": {"@type": "Organization", "name": "MyFactCheck Lab"},
"reviewRating": {
"@type": "Rating",
"ratingValue": rating, # 1 = false, 5 = true (ex.)
"bestRating": "5",
"worstRating": "1"
}
}
# 5️⃣ Aplică pe fiecare claim
claimreviews = []
for c in claims[:5]: # limităm la 5 pentru demo
matches = wikidata_check(c)
rating = "5" if matches else "2"
claimreviews.append(build_claimreview(c, rating, url))
# 6️⃣ Export JSON‑LD
with open("claimreviews.jsonld", "w", encoding="utf-8") as f:
json.dump(claimreviews, f, ensure_ascii=False, indent=2)
Acest script:
- Descarcă un articol și extrage textul.
- Detectează propoziţiile care conţin claim‑uri cu un model pre‑antrenat.
- Caută în Wikidata dacă există o entitate exactă (exemplu simplu de cross‑checking).
- Generează obiecte ClaimReview în format JSON‑LD, gata de încărcare în Google Fact‑Check Tools sau de stocare internă.
📌 Cum poţi combina NNA cu ClaimReview?
- Colectează postări/tweeturi legate de un subiect (ex. „vaccin COVID”).
- Aplică NNA pentru a identifica narativele dominante (ex. „vaccinul este periculos”, „vaccinul salvează vieţi”).
- Foloseşte pipeline‑ul ClaimReview pentru a verifica automat fiecare afirmaţie cheie din acele narative.
- Îmbină rezultatele: în graficul NNA adaugă un atribut la fiecare nod (ex. rating = 1‑5) și colorează nodurile în funcţie de veridicitate. Astfel poţi vedea nu doar cum se răspândeşte un narativ, ci şi cât de „înșelător” este.
Vizualizare exemplificativă (Gephi)
| Nod (unitate narativă) | Grad | Rating ClaimReview | Culoare |
|---|---|---|---|
| „vaccinul conţine microcip” | 45 | 1 (Fals) | roșu |
| „vaccinul reduce riscul de spitalizare” | 78 | 5 (Adevărat) | verde |
| „vaccinul este gratuit” | 62 | 4 (În mare parte adevărat) | galben |
Acest tip de diagramă permite jurnaliştilor, cercetătorilor sau factorilor de decizie să comunice rapid „Ce se spune?” și „Cât de adevărat este?” în acelaşi timp.
✅ Paşi concreţi pentru a porni proiectul tău
| Etapă | Ce faci | Resurse recomandate |
|---|
| **1. Definirea întrebării
Pipeline complet – de la scraping la JSON‑LD ClaimReview
Mai jos găseşti un script self‑contained (Python 3.9+) care:
- Colectează textul unui articol web.
- Împarte textul în propoziţii și identifică claim‑urile cu un model BERT finetuned (ClaimBuster).
- Verifică fiecare claim în Wikidata (SPARQL) și, dacă nu găseşte nimic, îl trimite la Google Fact‑Check Tools API (opțional).
- Curăţă rezultatele cu pandas (de‑dup, normalizare).
- Construieşte obiecte
ClaimReviewconform schema.org și le serialisează în JSON‑LD cu ajutorul librărieipython‑json‑ld.
Notă – Pentru o soluţie de producţie ar trebui să adaugi:
– gestionarea rate‑limiting‑ului și a erorilor de reţea,
– caching (ex. Redis) pentru interogările SPARQL,
– un model de clasificare a rating‑ului (True/False/Partially true etc.),
– integrarea cu platforme de fact‑checking (FactCheck.org, Media‑Bias/Fact‑Check API).
1️⃣ Dependinţe
pip install requests beautifulsoup4 spacy pandas
transformers torch
python-json-ld tqdm
python -m spacy download en_core_web_sm # (sau ro_core_news_sm dacă lucrezi cu româna)
pip install requests beautifulsoup4 spacy pandas
transformers torch
python-json-ld tqdm
python -m spacy download en_core_web_sm # (sau ro_core_news_sm dacă lucrezi cu româna)2️⃣ Codul complet
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import json
import requests
from bs4 import BeautifulSoup
import pandas as pd
from tqdm import tqdm
import spacy
from transformers import pipeline
from pyld import jsonld # python‑json‑ld expune funcţia jsonld.compact()
from datetime import datetime
# ----------------------------------------------------------------------
# 0️⃣ CONFIGURARE
# ----------------------------------------------------------------------
ARTICLE_URL = " https://example.com/news/article" # ← înlocuieşte cu URL‑ul tău
WIKIDATA_SPARQL_ENDPOINT = " https://query.wikidata.org/sparql"
GOOGLE_FACTCHECK_API_KEY = "<YOUR_GOOGLE_API_KEY>" # dacă vrei să apelezi API‑ul
GOOGLE_FACTCHECK_ENDPOINT = " https://factchecktools.googleapis.com/v1alpha1/claims:search"
# Modelul ClaimBuster (roberta‑base) - poate fi înlocuit cu altul finetuned
CLAIM_EXTRACTOR = pipeline(
"text-classification",
model="microsoft/claimbuster-roberta-base",
return_all_scores=False,
device=0 # -1 = CPU, 0 = primul GPU
)
# SpaCy pentru segmentare (mai robust decât split('.'))
NLP = spacy.load("en_core_web_sm") # sau ro_core_news_sm pentru română
# ----------------------------------------------------------------------
# 1️⃣ SCRAPING & TEXT EXTRACTION
# ----------------------------------------------------------------------
def fetch_article(url: str) -> str:
resp = requests.get(url, timeout=15)
resp.raise_for_status()
soup = BeautifulSoup(resp.text, "html.parser")
# Eliminăm scripturi, stiluri etc.
for tag in soup(["script", "style", "header", "footer", "nav", "aside"]):
tag.decompose()
return soup.get_text(separator=" ", strip=True)
# ----------------------------------------------------------------------
# 2️⃣ CLAIM DETECTION
# ----------------------------------------------------------------------
def extract_claims(text: str) -> list[str]:
"""Împarte textul în propoziţii și păstrează doar cele clasificate ca CLAIM."""
doc = NLP(text)
sentences = [sent.text.strip() for sent in doc.sents if sent.text.strip()]
claims = []
for sent in tqdm(sentences, desc="Detectare claim‑uri"):
try:
out = CLAIM_EXTRACTOR(sent)[0] # {'label': 'CLAIM', 'score': 0.98}
if out["label"] == "CLAIM":
claims.append(sent)
except Exception as e:
# Uneori modelul poate să dea eroare pe texte foarte scurte
continue
return claims
# ----------------------------------------------------------------------
# 3️⃣ VERIFICARE în Wikidata
# ----------------------------------------------------------------------
def wikidata_check(claim: str) -> dict | None:
"""Întoarce primul rezultat Wikidata care conţine exact claim‑ul (case‑insensitive)."""
query = f"""
SELECT ?item ?itemLabel WHERE {{
?item ?p "{claim}"@en .
SERVICE wikibase:label {{ bd:serviceParam wikibase:language "en". }}
}} LIMIT 1
"""
resp = requests.get(
WIKIDATA_SPARQL_ENDPOINT,
params={"query": query, "format": "json"},
headers={"User-Agent": "FactCheckPipeline/0.1 (+ https://github.com/yourrepo)"},
timeout=20,
)
resp.raise_for_status()
data = resp.json()
bindings = data.get("results", {}).get("bindings", [])
return bindings[0] if bindings else None
# ----------------------------------------------------------------------
# 4️⃣ (OPŢIONAL) VERIFICARE cu Google Fact‑Check Tools
# ----------------------------------------------------------------------
def google_factcheck_check(claim: str) -> dict | None:
"""Caută claim‑ul în baza Google Fact‑Check Tools. Returnează primul rezultat."""
if not GOOGLE_FACTCHECK_API_KEY:
return None
params = {
"key": GOOGLE_FACTCHECK_API_KEY,
"query": claim,
"languageCode": "en",
"maxAgeDays": 365,
}
resp = requests.get(GOOGLE_FACTCHECK_ENDPOINT, params=params, timeout=15)
if resp.status_code != 200:
return None
data = resp.json()
claims = data.get("claims", [])
return claims[0] if claims else None
# ----------------------------------------------------------------------
# 5️⃣ CURĂŢARE & NORMALIZARE (pandas)
# ----------------------------------------------------------------------
def clean_claims(raw_claims: list[str]) -> pd.DataFrame:
"""Înlătură duplicate, normalizează whitespace și adaugă ID unic."""
df = pd.DataFrame({"claim_raw": raw_claims})
df["claim_clean"] = (
df["claim_raw"]
.str.strip()
.str.replace(r"s+", " ", regex=True)
.str.lower()
)
df = df.drop_duplicates(subset="claim_clean").reset_index(drop=True)
df["claim_id"] = df.index + 1
return df
# ----------------------------------------------------------------------
# 6️⃣ BUILD CLAIMREVIEW (schema.org)
# ----------------------------------------------------------------------
def build_claimreview(
claim: str,
rating: str,
review_url: str,
author_name: str = "Fact‑Check Bot",
publisher_name: str = "MyFactCheck.org",
) -> dict:
"""Returnează un dicţionar compatibil cu schema.org/ClaimReview."""
return {
"@context": " https://schema.org",
"@type": "ClaimReview",
"datePublished": datetime.utcnow().date().isoformat(),
"url": review_url,
"claimReviewed": claim,
"author": {"@type": "Organization", "name": author_name},
"publisher": {"@type": "Organization", "name": publisher_name},
"reviewRating": {
"@type": "Rating",
"ratingValue": rating, # ex. "True", "False", "Partially true"
"bestRating": "True",
"worstRating": "False",
"alternateName": rating,
},
}
# ----------------------------------------------------------------------
# 7️⃣ SERIALIZARE JSON‑LD
# ----------------------------------------------------------------------
def to_jsonld(claimreview_obj: dict) -> str:
"""Compactează obiectul ClaimReview în JSON‑LD cu contextul schema.org."""
# Folosim contextul implicit din obiect (`@context`), deci compactarea este trivială
compacted = jsonld.compact(claimreview_obj, claimreview_obj["@context"])
return json.dumps(compacted, indent=2, ensure_ascii=False)
# ----------------------------------------------------------------------
# 8️⃣ PIPELINE MAIN
# ----------------------------------------------------------------------
def main():
# 1️⃣ Scrape
raw_text = fetch_article(ARTICLE_URL)
print(f"📰 Text preluat - {len(raw_text)} caractere")
# 2️⃣ Detectare claim‑uri
raw_claims = extract_claims(raw_text)
print(f"🔎 Claim‑uri găsite: {len(raw_claims)}")
# 3️⃣ Curăţare
df_claims = clean_claims(raw_claims)
print(f"🧹 Claim‑uri curăţate (dup‑eliminate): {len(df_claims)}")
# 4️⃣ Verificare și construire Review
claimreview_items = []
for _, row in tqdm(df_claims.iterrows(), total=len(df_claims), desc="Verificare claim‑uri"):
claim = row["claim_raw"]
# 4a) În primul rând căutăm în Wikidata
wikidata_res = wikidata_check(claim)
if wikidata_res:
rating = "True"
source = f" https://www.wikidata.org/wiki/{wikidata_res ['item']['value'].split('/')[-1]}"
else:
# 4b) Dacă nu există în Wikidata, încercăm Google Fact‑Check
google_res = google_factcheck_check(claim)
if google_res:
rating = google_res.get("textualRating", "Unrated")
source = google_res.get("claimReview", [{}])[0].get("url", "")
else:
rating = "Unrated"
source = ARTICLE_URL
# 5️⃣ Construim ClaimReview
cr = build_claimreview(
claim=claim,
rating=rating,
review_url=source,
author_name="Fact‑Check Bot",
publisher_name="MyFactCheck.org",
)
claimreview_items.append(cr)
# 6️⃣ Serializare JSON‑LD
output = {
"@context": " https://schema.org",
"@graph": claimreview_items,
}
jsonld_str = json.dumps(output, indent=2, ensure_ascii=False)
# Salvează în fişier
out_path = f"claimreview_{datetime.utcnow().strftime('%Y%m%d_%H%M%S')}.jsonld"
with open(out_path, "w", encoding="utf-8") as f:
f.write(jsonld_str)
print(f"✅ Pipeline terminat - {len(claimreview_items)} ClaimReview salvate în {out_path}")
if __name__ == "__main__":
main()
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import json
import requests
from bs4 import BeautifulSoup
import pandas as pd
from tqdm import tqdm
import spacy
from transformers import pipeline
from pyld import jsonld # python‑json‑ld expune funcţia jsonld.compact()
from datetime import datetime
# ----------------------------------------------------------------------
# 0️⃣ CONFIGURARE
# ----------------------------------------------------------------------
ARTICLE_URL = " https://example.com/news/article" # ← înlocuieşte cu URL‑ul tău
WIKIDATA_SPARQL_ENDPOINT = " https://query.wikidata.org/sparql"
GOOGLE_FACTCHECK_API_KEY = "<YOUR_GOOGLE_API_KEY>" # dacă vrei să apelezi API‑ul
GOOGLE_FACTCHECK_ENDPOINT = " https://factchecktools.googleapis.com/v1alpha1/claims:search"
# Modelul ClaimBuster (roberta‑base) - poate fi înlocuit cu altul finetuned
CLAIM_EXTRACTOR = pipeline(
"text-classification",
model="microsoft/claimbuster-roberta-base",
return_all_scores=False,
device=0 # -1 = CPU, 0 = primul GPU
)
# SpaCy pentru segmentare (mai robust decât split('.'))
NLP = spacy.load("en_core_web_sm") # sau ro_core_news_sm pentru română
# ----------------------------------------------------------------------
# 1️⃣ SCRAPING & TEXT EXTRACTION
# ----------------------------------------------------------------------
def fetch_article(url: str) -> str:
resp = requests.get(url, timeout=15)
resp.raise_for_status()
soup = BeautifulSoup(resp.text, "html.parser")
# Eliminăm scripturi, stiluri etc.
for tag in soup(["script", "style", "header", "footer", "nav", "aside"]):
tag.decompose()
return soup.get_text(separator=" ", strip=True)
# ----------------------------------------------------------------------
# 2️⃣ CLAIM DETECTION
# ----------------------------------------------------------------------
def extract_claims(text: str) -> list[str]:
"""Împarte textul în propoziţii și păstrează doar cele clasificate ca CLAIM."""
doc = NLP(text)
sentences = [sent.text.strip() for sent in doc.sents if sent.text.strip()]
claims = []
for sent in tqdm(sentences, desc="Detectare claim‑uri"):
try:
out = CLAIM_EXTRACTOR(sent)[0] # {'label': 'CLAIM', 'score': 0.98}
if out["label"] == "CLAIM":
claims.append(sent)
except Exception as e:
# Uneori modelul poate să dea eroare pe texte foarte scurte
continue
return claims
# ----------------------------------------------------------------------
# 3️⃣ VERIFICARE în Wikidata
# ----------------------------------------------------------------------
def wikidata_check(claim: str) -> dict | None:
"""Întoarce primul rezultat Wikidata care conţine exact claim‑ul (case‑insensitive)."""
query = f"""
SELECT ?item ?itemLabel WHERE {{
?item ?p "{claim}"@en .
SERVICE wikibase:label {{ bd:serviceParam wikibase:language "en". }}
}} LIMIT 1
"""
resp = requests.get(
WIKIDATA_SPARQL_ENDPOINT,
params={"query": query, "format": "json"},
headers={"User-Agent": "FactCheckPipeline/0.1 (+ https://github.com/yourrepo)"},
timeout=20,
)
resp.raise_for_status()
data = resp.json()
bindings = data.get("results", {}).get("bindings", [])
return bindings[0] if bindings else None
# ----------------------------------------------------------------------
# 4️⃣ (OPŢIONAL) VERIFICARE cu Google Fact‑Check Tools
# ----------------------------------------------------------------------
def google_factcheck_check(claim: str) -> dict | None:
"""Caută claim‑ul în baza Google Fact‑Check Tools. Returnează primul rezultat."""
if not GOOGLE_FACTCHECK_API_KEY:
return None
params = {
"key": GOOGLE_FACTCHECK_API_KEY,
"query": claim,
"languageCode": "en",
"maxAgeDays": 365,
}
resp = requests.get(GOOGLE_FACTCHECK_ENDPOINT, params=params, timeout=15)
if resp.status_code != 200:
return None
data = resp.json()
claims = data.get("claims", [])
return claims[0] if claims else None
# ----------------------------------------------------------------------
# 5️⃣ CURĂŢARE & NORMALIZARE (pandas)
# ----------------------------------------------------------------------
def clean_claims(raw_claims: list[str]) -> pd.DataFrame:
"""Înlătură duplicate, normalizează whitespace și adaugă ID unic."""
df = pd.DataFrame({"claim_raw": raw_claims})
df["claim_clean"] = (
df["claim_raw"]
.str.strip()
.str.replace(r"s+", " ", regex=True)
.str.lower()
)
df = df.drop_duplicates(subset="claim_clean").reset_index(drop=True)
df["claim_id"] = df.index + 1
return df
# ----------------------------------------------------------------------
# 6️⃣ BUILD CLAIMREVIEW (schema.org)
# ----------------------------------------------------------------------
def build_claimreview(
claim: str,
rating: str,
review_url: str,
author_name: str = "Fact‑Check Bot",
publisher_name: str = "MyFactCheck.org",
) -> dict:
"""Returnează un dicţionar compatibil cu schema.org/ClaimReview."""
return {
"@context": " https://schema.org",
"@type": "ClaimReview",
"datePublished": datetime.utcnow().date().isoformat(),
"url": review_url,
"claimReviewed": claim,
"author": {"@type": "Organization", "name": author_name},
"publisher": {"@type": "Organization", "name": publisher_name},
"reviewRating": {
"@type": "Rating",
"ratingValue": rating, # ex. "True", "False", "Partially true"
"bestRating": "True",
"worstRating": "False",
"alternateName": rating,
},
}
# ----------------------------------------------------------------------
# 7️⃣ SERIALIZARE JSON‑LD
# ----------------------------------------------------------------------
def to_jsonld(claimreview_obj: dict) -> str:
"""Compactează obiectul ClaimReview în JSON‑LD cu contextul schema.org."""
# Folosim contextul implicit din obiect (`@context`), deci compactarea este trivială
compacted = jsonld.compact(claimreview_obj, claimreview_obj["@context"])
return json.dumps(compacted, indent=2, ensure_ascii=False)
# ----------------------------------------------------------------------
# 8️⃣ PIPELINE MAIN
# ----------------------------------------------------------------------
def main():
# 1️⃣ Scrape
raw_text = fetch_article(ARTICLE_URL)
print(f"📰 Text preluat - {len(raw_text)} caractere")
# 2️⃣ Detectare claim‑uri
raw_claims = extract_claims(raw_text)
print(f"🔎 Claim‑uri găsite: {len(raw_claims)}")
# 3️⃣ Curăţare
df_claims = clean_claims(raw_claims)
print(f"🧹 Claim‑uri curăţate (dup‑eliminate): {len(df_claims)}")
# 4️⃣ Verificare și construire Review
claimreview_items = []
for _, row in tqdm(df_claims.iterrows(), total=len(df_claims), desc="Verificare claim‑uri"):
claim = row["claim_raw"]
# 4a) În primul rând căutăm în Wikidata
wikidata_res = wikidata_check(claim)
if wikidata_res:
rating = "True"
source = f" https://www.wikidata.org/wiki/{wikidata_res ['item']['value'].split('/')[-1]}"
else:
# 4b) Dacă nu există în Wikidata, încercăm Google Fact‑Check
google_res = google_factcheck_check(claim)
if google_res:
rating = google_res.get("textualRating", "Unrated")
source = google_res.get("claimReview", [{}])[0].get("url", "")
else:
rating = "Unrated"
source = ARTICLE_URL
# 5️⃣ Construim ClaimReview
cr = build_claimreview(
claim=claim,
rating=rating,
review_url=source,
author_name="Fact‑Check Bot",
publisher_name="MyFactCheck.org",
)
claimreview_items.append(cr)
# 6️⃣ Serializare JSON‑LD
output = {
"@context": " https://schema.org",
"@graph": claimreview_items,
}
jsonld_str = json.dumps(output, indent=2, ensure_ascii=False)
# Salvează în fişier
out_path = f"claimreview_{datetime.utcnow().strftime('%Y%m%d_%H%M%S')}.jsonld"
with open(out_path, "w", encoding="utf-8") as f:
f.write(jsonld_str)
print(f"✅ Pipeline terminat - {len(claimreview_items)} ClaimReview salvate în {out_path}")
if __name__ == "__main__":
main()Explicaţii pas cu pas
| Pas | Ce face | Instrumente / Biblioteci |
|---|---|---|
| 0️⃣ Config | Setează URL‑ul articolului, endpoint‑urile și cheia API‑ului Google (opţional). | requests, variabile globale |
| 1️⃣ Scraping | Descarcă HTML‑ul și extrage doar textul curat. | requests, BeautifulSoup |
| 2️⃣ Claim detection | Segmentarea cu spaCy → clasificare cu transformers (text‑classification). |
spacy, transformers (pipeline) |
| 3️⃣ Wikidata check | Interogare SPARQL simplă pentru a găsi entităţi care conţin exact claim‑ul. | requests + endpoint SPARQL |
| 4️⃣ Google Fact‑Check | (opţional) Foloseşte API‑ul Google pentru a căuta claim‑uri în baza lor de fact‑check. | requests |
| 5️⃣ Curăţare | Elimină duplicate, normalizează whitespace, adaugă ID‑uri. | pandas |
| 6️⃣ ClaimReview | Construcţia conform schema.org/ClaimReview (rating, autor, publisher). | Python dict |
| 7️⃣ JSON‑LD | Compactare cu python‑json‑ld (pyld). |
pyld.jsonld |
| 8️⃣ Output | Salvează un fişier .jsonld ce conţine un @graph cu toate revizuirile. |
json |
Cum poţi extinde acest pipeline
| Extensie | Scop | Bibliotecă / Resursă |
|---|
| Model de rating | În loc să presupui
Se pare că ai postat o bucată din codul pipeline‑ului tău (scraping, extragere de claim‑uri, curățare, verificare și serializare JSON‑LD).
Dacă ai o întrebare specifică – de exemplu:
* Cum să finalizezi bucla de verificare (ce să faci după else:)?
* Cum să structurezi obiectul ClaimReview înainte de a-l pasa la to_jsonld?
* Cum să gestionezi erorile în apelurile către Wikidata sau Google Fact‑Check?
* Cum să adaugi alte surse de verificare (ex. Snopes, FactCheck.org)?
* Cum să scrii rezultatele într‑un fișier sau să le exporţi ca JSON‑LD?
sau orice altă problemă pe care ai întâmpinat-o, te rog să îmi spui mai exact ce anume vrei să rezolvi. În funcție de detaliile pe care le oferi, îţi pot oferi:
- Cod complet pentru secţiunea lipsă (de exemplu, adăugarea rezultatului în
claimreview_itemsși scrierea în fişier); - Sugestii de design pentru obiectul
ClaimReviewconform schema.org; - Strategii de retry / back‑off pentru API‑urile externe;
- Instrucţiuni pentru validarea JSON‑LD (folosind
pyldsau alte unelte); - Orice altă clarificare legată de fluxul de lucru.
Aştept să îmi spui ce anume îţi doreşti să faci în continuare!
