INTRODUCCION

En este proyecto se realiza una extraccion (web scraping) de todo el carrusel de opiniones sobre un determinado producto en la web de Amazon, en este caso referido a un determinado modelo de aspiradora de mano.

Una vez extraidas estas opiniones de los clientes, realizaremos un analisis de sentimiento referidas a estas opiniones, con el fin de pulsar el sentimiento y valoracion que tiene el cliente hacia este producto determinado.

Finalmente veremos en funcion de algunos de estos tipos de emociones
una muestra de palabras de estas opiniones que son representativas de estos tipos a traves de una nube de palabras.

Para el desarrollo del proceso de este analisis de sentimiento hemos escogido la libreria de R ‘syuzhet’ con el lexico ‘NRC’ para el idioma español.

# Cargamos librerias

library(rvest)
library(robotstxt)
library(selectr)
library(xml2)
library(tibble)
library(DT)
library(tidyverse)
library(syuzhet)
library(RColorBrewer)
library(wordcloud)
library(tm)
library(ggpubr)
library(kableExtra)

# Enlace a la pagina que representa la seccion filtrada

url <- "https://www.amazon.es/Aspiradora-Sin-Cable-Potente-Mano-Coche/product-reviews/B09LLT9BG6/ref=cm_cr_getr_d_paging_btm_prev_1?ie=UTF8&reviewerType=all_reviews&pageNumber=1"

Vamos a ver si esta permitido bajar informacion de esta pagina

paths_allowed(paths = c(url))
## 
 www.amazon.es
## [1] TRUE

y vemos como resultado ‘TRUE’ esta permitido.

Obtenemos su codigo html de la pagina web

pag_web <- read_html(url)

Procedemos a la extraccion del texto de las variables de nuestros datos que nos interesen en este caso solo las opiniones de los clientes sobre el producto.

Opiniones_asp = pag_web%>%html_nodes(".review-text-content span")%>%html_text()

y recogemos en un dataframe estas opiniones

dt <- data.frame(Opiniones_asp)

kable(head(dt,6), booktabs = T) %>%
  kable_styling(font_size=12)
Opiniones_asp
Pequeña aspiradora con batería con buen funcionamiento por ahora. Una vez cargada, no es necesario tenerla conectada para poder aspirar. Como punto negativo no tiene selector de velocidad.A su favor, una luz led bastante agradable que facilita la labor de aspirado mientras se está usando. Incluye además un tubo más largo que facilita el aspirado de zonas más pequeñas o rincones, así como un cepillo que se puede acoplar a la punta.Basta ver la duración de la batería, pero habrá que esperar algo más para ello. En general muy buen rendimiento y un tamaño pequeño que facilita su guardado y transporte, además de su uso.
Por su tamaño y su capacidad de succión es ideal para dejarla en el coche y aspirar las cosas al llegar de un día de playa y así evitar llenar de arena todo el coche. Viene con muchos accesorios y es muy fácil de desmontar y limpiar. Tiene una luz para alumbrar cuando se está aspirando en lugares pon poca luz y resultando muy útil.Trae manual en español. Se carga muy rápidamente y la batería nos da suficiente para un correcto aspirado del coche.
<U+0001F6CD>Muy cómoda de manejar<U+0001F6CD>Sencilla<U+0001F6CD>Con potencia<U+0001F6CD> Queria una aspiradora de mano para el coche, pues siempre estar en la gasolinera me mataba así que busque una que tuviera autonomía de funcionamiento sin cable, pues obviamente que cuando quieres aspirar el coche en el garaje no tienes un enchufe a mano.<U+0001F6CD> El deposito es trasparente, con lo cual ves el nivel de llenado y si en algún momento se cuela una manera.<U+0001F6CD> Tiende 3 boquillas distintas para la aspiración , aunque los que mas uso son dos ,el normal y el alargado para los espacios entre los asientos.<U+0001F6CD> Una vez que la usas, la puedes limpiar con agua , pues los elementos son de plástico duro y el filtro es lavable.<U+0001F6CD> Una cosa importante, cargarla en algo mas de una hora esta lista para usarla 30 minutos sin el cable !!!<U+0001F6CD>Ale a limpiar el coche !!! y por supuesto para guardarla ocupa poco espacio.
No se ha podido cargar el contenido multimedia.
 Como la canción, todo depende.El poder de succión, limitado/justo para algunos y suficiente para otros.Mi nota sería, un 4,9.Ruidosa, con el video que muestro se aprecia el silencio no es una de sus virtudes.Hay que tener especial cuidado con la pestaña donde esta el filtro, puede romperse un día que no estés en modo delicado/torpe, si eso sucede puedes desperdirte.Única conexión un cable a una clavija con enchufe ordinario, por tanto no lo puedes dejar en el coche, cuando lo quieras cargar a casita debes regresar.Entre los habitantes de la casa, hay disidencias para devolverlo, sobretodo por escasa potencia.Por 34€ se puede “soportar” como aspirador residual.Al final lo devolví, con posterioridad el vendedor me explicó a través de un escrito, una serie de pautas que ya había realizado, una vez recibido cargarlo a tope por haber podido permanecer inactivo por tiempo, hacer los usos con filtros secos, etc….

