Volver al blog
Guías
Mihai MaximLast updated on Mar 31, 202611 min read

Extracción de datos web con Scrapy: la forma más fácil

Extracción de datos web con Scrapy: la forma más fácil

Extracción de datos web con Scrapy

Scrapy es una potente biblioteca de Python para extraer datos de sitios web. Es rápida, eficiente y fácil de usar; créeme, lo sé por experiencia. Tanto si eres un científico de datos, un desarrollador o alguien a quien le encanta jugar con datos, Scrapy tiene algo que ofrecerte. Y lo mejor de todo es que es gratuita y de código abierto.

Los proyectos de Scrapy vienen con una estructura de archivos que te ayuda a organizar tu código y tus datos. Facilita la creación y el mantenimiento de rastreadores web, por lo que sin duda vale la pena tenerlo en cuenta si tienes pensado dedicarte en serio al web scraping. Web scraping con Scrapy Es como tener a tu lado a un asistente muy útil (aunque sea virtual) mientras te embarcas en tu aventura de extracción de datos.

Lo que vamos a crear

Cuando estás aprendiendo una nueva habilidad, una cosa es leer sobre ella y otra muy distinta es ponerla en práctica. Por eso hemos decidido crear juntos un rastreador a medida que avanzamos en esta guía. Es la mejor manera de comprender de forma práctica cómo funciona el rastreo web con Scrapy.

Entonces, ¿qué vamos a crear exactamente? Crearemos un scraper que extraiga definiciones de palabras de la página web Urban Dictionary. Es un objetivo divertido para el scraping y ayudará a que el proceso de aprendizaje sea más ameno. Nuestro scraper será sencillo: devolverá las definiciones de varias palabras que se encuentran en la página web de Urban Dictionary. Usaremos el soporte integrado de Scrapy para seleccionar y extraer datos de documentos HTML y así obtener las definiciones que necesitamos.

¡Empecemos! En la siguiente sección, repasaremos los requisitos previos que necesitarás para seguir este tutorial. ¡Nos vemos allí!

Requisitos previos

Antes de ponernos manos a la obra con la creación de nuestro scraper, hay algunas cosas que tendrás que configurar. En esta sección, veremos cómo instalar Scrapy y configurar un entorno virtual para nuestro proyecto. La documentación de Scrapy recomienda instalar Scrapy en un entorno virtual dedicado. Al hacerlo, evitarás cualquier conflicto con los paquetes de tu sistema.

Yo estoy ejecutando Scrapy en Ubuntu 22.04.1 WSL (Subsistema de Windows para Linux), así que voy a configurar un entorno virtual para mi máquina.

Te recomiendo que leas el capítulo «Comprender la estructura de carpetas» para entender a fondo las herramientas con las que vamos a trabajar. Además, echa un vistazo al capítulo «Scrapy Shell», ya que te facilitará mucho la experiencia de desarrollo.

Configuración de un entorno virtual de Python

Para configurar un entorno virtual para Python en Ubuntu, puedes utilizar el comando mkvirtualenv. En primer lugar, asegúrate de que tienes instalados virtualenv y virtualenvwrapper:

$ sudo apt-get install virtualenv virtualenvwrapper

Añade estas líneas al final de tu archivo .bashrc:

export WORKON_HOME=$HOME/.virtualenvs

export PROJECT_HOME=$HOME/Deve

export VIRTUALENVWRAPPER_PYTHON='/usr/bin/python3'

source /usr/local/bin/virtualenvwrapper.sh

Yo utilicé Vim para editar el archivo, pero puedes elegir el editor que prefieras:

vim ~/.bashrc

// Use ctr + i to enter insert mode, use the down arrow to scroll to the bottom of the file.

// Paste the lines at the end of the file.

// Hit escape to exit insert mode, type wq and hit enter to save the changes and exit Vim.

A continuación, crea un nuevo entorno virtual con mkvirtualenv:

$ mkvirtualenv scrapy_env

Ahora deberías ver un (scrapy_env) añadido al principio de la línea de tu terminal.

Para salir del entorno virtual, escribe $ deactivate

Para volver al entorno virtual scrapy_env, escribe $ workon scrapy_env

Instalación de Scrapy

Puedes instalar Scrapy con el gestor de paquetes pip:

$ pip install scrapy

Esto instalará la última versión de Scrapy.

Puedes crear un nuevo proyecto con el comando scrapy startproject:

$ scrapy startproject myproject

Esto inicializará un nuevo proyecto de Scrapy llamado «myproject». Debería contener la estructura predeterminada del proyecto.

Comprender la estructura de carpetas

Esta es la estructura predeterminada del proyecto:

myproject

├── myproject

│ ├── __init__.py

│ ├── items.py

│ ├── middlewares.py

│ ├── pipelines.py

│ ├── settings.py

│ └── spiders

│ └── __init__.py

└── scrapy.cfg

items.py

items.py es un modelo para los datos extraídos. Este modelo se utilizará para almacenar los datos que extraigas del sitio web.

Ejemplo:

import scrapy

class Product(scrapy.Item):

    name = scrapy.Field()

    price = scrapy.Field()

    description = scrapy.Field()

Aquí hemos definido un elemento llamado Product. Puede ser utilizado por un Spider (véase /spiders) para almacenar información sobre el nombre, el precio y la descripción de un producto.

/spiders

/spiders es una carpeta que contiene clases de arañas. En Scrapy, las arañas son clases que definen cómo se debe rastrear un sitio web.

Ejemplo:

import scrapy

from myproject.items import Product

class MySpider(scrapy.Spider):

    name = 'myspider'

    start_urls = ['<example_website_url>']

    

    def parse(self, response):

        # Extract the data for each product

        for product_div in response.css('div.product'):

            product = Product()

            product['name'] = product_div.css('h3.name::text').get()

            product['price'] = product_div.css('span.price::text').get()

            product['description'] = product_div.css('p.description::text').get()

            yield product

La «araña» recorre las start_urls, extrae el nombre, el precio y la descripción de todos los productos encontrados en las páginas (utilizando selectores CSS) y almacena los datos en el elemento Product (véase items.py). A continuación, «devuelve» estos elementos, lo que hace que Scrapy los pase al siguiente componente de la canalización (véase pipelines.py).

Yield es una palabra clave en Python que permite a una función devolver un valor sin terminar la función. En su lugar, genera el valor y suspende la ejecución de la función hasta que se solicite el siguiente valor.

Por ejemplo:

def count_up_to(max):

    count = 1

    while count <= max:

        yield count

        count += 1

for number in count_up_to(5):

    print(number)

// returns 1 2 3 4 5 (each on a new line)  

pipelines.py

Los pipelines se encargan de procesar los elementos (véase items.py y /spiders) extraídos por los spiders. Puedes utilizarlos para limpiar el HTML, validar los datos y exportarlos a un formato personalizado o guardarlos en una base de datos.

Ejemplo:

import pymongo

class MongoPipeline(object):

    def __init__(self):

        self.conn = pymongo.MongoClient('localhost', 27017)

        self.db = self.conn['mydatabase']

        self.product_collection = self.db['products']

        self.other_collection = self.db['other']

        

    def process_item(self, item, spider):

        if spider.name == 'product_spider':

            //insert item in the product_collection

        elif spider.name == 'other_spider':

            //insert item in the other_collection

        return item

Hemos creado una pipeline llamada MongoPipeline. Se conecta a dos colecciones de MongoDB (product_collection y other_collection). La pipeline recibe elementos (véase items.py) de los spiders (véase /spiders) y los procesa en la función process_item. En este ejemplo, la función process_item añade los elementos a sus colecciones designadas.

settings.py

settings.py almacena una variedad de configuraciones que controlan el comportamiento del proyecto Scrapy, como los pipelines, middlewares y extensiones que deben utilizarse, así como configuraciones relacionadas con cómo el proyecto debe gestionar las solicitudes y respuestas.

Por ejemplo, puedes utilizarlo para establecer el orden de ejecución de los pipelines (véase pipelines.py):

ITEM_PIPELINES = {

    'myproject.pipelines.MongoPipeline': 300,

    'myproject.pipelines.JsonPipeline': 302,

}

// MongoPipeline will execute before JsonPipeline, because it has a lower order number(300)

O configurar un contenedor de exportación:

FEEDS = {

    'items': {'uri': 'file:///tmp/items.json', 'format': 'json'},

}

// this will save the scraped data to a items.json

scrappy.cfg

scrapy.cfg es el archivo de configuración de los ajustes principales del proyecto.

[settings]

default = [name of the project].settings

[deploy]

#url = http://localhost:6800/

project = [name of the project]

middlewares.py

Hay dos tipos de middlewares en Scrapy: middlewares de descarga y middlewares de araña.

Los middlewares de descarga son componentes que se pueden utilizar para modificar solicitudes y respuestas, gestionar errores e implementar lógica de descarga personalizada. Se sitúan entre el spider y el descargador de Scrapy.

Los middlewares de araña son componentes que se pueden utilizar para implementar lógica de procesamiento personalizada. Se sitúan entre el motor y la araña.

El Scrapy Shell

Antes de embarcarnos en el emocionante viaje de implementar nuestro rastreador de Urban Dictionary, primero debemos familiarizarnos con el Scrapy Shell. Esta consola interactiva nos permite probar nuestra lógica de rastreo y ver los resultados en tiempo real. Es como un entorno de pruebas virtual donde podemos experimentar y ajustar nuestro enfoque antes de lanzar nuestro Spider a la web. Créeme, a la larga te ahorrará mucho tiempo y dolores de cabeza. Así que divirtámonos un poco y conozcamos el Scrapy Shell.

Abrir el shell

Para abrir el Scrapy Shell, primero tendrás que navegar hasta el directorio de tu proyecto Scrapy en tu terminal. A continuación, solo tienes que ejecutar el siguiente comando:

scrapy shell

Esto abrirá el Scrapy Shell y te aparecerá un indicador donde podrás introducir y ejecutar comandos de Scrapy. También puedes pasar una URL como argumento al comando del shell para rastrear directamente una página web, así:

scrapy shell <url>

Por ejemplo:

scrapy shell https://www.urbandictionary.com/define.php?term=YOLO

Devolverá (en el objeto de respuesta) el código HTML de la página web que contiene las definiciones de la palabra YOLO (en el Urban Dictionary).

Como alternativa, una vez que entres en el shell, puedes usar el comando fetch para recuperar una página web.

fetch('https://www.urbandictionary.com/define.php?term=YOLO')

También puedes iniciar el shell con el parámetro nolog para que no muestre los registros:

scrapy shell --nolog

Trabajar con el shell

En este ejemplo, he recuperado la URL «https://www.urbandictionary.com/define.php?term=YOLO» y he guardado el código HTML en el archivo test_output.html.

(scrapy_env) mihai@DESKTOP-0RN92KH:~/myproject$ scrapy shell --nolog

[s] Available Scrapy objects:

[s] scrapy scrapy module (contains scrapy.Request, scrapy.Selector, etc)

[s] crawler <scrapy.crawler.Crawler object at 0x7f1eef80f6a0>

[s] item {}

[s] settings <scrapy.settings.Settings object at 0x7f1eef80f4c0>

[s] Useful shortcuts:

[s] fetch(url[, redirect=True]) Fetch URL and update local objects (by default, redirects are followed)

[s] fetch(req) Fetch a scrapy.Request and update local objects

[s] shelp() Shell help (print this help)

[s] view(response) View response in a browser

>>> response // response is empty

>>> fetch('https://www.urbandictionary.com/define.php?term=YOLO')

>>> response

<200 https://www.urbandictionary.com/define.php?term=Yolo>

>>> with open('test_output.html', 'w') as f:

... f.write(response.text)

...

118260

Ahora vamos a examinar test_output.html e identificar los selectores que necesitaríamos para extraer los datos para nuestro rastreador de Urban Dictionary.

Podemos observar que:

  • Cada contenedor de definición de palabra tiene la clase «definition».
  • El significado de la palabra se encuentra dentro del div con la clase «meaning».
  • Los ejemplos de la palabra se encuentran dentro del div con la clase «example».
  • La información sobre el autor y la fecha de la publicación se encuentra dentro del div con la clase «contributor».

Ahora probemos algunos selectores en el Scrapy Shell:

Para obtener referencias a todos los contenedores de definiciones, podemos utilizar selectores CSS o XPath:

Puedes obtener más información sobre los selectores XPath aquí: https://www.webscrapingapi.com/the-ultimate-xpath-cheat-sheet

definitions = response.css('div.definition')

definitions = response.xpath('//div[contains(@class,"definition")]')

Debemos extraer el significado, el ejemplo y la información de la publicación de cada contenedor de definición. Probemos algunos selectores con el primer contenedor:

>>> first_def = definitions[0]

>>> meaning = first_def.css('div.meaning').xpath(".//text()").extract()

>>> meaning

['Yolo ', 'means', ', '', 'You Only Live Once', ''.']

>>> meaning = "".join(meaning)

>>> meaning

'Yolo means, 'You Only Live Once'.'

>>> example = first_def.css('div.example').xpath(".//text()").extract()

>>> example = "".join(example)

>>> example

'"Put your seatbelt on." Jessica said.\r"HAH, YOLO!" Replies Anna.\r(They then proceed to have a car crash. Long story short...Wear a seatbelt.)'

>>> post_data = first_def.css('div.contributor').xpath(".//text()").extract()

>>> post_data

['by ', 'Soy ugly', ' April 24, 2019']

Al utilizar el Scrapy Shell, hemos podido encontrar rápidamente un selector general que se adapta a nuestras necesidades.

definition.css('div.<meaning|example|contributor>').xpath(".//text()").extract()

// returns an array with all the text found inside the <meaning|example|contributor>

ex: ['Yolo ', 'means', ', '', 'You Only Live Once', ''.']

Para obtener más información sobre los selectores de Scrapy, consulta la documentación. https://docs.scrapy.org/en/latest/topics/selectors.html

Implementación del rastreador de Urban Dictionary

¡Buen trabajo! Ahora que ya le has cogido el truco al uso del shell de Scrapy y entiendes el funcionamiento interno de un proyecto de Scrapy, es hora de sumergirnos en la implementación de nuestro scraper de Urban Dictionary. A estas alturas, deberías sentirte seguro y listo para asumir la tarea de extraer todas esas definiciones de palabras hilarantes (y a veces cuestionables) de la web. Así que, sin más preámbulos, ¡comencemos a construir nuestro scraper!

Definición de un Item

En primer lugar, implementaremos un Item: (véase items.py)

class UrbanDictionaryItem(scrapy.Item):

    meaning = scrapy.Field()

    author = scrapy.Field()

    date = scrapy.Field()

    example = scrapy.Field()

Esta estructura contendrá los datos extraídos por el Spider.

Definición de un Spider

Así es como definiremos nuestro Spider (véase /spiders):

import scrapy

from ..items import UrbanDictionaryItem

class UrbanDictionarySpider(scrapy.Spider):

name = 'urban_dictionary'

start_urls = ['https://www.urbandictionary.com/define.php?term=Yolo']

def parse(self, response):

definiciones = respuesta.css('div.definition')

for definición in definiciones:

elemento = UrbanDictionaryItem()

item['meaning'] = definition.css('div.meaning').xpath(".//text()").extract()

item['example'] = definition.css('div.example').xpath(".//text()").extract()

autor = definición.css('div.contributor').xpath(".//text()").extract()

elemento['fecha'] = autor[2]

elemento['autor'] = autor[1]

yield item

Para ejecutar el rastreador urban_dictionary, utiliza el siguiente comando:

scrapy crawl urban_dictionary

// the results should appear in the console (most probably at the top of the logs)

Creación de un pipeline

En este punto, los datos no están depurados.

{'author': 'Soy ugly',

 'date': ' April 24, 2019',

 'example': ['“Put your ',

             'seatbelt',

             ' on.” Jessica said.\n',

             '“HAH, YOLO!” Replies Anna.\n',

             '(They then proceed to have a ',

             'car crash',

             '. ',

             'Long story short',

             '...Wear a seatbelt.)'],

 'meaning': ['Yolo ', 'means', ', ‘', 'You Only Live Once', '’.']}

Queremos modificar los campos «example» y «meaning» para que contengan cadenas, no matrices. Para ello, escribiremos un Pipeline (véase pipelines.py) que transformará las matrices en cadenas concatenando las palabras.

class SanitizePipeline:

    def process_item(self, item, spider):

        # Sanitize the 'meaning' field

        item['meaning'] = "".join(item['meaning'])

        # Sanitize the 'example' field

        item['example'] = "".join(item['example'])

       

        # Sanitize the 'date' field

        item['date'] = item['date'].strip() 

        return item

 //ex: ['Yolo ', 'means', ', ‘', 'You Only Live Once', '’.'] turns to 

       'Yolo means, ‘You Only Live Once’.'

Habilitar el pipeline

Una vez definido el Pipeline, debemos habilitarlo. Si no lo hacemos, nuestros objetos UrbanDictionaryItem no se sanitizarán. Para ello, añádelo a tu archivo settings.py (véase settings.py):

 ITEM_PIPELINES = {

     'myproject.pipelines.SanitizePipeline': 1,

 }

