Ejemplo de WEB SCRAPING la automatización web – Parte 1

Esta es la primera entrada que no habla de SAP en toda la historia de este blog. Supongo que ire publicando más entradas no relacionadas 100% con el mundo SAP ya que de vez en cuando también me apetece compartir otras de las muchas cosas que hago fuera de SAP.

Pero, antes de empezar, ¿Qué es esto de web scraping?

El web scraping es una técnica que mediante software nos permite extraer de manera automática información de un sitio web simulando la acción humana.

www.webharvy.com/images/web%20scraping.png

Esta entrada será un caso práctico que constara de dos partes, una primera donde veremos como accedemos de manera automática a linkedin y una segunda donde pintaremos los resultados.

Aunque no es necesario tener conocimientos de python, os recomiendo que si esta entrada os genera interés aprendáis lo básico en python ya que gracias a las miles de librerías que tiene podemos hacer muchas tareas cotidianas de manera automática.

Vamos sin mas a programar nuestro script.

Prerequisitos

Como os podéis imaginar, necesitaremos realizar unas cuantas instalaciones antes de empezar, quizás alguna de ellas ya la tendréis en vuestro sistema.

  • Google chrome: Pues si, necesitamos el navegador ya que como veréis, el script arranca el navegador para realizar las automatizaciones

  • Python: podemos instalar la última version del interprete en https://www.python.org/

  • PIP: sistema de gestión de paquetes de python. Nos permitirá instalar de manera fácil librerías en python. Os paso un link que os explica como instalarlo ya que según el sistema operativo puede ser mas o menos facil: https://recursospython.com/guias-y-manuales/instalacion-y-utilizacion-de-pip-en-windows-linux-y-os-x/

Montando nuestro script

Antes de empezar, necesitamos instalar las librerías. Para poder extraer la información en mi caso he usado Selenium por lo que necesitaremos instalar las librerías de selenium y el conector de chrome.

Para instalar y mediante PIP vamos a un terminal y lanzaremos estos comandos:

pip install webdriver-manager
pip install selenium

Main.py

Ya tenemos las librerías apunto. Para poder hacerlo un poco mas claro, he creado dos ficheros, el primero tiene el proceso principal que consta de estos pasos:

  • setup_method: Configura el arranque del navegador y arranca el navegador
  • login: Hace el login en Linkedin, esto quiere decir que accederá a una URL concreta, añadirá los campos de login y pulsara el botón de login
  • list: Extrea la lista de contactos de linkedin
  • teardown_method: Cierra el navegador

Y este sera el resultado del primer script al que llamaremos main.py:

from run import run


Linkedin = run()
Linkedin.setup_method()

Linkedin.login()
Linkedin.list()

Linkedin.teardown_method()

run.py

En la misma carpeta crearemos un fichero llamado run.py que tendrá toda la lógica. Esta parte la explicaré por partes y luego os pondré el resultado final.

Importando las librerías, esta parte tiene poco misterio, son únicamente las librerías que hemos instalado al inicio y alguna extra que nos ayudará en el processo :

import time
import json
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from webdriver_manager.chrome import ChromeDriverManager
import re
import time

El siguiente paso es iniciar el navegador con las opciones de configuración que necesitemos. En este caso tiraremos de lo mas “estándar”.

También podríamos hacer todo este proceso en fondo y no ver como el navegador realiza cada uno de los pasos.

Aun así no lo haremos ya que Linkedin puede detectar que estamos haciendo un inicio de sesión automático, por lo que Linkedin nos propondrá un pequeño puzle a resolver, así que esta parte será manual (aunque esto solo pasará si ejecutáis este script muchas veces seguidas):

  def setup_method(self):
    options = webdriver.ChromeOptions()
    options.add_argument('ignore-certificate-errors')
    options.add_argument("--disable-notifications")
        

    self.driver = webdriver.Chrome(ChromeDriverManager().install(),chrome_options=options)
    self.vars = {}

