En este post hablaremos de cómo automatizar la recogida de datos de Instagram. Aplicaremos técnicas de Web Scrapping implementadas con Python para extraer imágenes, comentarios y likes de Instagram buscando por un hashtag.
Se trata por tanto de un artículo técnico, para el que se debe tener nociones básicas de HTML y programación en Python.
Cuando estamos entrenando un algoritmo de Machine Learning o visualizando un problema muchas veces vemos que nos hace falta más información para completar o enriquecer el análisis. Es aquí donde entra en juego las técnicas de recogida de datos de fuentes externas, usando metodologías como el Web Scrapping.
¿Qué es el Web Scrapping?
Antes de comenzar vamos a introducir brevemente este concepto que hemos utilizado en la introducción. Se trata de un tecnicismo que anglosajón cuya traducción literal es «escarbado o raspado web».
Es decir, son técnicas que automatizan la extracción de información de las páginas web. Básicamente se trata de programas, o scripts, que simulan el comportamiento humano navegando por la web.
Por tanto, mediante estas técnicas programamos los pasos que seguiríamos nosotros en la web, de forma que se pueda ejecutar por una máquina mucho más rápido.
Librerías de Web Scrapping
Como hemos comentado usaremos como lenguaje base Python. Además usaremos librerías que nos facilitan la implementación de estas técnicas de Web Scrapping:
- Selenium: librería que facilita el uso de un Webdriver, el cuál se utiliza para simular acciones de un ser humano en una web, tales como un click.
- Chromedriver: herramienta de código abierto que proporciona la ejecución de las funciones sobre el navegador Google Chrome. Será necesario descargarnos un ejecutable y guardarlo en una carpeta al que apuntaremos en nuestro programa Python. En este link podéis descargaros dicho ejecutable. Usaremos una variable de entorno para definir ese path, por ejemplo:
chromedriver_path = '/Users/Ignacio/documents/chromedriver'
IMPORTANTE: deberemos tener instalado Google Chrome para que se pueda ejecutar todo correctamente.
Ahora ya sí copio el import de las librerías necesarias:
#Importing all needed libraries from selenium import webdriver import time import os import time import requests from pprint import pprint import pandas as pd import json from lxml import html import re import csv import numpy as np from selenium import webdriver from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.remote.webelement import WebElement from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.ui import WebDriverWait from selenium.common.exceptions import ( NoSuchElementException, TimeoutException, WebDriverException, ) import pandas as pd
Funciones
En esta sección vamos a detallar las funciones implementadas para la extracción de la información. En todo momento vamos a simular el comportamiento o la interacción de un humano de forma automátizada.
Cargar Instagram
Esta función carga la página de Instagram. Nada más cargar la página nos salta un pop-up preguntándonos si aceptamos las cookies o no, tal y cómo se muestra en la siguiente imagen.

Implementamos o simulamos el click en aceptar con código:
# Accept cookies cookies = WebDriverWait(browser, WAIT_TIME_3).until( EC.element_to_be_clickable((By.XPATH,'//button[contains(text(), "Aceptar")]'))).click()
Y esta es la función completa con el código que acepta las cookies incluído:
def load_instagram(): """ Function to initialize Instagram and launch it in a browser using Selenium """ # Chrome driver should be in executable_path=os.path.join(chromedriver_path) options = webdriver.ChromeOptions() options.add_argument('--ignore-certificate-errors') options.add_argument('--disable-notifications') # 1-Allow, 2-Block, 0-default preferences = { "profile.default_content_setting_values.notifications" : 2, "profile.default_content_setting_values.location": 2, # We don't need images, only the URLs. "profile.managed_default_content_settings.images": 2, } options.add_experimental_option("prefs", preferences) browser = webdriver.Chrome( executable_path=executable_path, chrome_options=options, ) browser.wait = WebDriverWait(browser, WAIT_TIME) #Opening the browser and getting the url url = "https://www.instagram.com/" browser.get(url) #wait 5 seconds to load time.sleep(WAIT_TIME_2) # Accept cookies cookies = WebDriverWait(browser, WAIT_TIME_3).until( EC.element_to_be_clickable((By.XPATH,'//button[contains(text(), "Aceptar")]'))).click() return browser
Inicio de sesión
Una vez aceptamos las cookies nos sale la página de inicio de sesión donde deberemos introducir nuestras credenciales.

