<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Web Scraping - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Descubre miles de cursos de programación escritos por expertos. Aprende Desarrollo Web, Ciencia de Datos, DevOps, Seguridad y obtén asesoramiento profesional para desarrolladores. ]]>
        </description>
        <link>https://www.freecodecamp.org/espanol/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Web Scraping - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/espanol/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Wed, 27 May 2026 16:02:12 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/espanol/news/tag/web-scraping/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ Cómo hacer scrape de sitios web con Python 3 ]]>
                </title>
                <description>
                    <![CDATA[ Hacer scrape es el proceso de extraer datos de sitios web. Antes de realizar la extracción de datos de una página web, debes asegurarte de que el proveedor lo permita en sus términos de servicio. Además, deberías verificar si no puedes usar una API en su lugar. Una extracción masiva ]]>
                </description>
                <link>https://www.freecodecamp.org/espanol/news/como-hacer-scrape-de-una/</link>
                <guid isPermaLink="false">5fea33008c7cd154bb980e20</guid>
                
                    <category>
                        <![CDATA[ Web Scraping ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Fernando Cardellino ]]>
                </dc:creator>
                <pubDate>Fri, 29 Jan 2021 14:00:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/espanol/news/content/images/2021/01/python.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Hacer scrape es el proceso de extraer datos de sitios web.</p><p>Antes de realizar la extracción de datos de una página web, debes asegurarte de que el proveedor lo permita en sus términos de servicio. Además, deberías verificar si no puedes usar una API en su lugar.</p><p>Una extracción masiva de datos puede poner al servidor bajo un enorme esfuerzo, lo cual puede resultar en una denegación de servicio. Y no quieres eso.</p><h2 id="-qui-n-deber-a-leer-esto">¿Quién debería leer esto?</h2><p>Este artículo es para lectores avanzados. Se asume que ya estás familiarizado con el lenguaje de programación de Python.</p><p>Como mínimo, debes entender la comprensión de listas, el administrador de contexto y las funciones. También debes saber cómo configurar un entorno virtual.</p><p>Ejecutaremos el código en tu máquina local para explorar algunos sitios web. </p><h2 id="qu-aprender-s-en-este-art-culo">Qué aprenderás en este artículo</h2><p>Al final de este artículo, sabrás cómo descargar una página web, analizarla en busca de información interesante y darle un formato utilizable para su posterior procesamiento. Esto también se conoce como ETL.</p><p>Este artículo también explicará qué hacer si ese sitio web usa JavaScript para representar (render) contenido (como React.js o Angular).</p><h2 id="pre-requisitos">Pre-requisitos</h2><p>Antes de comenzar, deseo asegurarme de que estemos listo para dar inicio. Por favor, establece un entorno virtual e instálale los siguientes paquetes:</p><ul><li>beautifulsoup4 (versión 4.9.0 al momento de estar escribiendo el artículo)</li><li>requests (versión 2.23.0 al momento de estar escribiendo el artículo)</li><li>wordcloud (versión 1.17.0 al momento de estar escribiendo el artículo, opcional)</li><li>selenium (versión 3.141.0 al momento de estar escribiendo el artículo, opcional)</li></ul><p>Puedes encontrar el código	 de este proyecto en este <a href="https://github.com/Ryuno-Ki/fcc-web-scraping-example">repositorio git en GitHub</a></p><p>Para este ejemplo, realizaremos una extracción (scrape) de la <a href="https://www.gesetze-im-internet.de/gg/index.html">Ley Básica para la República Federal de Alemania</a>. (No te preocupes, ya verifiqué los Términos de Servicios. Ofrecen una versión XML para procesamiento de máquina, pero esta página sirve como un ejemplo de procesamiento de HTML. Entonces debería estar bien.)</p><h2 id="paso-1-descargar-la-fuente">Paso 1: Descargar la fuente</h2><p>Primero lo primero: creé un archivo <code>urls.txt</code> que contiene todos los URLs que deseo descargar:</p><figure class="kg-card kg-code-card"><pre><code>https://www.gesetze-im-internet.de/gg/art_1.html