Ya que estás, también podrías especificar un archivo de salida donde se guardarán los datos extraídos.

 FEEDS = {

     'items': {'uri': 'file:///tmp/items.json', 'format': 'json'},

 }

  // this will put the scraped data in an items.json file.

Opcional: Implementación de un middleware proxy

El scraping web puede resultar un poco complicado. Uno de los principales problemas con los que nos topamos a menudo es que muchos sitios web requieren la ejecución de JavaScript para mostrar su contenido al completo. Esto puede causarnos grandes problemas como scrapers web, ya que nuestras herramientas a menudo no tienen la capacidad de ejecutar JavaScript como lo hace un navegador web normal. Esto puede llevar a que se extraigan datos incompletos o, peor aún, a que nuestra IP sea bloqueada por el sitio web por realizar demasiadas solicitudes en un breve periodo de tiempo.

Nuestra solución a este problema es WebScrapingApi. Con nuestro servicio, solo tienes que enviar solicitudes a la API y esta se encargará de todo el trabajo pesado por ti. Ejecutará JavaScript, rotará proxies e incluso gestionará CAPTCHAs, asegurando que puedas extraer datos incluso de los sitios web más rebeldes con facilidad.

Un middleware de proxy reenviará todas las solicitudes de obtención realizadas por Scrapy al servidor proxy. A continuación, el servidor proxy realizará la solicitud por nosotros y nos devolverá el resultado.

En primer lugar, definiremos una clase ProxyMiddleware dentro del archivo middlewares.py.


import base64

class ProxyMiddleware:

    def process_request(self, request, spider):

        # Set the proxy for the request

        request.meta['proxy'] = "http://proxy.webscrapingapi.com:80"

        request.meta['verify'] = False

        # Set the proxy authentication for the request

        proxy_user_pass =  "webscrapingapi.proxy_type=residential.render_js=1:<API_KEY>"

        encoded_user_pass = base64.b64encode(proxy_user_pass.encode()).decode()

        request.headers['Proxy-Authorization'] = f'Basic {encoded_user_pass}'


En este ejemplo, hemos utilizado el servidor proxy WebScrapingApi.

webscrapingapi.proxy_type=residential.render_js=1 es el nombre de usuario de autenticación del proxy, <API_KEY> la contraseña.

Puedes obtener una API_KEY gratuita creando una nueva cuenta en https://www.webscrapingapi.com/

Una vez definido ProxyMiddleware, solo tenemos que habilitarlo como DOWNLOAD_MIDDLEWARE en el archivo settings.py.

DOWNLOADER_MIDDLEWARES = {

    'myproject.middlewares.ProxyMiddleware': 1,

}

Y eso es todo. Como puedes ver, el web scraping con Scrapy puede simplificarnos mucho el trabajo.

Conclusión

¡Lo hemos conseguido! Hemos creado un scraper capaz de extraer definiciones de Urban Dictionary y hemos aprendido mucho sobre Scrapy por el camino. Desde la creación de elementos personalizados hasta el uso de middlewares y pipelines, hemos demostrado lo potente y versátil que puede ser el web scraping con Scrapy. ¿Y lo mejor de todo? Aún queda mucho más por descubrir.

Enhorabuena por haber llegado hasta el final de este viaje conmigo. Ahora deberías sentirte seguro de tu capacidad para abordar cualquier proyecto de web scraping que se te presente. Solo recuerda que el web scraping no tiene por qué ser intimidante ni abrumador. Con las herramientas y los conocimientos adecuados, puede ser una experiencia divertida y gratificante. Si alguna vez necesitas ayuda, no dudes en ponerte en contacto con nosotros aquí, en https://www.webscrapingapi.com/. Sabemos todo sobre el web scraping y estaremos encantados de ayudarte en todo lo que podamos.

Acerca del autor
Mihai Maxim, Desarrollador Full Stack @ WebScrapingAPI
Mihai MaximDesarrollador Full Stack

Mihai Maxim es desarrollador full stack en WebScrapingAPI, donde colabora en todas las áreas del producto y ayuda a crear herramientas y funciones fiables para la plataforma.

Empieza a crear

¿Estás listo para ampliar tu recopilación de datos?

Únete a más de 2000 empresas que utilizan WebScrapingAPI para extraer datos de la web a escala empresarial sin ningún gasto de infraestructura.