Tenemos pues el vector de una pagina n=1, creemos pues el de todas las paginas que tenemos segun condiciones de filtro que son 10.

Para ello establecemos un bucle para procesar esta iteraciones, de tal manera que:

Creamos primeramente el vector de todas las páginas

tot_pags <- paste0("https://www.amazon.es/Aspiradora-Sin-Cable-Potente-Mano-Coche/product-reviews/B09LLT9BG6/ref=cm_cr_getr_d_paging_btm_prev_1?ie=UTF8&reviewerType=all_reviews&pageNumber=", 1:10)

Sabemos que son 10 páginas comprobando el final de todos los comentarios y paginas

Creamos tabla en blanco

dat_tot <- data.frame()

Efectuamos el bucle a partir del primer caso o pagina

for(pag in tot_pags){
  
url <- pag

pag_web <- read_html(url)

Opiniones_asp = pag_web%>%html_nodes(".review-text-content span")%>%html_text()

dt <- data.frame(Opiniones_asp)

dat_tot <- rbind.data.frame(dat_tot,dt)

# Visualizamos salida contador de avance(proceso): 

print(paste0("Extraccion de pag: ", pag))

}
## [1] "Extraccion de pag: https://www.amazon.es/Aspiradora-Sin-Cable-Potente-Mano-Coche/product-reviews/B09LLT9BG6/ref=cm_cr_getr_d_paging_btm_prev_1?ie=UTF8&reviewerType=all_reviews&pageNumber=1"
## [1] "Extraccion de pag: https://www.amazon.es/Aspiradora-Sin-Cable-Potente-Mano-Coche/product-reviews/B09LLT9BG6/ref=cm_cr_getr_d_paging_btm_prev_1?ie=UTF8&reviewerType=all_reviews&pageNumber=2"
## [1] "Extraccion de pag: https://www.amazon.es/Aspiradora-Sin-Cable-Potente-Mano-Coche/product-reviews/B09LLT9BG6/ref=cm_cr_getr_d_paging_btm_prev_1?ie=UTF8&reviewerType=all_reviews&pageNumber=3"
## [1] "Extraccion de pag: https://www.amazon.es/Aspiradora-Sin-Cable-Potente-Mano-Coche/product-reviews/B09LLT9BG6/ref=cm_cr_getr_d_paging_btm_prev_1?ie=UTF8&reviewerType=all_reviews&pageNumber=4"
## [1] "Extraccion de pag: https://www.amazon.es/Aspiradora-Sin-Cable-Potente-Mano-Coche/product-reviews/B09LLT9BG6/ref=cm_cr_getr_d_paging_btm_prev_1?ie=UTF8&reviewerType=all_reviews&pageNumber=5"
## [1] "Extraccion de pag: https://www.amazon.es/Aspiradora-Sin-Cable-Potente-Mano-Coche/product-reviews/B09LLT9BG6/ref=cm_cr_getr_d_paging_btm_prev_1?ie=UTF8&reviewerType=all_reviews&pageNumber=6"
## [1] "Extraccion de pag: https://www.amazon.es/Aspiradora-Sin-Cable-Potente-Mano-Coche/product-reviews/B09LLT9BG6/ref=cm_cr_getr_d_paging_btm_prev_1?ie=UTF8&reviewerType=all_reviews&pageNumber=7"
## [1] "Extraccion de pag: https://www.amazon.es/Aspiradora-Sin-Cable-Potente-Mano-Coche/product-reviews/B09LLT9BG6/ref=cm_cr_getr_d_paging_btm_prev_1?ie=UTF8&reviewerType=all_reviews&pageNumber=8"
## [1] "Extraccion de pag: https://www.amazon.es/Aspiradora-Sin-Cable-Potente-Mano-Coche/product-reviews/B09LLT9BG6/ref=cm_cr_getr_d_paging_btm_prev_1?ie=UTF8&reviewerType=all_reviews&pageNumber=9"
## [1] "Extraccion de pag: https://www.amazon.es/Aspiradora-Sin-Cable-Potente-Mano-Coche/product-reviews/B09LLT9BG6/ref=cm_cr_getr_d_paging_btm_prev_1?ie=UTF8&reviewerType=all_reviews&pageNumber=10"

Hacemos limpieza y eliminamos las filas vacias y algunas como consecuencia de fallo de carga de videos ajenos al texto, ademas de una sola fila con simbolos extraños que vamos a descartar en nuestro caso.

dat_tot <- dat_tot[-3,]

dat_tot <- as.data.frame(dat_tot)%>%rename(Opiniones_asp=dat_tot)

dat_tot <- dat_tot%>%filter(str_detect(Opiniones_asp, ""))%>%filter(!str_detect(Opiniones_asp,
           "No se ha podido cargar el contenido multimedia."))

Visualizamos finalmente todos nuestros datos en una tabla bien presentada con los resultados

datatable(dat_tot, class = 'cell-border stripe')

Vamos consiguientemente a realizar el analisis de sentimientos de las opiniones vertidas de los clientes sobre este tipo y modelo de aspiradora. Esto lo vamos a hacer con el paquete ‘syuzhet’ utilizando el idioma español.

En primer lugar vamos a tomar el vector representativo de nuestras opiniones del dataframe de nuestros datos.

Opiniones <- dat_tot[,1]

Procedemos a limpiar el texto con tm_map

Creamos corpus Opiniones

texto = Corpus(VectorSource(Opiniones))

Ponemos todos los datos a minuscula

Opiniones=tm_map(texto, tolower)

Quitamos numeros

Opiniones = tm_map(Opiniones, removeNumbers)

Mostramos palabras vacias y genericas

stopwords("spanish")
##   [1] "de"           "la"           "que"          "el"           "en"          
##   [6] "y"            "a"            "los"          "del"          "se"          
##  [11] "las"          "por"          "un"           "para"         "con"         
##  [16] "no"           "una"          "su"           "al"           "lo"          
##  [21] "como"         "más"          "pero"         "sus"          "le"          
##  [26] "ya"           "o"            "este"         "sí"           "porque"      
##  [31] "esta"         "entre"        "cuando"       "muy"          "sin"         
##  [36] "sobre"        "también"      "me"           "hasta"        "hay"         
##  [41] "donde"        "quien"        "desde"        "todo"         "nos"         
##  [46] "durante"      "todos"        "uno"          "les"          "ni"          
##  [51] "contra"       "otros"        "ese"          "eso"          "ante"        
##  [56] "ellos"        "e"            "esto"         "mí"           "antes"       
##  [61] "algunos"      "qué"          "unos"         "yo"           "otro"        
##  [66] "otras"        "otra"         "él"           "tanto"        "esa"         
##  [71] "estos"        "mucho"        "quienes"      "nada"         "muchos"      
##  [76] "cual"         "poco"         "ella"         "estar"        "estas"       
##  [81] "algunas"      "algo"         "nosotros"     "mi"           "mis"         
##  [86] "tú"           "te"           "ti"           "tu"           "tus"         
##  [91] "ellas"        "nosotras"     "vosotros"     "vosotras"     "os"          
##  [96] "mío"          "mía"          "míos"         "mías"         "tuyo"        
## [101] "tuya"         "tuyos"        "tuyas"        "suyo"         "suya"        
## [106] "suyos"        "suyas"        "nuestro"      "nuestra"      "nuestros"    
## [111] "nuestras"     "vuestro"      "vuestra"      "vuestros"     "vuestras"    
## [116] "esos"         "esas"         "estoy"        "estás"        "está"        
## [121] "estamos"      "estáis"       "están"        "esté"         "estés"       
## [126] "estemos"      "estéis"       "estén"        "estaré"       "estarás"     
## [131] "estará"       "estaremos"    "estaréis"     "estarán"      "estaría"     
## [136] "estarías"     "estaríamos"   "estaríais"    "estarían"     "estaba"      
## [141] "estabas"      "estábamos"    "estabais"     "estaban"      "estuve"      
## [146] "estuviste"    "estuvo"       "estuvimos"    "estuvisteis"  "estuvieron"  
## [151] "estuviera"    "estuvieras"   "estuviéramos" "estuvierais"  "estuvieran"  
## [156] "estuviese"    "estuvieses"   "estuviésemos" "estuvieseis"  "estuviesen"  
## [161] "estando"      "estado"       "estada"       "estados"      "estadas"     
## [166] "estad"        "he"           "has"          "ha"           "hemos"       
## [171] "habéis"       "han"          "haya"         "hayas"        "hayamos"     
## [176] "hayáis"       "hayan"        "habré"        "habrás"       "habrá"       
## [181] "habremos"     "habréis"      "habrán"       "habría"       "habrías"     
## [186] "habríamos"    "habríais"     "habrían"      "había"        "habías"      
## [191] "habíamos"     "habíais"      "habían"       "hube"         "hubiste"     
## [196] "hubo"         "hubimos"      "hubisteis"    "hubieron"     "hubiera"     
## [201] "hubieras"     "hubiéramos"   "hubierais"    "hubieran"     "hubiese"     
## [206] "hubieses"     "hubiésemos"   "hubieseis"    "hubiesen"     "habiendo"    
## [211] "habido"       "habida"       "habidos"      "habidas"      "soy"         
## [216] "eres"         "es"           "somos"        "sois"         "son"         
## [221] "sea"          "seas"         "seamos"       "seáis"        "sean"        
## [226] "seré"         "serás"        "será"         "seremos"      "seréis"      
## [231] "serán"        "sería"        "serías"       "seríamos"     "seríais"     
## [236] "serían"       "era"          "eras"         "éramos"       "erais"       
## [241] "eran"         "fui"          "fuiste"       "fue"          "fuimos"      
## [246] "fuisteis"     "fueron"       "fuera"        "fueras"       "fuéramos"    
## [251] "fuerais"      "fueran"       "fuese"        "fueses"       "fuésemos"    
## [256] "fueseis"      "fuesen"       "siendo"       "sido"         "tengo"       
## [261] "tienes"       "tiene"        "tenemos"      "tenéis"       "tienen"      
## [266] "tenga"        "tengas"       "tengamos"     "tengáis"      "tengan"      
## [271] "tendré"       "tendrás"      "tendrá"       "tendremos"    "tendréis"    
## [276] "tendrán"      "tendría"      "tendrías"     "tendríamos"   "tendríais"   
## [281] "tendrían"     "tenía"        "tenías"       "teníamos"     "teníais"     
## [286] "tenían"       "tuve"         "tuviste"      "tuvo"         "tuvimos"     
## [291] "tuvisteis"    "tuvieron"     "tuviera"      "tuvieras"     "tuviéramos"  
## [296] "tuvierais"    "tuvieran"     "tuviese"      "tuvieses"     "tuviésemos"  
## [301] "tuvieseis"    "tuviesen"     "teniendo"     "tenido"       "tenida"      
## [306] "tenidos"      "tenidas"      "tened"

Quitamos palabras genericas

Opiniones=tm_map(Opiniones, removeWords,stopwords("spanish"))

A partir de aqui vamos a dividir la cadena de caracteres en un listado de palabras, con la funcion ‘get_tokens()’.

Opiniones_palabras <- get_tokens(Opiniones)

En el paso anteriormente descrito, dividimos el texto(Opiniones), en una lista de palabras en las cuales nos hemos desecho de los signos de puntuacion.

Ahora podemos visualizar el numero de las palabras o tokens descritas en este texto

length(Opiniones_palabras)
## [1] 1862

Ahora vamos a seguir analizando el texto por palabras y vamos a generar un nuevo dataframe partiendo del vector que hemos sacado a traves de la funcion ‘get_nrc_sentiment’ aplicado al idioma español.

Opinion_dt <- get_nrc_sentiment(char_v = Opiniones_palabras, language = "spanish")

kable(head(Opinion_dt,50), booktabs = T) %>%
  kable_styling(font_size=12)
anger anticipation disgust fear joy sadness surprise trust negative positive
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 1 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 1 0 0 1 0 0 0 0 1
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 1 0 0 1 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 1 0 1
0 1 0 0 1 0 1 2 0 4
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 1 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 1
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 1
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 1 0 1 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 3 0 0 0 0 1 1 1 1
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 1 0 1

En este dataframe anteriormente visto se representa el puntuaje de las palabras de nuestros textos(opiniones), reconvertidas en puntacion segun 8 sentimientos(enfado, anticipación, disgusto, miedo, alegría, tristeza, sorpresa, confianza) y 2 valoraciones positiva y negativa.

Un valor mayor que ‘0’ quiere decir que la palabra existe en el diccionario ‘NRC’ y segundo que tiene algun valor asignado para alguna emocion y/o sentimiento.

Vemos pues la valoracion total dada en funcion de cada sentimiento puntuado