Para ello hemos implementado la siguiente función:
def instagram_login(driver): """ Login to Instagram using username and password. """ usr = driver.find_element_by_name("username") usr.send_keys(USERNAME) password = driver.find_element_by_name("password") password.send_keys(PASSWORD) password.send_keys(Keys.RETURN) time.sleep(WAIT_TIME_2)
Donde USERNAME y PASSWORD son variables genéricas definidas anteriormente. Debemos especificar nuestro usuario y contraseña en formato string. Un ejemplo:
PASSWORD = '123456' USERNAME = 'ejemplo@gmail.com'
Búsqueda de información
El objetivo principal es recoger toda la información asociada a un hastag, es decir, imágenes y comentarios, y almacenarlo en local.
Por tanto, para la búsqueda por hashtag deberemos introducirlo en la caja de búsqueda una vez hayamos iniciado sesión. Esta se encuentra en la parte superior:

Como se puede apreciar, aparece un texto Busca en dicha caja. Para saber como encontrarlo es necesario inspeccionar el código fuente HTML de la página. Si hacemos click derecho sobre Busca->Inspeccionar, nos aparecerá el siguiente recuadro lateral:

Se puede apreciar como la etiqueta input contiene un atributo placeholder=»Busca». Esto lo usaremos para encontrar con código dicha caja de búsqueda, implementándolo de la siguiente forma:
# Get the search box searchbox = WebDriverWait(browser,WAIT_TIME).until( EC.element_to_be_clickable((By.XPATH, "//input[@placeholder='Busca']"))) searchbox.clear()
Una vez estamos situados y hemos limpiado el texto, podemos proceder a escribir el hashtag objetivo, que lo denominamos keyword:
# Search by tag searchbox.send_keys(keyword) time.sleep(WAIT_TIME_3) searchbox.send_keys(Keys.ENTER) time.sleep(WAIT_TIME_3) searchbox.send_keys(Keys.ENTER) time.sleep(WAIT_TIME_3)
NOTA: se ha implementado dos veces el click sobre enter ya que la primera vez posiciona el cursor en la primera lista del desplegable y el segundo ejecuta la búsqueda en sí.
Imágenes
Una vez procedemos a buscar, nos aparecen numerosas imágenes como se muestra en la siguiente figura. Además hemos pulsado click derecho->inspeccionar sobre una imagen para analizar los atributos.

Se puede apreciar como la etiqueta <img> tiene un atributo llamado src, que contiene la url de la imagen en cuestión. Por tanto con el siguiente código recuperamos todas las url de las imágenes que aparecen en pantalla.
images = browser.find_elements_by_tag_name('img') images = [image.get_attribute('src') for image in images] images = images[1:-2] #slicing-off first photo, IG logo and Profile picture
Número de likes
Una vez disponemos de todas las urls de las imágenes, tenemos que ir pinchando foto a foto para que aparezcan el número de likes. Si pinchamos en una imagen cualquiera se abre la siguiente ventana:

Para ejecutar cada click con código usaremos el siguiente código, donde image será cada url:
# Click and open posts browser.execute_script("arguments[0].click();", browser.find_element_by_xpath('//img[@src="'+str(image)+'"]')) time.sleep(WAIT_TIME_5)
Al igual que en el caso anterior, vamos a inspeccionar para ver que etiquetas podemos usar como referencia en el scrapping:

Vemos como el caso más habitual se trata de una etiqueta <div> con clase Nm9Fw con una etiqueta <span> por debajo. Por tanto usaremos dicha clase de referencia mediante llamadas con estilo CSS:
try: el_likes = browser.find_element_by_css_selector(".Nm9Fw > * > span").text except Exception as e: try: el_likes = browser.find_element_by_css_selector(".Nm9Fw > button").text except Exception as e2: try: el_likes = browser.find_element_by_css_selector(".vcOH2").text except Exception as e3: print(f"ERROR - Could not fetch like {e3}")
NOTA: hemos comprobado que en algunos casos en lugar de etiqueta span, aparece un button. También en casos muy puntuales hemos visto que los likes aparecen directamente en una etiqueta de clase vcOH2.
Una vez extraídos los likes, es posible que la imagen no tenga ningún like, o que en lugar de imagen sea un vídeo y aparezcan número de reproducciones. Por tanto, es necesario una limpieza de datos ex-post para quedarnos únicamente con el número:
# Transform the text when there are no Likes if el_likes == "indicar que te gusta esto": el_likes = '0' # Clean the info to only retrieve numbers instead of text if "Me gusta" in str(el_likes) or "reprodu" in str(el_likes): el_likes = el_likes[:1]
Comentarios
Al igual que con los likes es necesario pulsar en cada imagen usando el siguiente código:
# Click and open posts browser.execute_script("arguments[0].click();", browser.find_element_by_xpath('//img[@src="'+str(image)+'"]')) time.sleep(WAIT_TIME_5)
Inspeccionamos la imagen:

Vemos que sobre la clase C4VMK cuelgan diferentes etiquetas <span > con los comentarios. Por tanto usamos el siguiente código que nos devuelve una lista con todos los comentarios:
try: comment_elements = browser.find_elements_by_css_selector(".eo2As .gElp9 .C4VMK") comment = [element.find_elements_by_tag_name('span')[1].text for element in comment_elements] except Exception as e: print(f"ERROR - Could not fetch comment {e}")
Procesamiento de la información
En este punto ya hemos obtenido todos los likes, urls de las imágenes y comentarios. Por tanto, estamos en disposición de procesar la información para almacenarla en local.
Básicamente vamos a estructurar la información en un Pandas Dataframe, de forma que el Título del post sea el primer comentario. Además separamos los hashtags principales en otra columna. En la siguiente imagen, mostramos lo que hemos considerado como hashtags principales:

La función implementada para llevar a cabo todo este procesado es la siguiente:
def process_info(insta_info): """ Function that process the info retrieved from posts """ df = pd.DataFrame(insta_info) # The first comment is considered as a title df["Title"] = [i[0] for i in list(df["Comments"])] df["Comments"] = [i[1:] for i in list(df["Comments"])] # Get all the hashtags from the title df["Principal Hashtags"] = [ re.findall("#(\w+)", title) for title in list(df["Title"])] return df
El dataframe resultado final tiene la siguiente forma:

Descarga de la información
Finalmente vamos a proceder a descargar en local toda la información obtenida. Por un lado las imágenes en formato .jpg y por otro el dataframe con comentarios likes, etc anterior en formato excel.
df["Download name"] = download_images(keyword,list(df["Image URL"])) df.to_csv(str(keyword)+'.csv',index=False, header=True)
La función download_images es la siguiente:
def download_images(keyword,images): """ Function used to download images """ fpath = os.getcwd() fpath = os.path.join(fpath, keyword[1:]) if(not os.path.isdir(fpath)): os.mkdir(fpath) #download images counter = 0 image_name_lst = [] for image in images: persist_image(fpath,image,counter,image_name_lst) counter += 1 return image_name_lst def persist_image(folder_path:str,url:str, counter,image_name_lst): """ Function used to persist physically in localhost an image from an url """ try: image_content = requests.get(url).content except Exception as e: print(f"ERROR - Could not download {url} - {e}") try: img_name = 'jpg' + "_" + str(counter) + ".jpg" f = open(os.path.join(folder_path,img_name), 'wb') f.write(image_content) f.close() print(f"SUCCESS - saved {url} - as {folder_path}") image_name_lst.append(img_name) except Exception as e: print(f"ERROR - Could not save {url} - {e}")
Espero que os haya gustado! Si tenéis cualquier duda o necesitáis alguna explicación del código más en detalle, no dudéis en poner un comentario!
Buen articulo. ¿Hay manera de extraer correos electrónicos de ciertas publicaciones? , de igual forma si tienes un consolidado de todos los códigos que usaste o el proyecto en tu Git hub, podrías compartirlo?
saludos.
Muchas gracias por el comentario! El código completo lo puedes encontrar en mi github: https://github.com/iramosgarcia/instagram-scrapping/blob/main/Instagram.ipynb.
Respecto a la pregunta de correos electrónicos no hemos trabajado con datos de usuarios. Te animo a que aplicando las técnicas de exploración explicadas en el post, investigues la etiqueta HTML donde se encuentra la información que buscas.
Hola! Mil gracias por compartir tu trabajo, esto es justo lo que estaba buscando. Solo debo agregar para extraer los Shares, pero el resto me sirve perfectamente. Ahora bien, me está saltando un error al final del código cuando corres la función principal de «scrapping_instagram», y al tratar de visualizar el «df» no existe porque por el error no se crea. Te comparto el error que me salta por si pudieras ayudarme:
Could not fetch like Message: no such element: Unable to locate element: {«method»:»css selector»,»selector»:».vcOH2″}
Luego me salen otros errores pero creo que este es el principal. Por lo demás, me funciona ingresar a chrome, abrir instagram, aceptar las cookies, ingresar, buscar el hashtag, abrir los posts, e irse moviendo en cada uno.
Gracias nuevamente!
Buenas tardes, muy buena publicacion. ¿Sabes si hay alguna manera de saber si ya has dado a Me Gusta a esa publicacion?
Muchas gracias