Glassdoor almacena más de 100 millones de reseñas, salarios e información; tiene 2,2 millones de empleadores que publican activamente trabajos en el mercado y recibe alrededor de 59 millones de visitas únicas por mes. Con tantos datos y demanda, Glassdoor es una mina de oro para datos de empleos y empresas.
En el tutorial de hoy, extraeremos datos de la lista de trabajos de Glassdoor sin usar ningún tipo de navegador sin interfaz ni iniciar sesión en el sitio, manteniendo nuestras actividades legales y éticamente responsables.
Hablando de legalidad…
La respuesta directa es sí, raspar Glassdoor es legal siempre y cuando no rompa algunas reglas esenciales.
En general, a Glassdoor no le gusta que lo raspen, como se indica en sus Términos de uso . Sin embargo, hay algunos matices, ya que debe aceptar estos términos para que le afecten.
Por ejemplo, crear una cuenta y luego extraer datos detrás del muro de registro se consideraría ilegal porque aceptó los términos de uso en el momento en que creó la cuenta.
Dicho esto, todas las páginas a las que se puede acceder sin una cuenta se consideran públicas; por lo tanto, es legal que raspe esas páginas.
Aún así, recomendamos utilizar algunas de las mejores prácticas para asegurarse de que está tratando el sitio web con respeto y no perjudicando a sus usuarios.
Recurso: para obtener una explicación más completa, consulte nuestra guía sobre la legalidad del web scraping .
Para este proyecto, rasparemos las oportunidades de trabajo a tiempo parcial de Glassdoor en Milán para extraer el título del trabajo, la contratación de la empresa y el enlace a la publicación del trabajo.
Estas páginas de categorías están disponibles públicamente (no están detrás de ningún tipo de inicio de sesión o muro de pago), por lo que estamos haciendo este 100% sombrero blanco.
Aunque explicaremos cada paso del proceso, asumimos que tiene los conocimientos básicos cubiertos. Sin embargo, si alguna vez se siente perdido o confundido, aquí hay algunos proyectos de raspado web más fáciles que puede usar para desarrollar sus habilidades gradualmente:
Con esto fuera del camino, comencemos por configurar el proyecto.
Para que todo esté en funcionamiento, deberá crear una nueva carpeta para su proyecto (llamamos a nuestra carpeta glassdoor-scraper ) y abrirla en VS Code o su IDE favorito.
Una vez dentro de la carpeta, abre una terminal e inicia Node.JS así:
1npm init -``y
Creará dos archivos JSON necesarios dentro de su proyecto.
Luego, instalaremos nuestras tres dependencias favoritas:
1npm install axios cheerio objects``-``to``-``csv
A partir de ahí, cree un nuevo nombre de archivo glassdoorScraper.js e importe las dependencias en la parte superior:
1const axios = require(``"axios"``); 2const cheerio = require(``"cheerio"``); 3const ObjectsToCsv = require(``"objects-to-csv"``);
Para el siguiente paso, exploremos Glassdoor para comprender cómo acceder a cada punto de datos que estamos buscando.
Al hacer clic en el enlace "Offerte di lavoro Part time a Milano".
El sitio web lo llevará a una lista de ofertas de trabajo en forma de tarjetas a la izquierda y más detalles a la derecha. Cada tarjeta representa un trabajo y contiene toda la información que estamos buscando: nombre de la empresa, título del trabajo y un enlace al puesto de trabajo.
Pero no estamos interesados en la representación visual, ¿verdad? Para encontrar nuestros objetivos de CSS, inspeccionemos la página y veamos cómo están estructuradas estas tarjetas.
El primer elemento de la tarjeta es el nombre de la empresa, que se encuentra tres niveles hacia abajo dentro de su contenedor: div > a > span
. Es importante notar esto porque el <span>
que contiene el texto no tiene ningún atributo al que podamos apuntar.
Si solo buscamos <span>
dentro de nuestro analizador, extraeríamos todos los elementos <span>
de la página; eso no es nada bueno.
En su lugar, podemos subir un nivel y apuntar a la etiqueta principal <a>
porque tiene muchos atributos para elegir.
El selector para el nombre de la empresa sería algo así como " a.job-search-key-l2wjgv.e1n63ojh0.jobLink > span
".
En otras palabras, apuntamos a cada etiqueta <a>
con el atributo de clase “ job-search-key-l2wjgv.e1n63ojh0.jobLink
” y luego nos desplazamos hacia el elemento secundario <span>
.
Nota: Esta etiqueta tiene asignados tres valores de clase diferentes; en la mayoría de los casos, estos valores se separan con un espacio al revisar el HTML de la página, como en la imagen de arriba. Sin embargo, esto puede convertirse en un problema al construir el selector en su código, por lo tanto, reemplace estos espacios con un punto.
Si haces el mismo proceso para el resto de los elementos, se verá así:
<span>
.
Pero, ¿cómo podemos saber si estos van a funcionar? Bueno, podríamos construir el raspador y probar los selectores en la primera página, pero si no funciona, ¿seguirá enviando solicitud tras solicitud?
¡No! Antes de poner en peligro nuestra IP, mejor utilizar la consola del Navegador para probar estos selectores.
Desde donde se encuentra, haga clic en la pestaña Consola. Verá mucha información impresa allí.
Para deshacerse de él, presione CTRL + L en su palabra clave para borrar la consola.
Con una pizarra limpia, pasemos el primer selector a la función querySelectorAll() y veamos qué se devuelve:
¡Bien, eso funcionó! Como puede ver, devuelve un total de 30 nodos y, a medida que pasamos el mouse sobre ellos, resaltan el nombre de la empresa en cada tarjeta. Además, ahora sabemos que hay 30 trabajos por página. .
Prueba a probar el resto de selectores para ver el proceso tú mismo. Cuando esté listo, volvamos a VS Code.
Sabes lo que quieres extraer y ahora sabes dónde encontrarlo. Es hora de enviar nuestro raspador a la naturaleza.
En nuestro archivo glasssdoorScraper.js, creemos una nueva función asíncrona e inicialicemos Axios pasando la URL a la que nos dirigimos.
1(``async function () { 2const page = await axios(""); 3})();
¡Vaya! Pero aún no hemos elegido una URL, ¿verdad? Volviendo a la página actual, la URL se parece a esto:
1[https://www.glassdoor.it/Lavoro/milano-part-time-lavori-SRCH_IL.0](https://www.glassdoor.it/Lavoro/milano-part-time-lavori-SRCH_IL.0),6_IC2802090_KO7,16.htm
Pero nunca debe tomar la primera URL sin evaluar primero si hay una variante mejor.
Por ejemplo, si navegamos por el resto de las URL en la serie paginada, aquí hay una tendencia común de una página a otra:
Página 2:
Hay mucho ruido en estas URL, pero mire más de cerca la base de la URL resaltada en amarillo.
Si usamos solo esa parte, obtendremos los mismos resultados que si nos estuviéramos moviendo a través de la paginación. Así que usemos esa estructura de ahora en adelante.
1(``async function () { 2const page = await axios( 3"[https://www.glassdoor.it/Lavoro/milano-part-time-lavori-SRCH_IL.0](https://www.glassdoor.it/Lavoro/milano-part-time-lavori-SRCH_IL.0),6_IC2802090_KO7,16_IP1.htm?includeNoSalaryJobs=true" 4); 5 6console.log(page.status); 7})();
Y estamos iniciando sesión en la consola por si acaso.
¡Impresionante, un código 200 exitoso! Sin embargo, antes de continuar, tendremos que hacer una cosa más para que nuestro raspador sea más resistente una vez que escalamos nuestro proyecto a más solicitudes.
Algo a tener en cuenta al raspar sitios web con mucho tráfico o muchos datos es que a la mayoría de ellos no les gusta que los raspen, por lo que tienen varios trucos para bloquear el acceso de sus scripts a sus servidores.
Para evitar esto, deberá codificar diferentes comportamientos que convengan a los servidores de que su raspador es en realidad un ser humano real que interactúa con la página, como manejar CAPTCHA, rotar su dirección IP, crear y mantener un grupo de direcciones IP para rotar, enviar los encabezados correctos y cambiar su ubicación de IP para acceder a datos geosensibles.
O podemos usar una API simple para manejar todo esto por nosotros.
ScraperAPI utiliza el aprendizaje automático, años de análisis estadístico y enormes granjas de navegadores para evitar que sus bots de raspado sean marcados y bloqueados.
Primero, creemos una cuenta gratuita de ScraperAPI para generar una clave API, que encontrará en su tablero.
Y usaremos la siguiente estructura para modificar nuestra solicitud inicial:
1http:``/``/``api.scraperapi.com?api_key``=``{yourApiKey}&url``=``https:``/``/``www.glassdoor.it``/``Lavoro``/``milano``-``part``-``time``-``lavori``-``SRCH_IL.``0``,``6_IC2802090_KO7``,``16_IP1``.htm?includeNoSalaryJobs``=``true
Ahora, nuestra solicitud se enviará desde los servidores de ScraperAPI, rotando nuestra dirección IP en cada solicitud y manejando todas las complejidades y sistemas anti-raspado que encuentra nuestro raspador.
¡Comienza la parte divertida! El primer paso para extraer los datos deseados es analizar la respuesta para que podamos navegar a través de los nodos y elegir los elementos usando los selectores construidos previamente.
1const html = page.data; 2const $ = cheerio.load(html);
Lo que ha hecho ahora es almacenar los datos de la respuesta (que son datos HTML) en una variable que luego pasó a Cheerio para analizar.
Cheerio transformará todos los elementos del archivo HTML en objetos Node que podemos atravesar usando XPath o, en nuestro caso, selectores CSS.
Aún así, hay un selector que aún no hemos discutido: el contenedor principal.
En la página, cada listado de trabajo está representado por una tarjeta, y cada tarjeta contiene los datos que queremos. Para facilitar que nuestro raspador encuentre la información, y reducir la probabilidad de que se filtren datos inútiles en nuestro proyecto, primero debemos seleccionar todas las tarjetas y luego recorrerlas para extraer los puntos de datos.
Cada tarjeta es un elemento <li>
y podemos elegirlas usando el atributo [data-test="jobListing"]
.
Nota: no puede verlo en la imagen debido al corte en la captura de pantalla, pero podrá encontrar el atributo en la página.
Así es como podemos escribir el analizador completo:
1let allJobs = $(``'[data-test="jobListing"]'``); 2allJobs.each((index, element) =``> { 3const jobTitle = $(element).find(``"a[data-test='job-link'] > span"``).text(); 4const company = $(element) 5.find(``"a.job-search-key-l2wjgv.e1n63ojh0.jobLink > span"``) 6.text(); 7const jobLink = $(element).find(``"a[data-test='job-link']"``).attr(``"href"``); 8});
¿Observe el método .text()
al final de la cadena? Como probablemente descubras, el método extrae los datos de texto del elemento. Sin él, devolvería el marcado y el texto, lo que no es muy útil.
Por otro lado, cuando queremos extraer el valor de un atributo dentro de un elemento, podemos usar el método .attr()
y pasar el atributo del que queremos el valor.
Si ejecutamos nuestra secuencia de comandos ahora, en realidad no sucedería nada porque la secuencia de comandos no está haciendo nada con los datos que está seleccionando.
Podemos continuar y registrar los datos en la terminal, pero todo será muy confuso de ver. Entonces, antes de registrarlo, formateémoslo usando una matriz.
Fuera de la función asíncrona principal, cree una matriz vacía como esta:
1let jobListings = [];
Para agregar los datos raspados adentro, todo lo que necesitamos usar es .push()
en la matriz:
1jobListings.push({ 2"Job Title"``: jobTitle, 3"Hiring Company"``: company, 4"Job Link"``: "[https://www.glassdoor.it](https://www.glassdoor.it/)" + jobLink, 5});
¿Captaste eso? Estamos insertando una cadena antes del valor devuelto por jobLink
. ¿Pero por qué?
Esta es exactamente la razón por la cual el raspado web se trata de los detalles. Volvamos a la página y veamos el valor href
:
Hay mucha información allí, pero falta una pieza en la URL: " https://www.glassdoor.it ". Esta es una forma inteligente de proteger la URL de raspadores como nosotros.
Estamos concatenando los dos en una cadena al pasar esta información faltante como una cadena junto con el valor de jobLink. Por lo tanto, haciéndolo útil de nuevo.
Con esto fuera del camino, probemos nuestro código por consola registrando la matriz resultante:
Excelente trabajo hasta ahora; ¡has construido la parte más difícil! Ahora, saquemos esos datos de la terminal, ¿de acuerdo?
Exportar la información raspada a un archivo CSV es bastante simple, gracias al paquete ObjectsToCsv. Todo lo que necesita hacer es agregar el siguiente fragmento fuera del método .each()
:
1const csv = new ObjectsToCsv(jobListings); 2await csv.toDisk(``"./glassdoorJobs.csv"``, { append: true }); 3console.log(``"Save to CSV"``);
Es importante que establezcamos append en verdadero, de modo que no sobrescribamos el archivo cada vez que lo usamos.
Hemos probado esto antes, así que no ejecutes tu código todavía. Todavía queremos hacer una cosa más antes.
Ya hemos descubierto cómo cambia la estructura de la URL de una página a otra dentro de la serie paginada. Con esa información, podemos crear un for loop
para aumentar el número de IP{x} hasta llegar a la última página de la paginación:
1for (let pageNumber = 1``; pageNumber < 31``; pageNumber +``= 1``){}
Además, necesitaremos agregar este número dinámicamente en la solicitud axios()
:
1const page = await axios( 2http://api.scraperapi.com?api_key={yourApiKey}&url=https://www.glassdoor.it/Lavoro/milano-part-time-lavori-SRCH_IL.0,6_IC2802090_KO7,16_IP${pageNumber}.htm?includeNoSalaryJobs=true 3);
Finalmente, movemos todo el código dentro del for loop
, dejando la parte CSV fuera del bucle por motivos de simplicidad.
Si me has estado siguiendo (si llegaste directamente a esta sección: Hola ) su base de código debería verse así:
1const axios = require(``"axios"``); 2const cheerio = require(``"cheerio"``); 3const ObjectsToCsv = require(``"objects-to-csv"``); 4 5let jobListings = []; 6 7(``async function () { 8for (let pageNumber = 1``; pageNumber < 31``; pageNumber +``= 1``) { 9const page = await axios( 10 `http:```//api.scraperapi.com?api_key=51e43be283e4db2a5afb62660fc6ee44&url=https://www.glassdoor.it/Lavoro/milano-part-time-lavori-SRCH_IL.0,6_IC2802090_KO7,16_IP${pageNumber}.htm?includeNoSalaryJobs=```true` `` 11); 12const html = await page.data; 13const $ = cheerio.load(html); 14 15let allJobs = $(``'[data-test="jobListing"]'``); 16allJobs.each((index, element) =``> { 17const jobTitle = $(element).find(``"a[data-test='job-link'] > span"``).text(); 18const company = $(element).find(``"a.e1n63ojh0 > span"``).text(); 19const jobLink = $(element).find(``"a[data-test='job-link']"``).attr(``"href"``); 20jobListings.push({ 21"Job Title"``: jobTitle, 22"Hiring Company"``: company, 23"Job Link"``: "[https://www.glassdoor.it/](https://www.glassdoor.it/)" + jobLink, 24}); 25}); 26 27console.log(pageNumber + " Done!"``); 28} 29 30const csv = new ObjectsToCsv(jobListings); 31await csv.toDisk(``"./glassdoorJobs.csv"``); 32console.log(``"Save to CSV"``); 33console.log(jobListings); 34})();
Después de ejecutar su código, se creará un nuevo archivo CSV dentro de su carpeta.
Nota: Para que esto funcione, recuerde que debe agregar su clave ScraperAPI al script, reemplazando el marcador de posición {yourApiKey}
.
Hicimos algunos cambios:
console.log(pageNumber + " Done!")
para brindar comentarios visuales mientras se ejecuta el script.{ append: true }
del método .toDisk()
; como ya no está dentro del for loop
, no agregaremos (agregaremos) más datos.
¡Felicitaciones, creaste tu primer raspador de Glassdoor en JavaScript!
Puede usar los mismos principios para raspar básicamente todas las páginas de Glassdoor y, con la misma lógica, puede traducir este script a otros idiomas.
Vamos a crear un script de Python para hacer lo mismo que una demostración.
Al escribir un raspador de Glassdoor en Python, es posible que se incline por usar una herramienta como Selenium. Sin embargo, al igual que con JavaScript, no necesitamos usar ningún tipo de navegador sin interfaz.
En su lugar, usaremos Requests y Beautiful Soup para crear un bucle para acceder y analizar el HTML de las páginas paginadas, extrayendo los datos como lo hicimos anteriormente.
Dentro de la carpeta de su proyecto, cree un nuevo directorio glassdoor-python-scraper y agregue un archivo glassdoor_scraper.py, y pip install Requests y Beautiful Soup desde la terminal:
1pip install requests beautifulsoup4
Finalmente, importe ambas dependencias a la parte superior del archivo:
1import requests 2from bs4 import BeautifulSoup
Solo así, estamos listos para el siguiente paso.
Por si acaso, envíe la solicitud inicial al servidor e imprima el código de estado.
1response = requests.get( 2"[https://www.glassdoor.it/Lavoro/milano-part-time-lavori-SRCH_IL.0](https://www.glassdoor.it/Lavoro/milano-part-time-lavori-SRCH_IL.0),6_IC2802090_KO7,16_IP1.htm?includeNoSalaryJobs=true"``) 3 4print``(response.status_code)
Nota: Recuerde que necesitará un CD a la nueva carpeta antes de poder ejecutar su secuencia de comandos de Python.
¡Está funcionando hasta ahora! Ahora, pongamos esto en un for loop
e intentemos acceder a las tres primeras páginas de la paginación. Para hacerlo, crearemos un rango de 1 a 4 (no incluirá 4 en el rango) y agregaremos una variable {x} a la cadena:
1for x in range``(``1``, 4``): 2response = requests.get( 3"[https://www.glassdoor.it/Lavoro/milano-part-time-lavori-SRCH_IL.0](https://www.glassdoor.it/Lavoro/milano-part-time-lavori-SRCH_IL.0),6_IC2802090_KO7,16_IP{x}.htm?includeNoSalaryJobs=true"``) 4 5print``(response.status_code)
Con este bucle for simple, nuestro raspador podrá moverse a través de la paginación sin ningún problema.
Para fines de prueba, no queremos que nuestro raspador falle en tres páginas diferentes, así que reduzcamos el rango a 1 – 2; solo raspará la página uno.
Como antes, elegiremos todas las tarjetas de trabajo usando el selector de atributos [data-test="jobListing"]
:
1 all_jobs
=
soup.select(``"[data-test='jobListing']"``)
Con todas las tarjetas almacenadas dentro de la variable all_jobs
, podemos recorrerlas para extraer los puntos de datos de destino:
1for job in all_jobs: 2job_title = job.find(``"a"``, attrs``=``{``"data-test"``: "job-link"``}).text 3company = job.select_one( 4"a.job-search-key-l2wjgv.e1n63ojh0.jobLink > span"``).text 5job_link = job.find(``"a"``, attrs``=``{``"data-test"``: "job-link"``})[``"href"``]
Nota: Por alguna razón, al using
.find()
para extraer el nombre de la empresa, no funcionaba, por lo que decidimos usar el método select_one()
en su lugar.
Entramos en más detalles sobre el manejo de archivos JSON en nuestro tutorial de extracción de datos tabulares con Python . Aún así, para una breve explicación, agregaremos los datos a una matriz vacía y usaremos el método json.dump() para almacenar la matriz en un archivo JSON:
1glassdoor_jobs.append({ 2"Job Title"``: job_title, 3"Company"``: company, 4"Job Link"``: "[https://www.glassdoor.it](https://www.glassdoor.it/)" + job_link 5})
Nota: Deberá importar json en la parte superior del archivo y crear una nueva matriz vacía glassdoor_jobs = []
fuera del ciclo para que esto funcione.
Con la matriz lista con nuestros datos en un formato agradable, volcaremos los datos en un archivo JSON con el siguiente fragmento:
1with open``(``'glassdoor_jobs'``, 'w'``) as json_file: 2json.dump(glassdoor_jobs, json_file, indent``=``2``)
Una última cosa por hacer: ¡pruébalo!
Sin más preámbulos, aquí está la secuencia de comandos completa de Python para raspar los datos del trabajo de Glassdoor:
1import requests 2from bs4 import BeautifulSoup 3import json 4 5glassdoor_jobs = [] 6 7for x in range``(``1``, 31``): 8response = requests.get( 9"[http://api.scraperapi.com?api_key=](http://api.scraperapi.com/?api_key=){your_api_key}&url=[https://www.glassdoor.it/Lavoro/milano-part-time-lavori-SRCH_IL.0](https://www.glassdoor.it/Lavoro/milano-part-time-lavori-SRCH_IL.0),6_IC2802090_KO7,16_IP{x}.htm?includeNoSalaryJobs=true"``) 10soup = BeautifulSoup(response.content, "html.parser"``) 11 12all_jobs = soup.select(``"[data-test='jobListing']"``) 13for job in all_jobs: 14job_title = job.find(``"a"``, attrs``=``{``"data-test"``: "job-link"``}).text 15company = job.select_one( 16"a.job-search-key-l2wjgv.e1n63ojh0.jobLink > span"``).text 17job_link = job.find(``"a"``, attrs``=``{``"data-test"``: "job-link"``})[``"href"``] 18glassdoor_jobs.append({ 19"Job Title"``: job_title, 20"Company"``: company, 21"Job Link"``: "[https://www.glassdoor.it](https://www.glassdoor.it/)" + job_link 22}) 23print``(``"Page " + str``(x) + " is done"``) 24 25with open``(``'glassdoor_jobs'``, 'w'``) as json_file: 26json.dump(glassdoor_jobs, json_file, indent``=``2``)
Algunos cambios que hemos hecho:
print("Page: " + str(x) + " is done")
para recibir comentarios visuales a medida que se ejecuta el código. Convierte nuestra variable x de un número entero a una cadena para que podamos concatenar la frase completa.
Aquí está el resultado final:
30 páginas extraídas y todos los datos formateados en un archivo JSON reutilizable.
Al escalar este proyecto, puede raspar más páginas y obtener aún más puntos de datos. También puede eliminar trabajos específicos filtrando la información como solo trabajos con un determinado título, ubicación o valor (es decir, trabajos que muestren salario) y crear una bolsa de trabajo curada o un boletín de oportunidades de trabajo.
Con tanta información, el cielo es el límite, así que mantén tu mente abierta a las posibilidades.
¡Hasta la próxima, feliz raspado!