poster

Voici le troisième billet d'une série portant sur l'analyse du contenu textuel de commentaires Facebook en lien avec des articles de la presse écrite. Le second billet peut être lu ici.

Introduction

Alors que le second billet traitait des entités nommées, prises de parole et relations entre les commentaires, nous parlerons ici de modélisation et présenterons les données qui seront analysées.

Modélisation

Représentation vectorielle

Étant donné que les commentaires sont de courts textes, souvent formés à partir d'un très lange lexique, en tenant compte des nombreuses fautes d'orthographe et mécanismes d'emphase, leur représentation sous forme vectorielle est très creuse. Il faudra donc nécessairement utiliser une forme de réduction de dimensions. Liebeskind et al. [8] propose quatre approches : analyse sémantique latente (LSA), allocation dynamique de Dirichlet (LDA), projection aléatoire (Lemme de Johnson-Lindenstrauss) ou plongements de mots.

Comme nos commentaires ne sont pas étiquetés, nous devons utiliser un apprentissage non supervisé. Les deux approches consistent à construire soit un modèle commun pour les articles et les commentaires, ou deux modèles distincts. Afin d'avoir une représentation qui est davantage axée vers les thèmes généraux, les auteurs recommandent d'utiliser des vecteurs denses et cours, tout au plus quelques centaines.

Classification de la pertinence

Selon les observations de Liebeskind et al. [8], les principaux obstacles à la modélisation de la pertinence des commentaires est la présence de salutations et de commentaires obscènes, les nombreux sous-entendus entre les commentaires (contexte latent) ainsi que la présence de sarcasme. Les commentaires qui ont été classés comme non pertinents étaient principalement des références implicites ou des salutations.

Exemple d'application

Description des corpus de textes

Nous analyserons les articles provenant des pages Facebook de trois médias écrits francophones : Le Figaro (FIG), Radio-Canada (RC) et TVA Nouvelles (TVA). Pour chacun de ces médias, nous avons respectivement une publication Facebook contenant un lien vers un article journalistique, ainsi qu'un corpus de commentaires extraits depuis celle-ci.

media Nombre de publications
FIG 25
RC 22
TVA 24

Le premier corpus étudié est constitué du texte des articles liés dans les publications (l'utilisateur de Facebook devant cliquer sur le lien pour y accéder). Le titre de l'article n'est pas inclus dans ce corpus. Le second corpus est constitué d'un ensemble de commentaires publiés par des utilisateurs du réseau social et associés à chacune des publications précédentes.

media Nombre de commentaires
FIG 7155
RC 3947
TVA 6262

Ces deux corpus ont été créés à l'aide des données de commentaires extraites depuis l'application en ligne exportcomments.com [2] dans des fichiers XLSX. Les fichiers ont par la suite été utilisés par les programmes Python suivants :

  • commentaires.py pour extraire les commentaires depuis les fichiers téléchargés à l'aide de Pandas [10].
  • textes_articles.py pour extraire les textes depuis les URL disponibles dans les fichiers, par récupération de données (web scraping), en utilisant la librairie Python newspaper [11]. Du même coup, cette librairie permet d'extraction d'entités nommées et l'étiquetage des parties du discours.
  • parsing_functions.py: fonctions d'extraction communes

Suite

La suite de ce projet dans les prochains billets !

Billet(s) précédent(s)

Références

  • [2] 2019. exportcomments.com. Consulté à l'adresse https://exportcomments.com/
  • [8] Chaya Liebeskind, Shmuel Liebeskind, et Yaakov HaCohen-Kerner. 2018. Comment Relevance Classification in Facebook. In Computational Linguistics and Intelligent Text Processing, Springer International Publishing, Cham, 241‑254.
  • [10] Wes McKinney. 2010. Data Structures for Statistical Computing in Python. In Proceedings of the 9th Python in Science Conference, 51‑56.
  • [11] Lucas Ou-Yang. 2019. Newspaper3k: Article scraping & curation. Consulté à l'adresse https://github.com/codelucas/newspaper/

Programmes

  • commentaires.py
import parsing_functions as pf
import re
import pandas as pd
import time

listOfFiles = pf.getListOfFiles("data")

commentaires = []

for xlpath in listOfFiles:
    comments_df = []
    media, post_id = re.match(r"data/([A-Z]+)/comments([0-9a-z\-]+)\.xlsx",xlpath).groups()
    comments_df = pf.get_comments(xlpath)
    comments_df['media']=media
    comments_df['post_id']=post_id
    commentaires.append(comments_df)

commentaires_df = pd.concat(commentaires, ignore_index=True)

commentaires_df.to_csv("refined_data/commentaires_df.csv")
  • textes_articles.py
import parsing_functions as pf
import re
import pandas as pd
import time

textes_articles = []
for xlpath in listOfFiles:
    time.sleep(3)
    media, post_id = re.match(r"data/([A-Z]+)/comments([0-9a-z\-]+)\.xlsx",xlpath).groups()
    textes_articles.append([media,post_id,pf.get_text_article(xlpath)])

textes_articles_df = pd.DataFrame(textes_articles, columns=['media','post_id','text'])

textes_articles_df.to_csv("textes_articles_df.csv")
  • parsing_functions.py
import os
import re
import pandas as pd
import requests
from urllib.parse import unquote
import newspaper

def get_comments(file_path):
    df = pd.read_excel(file_path, skiprows=5, 
                       names=['comment_id',
                              'nested_id',
                              'name',
                              'id',
                              'date',
                              'likes',
                              'comment',
                              'source'])
    df["comment_id"] = df["comment_id"].mask(pd.isnull,df["nested_id"].mask(pd.isnull,"0-0").apply(lambda x: re.match(r"([0-9]+)\-([0-9]+)",x).group(1)))
    df["nested_id"] = df["nested_id"].mask(pd.isnull,"0-0").apply(lambda x: re.match(r"([0-9]+)\-([0-9]+)",x).group(2))
    del df["source"]
    return df

def get_text_article(file_path):
    url = pd.read_excel(file_path,skiprows=1,nrows=1,header=None,names=['source','url']+['']*6)['url'][0]
    request_url = requests.get(url)
    html_content = str(request_url.content)
    link_urls = re.findall(r'http[s]?://l\.facebook(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+',html_content)
    u_link_urls = [unquote(unquote(u.replace("https://l.facebook.com/l.php?u=",""))) for u in link_urls]
    article = newspaper.Article('')
    try:
        url_article = [re.search("(.*)&.*",u).group(1) for u in u_link_urls][-3]
        request_url_article = requests.get(url_article)
        article.set_html(request_url_article.content)
    except:
        article.set_html(html_content)
    try:
        article.parse()
        text_article = article.text.replace("\n\n"," ")
    except:
        text_article = ""
    return text_article

def getListOfFiles(dirName):
    # create a list of file and sub directories 
    # names in the given directory 
    listOfFile = os.listdir(dirName)
    allFiles = list()
    # Iterate over all the entries
    for entry in listOfFile:
        # Create full path
        fullPath = os.path.join(dirName, entry)
        # If entry is a directory then get the list of files in this directory 
        if os.path.isdir(fullPath):
            allFiles = allFiles + getListOfFiles(fullPath)
        else:
            allFiles.append(fullPath)
    return allFiles

Article précédent Article suivant