Ce code montre comment réaliser une analyse de sentiment sur base des reviews Amazon pour un produit donné.
""" | |
Ce code montre comment réaliser une analyse de sentiment | |
sur base des reviews Amazon pour un produit donné. | |
""" | |
import matplotlib.pyplot as plt | |
import numpy as np | |
import pandas as pd | |
import requests | |
from adjustText import adjust_text | |
from bs4 import BeautifulSoup | |
from pylab import rcParams | |
from sklearn.feature_extraction.text import CountVectorizer | |
from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer | |
HEADERS = { | |
'authority': 'www.amazon.com', | |
'pragma': 'no-cache', | |
'cache-control': 'no-cache', | |
'dnt': '1', | |
'upgrade-insecure-requests': '1', | |
'user-agent': ('Mozilla/5.0 (X11; CrOS x86_64 8172.45.0) AppleWebKit/537.36 ' | |
'(KHTML, like Gecko) Chrome/51.0.2704.64 Safari/537.36'), | |
'accept': ('text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,' | |
'image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9'), | |
'sec-fetch-site': 'none', | |
'sec-fetch-mode': 'navigate', | |
'sec-fetch-dest': 'document', | |
'accept-language': 'en-GB,en-US;q=0.9,en;q=0.8', | |
} | |
def get_amazon_reviews(product_id, output_file): | |
""" | |
Cette fonction télécharge les reviews Amazon | |
sur base d'un product_id et stocke le résultat | |
dans un fichier CSV. | |
""" | |
# La liste dans laquelle nous stockons les commentaires | |
reviews = [] | |
# Boucle qui va aller de la page 1 à la page 1000 | |
for i in range(1, 1000): | |
# Nous téléchargons le contenu de la page de commentaires ici | |
article_url = f"https://www.amazon.com/product-reviews/{product_id}/?pageNumber={i}" | |
html = requests.get(article_url, headers=HEADERS).text | |
# Nous extrayons les commentaires du HTML avec BeautifulSoup | |
cnt = 0 | |
for span in BeautifulSoup(html).find_all('span'): | |
classes = span.get('class') | |
if classes and 'review-text' in classes: | |
cnt += 1 | |
reviews.append(dict( | |
page=i, | |
text=span.text | |
)) | |
# S'il n'y a plus de commentaires, nous arrêtons la boucle | |
if cnt == 0: | |
break | |
# Sauver le résultant dans un fichier CSV | |
this_df = pd.DataFrame(reviews) | |
this_df.to_csv(output_file) | |
def add_sentiment(input_file='cache/amazon_joby_review.csv'): | |
""" | |
Cette fonction ajouter une colonne sentiment à un | |
data frame sur base d'une colonne de texte nommée "text" | |
""" | |
# Nettoyage des données | |
this_df = pd.read_csv(input_file) | |
this_df = this_df.drop('Unnamed: 0', axis='columns') | |
this_df = this_df.loc[not this_df.text.isnull()] | |
# On créé le modèle d'analyse de sentiment | |
model = SentimentIntensityAnalyzer() | |
# Nous appliquons le modèle à la colonne "text" et stockons | |
# le résultant dans une nouvelle colonne "sentiment". | |
this_df['sentiment'] = np.vectorize( | |
lambda x: model.polarity_scores(x).get('compound') | |
)(this_df.text) | |
return this_df | |
def get_term_report(this_df): | |
""" | |
Cette fonction créé un rapport de distribution de termes | |
sur base du sentiment des commentaires. | |
Le texte doit se trouver dans une colonne "text" et le | |
sentiment dans une colonne "sentiment". | |
""" | |
# Ajouter un flag si un commentaire est positif | |
this_df['is_positive'] = np.vectorize(lambda x: x > 0)(this_df.sentiment) | |
# Créer un data frame comptant les mots | |
vectorizer = CountVectorizer( | |
min_df=50, max_df=0.5, ngram_range=(1, 2), stop_words='english') | |
vector = vectorizer.fit_transform(this_df.text) | |
word_count = pd.DataFrame( | |
vector.toarray(), columns=vectorizer.get_feature_names()) | |
# Nettoyer | |
temp = word_count.copy() | |
temp['is_positive'] = this_df.is_positive | |
pos = temp.loc[temp.is_positive].drop( | |
'is_positive', axis='columns') | |
neg = temp.loc[not temp.is_positive].drop( | |
'is_positive', axis='columns') | |
# Concaténer les dataframes | |
this_report = pd.concat([ | |
pos.sum(), | |
neg.sum(), | |
word_count.sum() | |
], axis='columns') | |
this_report.columns = ['pos', 'neg', 'total'] | |
# Calculer leur distributions | |
this_report['this_pos_ratio'] = this_report.pos / this_report.total | |
this_report['all_pos_ratio'] = this_report.pos.sum() / this_report.total.sum() | |
this_report['diff_ratio'] = this_report.this_pos_ratio - this_report.all_pos_ratio | |
return this_report | |
def plot_scatter_terms(this_report, positive=True): | |
""" | |
Cette fonction crée une visualisation simple qui permet | |
d'identifier quels sont les termes négatifs ou positifs | |
pour un produit donné. | |
""" | |
# Constantes | |
rcParams['figure.figsize'] = 7, 7 | |
y_col = 'diff_ratio' | |
n_obs = 15 | |
if positive: | |
x_col, asc = 'pos', False | |
else: | |
x_col, asc = 'neg', True | |
# Préparer les données | |
tmp = this_report[[x_col, y_col]] | |
tmp.columns = ['Volume', 'Intensité'] | |
tmp = tmp.sort_values(by='Intensité', ascending=asc).head(n_obs) | |
x_col, y_col = tmp['Volume'].tolist(), tmp['Intensité'].tolist() | |
labels = tmp.index.tolist() | |
# Construire le scatter | |
plt.scatter(x_col, y_col) | |
plt.ylabel('Intensité') | |
plt.xlabel('Volume') | |
# Ajouter des labels | |
annotations = [] | |
for x_i, y_i, label in zip(x_col, y_col, labels): | |
annotations.append(plt.text(x_i, y_i, label)) | |
adjust_text(annotations, x=x_col, y=y_col) | |
plt.show() | |
if __name__ == '__main__': | |
PRODUCT_ID = 'B074WC9YKL' | |
CACHE_FILE = 'cache/amazon_joby_review.csv' | |
# Collecter les reviews | |
get_amazon_reviews(PRODUCT_ID, CACHE_FILE) | |
# Analyser les sentiments | |
data_frame = add_sentiment(CACHE_FILE) | |
# Visualiser l'histogramme | |
data_frame.sentiment.plot.hist(bins=12, alpha=0.5) | |
# Construire un rapport de fréquence des termes | |
report = get_term_report(data_frame) | |
# Affichier les dix mots les plus lié aux commentaires négatifs | |
report.sort_values(by='diff_ratio', ascending=True).head(10) | |
# Visualiser les termes positifs | |
plot_scatter_terms(report, positive=True) | |
# Visualiser les termes négatifs | |
plot_scatter_terms(report, positive=True) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment