Esta es una necesidad del proyecto Mapa de Afectos, de Zemos98 y Jartura. Se trataría de poder leer posts de Instagram con un determinado hashtag e importar automáticamente dichos contenidos a un Django.
A grandes rasgos la manera de hacer esto es mediante comandos de gestión de django que usen Selenium para hacer la interacción y que sean lanzados por un script de cron cada x tiempo, por ejemplo 10 minutos. Si se quiere hacer un poco más fancy en cliente, se puede hacer un script de long polling en JS que actualice el frontend mágicamente cada vez que se produzca una importación.
Selenium es un framework de automatización web. Se instala como cualquier otro paquete de python:
1 2 | |
Tiene como dependencia los motores de navegación que se quieran emplear. Vamos a emplear por ejemplo firefox/gecko. Para ello visitamos https://github.com/mozilla/geckodriver/releases y descargamos la última versión. Le damos permisos de ejecución y la movemos a cualquier ubicación en el PATH de sistema:
1 2 3 4 | |
Los comandos de gestión de django se albergan en apps que han de estar en la lista de INSTALLED_APPS de settings.py. Creamos por ejemplo una app connectors y dentro de ella creamos una carpeta management con un __init__.py y otra carpeta dentro, commands:
1 2 3 4 | |
Dentro de esta creamos el comando en cuestión, por ejemplo instagram_import.py. Un comando es un objeto de la clase Command y tiene que tener un método handle, que es la secuencia lineal que ejecuta el mismo. También le podemos implementar un método add_arguments, de modo que pueda aceptar parámetros de ejecución. Le añadimos un par de ellos, que nos permitan especificar un hashtag y un número máximo de posts a ser importados:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | |
Ahora es cuestión de definir el comando que escrapea Instagram. Para ello usamos selenium, especificando que lo corremos correr de manera headless, es decir, sin interfaz gráfica:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
Lo primero que tenemos que sortear es el acceso usando una cuenta válida de Instagram. La creamos e introducimos estos datos en private_settings.py:
1 2 3 | |
Una vez hemos accedido a la home de Instagram, introducimos los datos y hacemos login:
1 2 3 4 5 6 7 8 9 10 | |
Para ello usamos los selectores de selenium, browser.find_element_by_css_selector, que sería un análogo a un selector tipo querySelector, y find_element_by_xpath, que busca elementos descendiendo por el árbol de un nodo y donde '/' equivaldría a bajar un nivel y '//' equivaldría a bajar un número arbitrario de niveles, de modo que browser.find_element_by_xpath("//button[@type='submit']") sería equivalente a decir búscame en cualquier nivel de la DOM el primer botón de tipo submit.
Una vez sorteado el login tenemos que hacer dismiss sobre los modales habituales con los que Instagram tortura a sus usuarias:
1 2 3 4 5 6 | |
Aquí el selector es algo más complejo porque Instagram como cualquier red social grande está muy protegida contra el escrapeo, para lo que usa una DOM ultra-hermética llena de clases e IDs generados al azar. La única manera es seleccionar el botón por su contenido de texto, para lo que usamos la función contains.
Una vez superadas ambos escollos lo que hacemos es parsear la DOM para obtener una lista con las urls de los posts más recientes:
1 2 3 4 5 6 7 8 9 10 | |
Para ello usamos ahora el selector /following-sibling::div, que nos permite seleccionar a un sibling/hermano en la DOM, en este caso del H2 que encabeza la sección 'Most recent'. Una vez seleccionado dicho contenedor simplemente obtenemos todas las URLs que hay dentro, pillando sus elementos a y haciendo una lista por compresión de django con los contenidos de sus atributos href. Si hemos explicitado un límite lo aplicamos a dicha lista.
Ya por último vamos navegando por esa lista y vamos obteniendo las URLs de las imágenes asociadas a cada post y cerramos.
1 2 3 4 5 6 | |