Emotions <- Opinion_dt%>%select(-negative,-positive)%>%names()

Scoring <- Opinion_dt[, 1:8]%>%colSums()

graf_dimen <- cbind(Emotions,Scoring)

graf_dimen <- as.data.frame(graf_dimen)

graf_dimen$Scoring <- as.numeric(graf_dimen$Scoring)


ggbarplot(graf_dimen, x = "Emotions", y = "Scoring",
          fill = "Emotions", color = "Emotions", palette = "rainbow",label=TRUE,
          lab.pos = "out",lab.size=3.5, lab.col = "black")+theme(legend.position="none")

CONCLUSION: Se muestra la emocion de confianza como la mas destacada , con diferencia, seguida a cierta distancia de anticipacion y alegria.

Tambien vemos el conjunto de valoraciones positivas y negativas

Assessment <- Opinion_dt%>%select(negative,positive)%>%names()

Scoring <- Opinion_dt[, 9:10]%>%colSums()

graf_dimen2 <- cbind(Assessment,Scoring)

graf_dimen2 <- as.data.frame(graf_dimen2)

graf_dimen2$Scoring <- as.numeric(graf_dimen2$Scoring)


ggbarplot(graf_dimen2, x = "Assessment", y = "Scoring",
          fill = "Assessment", color = "Assessment", palette = "paired",label=TRUE,
          lab.pos = "out",lab.size=3.5, lab.col = "black")+theme(legend.position="none")

CONCLUSION: Vemos que para este producto(Aspiradora de mano), hay claramente de forma muy destacada mas valoraciones ‘positivas’ que ‘negativas’.

NUBE DE PALABRAS PARA LAS EMOCIONES A CONSIDERAR

En nuestro caso vamos a establecer una nube de palabras a raiz de 4 emociones diferentes:

‘anger’, ‘joy’, ‘sadness’, ‘trust’.

Asi pues generamos el vector que contenga a estas emociones:

emotional_cloud <- c(
  paste(Opiniones_palabras[Opinion_dt$anger> 0], collapse = " "),
  paste(Opiniones_palabras[Opinion_dt$joy > 0], collapse = " "),
  paste(Opiniones_palabras[Opinion_dt$sadness > 0], collapse = " "),
  paste(Opiniones_palabras[Opinion_dt$trust > 0], collapse = " "))

emotional_cloud <- iconv(emotional_cloud, "latin1", "UTF-8")

Una vez que tenemos el vector creamos un corpus de palabras con 4 ‘documentos’ para la nube.

corpus_cloud <- Corpus(VectorSource(emotional_cloud))

Ahora transformamos el corpus en una matriz “término-documento” con la funcion ‘TermDocumentMatrix()’, y luego utilizamos ‘as.matrix’ para convertir esa matriz “termino-documento” en una matriz que cuenta con el listado de los terminos del texto con un mayor valor a 0 dentro de cada una de las 4 emociones que hemos extraido.

cloud_TDM <- TermDocumentMatrix(corpus_cloud)
cloud_TDM <- as.matrix(cloud_TDM)
head(cloud_TDM)
##           Docs
## Terms      1 2 3 4
##   dinero   1 1 0 1
##   evitar   1 0 1 0
##   fuerza   8 0 0 8
##   golpe    1 0 0 0
##   limitado 1 0 1 0
##   mal      1 0 1 0

Ahora aplicamos los nombres de las emociones en español para visualizar a posteriori en la nube de palabras que creemos.

colnames(cloud_TDM) <- c('enfado', 'alegria', 'tristeza', 'confianza')
head(cloud_TDM)
##           Docs
## Terms      enfado alegria tristeza confianza
##   dinero        1       1        0         1
##   evitar        1       0        1         0
##   fuerza        8       0        0         8
##   golpe         1       0        0         0
##   limitado      1       0        1         0
##   mal           1       0        1         0

Finalmente visualizamos la nube de palabras de acuerdo con cada emocion considerada cuyo tamaño y localizacion corresponde a su mayor o menor aparicion de acuerdo con el valor de la emocion asignada en el texto. Para ello utilizaremos la funcion ‘comparison.cloud()’.

set.seed(45)

comparison.cloud(cloud_TDM, random.order = FALSE,
                 colors = c("green", "red", "orange", "blue"),
                 title.size = 1, max.words = 50, scale = c(2.5, 1), rot.per = 0.4)

y nos da como resultado la division de estas 4 emociones seleccionadas con sus respectivas palabras representativas, cada una de ellas determinada por palabras de un determinado color, tamaño y localizacion.