https://www.gesetze-im-internet.de/gg/art_2.html
https://www.gesetze-im-internet.de/gg/art_3.html
https://www.gesetze-im-internet.de/gg/art_4.html
https://www.gesetze-im-internet.de/gg/art_5.html
https://www.gesetze-im-internet.de/gg/art_6.html
https://www.gesetze-im-internet.de/gg/art_7.html
https://www.gesetze-im-internet.de/gg/art_8.html
https://www.gesetze-im-internet.de/gg/art_9.html
https://www.gesetze-im-internet.de/gg/art_10.html
https://www.gesetze-im-internet.de/gg/art_11.html
https://www.gesetze-im-internet.de/gg/art_12.html
https://www.gesetze-im-internet.de/gg/art_12a.html
https://www.gesetze-im-internet.de/gg/art_13.html
https://www.gesetze-im-internet.de/gg/art_14.html
https://www.gesetze-im-internet.de/gg/art_15.html
https://www.gesetze-im-internet.de/gg/art_16.html
https://www.gesetze-im-internet.de/gg/art_16a.html
https://www.gesetze-im-internet.de/gg/art_17.html
https://www.gesetze-im-internet.de/gg/art_17a.html
https://www.gesetze-im-internet.de/gg/art_18.html
https://www.gesetze-im-internet.de/gg/art_19.html</code></pre><figcaption>urls.txt</figcaption></figure><p>Luego, escribí un poco de código en Python en un archivo llamado <code>scraper.py</code> para descargar el HTML de estos archivos.</p><p>En un escenario real, esto sería demasiado costoso y, en su lugar, utilizarías una base de datos. Para simplificar las cosas, descargaré archivos en el mismo directorio y usaré su nombre como nombre de archivo.</p><figure class="kg-card kg-code-card"><pre><code class="language-py">from os import path
from pathlib import PurePath

import requests

with open('urls.txt', 'r') as fh:
    urls = fh.readlines()
urls = [url.strip() for url in urls]  # strip `\n`

for url in urls:
    file_name = PurePath(url).name
    file_path = path.join('.', file_name)
    text = ''

    try:
        response = requests.get(url)
        if response.ok:
            text = response.text
    except requests.exceptions.ConnectionError as exc:
        print(exc)
    
    with open(file_path, 'w') as fh:
        fh.write(text)

    print('Written to', file_path)</code></pre><figcaption>scraper.py</figcaption></figure><p>Al descargar los archivos, los puedo procesar localmente tanto como lo desee sin depender de unos servidos. Trata de ser un buen ciudadano web, ¿si?</p><h2 id="paso-2-analizar-la-fuente">Paso 2: Analizar la fuente</h2><p>Ahora que he descargado los archivos, es tiempo de extraer información interesante. Por lo tanto me dirijo a alguna de las páginas que descargué, la abro en un navegador web, y aprieto Crtl-U para ver su código fuente. Al inspeccionarlo me mostrará su estructura HTML.</p><p>En mi caso, quería el texto de la ley sin ningún marcado. El elemento que lo envuelve tiene un id de <code>container</code>. Usando BeautifulSoup puedo ver que una combinación de <a href="https://www.crummy.com/software/BeautifulSoup/bs4/doc/#find"><code>find</code></a> y <code><a href="https://www.crummy.com/software/BeautifulSoup/bs4/doc/#get-text">get_text</a></code> hará lo que quiero.</p><p>Como tengo un segundo paso ahora, voy a refactorizar un poco el código poniéndolo en funciones y agregando una CLI mínima.</p><figure class="kg-card kg-code-card"><pre><code class="language-py">from os import path
from pathlib import PurePath
import sys

from bs4 import BeautifulSoup
import requests


def download_urls(urls, dir):
    paths = []

    for url in urls:
        file_name = PurePath(url).name
        file_path = path.join(dir, file_name)
        text = ''

        try:
            response = requests.get(url)
            if response.ok:
                text = response.text
            else:
                print('Mala respuesta para', url, response.status_code)
        except requests.exceptions.ConnectionError as exc:
            print(exc)
    
        with open(file_path, 'w') as fh:
            fh.write(text)

        paths.append(file_path)

    return paths