El siguiente paso sera la pantalla de login, aquí añadiremos nuestras credenciales de manera manual. Como este script es de ejemplo las añadiremos directamente en el código, aunque para hacerlo bien, deberíamos usar por ejemplo variables de entorno.

  def login(self):
    # 1 | open | /ca/ | 
    self.driver.get("https://www.linkedin.com/mynetwork/invite-connect/connections/")
    # 2 | setWindowSize | 1166x730 | 
    self.driver.set_window_size(973, 878)
    # 3 | click | linkText=Sign in | 
    WebDriverWait(self.driver, 30).until(expected_conditions.presence_of_element_located((By.CSS_SELECTOR, ".main__sign-in-link")))
    self.driver.find_element(By.CSS_SELECTOR, ".main__sign-in-link").click()
    # 3 | waitForElementPresent | id=username |  x
    WebDriverWait(self.driver, 30).until(expected_conditions.presence_of_element_located((By.ID, "username")))
    # 4 | type | id=username | Tu mail
    self.driver.find_element(By.ID, "username").send_keys("TuMail@TuMail.com")
    # 5 | click | id=password | 
    self.driver.find_element(By.ID, "password").click()
    # 6 | type | id=password | Tu password
    self.driver.find_element(By.ID, "password").send_keys("TuPassword")
    # 7 | sendKeys | id=password | ${KEY_ENTER}
    self.driver.find_element(By.ID, "password").send_keys(Keys.ENTER)
    # 8 | waitForElementPresent | id=ember19 | X
    WebDriverWait(self.driver, 30).until(expected_conditions.presence_of_element_located((By.ID, "ember19")))

Importante los pasos 4 donde añadiremos el mail y el 6 donde añadiremos el password.

Al finalizar este punto ya estaríamos en la lista de contactos de nuestro perfil. Seguimos con la exportación del listado. Lo importante aquí es que Linkedin, como toda buena plataforma optimizada, no retorna todo el listado, sino que retorna solo 20 contactos.

Si hacemos scroll hacia abajo nos cargará 20 contactos mas. Así que aquí el truco sera hacer scroll hacia abajo hasta tener todos los contactos. Para ello haremos una pequeña trampa haciendo scroll up y scroll down hasta completarla lista:

  def generateScrollList(self,last):
      scrollUp = last - 4481
      ScrollDown = last + 4481

      if scrollUp < 0:
        scrollUp = 0

      self.driver.execute_script("window.scrollTo(0,"+str(scrollUp)+")")
      self.driver.execute_script("window.scrollTo(0,"+str(ScrollDown)+")")
      time.sleep(1)
      return ScrollDown

Ya solo nos queda cargar el listado y añadir los resultados a un fichero CSV:

  def list(self):


    scrollCount = 0
    totalContacts = 0
    countContacts = 0

#   get contacts total
    totalContacts = int(re.findall(r'\d+', self.driver.find_element(By.CSS_SELECTOR, ".t-18").text)[0]) - 20

    while True and countContacts < totalContacts:
      scrollCount = self.generateScrollList(scrollCount)

      parentElement = self.driver.find_element(By.CLASS_NAME, "scaffold-finite-scroll__content")
      list = parentElement.find_element(By.TAG_NAME,"ul")
      elementsList = list.find_elements(By.TAG_NAME,"li")
      countContacts = len(elementsList)
    
    scrollCount = self.generateScrollList(scrollCount)
    
    parentElement = self.driver.find_element(By.CLASS_NAME, "scaffold-finite-scroll__content")
    list = parentElement.find_element(By.TAG_NAME,"ul")
    elementsList = list.find_elements(By.TAG_NAME,"li")

    # export file
    f = open("export.csv", "w", encoding="utf-8")

    for x in range (len(elementsList)):

      output = elementsList[x].text.splitlines()[1] + ";" + elementsList[x].text.splitlines()[3] + ";" + elementsList[x].text.splitlines()[4] + "\n"
      f.write(output)

    f.close()

Algunas cosas interesantes de esta parte del código:

  • Mediante la instrucción find element y buscando la clase t-18 obtenemos el número total de contactos.

self.driver.find_element(By.CSS_SELECTOR, ".t-18").text

  • Al seleccionar un elemento de la jerarquía html, podemos acceder a sus hijos directamente con variables, por ejemplo en el siguiente código bajamos por la jerarquía de una lista “desordenada” typical de html, esto equivale a los tags <ul> y <li>.


    parentElement = self.driver.find_element(By.CLASS_NAME, "scaffold-finite-scroll__content")
    list = parentElement.find_element(By.TAG_NAME,"ul")
    elementsList = list.find_elements(By.TAG_NAME,"li")

A continuación os añado el script entero:

# Generated by Selenium IDE
import time
import json
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from webdriver_manager.chrome import ChromeDriverManager
import re
import time

class run():
  def setup_method(self):
    options = webdriver.ChromeOptions()
    options.add_argument('ignore-certificate-errors')
    options.add_argument("--disable-notifications")
        
        # Bloqueja el log
    
    """
    options.add_argument("--no-sandbox")
    options.add_argument("--headless")
    options.add_argument("--disable-gpu")
    options.add_argument("--disable-crash-reporter")
    options.add_argument("--disable-extensions")
    options.add_argument("--disable-in-process-stack-traces")
    options.add_argument("--disable-logging")
    options.add_argument("--disable-dev-shm-usage")
    options.add_argument("--log-level=3")
    options.add_argument("--output=/dev/null")
    options.add_experimental_option('excludeSwitches', ['enable-logging'])
    """
    self.driver = webdriver.Chrome(ChromeDriverManager().install(),chrome_options=options)
    self.vars = {}
  
  def teardown_method(self):
    self.driver.quit()
  
  def login(self):
    # 1 | open | /ca/ | 
    self.driver.get("https://www.linkedin.com/mynetwork/invite-connect/connections/")
    # 2 | setWindowSize | 1166x730 | 
    self.driver.set_window_size(973, 878)
    # 3 | click | linkText=Sign in | 
    WebDriverWait(self.driver, 30).until(expected_conditions.presence_of_element_located((By.CSS_SELECTOR, ".main__sign-in-link")))
    self.driver.find_element(By.CSS_SELECTOR, ".main__sign-in-link").click()
    # 3 | waitForElementPresent | id=username |  x
    WebDriverWait(self.driver, 30).until(expected_conditions.presence_of_element_located((By.ID, "username")))
    # 4 | type | id=username | tumail@mail.com
    self.driver.find_element(By.ID, "username").send_keys("tumail@mail.com")
    # 5 | click | id=password | 
    self.driver.find_element(By.ID, "password").click()
    # 6 | type | id=password | Password
    self.driver.find_element(By.ID, "password").send_keys("TuPassword")
    # 7 | sendKeys | id=password | ${KEY_ENTER}
    self.driver.find_element(By.ID, "password").send_keys(Keys.ENTER)
    # 8 | waitForElementPresent | id=ember19 | X
    WebDriverWait(self.driver, 30).until(expected_conditions.presence_of_element_located((By.ID, "ember19")))

  def list(self):

    
    scrollCount = 0
    totalContacts = 0
    countContacts = 0

#   get contacts total
    totalContacts = int(re.findall(r'\d+', self.driver.find_element(By.CSS_SELECTOR, ".t-18").text)[0]) - 20

    while True and countContacts < totalContacts:
      scrollCount = self.generateScrollList(scrollCount)

      parentElement = self.driver.find_element(By.CLASS_NAME, "scaffold-finite-scroll__content")
      list = parentElement.find_element(By.TAG_NAME,"ul")
      elementsList = list.find_elements(By.TAG_NAME,"li")
      countContacts = len(elementsList)
      print(len(elementsList),"/",totalContacts)
    
    scrollCount = self.generateScrollList(scrollCount)
    
    parentElement = self.driver.find_element(By.CLASS_NAME, "scaffold-finite-scroll__content")
    list = parentElement.find_element(By.TAG_NAME,"ul")
    elementsList = list.find_elements(By.TAG_NAME,"li")

    # export file
    f = open("export.csv", "w", encoding="utf-8")

    for x in range (len(elementsList)):
      print(elementsList[x].text.splitlines())
      print("Nom: ",elementsList[x].text.splitlines()[1])
      print("Lloc de treball: ",elementsList[x].text.splitlines()[3])
      print("Connectat: ",elementsList[x].text.splitlines()[4])
      output = elementsList[x].text.splitlines()[1] + ";" + elementsList[x].text.splitlines()[3] + ";" + elementsList[x].text.splitlines()[4] + "\n"
      f.write(output)

    f.close()
  


  def generateScrollList(self,last):
      scrollUp = last - 4481
      ScrollDown = last + 4481

      if scrollUp < 0:
        scrollUp = 0

      self.driver.execute_script("window.scrollTo(0,"+str(scrollUp)+")")
      self.driver.execute_script("window.scrollTo(0,"+str(ScrollDown)+")")
      time.sleep(1)
      return ScrollDown

Ejecutando el script

Ya con todo el código, que a modo resumen tenemos los archivos run.py y main.py vamos con la ejecución por terminal. Únicamente tendremos que ejecutar este comado:

python .\main.py

Y empezara la magia:

Esto nos generará un fichero csv:


Y hasta aquí el script, con esta base ya podéis explotar los datos con excel o algún otro editor, aunque en la próxima entrada vamos a ver como mostrar los resultados en gráficos.

Como siempre suscribete, dale a la campanita de notificaciones y comparte en redes para estar a la última.

Deja una respuesta

Tu dirección de correo electrónico no será publicada.

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.