def parse_html(path):
    with open(path, 'r') as fh:
        content = fh.read()

    return BeautifulSoup(content, 'html.parser')

def download(urls):
    return download_urls(urls, '.')

def extract(path):
    return parse_html(path)

def transform(soup):
    container = soup.find(id='container')
    if container is not None:
        return container.get_text()

def load(key, value):
    d = {}
    d[key] = value
    return d

def run_single(path):
    soup = extract(path)
    content = transform(soup)
    unserialised = load(path, content.strip() if content is not None else '')
    return unserialised

def run_everything():
    l = []

    with open('urls.txt', 'r') as fh:
        urls = fh.readlines()
    urls = [url.strip() for url in urls]

    paths = download(urls)
    for path in paths:
        print('Written to', path)
        l.append(run_single(path))

    print(l)

if __name__ == "__main__":
    args = sys.argv

    if len(args) is 1:
      run_everything()
    else:
        if args[1] == 'download':
            download([args[2]])
            print('Done')
        if args[1] == 'parse':
            path = args[2]
            result = run_single(path)
            print(result)
</code></pre><figcaption>scraper.py</figcaption></figure><p>Ahora puedo ejecutar el código de tres maneras:</p><ol><li>Sin ningún argumento para ejecutar todo (es decir, descargar todas las URL y extraerlas, luego guardarlas en el disco) a través de: <code>python scraper.py</code></li><li>Con un argumento de <code>download</code> y una url para descargar: <code>python scraper.py download https://www.gesetze-im-internet.de/gg/art_1.html</code>. Esto no procesará el archivo.</li><li>Con un argumento de <code>parse</code> y una ruta de archivo para analizar: <code>python scraper.py art_1.html</code>. Esto omitirá el paso de descarga.</li></ol><p>Con esto, solo falta una última cosa.</p><h2 id="paso-3-dar-formato-a-la-fuente-para-su-posterior-procesamiento">Paso 3: Dar formato a la fuente para su posterior procesamiento</h2><p>Digamos que quiero generar una nube de palabras para cada artículo. Esta puede ser una forma rápida de tener una idea de lo que trata un texto. Para ello, instala el paquete <code>wordcloud</code> y actualiza el archivo así:</p><figure class="kg-card kg-code-card"><pre><code class="language-py">from os import path
from pathlib import Path, PurePath
import sys

from bs4 import BeautifulSoup
import requests
from wordcloud import WordCloud

STOPWORDS_ADDENDUM = [
    'Das',
    'Der',
    'Die',
    'Diese',
    'Eine',
    'In',
    'InhaltsverzeichnisGrundgesetz',
    'im',
    'Jede',
    'Jeder',
    'Kein',
    'Sie',
    'Soweit',
    'Über'
]
STOPWORDS_FILE_PATH = 'stopwords.txt'
STOPWORDS_URL = 'https://raw.githubusercontent.com/stopwords-iso/stopwords-de/master/stopwords-de.txt'


def download_urls(urls, dir):
    paths = []

    for url in urls:
        file_name = PurePath(url).name
        file_path = path.join(dir, file_name)
        text = ''

        try:
            response = requests.get(url)
            if response.ok:
                text = response.text
            else:
                print('Mala respuesta para', url, response.status_code)
        except requests.exceptions.ConnectionError as exc:
            print(exc)
    
        with open(file_path, 'w') as fh:
            fh.write(text)

        paths.append(file_path)

    return paths

def parse_html(path):
    with open(path, 'r') as fh:
        content = fh.read()

    return BeautifulSoup(content, 'html.parser')

def download_stopwords():
    stopwords = ''

    try:
        response = requests.get(STOPWORDS_URL)
        if response.ok:
            stopwords = response.text
        else:
            print('Mala respuesta para', url, response.status_code)
    except requests.exceptions.ConnectionError as exc:
        print(exc)

    with open(STOPWORDS_FILE_PATH, 'w') as fh:
        fh.write(stopwords)

    return stopwords

def download(urls):
    return download_urls(urls, '.')

def extract(path):
    return parse_html(path)

def transform(soup):
    container = soup.find(id='container')
    if container is not None:
        return container.get_text()

def load(filename, text):
    if Path(STOPWORDS_FILE_PATH).exists():
        with open(STOPWORDS_FILE_PATH, 'r') as fh:
            stopwords = fh.readlines()
    else:
        stopwords = download_stopwords()

    # Tira de espacios en blanco alrededor
    stopwords = [stopword.strip() for stopword in stopwords]
    # Extienda las stopwords con las propias, que se determinaron después de 	  la primera ejecución stopwords = stopwords + STOPWORDS_ADDENDUM

    try:
        cloud = WordCloud(stopwords=stopwords).generate(text)
        cloud.to_file(filename.replace('.html', '.png'))
    except ValueError:
        print('No se pudo generar la nube de palabras para', key)

def run_single(path):
    soup = extract(path)
    content = transform(soup)
    load(path, content.strip() if content is not None else '')

def run_everything():
    with open('urls.txt', 'r') as fh:
        urls = fh.readlines()
    urls = [url.strip() for url in urls]

    paths = download(urls)
    for path in paths:
        print('Written to', path)
        run_single(path)
    print('Done')

if __name__ == "__main__":
    args = sys.argv

    if len(args) is 1:
      run_everything()
    else:
        if args[1] == 'download':
            download([args[2]])
            print('Done')
        if args[1] == 'parse':
            path = args[2]
            run_single(path)
            print('Done')</code></pre><figcaption>scraper.py</figcaption></figure><p>¿Qué cambió? Por un lado, descargué una <a href="https://github.com/stopwords-iso/stopwords-de/">lista de palabras vacías</a> (stopwords) alemanas de GitHub. De esta manera, puedo eliminar las palabras más comunes del texto de la ley descargado.</p><p>Luego, creé una instancia de WordCloud con la lista de palabras vacías que descargué y el texto de la ley. Se convertirá en una imagen con el mismo nombre de base.</p><p>Después de la primera ejecución, descubrí que la lista de palabras vacías está incompleta. Así que agregué palabras adicionales que quiero excluir de la imagen resultante.</p><p>Con esto, la parte principal de la extracción web está completa.</p><h2 id="bonus-y-los-spa">Bonus: ¿Y los SPA?</h2><p>Los SPA, o aplicaciones de página única, son aplicaciones web donde toda la experiencia está controlada por JavaScript, que se ejecuta en el navegador. Como tal, descargar el archivo HTML no nos lleva muy lejos. ¿Qué deberíamos hacer en su lugar?</p><p>Usaremos el navegador. Con Selenium. Asegúrate de instalar también un controlador. Descarga el archivo .tar.gz y descomprímelo en la carpeta bin de tu entorno virtual para que Selenium lo encuentre. Ese es el directorio donde puedes encontrar el script de activación (en sistemas GNU / Linux).</p><p>Como ejemplo, estoy usando el sitio web de Angular aquí. Angular es un SPA-Framework popular escrito en JavaScript y se garantiza que será controlado por él por el momento.</p><p>Dado que el código será más lento, creé un nuevo archivo llamado crawler.py para él. El contenido tiene este aspecto:</p><figure class="kg-card kg-code-card"><pre><code class="language-py">from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from wordcloud import WordCloud

def extract(url):
    elem = None
    driver = webdriver.Firefox()
    driver.get(url)

    try:
        found = WebDriverWait(driver, 10).until(
            EC.visibility_of(
                driver.find_element(By.TAG_NAME, "article")
            )
        )
        # Haz una copia de los datos relevantes, porque Selenium arrojará si
        # intenta acceder a las propiedades después de que el controlador se 			cierre
        elem = {
          "text": found.text
        }
    finally:
        driver.close()

    return elem

def transform(elem):
    return elem["text"]
        
def load(text, filepath):
    cloud = WordCloud().generate(text)
    cloud.to_file(filepath)

if __name__ == "__main__":
    url = "https://angular.io/"
    filepath = "angular.png"

    elem = extract(url)
    if elem is not None:
        text = transform(elem)
        load(text, filepath)
    else:
        print("Lo siento, no se pudieron extraer datos")</code></pre><figcaption>crawler.py</figcaption></figure><p>Aquí, Python abre una instancia de Firefox, navega por el sitio web y busca un elemento <code>&lt;article&gt;</code>. Está copiando su texto en un diccionario, que se lee en el paso <code>transform</code> y se convierte en WordCloud durante <code>load</code>.</p><p>Cuando se trata de sitios con mucho JavaScript, a menudo es útil usar Waits y tal vez incluso ejecutar <code><a href="https://selenium-python.readthedocs.io/api.html#selenium.webdriver.remote.webdriver.WebDriver.execute_script">execute_script</a></code> para diferir a JavaScript si es necesario.</p><h2 id="resumen">Resumen</h2><p>¡Gracias por leer hasta aquí! Resumamos lo que hemos aprendido ahora:</p><ol><li>Cómo hacer una extracción (scrape) un sitio web con el paquete <code>requests</code> de Python.</li><li>Cómo traducirla en una estructura con sentido usando <code>beautifulsoup</code>.</li><li>Cómo procesar aún más esa estructura en algo con lo que puedas trabajar. </li><li>Qué hacer si la página de destino se basa en JavaScript.</li></ol><h2 id="otras-lecturas">Otras lecturas</h2><p>Si quieres saber más sobre mí, puedes <a href="https://twitter.com/AndreJaenisch">seguirme en Twitter</a> o visitar mi <a href="https://jaenis.ch/">sitio web</a>.</p><p>No soy el primero que escribió sobre Web Scraping aquí en freeCodeCamp. Yasoob Khalid y Dave Gray también lo hicieron en el pasado:</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://www.freecodecamp.org/news/an-intro-to-web-scraping-with-lxml-and-python-b02b7a3f3098/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">An Intro to Web Scraping with lxml and Python</div><div class="kg-bookmark-description">by Timber.io An Intro to Web Scraping with lxml and PythonPhoto by Fabian Grohs[https://unsplash.com/photos/dC6Pb2JdAqs?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText] on Unsplash[https://unsplash.com/search/photos/web?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText…</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://www.freecodecamp.org/news/favicon.png" width="600" height="400" alt="favicon" loading="lazy"><span class="kg-bookmark-author">freeCodeCamp.org</span><span class="kg-bookmark-publisher">freeCodeCamp.org</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://cdn-media-1.freecodecamp.org/images/0*AXQfWm6LMJwLwS2f.png" width="800" height="357" alt="0*AXQfWm6LMJwLwS2f" loading="lazy"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://www.freecodecamp.org/news/better-web-scraping-in-python-with-selenium-beautiful-soup-and-pandas-d6390592e251/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Better web scraping in Python with Selenium, Beautiful Soup, and pandas</div><div class="kg-bookmark-description">by Dave Gray Web ScrapingUsing the Python programming language, it is possible to “scrape” data from theweb in a quick and efficient manner. Web scraping is defined as: &amp;gt; a tool for turning the unstructured data on the web into machine readable,structured data which is ready for analysis. (sou…</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://www.freecodecamp.org/news/favicon.png" width="600" height="400" alt="favicon" loading="lazy"><span class="kg-bookmark-author">freeCodeCamp.org</span><span class="kg-bookmark-publisher">freeCodeCamp.org</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://cdn-media-1.freecodecamp.org/images/1*DiffPQdgEAjDK4M_unUd4Q.jpeg" width="800" height="533" alt="1*DiffPQdgEAjDK4M_unUd4Q" loading="lazy"></div></a></figure><p>Traducido del artículo de <strong><a href="https://www.freecodecamp.org/news/author/ryuno-ki/">André Jaenisch</a> - <a href="https://www.freecodecamp.org/news/webscraping-in-python/">How Scrape Websites with Python 3</a></strong></p> ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
