No tienes acceso a esta clase

¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera

Guardando las noticias en archivos de texto

20/21
Recursos

Aportes 92

Preguntas 36

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

Hola, les dejo la solución de los títulos y en mi caso también de los links.
Por algún motivo el response toma el tag h2 como text-fill.

Excelente día veo que aun no hay solución al problema de no generar los archivos, esto se debe a un problema con el título de la noticia, lo que regresa es una lista vacía, y por eso entra en el “except”, sin embargo en la propia URL de la noticia se encuentra el título, por lo tanto e creado una pequeña función que extrae dicho título.

Es la solución que encontré, sin embargo seria bueno que alguien que sepa más del tema pueda ayudar con este error

import requests
import lxml.html as html
import os
import datetime


HOME_URL = 'https://www.larepublica.co/'

XPATH_LINK_TO_ARTICLE = '//a[contains(@class,"kicker")]/@href'
#XPATH_TITLE = Titulo
XPATH_SUMMARY = '//div[@class="lead"]/p/text()'
XPATH_BODY ='//div[@class="html-content"]//text()'

def get_title(link):
    #separamos por "/" y nos quedamos con el ultimo que elemento 
    url = link.split('/')[-1]
    #separamos por "-" y eliminamos el ultimo elemento
    title_list=url.split('-')[:-1]
    #Unimos lo anterior
    title = " ".join(title_list)

    return(title)

def parse_notice(link, today):
    try:
        response = requests.get(link)
        if response.status_code == 200:
            
            notice = response.content.decode('utf-8')
            parsed = html.fromstring(notice)

            try:
                title = get_title(link)
                summary = parsed.xpath(XPATH_SUMMARY)[0]
                body = parsed.xpath(XPATH_BODY)
            except IndexError:
                print("as")
                return
            
            with open(f'{today}/{title}.txt', 'w', encoding='utf-8') as f:
                f.write(title)
                f.write('\n\n')
                f.write(summary)
                f.write('\n\n')
                for p in body:
                    f.write(p)
                    f.write('\n')
            
        else:
            raise ValueError(f'Error: {response.status_code}')
    except ValueError as ve:
        print(ve)


def parse_home():
    try:
        response = requests.get(HOME_URL)
        if response.status_code == 200:
            home = response.content.decode('utf-8')
            parsed = html.fromstring(home)
            links_to_notices = parsed.xpath(XPATH_LINK_TO_ARTICLE)
            #print(links_to_notices)
            today = datetime.date.today().strftime('%d-%m-%Y')
            if not os.path.isdir(today):
                os.mkdir(today)
            
            for link in links_to_notices:
                parse_notice(link, today)

        else:
            raise ValueError(f'Error: {response.status_code}')
    except ValueError as ve:
        print(ve)


def run():
    parse_home()
    

if __name__ == "__main__":
    run()

Les comparto mi repositorio con mejoras en el proyecto del curso aquí.

Modularize el código y ahora se puede scrapear varios sitios.

¡Saludos!

PROBLEMA CARPETA SE CREA PERO NO SE GUARDAN LOS ARCHIVOS.
Todo funcionaba bien el momento de crear la carpeta, pero los archivos no se creaban dentro. El problema estaba en uno de los XPATH. Originalmente estaba usando las siguientes constantes:

XPATH_LINK_TO_ARTICLE = '//a[@class="globoeconomiaSect"]/@href'
XPATH_TITLE = '//div[@class="mb-auto"]/h2/span/text()'
XPATH_SUMMARY = '//div[@class="lead"]/p/text()'
XPATH_BODY = '//div[@class="html-content"]/p[not(@class)]/text()'

Después de buscar entre los aportes de la comunidad encontré un ejemplo que me ayudó.
Sin modificar mucho, realicé un cambio en la constante XPATH_TITLE y quedo de la siguiente manera.

#ANTES
XPATH_TITLE = '//div[@class="mb-auto"]/h2/span/text()'
#DESPUES
XPATH_TITLE = '//div[@class="mb-auto"]//span/text()'

Lo que hice fue remover la etiqueta h2 de la sentencia. Sin embargo hacer este ajuste implicó modificar el índice en la siguiente línea.

#Antes
title = parsed.xpath(XPATH_TITLE)[0]
#Despues
title = parsed.xpath(XPATH_TITLE)[1]

Este cambio se debe a que la nueva estructura de XPATH_TITLE devuelve dos elementos y el título es el segundo, por esta razón utilicé el índice [1].

Utilicé el código tal cual se desarrolló en la clase solo realice estos dos ajustes para que me funcionara.

RESUMEN:Guardando las noticias en archivos de texto
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■

Vamos a realizar una lógica para ir de cada link al sitio de cada noticia y de ahí extraer:

  • Titulo
  • Resumen
  • Cuerpo
import requests
import lxml.html as html
# Lo usaremso para crear una carpeta
import os
# Lo usaremos para manipular fechas.
import datetime

# Creamos constantes

HOME_URL = 'https://www.larepublica.co/'

#Recuerda que tu Xpath puede variar.
XPATH_LINK_TO_ARTICLE = '//text-fill[not(@class)]/a/@href'
XPATH_LINK_TO_TITLE = '//div[@class="mb-auto"]/h2/a/text()'
XPATH_LINK_TO_SUMMARY = '//div[@class="wrap-post col-9"]/div/div[@class="lead"]/p/text()'
XPATH_LINK_TO_BODY = '//div[@class="html-content"]/p[not(@class)]/text()'


def parse_notice(link, today):
    try:
        response = requests.get(link)
        if response.status_code == 200:

            # Traigo el docuemnto html de la noticia.
            notice = response.content.decode('utf-8')
            parsed = html.fromstring(notice)


            #Quiero traer el título, el cuerpo y el resumen, hago una validación
            # try -except, estoy haciendolo para los índices de la lista.
            # Pueden haber noticias con nodos faltantes por lo que arrojará un error
            try:
                #Traemos el primer elemento de la lista.
                title = parsed.xpath(XPATH_LINK_TO_TITLE)[0]

                # No es deseable tener comillas en los títulos porque presentan un error en OS.
                # Para solucionar esto, hacemos uso de que title es un str y del metodo replace()                title = title

                title = title.replace('\"','')

                summary = parsed.xpath(XPATH_LINK_TO_SUMMARY)[0]
                body = parsed.xpath(XPATH_LINK_TO_BODY)
            except IndexError:
                return

            # Guardamos en  un archivo

            # with es un manejador contextual. Si algo sucede y el script se cierra, mantiene las cosas
            # de manera segura y así no se corrompe el archivo.

            with open(f'{today}/{title}.txt','w', encoding ='utf-8') as f:
                f.write(title)
                f.write('\n\n')
                f.write(summary)
                f.write('\n\n')
                for p in body:
                    f.write(p)
                    f.write('\n')

        else:
            raise ValueError(f'Error: {response.status_code}')
    except ValueError as ve:
        print(ve)





# Creamos las funcioens para ejecutar el script.

def parse_home():
    # Creamos un bloque try para manejar los errores. Y manejar los Status Code.
    try:
        response = requests.get(HOME_URL)
        # Aqui va la lógica para traer los links.
        if response.status_code == 200:
            # .content trae  el HTML que necesita ser traducido con un decode para que python lo entienda
            # en terminos de caracteres, me devuelve un string que no es más que el HTML crudo.
            home = response.content.decode('utf-8')
            # home = response.text
            # print(home)

            # En esta línea uso el parser html para transformar el contentido
            # html a un archivo que sea de utilidad para las expresiones xpath
            parsed = html.fromstring(home)
            print(parsed)

            # En esta línea estoy usando el archivo parseado con la función xpath y le paso por parámetro mi constante
            # la cual almacena la expresión Xpath.

            links_to_notices = parsed.xpath(XPATH_LINK_TO_ARTICLE)
            # La línea de código me arroja un array vacío. Pero en google si lo enseña.

            print(len(links_to_notices))  # Depende de tu Xpath y la página web.
            print(type(links_to_notices)) # Tipo lista
            print(links_to_notices)

            # Traigo una fecha con la función fecha. Y Today, la fecha del dái de hoy. La variable today
            # se almacena un objeto de tipo fecha, pero nos interesa más tener una cadena de caracteres que contenga la fecha
            # en determinado formato que será guardado en la carpeta y con la función strftime logramos esto

            today = datetime.date.today().strftime('%d-%m-%Y')

            # Este condicional sirve para decirle que si no existe una carpeta con la fehca del día de hoy
            # me cree una carpeta.
            if not os.path.isdir(today):
                os.mkdir(today)

            # Creo la función para recorrer la lista de links y ejecuto en cada ciclo la función parse_notice()
            for link in links_to_notices:
                parse_notice(link,today)



        else:
            #Elevamos el error para ser capturado en el try-except, too lo que sea un error.
            raise ValueError(f'Error: {response.status_code}')
    except ValueError as ve:
        print(ve)

def run():
    parse_home()

if __name__ == '__main__':
    run()


Les compartomi codigo realizado en Abrl 15 (He visto difernecia entre varios compañeros en la estructura de la pagina). Como varios tuve problemas con los h2 que se solucionó reemplazando por text-fill. Así mismo tuve problemas con otras etiquetas que soluciones haciendo la expresión regular lo mas sencilla posible.

Saludos a todos.

<code>
 import requests 
import lxml.html as html
import os
import datetime

HOME_URL='https://www.larepublica.co/'

XPATH_LINK_TO_ARTICLE= '//text-fill/a[@class="economiaSect" or @class="empresasSect" or @class="ocioSect" or @class="globoeconomiaSect" or @class="analistas-opinionSect"]/@href'
XPATH_TITTLE='//div[@class="mb-auto"]/text-fill/span/text()'
XPATH_SUMMARY='//div[@class ="lead"]/p/text()'
XPTAH_BODY='//div[@class="html-content"]/p[not(@class)]/text()'


def parse_notice(link,today):
    try:
        response=requests.get(link)
        if response.status_code==200:
            notice=response.content.decode('utf-8')
            parsed=html.fromstring(notice)
            
            try:
                tittle=parsed.xpath(XPATH_TITTLE)[0]
                print(tittle)
                tittle=tittle.replace('\"','')
                summary=parsed.xpath(XPATH_SUMMARY)[0]
                body=parsed.xpath(XPTAH_BODY)
            except IndexError:
                return

            with open(f'{today}/{tittle}.txt','w',encoding='utf-8') as f:
                f.write(tittle)
                f.write('\n\n')
                f.write(summary)
                f.write('\n\n')
                for p in body:
                    f.write(p)
                    f.write('\n')

        else:
            raise ValueError(f'Error: {response.status_code}')
    except ValueError as ve:
        print(ve)

def parse_home():
    try:
        response= requests.get(HOME_URL)
        if response.status_code==200:
            home=response.content.decode('utf-8')
            parsed=html.fromstring(home)
            links_to_notices=parsed.xpath(XPATH_LINK_TO_ARTICLE)
            
            today=datetime.date.today().strftime('%d-%m-%Y')
            if not os.path.isdir(today):
                os.mkdir(today)
            
            for link in links_to_notices:
                parse_notice(link,today)
        else:
            raise ValueError(f'Error: {response.status_code}')
    except ValueError as ve:
        print(ve)

def run():
    parse_home()

if __name__=='__main__':
    run()

02/2022

Para la solución removí el tag h2 del xpath titile, de resto todo continuo igual a la clase.

import requests
import lxml.html as html
import os
import datetime


HOME_URL = 'https://www.larepublica.co/'


XPATH_LINK_TO_ARTICLE = '//text-fill[not(@class)]/a/@href'
XPATH_TITTLE = '//div[@class="mb-auto"]//span/text()'
XPATH_SUMMARY = '//div[@class = "lead"]/p/text()'
XPATH_BODY = '//div[@class = "html-content"]/p[not (@class)]/text()'

def parse_notice(link,today):
    try:
        response=requests.get(link)
        if response.status_code==200:
            notice=response.content.decode('utf-8')
            parsed=html.fromstring(notice)
            
            try:
                tittle=parsed.xpath(XPATH_TITTLE)[0]
                print(tittle)
                tittle=tittle.replace('\"','')
                summary=parsed.xpath(XPATH_SUMMARY)[0]
                body=parsed.xpath(XPATH_BODY )
            except IndexError:
                return

            with open(f'{today}/{tittle}.txt','w',encoding='utf-8') as f:
                f.write(tittle)
                f.write('\n\n')
                f.write(summary)
                f.write('\n\n')
                for p in body:
                    f.write(p)
                    f.write('\n')

        else:
            raise ValueError(f'Error: {response.status_code}')
    except ValueError as ve:
        print(ve)

def parse_home():
    try:
        response= requests.get(HOME_URL)
        if response.status_code==200:
            home=response.content.decode('utf-8')
            parsed=html.fromstring(home)
            links_to_notices=parsed.xpath(XPATH_LINK_TO_ARTICLE)
            
            today=datetime.date.today().strftime('%d-%m-%Y')
            if not os.path.isdir(today):
                os.mkdir(today)
            
            for link in links_to_notices:
                parse_notice(link,today)
        else:
            raise ValueError(f'Error: {response.status_code}')
    except ValueError as ve:
        print(ve)

def run():
    parse_home()

if __name__=='__main__':
    run()

Funcional al 3/1/2023

saque lo del titulo del comentario de alonmar
y de Pamela Pimentel lo de remplazar el h2

import requests
import lxml.html as html
import os
import datetime

#Url de la pagina
HOME_URL = 'https://www.larepublica.co'

#url del articulo
XPATH_LINK_TO_ARTICLE = '//text-fill/a/@href'

#Extraer de cada articulo
XPATH_TITTLE='//div/text-fill/span/text()'
XPATH_SUMMARY='//*[@id="proportional-anchor-1"]/div/div/p/text()'
XPATH_BODY='//div/div[4]/p/text()'



def get_title(link):
    #separamos por "/" y nos quedamos con el ultimo que elemento 
    url = link.split('/')[-1]
    #separamos por "-" y eliminamos el ultimo elemento
    title_list=url.split('-')[:-1]
    #Unimos lo anterior
    title = " ".join(title_list)

    return(title)



def parse_notice(link, today):
    try:
        response = requests.get(link)
        if response.status_code==200:
            notice = response.content.decode('utf-8') #Guarda el contenido
            parsed = html.fromstring(notice)
            
            try:
                tittle = get_title(link) #obtiene el titulo
                
                summary = parsed.xpath(XPATH_SUMMARY)[0] #get te summary
                body = parsed.xpath(XPATH_BODY) #get the body


            except IndexError as ie:
                print(ie)
                return

            try:

                with open(f'{today}/{tittle}.txt', 'w', encoding='utf-8') as f:
                    f.write(tittle)
                    f.write('\n\n')
                    f.write(summary)
                    f.write('\n\n')
                    for p in body:
                        if p.endswith('.'):
                             f.write(p)
                             f.write('\n')
                        else: 
                             f.write(p)

            except:
                print('no se pudo escribir')

        else:
            raise ValueError(f'Error: {response.status_code}')
    except ValueError as ve:
        print(ve)

#Extraer links
def parse_home():
    try:
        response = requests.get(HOME_URL)
        if response.status_code == 200: #si la conexion es correcta
            home = response.content.decode('utf-8') #guarda en formato leible
            parsed = html.fromstring(home) #parsea
            links_to_notice = parsed.xpath(XPATH_LINK_TO_ARTICLE)
            # print(links_to_notice)

            today = datetime.date.today().strftime('%d-%m-%Y')
            if not os.path.isdir(today):
                os.mkdir(today)

            for link in links_to_notice:
                parse_notice(link,today)

        else:
            raise ValueError(f'Error: {response.status_code}')
    except ValueError as ve:
        print(ve)

def run():
    parse_home()

if __name__ == '__main__':
    run()

06 de noviembre 2021
Con esto me funcionó:

HOME_URL ='https://www.larepublica.co/'
XPATH_LINK_TO_ARTICLE = '//text-fill/a/@href'
XPATH_TITLE = '//div[@class="mb-auto"]/text-fill/span/text()'
XPATH_SUMMARY = '//div[@class="lead"]/p/text()'
XPATH_BODY = '//div[@class="html-content"]/p[not(@class)]/text()'

El resto, todo es igual 😃

Xpath a 14 de agosto de 2022

En Body se usa descendant-or-self:: * para poder extraer TODO el texto ya que las palabras resaltadas en algunos artículos no son tomadas en la extracción.

from string import printable
import requests
import lxml.html as html
import os
import datetime

HOME_URL = 'https://www.larepublica.co/'
XPATH_LINK_TO_ARTICLE = "//text-fill/a/@href"
XPATH_TITLE = "//div[@class='mb-auto']/h2/span/text()"
XPATH_SUMMARY = "//div[@class='lead']/p/text()" 
XPATH_BODY = "//div[@class='html-content']/p/descendant-or-self::*/text()" #to do: check how to extract the u html

def parse_notice(link, today):
    try:
        response = requests.get(link)
        if response.status_code == 200:
            notice = response.content.decode('utf-8') #save the content of the page in a variable
            parsed = html.fromstring(notice) #parse the html content

            try:
                title = parsed.xpath(XPATH_TITLE)[0] #get the title of the article
                title.replace('\"', '') #remove the quotes from the title
                title = title.replace('\'', '') #remove the quotes from the title
                title = title.replace('\n', '') #remove the newlines from the title
                title = title.replace('\t', '') #remove the tabs from the title
                title = title.replace('\r', '') #remove the carriage returns from the title
                title = title.strip() #remove the whitespaces from the title
                final_title = title

                title = title.replace('á', 'a').replace('é', 'e').replace('í', 'i').replace('ó', 'o').replace('ú', 'u') #replace the accents from the title
                title = title.replace('Á', 'A').replace('É', 'E').replace('Í', 'I').replace('Ó', 'O').replace('Ú', 'U') #replace the accents from the title
                title = title.replace('?', '').replace('¿', '').replace('!', '').replace('¡', '') #remove the question marks and exclamation marks from the title
                title = title.replace(':', '').replace(';', '').replace(',', '').replace('.', '').replace('(', '').replace(')', '') #remove the colons, semicolons, commas, dots, parentheses and spaces from the title
                title = title.replace('%', '').replace('$', '').replace('#', '').replace('@', '').replace('&', '').replace('*', '').replace('+', '').replace('=', '').replace('-', '').replace('_', '').replace('/', '').replace('\\', '').replace('|', '').replace('<', '').replace('>', '').replace('"', '').replace('\'', '') #remove the special characters from the title
                summary = parsed.xpath(XPATH_SUMMARY)[0] #get the summary of the article
                body = parsed.xpath(XPATH_BODY) #get the body of the article
            except IndexError as ie:
                print(ie)
                return
            try:

                with open(f'{today}/{title}.txt', 'w', encoding='utf-8') as file:
                    file.write(final_title)
                    file.write('\n\n')
                    file.write(summary)
                    file.write('\n\n')
                    for p in body:
                        file.write(p)
                        file.write('\n')

            except:
                print('No se pudo escribir el archivo')

                
        else:
            raise ValueError(f'Error {response.status_code}')
    except ValueError as ve:
        print(ve)
        


def parse_home():
    try:
        response = requests.get(HOME_URL)
        if response.status_code == 200:
            home = response.content.decode('utf-8') #save the content of the page in a variable
            parsed = html.fromstring(home) #parse the html content
            links_to_notices = parsed.xpath(XPATH_LINK_TO_ARTICLE) #get the links to the articles
            # print(links_to_notices)


            today = datetime.date.today().strftime('%d-%m-%Y') #get the current date
            if not os.path.isdir(today): #if the directory doesn't exist, create it
                os.mkdir(today)

            for link in links_to_notices: #for each link
                parse_notice(link, today)
        else :
            raise ValueError(f'Error {response.status_code}')
    except ValueError as ve:
        print(ve)
        

def run():
    parse_home()

if __name__ == "__main__":
    run()

Hola a todos, os comparto como ha quedado mi codigo al final:

#el primer paso es importar todas las libreias que voy
#a utilizar:

import requests
from lxml import html #la funcion HTML sirve para convertir archivos de HTML a un tipo de archivo con el cual pueda aplicar XPath
import os #vamos a utilizar este modulo para crear una carpeta con la fecha de hoy
import datetime #nos permite traer la fecha de hoy

#despues debo crear las constantes que me llevaran 
#al cuerpo, el titulo y el resumen de la noticia.

#contiene el link a la pagina principal de la republica
HOME_URL = 'https://www.larepublica.co'

XPATH_LINK_TO_ARTICLE = '//div[@class="V_Title"]/text-fill/a/@href'
XPATH_LINK_TO_TITLE = '//div[@class="mb-auto"]/text-fill/span/text()'
XPATH_LINK_TO_SUMMARY = '//div[@class="lead"]/p/text()'
XPATH_LINK_TO_BODY = '//div[@class="html-content"]/p[not(@class)]/text()'


#ahora voy a crear las funciones de este programa

#la funcion parse_home es la que se encarga de obtener
#los links a las noticias:


def parsed_notice(link, today):
    #envuelvo en un bloque try en caso de que el status_code de mi
    #peticion sea distinto a 200
    try:
        response = requests.get(link)#solicito respusta al link de la noticia
        if response.status_code == 200:
            #primero quiero traer el documento html de la noticia
            notice = response.content.decode('utf-8')
            parsed = html.fromstring(notice)#lo convierto a un documento para aplicar xpath(un HTML con superpoderes)

            #el error contra el que me quiero proteger es si summary no tiene indices
            #es decir, no existe, en ese caso lo que quiero es salirme de la fucnion porque no
            #quiero noticias sin resumen
            try:
                #a estas variables pongo indices porque el resultado de aplicar xpath
                #a un html con superpoderes (parsed) nos devuleve una lista que puede tener uno a varios elementos
                #en este caso nosotros sabemos que el titulo, el resumen y el cuerpo
                #son el elemento 1 de una lista (index 0)
                title = parsed.xpath(XPATH_LINK_TO_TITLE)[0]
                #ahora voy a manejar el posible error de que el titulo tenga comillas
                #este link explica porque https://platzi.com/comentario/2198698/
                title = title.replace('\"','')
                summary = parsed.xpath(XPATH_LINK_TO_SUMMARY)[0]
                #a body lo dejamos sin indice porque body es una lista de parrafos y quiero traerme la lista completa
                body = parsed.xpath(XPATH_LINK_TO_BODY)
            except IndexError:
                return 
    #ahora vamos a guardar esta informacion. with es un gestor de contexto
    #es como un try bloc, en caso de que algo salga mal hace que el 
    #archivo se cierre y no se corrampa. despues uso la funcion open para abrir archivo. Con esta fucnion
    #busco la carpeta que se creo con la fecha de hoy y dentro de esa carpeta
    #guardo un archivo que tiene como nombre el titulo de la noticia.
    #dentro de la funcion open el primer parametro nos indica la ruta del archivo, el segundo
    #nos dice que entramos en modo escritura, y el encoding nos permite guardar los caracteres especiales
    #de manera correcta para que no haya errores. Finalmente, nombro todo esto como
    # f
            with open(f'{today}/{title}.txt','w', encoding='utf-8') as f:
                f.write(title)
                f.write('\n\n')
                f.write(summary)
                f.write('\n\n')
                #necesito el for porque el body es una lista de parrafos
                for p in body:
                    f.write(p)
                    f.write('\n')

            
        else:
            raise ValueError(f'ERROR: {response.status_code}')
    except ValueError as ve:
        print(ve)


def parse_home():
    #sin embargo, tendo que proteger mi codigo en caso
    #de errores como el codigo 404
    try:
        #con la funcion de request me traigo el archivo
        #HTML de la pagina
        response = requests.get(HOME_URL)#con esto no solo obtengo el documentos HTML sino tambien http, como las cabeceras
        if response.status_code == 200:
            home = response.content.decode('utf-8')
            #.content traer el archivo HTML y el .decode('utf-8') 
            #me ayuda a convertir los caracteres raros (ñ, tildes) en algo que 
            #python pueda entender
            
            parsed = html.fromstring(home)
            #toma el archivo html de home y lo convierte en un tipo de archivo 
            #a partir del cual yo puedo hacer XPath


            #ahora lo que me falta es obtener una lista de los links
            #que obtenga que he obtenido hasta ahora
            links_to_notices = parsed.xpath(XPATH_LINK_TO_ARTICLE)
            #print(links_to_notices)
            
            today = datetime.date.today().strftime('%d-%m-%Y')
            #del modulo datetime traemos la funcion date y traemos
            #la fecha de hoy. Hasta esta parte guardamos un objeto 
            #que nos da la fecha de hoy, pero lo que yo quiero es una string
            #que me de la fecha en el formato dia/mes/año, par ello uso
            #la funcion .strftime('%d-%m-%Y')

            
            
            #os.path.isdir(today) trae un booleano cuyo valor dependen de si hay 
            #o no una carpeta con el nombre que hemos establecido (today) en la
            #carpeta en la que estamos. Si esa carpeta no existe creamos esa carpeta. isdir() method in Python is 
            #used to check whether the specified path is an existing directory or not.
            if not os.path.isdir(today):
                #con esto creamos una carpeta con el nombre de la fecha de hoy si esa carpeta no existe
                os.mkdir(today)

                #por cada link en la lista vamos a entrar y extraer lo que queremos
                for link in links_to_notices:
                    #esta funcion entra al link y extrae la infomacion que queremos
                    #y eso lo va a guardar usando la fecha de hoy.
                    parsed_notice(link, today)
            
            
        else:
            raise ValueError(f'ERROR: {response.status_code}')
    except ValueError as ve:
        print(ve)



#la siguiente es la funcion principal, la que se va a ejecutar

def run():
    parse_home()


if __name__ == '__main__':
    run()

Lo que genera el cambio de h2 a text-fill és el decode.

response.content.decode(“utf-8”)

Si lo quitan no realitzarà cambios

Les comparto mi código con la solución del ejercicio. Haciendo el análisis en la web, para sacar el titulo lo hice con //div[@class="mb-auto"]/h2/a/text(), pero la respuesta era diferente y tuve que usar //div[@class="mb-auto"]/text-fill/a/text()

Vi en la sección de comentarios que también le paso a varios.

import requests
import lxml.html as html
import os
import datetime


HOME_URL = 'https://www.larepublica.co/'

XPATH_LINK_TO_ARTICLE = '//div[@class="V_Title"]/a/@href'
XPATH_TITLE = '//div[@class="mb-auto"]/text-fill/a/text()'
XPATH_SUMMARY = '//div[@class="lead"]/p/text()'
XPATH_BODY = '//div[@class="html-content"]/p[not(@class)]/text()'


def parse_notice(link, today):
    try:
        response = requests.get(link)
        if response.status_code == 200:
            notice = response.content.decode('utf-8')
            parsed = html.fromstring(notice)

            try:
                title = parsed.xpath(XPATH_TITLE)[0]
                title = title.replace('\"', '')
                summary = parsed.xpath(XPATH_SUMMARY)[0]
                body = parsed.xpath(XPATH_BODY)
            except IndexError:
                return

            with open(f'{today}/{title}.txt', 'w', encoding='utf-8') as f:
                f.write(title)
                f.write('\n\n')
                f.write(summary)
                f.write('\n\n')
                for p in body:
                    f.write(p)
                    f.write('\n')

                f.write('\n\n')
                f.write(f'Más información en: {link}')
        else:
            raise ValueError(f'Error: {response.status_code}')
    except ValueError as ve:
        print(ve)


def parse_home():
    try:
        response = requests.get(HOME_URL)
        if response.status_code == 200:
            home = response.content.decode('utf-8')
            parsed = html.fromstring(home)
            link_to_notices = parsed.xpath(XPATH_LINK_TO_ARTICLE)
            # print(link_to_notices)

            today = datetime.date.today().strftime('%d-%m-%Y')

            if not os.path.isdir(today):
                os.mkdir(today)

            for link in link_to_notices:
                parse_notice(link, today)
        else:
            raise ValueError(f'Error: {response.status_code}')
    except ValueError as ve:
        print(ve)


def run():
    parse_home()


if __name__ == '__main__':
    run()

comence sacando noticias y el what if en mi cabeza se encendio y termine sacando todas las url de las paginas que ponia

Los archivos no se pueden nombrar con caracteres como el ?, ¿ o el >, <, sin embargo, es posible que las noticias los tengan en sus títulos. Por esto se puede crear una simple solución con el mismo método que utiliza el profe cambiando las comillas por otros caracteres que nos interesen:

title = title.replace('\"', '')
title = title.replace('¿', '')
title = title.replace('?', '')

Yo decidi hacer como ejercicio extraer la informacion de los libros de la tienda de toscrape(pagina que recomendo el profe facundo para practicar) con categoria “sequential art” extrae(titulo, precio, existencia y calificacion):

import requests
import lxml.html as html
import os

#BOOKS DATA
XPATH_LINKS='//section/div/ol[@class="row"]/li/article[@class="product_pod"]/h3/a/@href'
XPATH_TITLE='//section/div/ol[@class="row"]/li/article[@class="product_pod"]/h3/a/text()'
XPATH_PRICE='//section/div/ol[@class="row"]/li/article[@class="product_pod"]/div[@class="product_price"]/p[@class="price_color"]/text()'
XPATH_AVAILABILITY='//section/div/ol[@class="row"]/li/article[@class="product_pod"]/div[@class="product_price"]/p[@class="instock availability"]/text()'
XPATH_RATE='//section/div/ol[@class="row"]/li/article[@class="product_pod"]/p/@class'

URL='http // books toscrape com/catalogue/category/books/sequential-art_5/'#platzi me pidio romper el codigo
URL_LIST= []
LINK_LIST=[]
TITLE_LIST=[]
PRICE_LIST=[]
AVAILABILITY_LIST=[]
RATE_LIST=[] #rate 1 to 5

def URL_PAGES():
    for i in range(1,5):
        x=i
        y=(f'{URL}page-{x}.html')
        URL_LIST.append(y)  

def BOOK_INFO():
    LINK=[]
    TITLE=[]
    PRICE=[]
    AVAILABILITY=[]
    RATE=[] 
    for a in range(len(URL_LIST)):
        try:
            response=requests.get(URL_LIST[a])
            if response.status_code == 200:
                home=response.content.decode('utf-8')
                parsed = html.fromstring(home)
                try:
                    link= parsed.xpath(XPATH_LINKS)
                    title= parsed.xpath(XPATH_TITLE)
                    for i in range(len(title)):
                        title[i]=title[i].replace('\"','')
                    price= parsed.xpath(XPATH_PRICE)
                    availability= parsed.xpath(XPATH_AVAILABILITY)
                    for i in range(len(availability)):
                        availability[i]=availability[i].strip('\n ')
                    rate= parsed.xpath(XPATH_RATE)
                    for i in range(len(rate)):
                        rate[i]=rate[i].replace('star-rating ','')
                        for x,n in enumerate(['One','Two','Three','Four','Five']):
                            if rate[i]==n:
                                rate[i]=x+1
                except IndexError:
                    return
            else:
                raise ValueError(response.status_code)
        except ValueError as ve:
            print(ve)
        LINK+=link
        TITLE+=title
        PRICE+=price
        AVAILABILITY+=availability
        RATE+=rate
    LINK_LIST.append(LINK)
    TITLE_LIST.append(TITLE)
    PRICE_LIST.append(PRICE)
    AVAILABILITY_LIST.append(AVAILABILITY)
    RATE_LIST.append(RATE) 



def run():
    URL_PAGES()
    BOOK_INFO()
    print(TITLE_LIST[0][3])
    print(PRICE_LIST[0][3])
    print(AVAILABILITY_LIST[0][3])
    print(RATE_LIST[0][3])


if __name__=='__main__':
    run()

No me dejó correr el script, me aparece:
UnboundLocalError: local variable ‘title’ referenced before assignment

Les comparto como termine haciendo el reto.
Cosas a considerar:

  • Como muchos otros notaron, en el XPath que usamos para extraer el titulo en Python, hay que cambiar el h2 por text-fill.

  • En el parse_home(), cuando extraigamos los links puede hayan algunos que estén repetidos. Por esto, usamos el links_to_notice = list(set(links_to_notice)) para quitar los duplicados de la lista.
    El set() transforma la lista en un conjunto donde todos sus elementos son diferentes, despues el list() lo vuelve a lista

import requests
import lxml.html as html
import os
import datetime

HOME_URL = 'https://www.larepublica.co/'

XPATH_LINK_TO_ARTICLE = '//div[@class="news V_Title_Img"]//a/@href'
XPATH_TITLE = '//div[@class="mb-auto"]/text-fill/span/text()'
XPATH_SUMMARY = '//div[@class="lead"]/p/text()'
XPATH_BODY = '//div[@class="html-content"]/p/text()'


def parse_notice(link,today):
  try:
    response = requests.get(link)
    if response.status_code == 200:
      notice = response.content.decode('utf-8')
      parsed = html.fromstring(notice)

      try:
        title = parsed.xpath(XPATH_TITLE)[0]
        title = title.replace('\"','')
        summary = parsed.xpath(XPATH_SUMMARY)[0]
        body = parsed.xpath(XPATH_BODY)
      except IndexError:
        print(IndexError)
      
      with open(f'{today}/{title}.txt', 'w', encoding='utf-8') as f:
        f.write(title)
        f.write('\n\n')
        f.write(summary)
        f.write('\n\n')
        for p in body:
          f.write(p)
          f.write('\n')

    else:
      raise ValueError(f'Error: {response.status_code}')
  except ValueError as ve:
    print(ve)


def parse_home():
  try:
    response = requests.get(HOME_URL)
    if response.status_code == 200:
      home = response.content.decode('utf-8')
      parsed = html.fromstring(home)
      links_to_notice = parsed.xpath(XPATH_LINK_TO_ARTICLE)
      # print(links_to_notice)
      links_to_notice = list(set(links_to_notice))

      today = datetime.date.today().strftime('%d-%m-%Y')
      if not os.path.isdir(today):
        os.mkdir(f'{today}')

      for link in links_to_notice:
        parse_notice(link,today)
    else:
      raise ValueError(f'Error: {response.status_code}')
  except ValueError as ve:
    print(ve)


def run():
  parse_home()


if __name__ == '__main__':
  run()

Adicional a los aportes de los compañeros acerca del conflicto con <h2>, caracteres especiales como los dos puntos : no los permiten en el nombre del archivo.

import requests
import lxml.html as html
import os        # Para crear las carpetas
import datetime  # Para asignar nombres con fechas

HOME_URL = 'https://www.larepublica.co/'

#XPATH_LINKS_TO_ARTICLE = '//h2/a/@href'
XPATH_LINKS_TO_ARTICLE = '//text-fill/a/@href'
#XPATH_TITLE = '//div[@class="mb-auto"]/h2/span/text()'
XPATH_TITLE = '//div[@class="mb-auto"]/text-fill/span/text()'
XPATH_SUMMARY = '//div[@class="lead"]/p/text()'
XPATH_BODY = '//div[@class="html-content"]/p[not(@class)]/text()'


def parse_notice(link, today):
    try:
        response = requests.get(link)
        if response.status_code == 200:
            notice = response.content.decode('utf-8')  # html de la noticia
            parsed = html.fromstring(notice) # html con permisos para trabajar

            # lectura de la noticia
            try:
                title = parsed.xpath(XPATH_TITLE)[0]
                title = title.replace('\"', '')
                title = title.replace(':', '')
                title = title[0:80]
                summary = parsed.xpath(XPATH_SUMMARY)[0]
                body =  parsed.xpath(XPATH_BODY)

            # Manejador contextual, permite si se llega a cerrar ele archivo de forma inesperada, mantiene todo seguro sin corromperse
                with open(f'{today}/{title}.txt', 'w', encoding='utf-8') as f:
                    f.write(title)
                    f.write('\n \n')
                    f.write(summary)
                    f.write('\n \n')
                    for p in body:
                        f.write(p)
                        f.write('\n')
            except IndexError:
                return

        else:
            raise ValueError(f'Error: {response.status_code}')
    except ValueError as ve:
        print(ve)


def parse_home():
    try:    ## Para manejar los errores del servidor como por ejemplo el 404
        response = requests.get(HOME_URL) # se obtiene todo el documento HTML junto con las cabeceras

        if response.status_code == 200:
            home = response.content.decode('utf-8') # .decode transforma todos los caracteres especiales de tal modo que se pueda leer
            parsed = html.fromstring(home) # toma el contenido HTML y lo transforma en un documento especial para hacer scraper
            links_to_notices = parsed.xpath(XPATH_LINKS_TO_ARTICLE)
            #print(links_to_notices)

            today = datetime.date.today().strftime('%d-%m-%Y')
            if not os.path.isdir(today):   # si no existe el directorio con el nombre today
                os.mkdir(today) # creación de la carpeta

            for link in links_to_notices:
                parse_notice(link, today)
        else:
            raise ValueError(f'Error: {response.status_code}')
    except ValueError as ve:
        print(ve)

def run():
    parse_home()

if __name__ == '__main__':
    run()

Una de las soluciones para los titulos que descrubri fue usando esta expresion:

//div[@class='mb-auto']/*[2]/span/text()

Al colocar el /*[2] , seleciono el segundo nodo del div que seria el “h2”

Codigo Completo:

import requests
import lxml.html as html
import os
import datetime

#get url page
HOME_URL = "https://www.larepublica.co/"

#expressions
XPATH_LINK_TO_ARTICLE = "//div/a[contains(@class,'kicker')]/@href"
XPATH_TITLE = "//div[@class='mb-auto']/*[2]/span/text()"
XPATH_SUMARY = "//div[@class='lead']/p/text()"
XPATH_BODY = "//div[@class='html-content']/p[not(@class)]/text()"

def parse_notice(link,today):
    try:
        response = requests.get(link)
        if response.status_code == 200:
            notice = response.content.decode('utf-8')
            parsed = html.fromstring(notice)

            try:
                title = parsed.xpath(XPATH_TITLE)[0]
                title = title.replace('\"','')
                sumary = parsed.xpath(XPATH_SUMARY)[0]
                body = parsed.xpath(XPATH_BODY)
            except IndexError:
                return
            
            with open(f'{today}/{title}.txt', 'w', encoding='utf-8') as f:
                f.write(title)
                f.write('\n\n')
                f.write(sumary)
                f.write('\n\n')
                for p in body:
                    f.write(p)
                    f.write('\n')

        else:
            raise ValueError(f'Error: {response.status_code}')
    except ValueError as ve:
        print(ve)


def parse_home():
    try:
        response = requests.get(HOME_URL)
        if response.status_code == 200:
            #extrayendo las url
            home = response.content.decode('utf-8')
            parsed = html.fromstring(home)
            links_to_notices= parsed.xpath(XPATH_LINK_TO_ARTICLE)
            
            #obtener la fecha de hoy
            today = datetime.date.today().strftime('%d, %m, %Y')

            #verificar si no existe una carpeta por la fecha
            if not os.path.isdir(today):
                os.mkdir(today)

            for link in links_to_notices:
                parse_notice(link,today)
        else:
            raise ValueError(f'Error: {response.status_code}')
    except ValueError as ve:
        print(ve)

def run():
    parse_home()

if __name__ == '__main__':
    run()

🚩 SOLUCION: OSError [Errno 22] 🚩
Me ocurría cuando los títulos de noticias llevaban uno de los 3 simbolos: ? ¿ o \ (Inválidos para Windows)

Lo solucioné colocando luego del title lo siguiente:

	for e in '?¿\"':
		title=title.replace(e,'')

pueden colocar luego de esto un

print(title)

para verificar que se hayan guardado todos los títulos.

Saludos

Importante:

Cuando usamos el xpath de Body existen etiquetas dentro de <p></p> como <strong>, <u>, etc. Estos no se incluyen en el archivo de texto porque son etiquetas anidadas que no están en <p>. Para solucionar esto podemos usar /descendant-or-self:: * al final de la linea, pero como nuestro objetivo es solo conseguir el texto usamos “//text” para incluir todos los campos dentro de la etiqueta <p>, quedando así:

//div[@class="html-content"]/p//text()'

Esto genera otro error en el archivo.txt agregando un salto de linea en todos las etiquetas que pudo haber tenido dentro de <p>. Para solucionar esto podemos usar el siguiente código:

with open(f'{today}/{title}.txt', 'w', encoding='utf-8') as f:
    f.write(title)
    f.write('\n\n')
    f.write(summary)
    f.write('\n\n')
    for p in body:
        if p.endswith('.'):
            f.write(p)
            f.write('\n')
        else: 
            f.write(p)

Detectará si la linea termina con un punto y agregará un salto de linea, si no es así, le seguirá adjuntando de manera continua al texto.

Lo que hice yo fue una especie de menú en el cual le especificas la categoría de noticia que quieres ver y te trae las noticias de esa categoría de manera paginada y puedes escoger la que gustes para leer

13 julio 2022

obtuve satisfactoriamente 10 archivos con el siguietne código, usenlo:

import requests 
import lxml.html as html 
import os 
import datetime

HOME_URL = 'https://www.larepublica.co/'

XPATH_LINK_TO_ARTICLE= '//text-fill/a/@href' # text-fill instead of h2 !!
XPATH_TITLE='//div[@class="mb-auto"]/text-fill/span/text()'
XPATH_SUMMARY='//div[@class="lead"]/p/text()'
XPATH_BODY='//div[@class="html-content"]/p[not(@class)]/text()'

def parse_notice(link, today):
    try:
        response = requests.get(link)
        if response.status_code == 200:
            notice = response.content.decode('utf-8') # html of the notice
            parsed = html.fromstring(notice) # better html -> [list]
            try:
                title = parsed.xpath(XPATH_TITLE)[0] # [0] just the first element of the [list]
                title.replace('\"','') # deteling (")
                summary = parsed.xpath(XPATH_SUMMARY)[0]
                body = parsed.xpath(XPATH_BODY)
            except IndexError: # in case title/summary/body does not exist -> jump the notice
                return

            # _with_ is an driver contextual driver
            with open(f'{today}/{title}.txt','w',encoding='utf-8') as f:
                f.write(title)
                f.write('\n\n\n\n')
                f.write(summary)
                f.write('\n\n\n\n')
                for p in body:
                    f.write(p)
                    f.write('\n\n')

        else:
            raise ValueError(f'Error {response.status_code}')

    except ValueError as ve:
        print(ve)

def parse_home():
    try:
        response = requests.get(HOME_URL)
        if response.status_code == 200:
            home = response.content.decode('utf-8')
            parsed = html.fromstring(home)
            links_to_notices=parsed.xpath(XPATH_LINK_TO_ARTICLE)
            # print(links_to_notices)
            # print(f' # of URLs : {len(links_to_notices)}')

            today = datetime.date.today().strftime('%d-%m-%Y')
            if not os.path.isdir(today):  # if does not directory named today? ( T or F)
                os.mkdir(today)           # generating a directory with today's date

            for link in links_to_notices: # generating filess
                parse_notice(link, today)    
        else:
            raise ValueError(f'Error: {response.status_code}')
    except ValueError as ve:
        print (ve)


def run():
    parse_home()

if __name__ == "__main__":
    run()

Para los que tienen el problema de que no se guarda nigún documento 😃 entre los comentarios hay explicaciones más detalladas. En resumen es cómo interpreta el XPATH de python con el de Chrome. h2 por text-fill

XPATH_TITLE = '//div/h2[@style]/span/text()'

Despues

XPATH_TITLE = '//div/text-fill/span/text()'

Lo logre
⭐️⭐️⭐️⭐️⭐️

a esta fecha , por si alguien necesita los xpath del ejercicio:

solo por decir que en lugar de tomar o hacer text-fill como algunos han sugerido, yo simplemente hice // para buscar el span y luego en el archivo python tome el [1] del array del titulo.

XPATH_LINK_TO_ARTICLE = '//div[@class="col mb-4"]/div[@class="news V_Title_Img"]/a[position()=1]/@href'

XPATH_TITLE = '//div[@class = "row OpeningPostNormal"]/div[@class = "col-8 order-2 d-flex flex-column"]/div[@class = "mb-auto"]//span/text()'
XPATH_SUMMARY = '//div[@class = "row article-wrapper"]/div[@class = "wrap-post col-9"]/div[position() = 1]/div[@class = "lead"]/p/text()'
XPATH_BODY = '//div[@class="row article-wrapper"]/div[@class="wrap-post col-9"]/div[position()=1]/div[@class="html-content"]/p/text()'

Al día de hoy el scraping se tiene que hacer con:
Links = //div[@class=“news V_Title_Img”]/a/@href
Titulo = //div[@class=“mb-auto”]/h2/span/text()
Resumen = //div[@class=“lead”]/p/text()
Cuerpo = //div[@class=“html-content”]/p/text()

Me costó un poco realizar mi código porque lo que me servía en el navegador no me funcionaba en Python. Como tal considero que lo único que debo dejar en la sección de comentarios es el lenguaje de XPath. No me ayudo mucho el hecho de estar revisando el título de las noticias en la página principal xd.

XPATH_LINK_TO_ARTICLE = '//text-fill/a/@href'
XPATH_TITLE = '//div[@class="mb-auto"]//span/text()'
XPATH_SUMMARY = '//div[@class="lead"]/p/text()'
XPATH_BODY = '//div[@class="html-content"]/p[not(@class)]/text()'

Les dejo por aquí esta función que les ayudará a quitar de los titulos todos aquellos carácteres que les darían error al guardar su noticia:

def remove_characters(str):
    characters = (
        '\"',
        '?',
        '|',
        '\\',
        '', #You need this line because the double backslash takes the next element. Try removing it
        '/',
        ':',
        '<',
        '>',
        '*'
    )

    for character in characters:
        str = str.replace(character, '')

    return str

Este Es un código de la clase de web scraping con Python y Xpath. Descarga información un sitio de noticias de colombia.
Utiliza un ambiente virtual en la consola de comandos, xpath y otras herramientas.++ Sustituí h2 en el path for text-fill++ y así logré que funcione.

import requests #librería para el scraping
import lxml.html as html #libreria para el scraping
import os  #libreria para crear una carpeta
import datetime #libreria para timestamps, se combinará con OS.

HOME_URL = 'https://larepublica.co/'
XPATH_LINK_TO_ARTICLE = '//text-fill[not(@class)]/a/@href'
XPATH_TITLE = '//div[@class="mb-auto"]/text-fill/a/text()'
XPATH_SUMMARY = '//div[@class="lead"]/p/text()'
XPATH_BODY = '//div[@class="news economiaSect"]/div[@class="html-content"]/p[not(@class)]/text()'

def parse_notice(link, today):
    try:
        response = requests.get(link)
        if response.status_code == 200:
            notice = response.content.decode('utf-8')
            parsed = html.fromstring(notice)

            try:
                title = parsed.xpath(XPATH_TITLE)[0] #tomar el primer título en la lista de titles del path
                title = title.replace('\"','') #eliminar las comillas de un título en caso de que existan para evitar errores
                summary = parsed.xpath(XPATH_SUMMARY)[0] #tomar el primer resumen
                body = parsed.xpath(XPATH_BODY) #tomar los parrafos de Body.
            except IndexError: #en caso de que no haya resumen o body, regresa a la función en vez de marcar un error.
                return
            with open(f'{today}/{title}.txt','w', encoding='utf-8') as f:
                f.write(title)
                f.write('\n\n')
                f.write(summary)
                f.write('\n\n')
                for p in body:
                    f.write(p)
                    f.write('\n')
        else:
            raise ValueError(f'Error: {response.status_code}')
    except ValueError as ve:
        print(ve)


def parse_home():
    try:
        response = requests.get(HOME_URL)
        if response.status_code == 200:
            home = response.content.decode('utf-8')
            parsed = html.fromstring(home)
            links_to_notices = parsed.xpath(XPATH_LINK_TO_ARTICLE)
            #print(links_to_notices) #se imprime para ver como progresa
            today = datetime.date.today().strftime('%d-%m-%y')#funcion trae una fecha, today trae la de hoy y strftime le da un formato deseado.
            if not os.path.isdir(today):
                os.mkdir(today)
            for link in links_to_notices:
                parse_notice(link, today)
        else:
            raise ValueError(f'Error: {response.status_code}')
    except ValueError as ve:
        print(ve)

def run():
    parse_home()


if __name__ == '__main__':
    run()

Es uno de los proyectos que más me han costado. Les comparto funcionando al 16/junio/2021:

import requests
import lxml.html as html
import os
import datetime

HOME_URL = 'https://www.larepublica.co/'

XPATH_LINK_TO_ARTICLE = '//text-fill/a[@class="economiaSect" or @class="empresasSect" or @class="ocioSect" or @class="globoeconomiaSect" or @class="analistas-opinionSect"]/@href'
XPATH_TITLE = '//div[@class="mb-auto"]/text-fill/span/text()'
XPATH_SUMMARY = '//div[@class="lead"]/p/text()'
XPATH_BODY = '//div[@class="html-content"]/p[not(@class)]/text()'


def parse_news(link, today):
    try:
        response = requests.get(link)
        if response.status_code == 200:
            notice = response.content.decode('utf-8')
            parsed = html.fromstring(notice)

            try:
                title = parsed.xpath(XPATH_TITLE)[0]
                print(title)
                title = title.replace('\"','')
                title = title.replace('/','-')
                title = title.replace('|','-')
                title = title.replace(':','-')
                title = title[0:80]
                summary = parsed.xpath(XPATH_SUMMARY)[0]
                body = parsed.xpath(XPATH_BODY)
            except IndexError:
                return

            with open(f'{today}/{title}.txt','w', encoding='utf-8') as f:
                f.write(title)
                f.write('\n\n')
                f.write(summary)
                f.write('\n\n')
                for p in body:
                    f.write(p)
                    f.write('\n')
        else:
            raise ValueError(f'Error: {response.status_code}')
    except ValueError as ve:
        print(ve)


def parse_home():
    try:
        response = requests.get(HOME_URL)
        if response.status_code == 200:
            home = response.content.decode('utf-8')
            parsed = html.fromstring(home)
            links_to_news = parsed.xpath(XPATH_LINK_TO_ARTICLE)
            #print(links_to_news)
            today = datetime.date.today().strftime('%d-%m-%Y')
            if not os.path.isdir(today):
                os.mkdir(today)
            for link in links_to_news:
                parse_news(link, today)
        else:
            raise ValueError(f'Error: {response.status_code}')
    except ValueError as ve:
        print(ve)

def run():
    parse_home()


if __name__ == '__main__':
    run()
import requests
import lxml.html as html 
import os 
import datetime
 
HOME_URL = 'https://www.larepublica.co/'

XPATH_LINK_TO_ARTICLE = '//h2[@class="headline"]/a/@href'
XPATH_TITLE = '//h1[@class]="headline"]/a/text()'
XPATH_SUMMARY = '//div[@class="lead"]/p/text()'
XPATH_BODY = '//div[@class"articlewrapper "]/p[not(@class)]/text()'

def parse_notice(link,today):
    try:
        response = requests.get(link)
        if response.status_code ==200:
            notice = response.content.decode('utf-8')
            parsed = html.fronstring(notice)
        
            try:
                title = parsed.xpath(XPATH_TITLE)[0]
                title = title.reolace('\"','')
                summary = parsed.xpath(XPATH_SUMMARY)[0]
                body = parsed.xpath(XPATH_BODY)
            except IndexError:
                return
            
            whit open('{today}')/{title}.txt' , 'w' , encoding='utf-8') as 

                f.write(title)
                f.write('\n\n')
                f.write(summary)
                f.write('\n\n')
                for p in body,
                    f.write(p)
                    f.write('\n')
                    
        else:
            raise ValueError(f'Error: {response.status_code}')
    except ValueError as ve: 
        print(ve)


def parse_home():
    try:
        response = requests.get(HOME_URL)
        if response.status_code == 200:
            home = response.contect.decode('utf-8')
            parsed = html.fronstring(home)
            links_to_notices = parsed.xpath(XPATH_LINK_TO_ARTICLE)
            #print(links_to_notices)

            today = datetime.date.today().strftime('%d-%m-%Y')
            if not os.path.isdir(today):
                os.mkdir(today)
            for link in links_to_notices:
                parse_notice(link,today)
            
        else:
            raise ValueError(f'Error: {response.status_code}')
    except ValueError as ve: 
        print(ve)

def run():
    parse_home()

if __name__ == '__main__':
    run()```

Les comparto mi código hoy 22/10/2020

import requests
import lxml.html as html
import os
import datetime

HOME_URL = 'https://www.larepublica.co/'
XPATH_LINK_TO_ARTICLE = '//div[contains(@class, "V")]/a[contains(@class, "kicker")]/@href'
XPATH_TITLE = '//h2[not(@class)]/a/text()'
XPATH_SUMMARY = '//div[@class="lead"]/p/text()'
XPATH_BODY = '//div[@class="html-content"]/p[not(@class)]/text()'

def parse_notice(link, today):
    try:
        response = requests.get(link)
        
        if response.status_code == 200:
            notice = response.content.decode('utf-8')
            parsed = html.fromstring(notice)

            try:
                title = parsed.xpath(XPATH_TITLE)[0]
                title = title.replace('\"','')
                title = title.replace('#','')
                title = title.replace('|','')
                summary = parsed.xpath(XPATH_SUMMARY)[0]
                body = parsed.xpath(XPATH_BODY)
            except IndexError:
                return
            
            with open(f'{today}/{title}.txt', 'w', encoding='utf-8') as f:
                f.write(title)
                f.write('\n\n')
                f.write(summary)
                f.write('\n\n')
                for p in body:
                    f.write(p)
                    f.write('\n')
        else:
            raise ValueError(f'Error: {response.status_code}')
    except ValueError as ve:
        print(ve)

def parse_home():
    try:
        response = requests.get(HOME_URL)
        if response.status_code == 200:
            home = response.content.decode('utf-8')
            parsed = html.fromstring(home)
            links_to_notices = parsed.xpath(XPATH_LINK_TO_ARTICLE)
            # print(links_to_notices)

            today = datetime.date.today().strftime('%d-%m-%Y')
            if not os.path.isdir(today):
                os.mkdir(today)
            for link in links_to_notices:
                parse_notice(link, today)
        else:
            raise ValueError(f'Error: {response.status_code}')
    except ValueError as ve:
        print(ve)

def run():
    parse_home()

if __name__ == '__main__':
    run()


Scraper del El Espectador v.2
https://www.elespectador.com/
con imagen del articulo

import requests
import lxml.html as html
import os
import datetime

BASE_URL = 'https://www.elespectador.com'
HOME_URL = 'https://www.elespectador.com/noticias/'
XPATH_LINKS = '//h2/a/@href'
XPATH_TITLE = '//div[contains(@class,"field--name-title")][1]/h1/text()'
XPATH_RESUMEN = '//div[contains(@class,"field--name-field-teaser")][1]//p/text()'
XPATH_IMAGE = '//div[contains(@class,"file-image")]/img/@src'
XPATH_BODY = '//div[contains(@class,"content_nota")]/p/text()'


def parse_notice(link, today):
    try:
        response = requests.get(BASE_URL+link)
        if response.status_code==200:
            notice = response.content.decode('utf-8');
            parsed = html.fromstring(notice)
            try:
                title = parsed.xpath(XPATH_TITLE)[0]
                title = title.replace('\"','')
                resumen = parsed.xpath(XPATH_RESUMEN)[0]
                image = parsed.xpath(XPATH_IMAGE)[0]
                content = parsed.xpath(XPATH_BODY)
            except IndexError:
                return

            with open(f'{today}/{title}.txt','w', encoding='utf-8') as f:
                f.write(title)
                f.write('\n\n')
                f.write(resumen)
                f.write('\n\n')
                f.write(image)
                f.write('\n\n')
                for p in content:
                    f.write(p)
                    f.write('\n')
        else:
            raise ValueError(f'Error: {response.status_code}')
    except ValueError as ve:
        print(ve)


def parse_home():
    try:
        response = requests.get(HOME_URL)
        if response.status_code==200:
            home = response.content.decode('utf-8');
            parsed = html.fromstring(home)
            links = parsed.xpath(XPATH_LINKS)
            #print(links)
            today = datetime.date.today().strftime('%d-%m-%y')
            if not os.path.isdir(today):
                os.mkdir(today)
            
            for link in links:
                parse_notice(link,today)

        else:
            raise ValueError(f'Error: {response.status_code}')
    except ValueError as ve:
        print(ve)

def run():
    parse_home()

if __name__ == "__main__":
    run()


Tuve un error con los titulos que tenian “/” en una parte que decia 24/7, hice unos cambios y me funciono. Simplemente aplique replace a “/” por “-”. Les dejo mi código.

import requests
import lxml.html as html
import os
import datetime

HOME_URL = 'https://www.larepublica.co/'

XPATH_LINK_TO_ARTICLE = '//h2[@class="headline"]/a/@href'
XPATH_TITLE = '//h1[@class="headline"]/a/text()'
XPAHT_SUMMARY = '//div[@class="lead"]/p/text()'
XPAHT_BODY = '//div[@class="articleWrapper  "]/p[not(@class)]/text()'

def parse_notice(link, today):
    try:
        response = requests.get(link)
        if response.status_code == 200:
            notice = response.content.decode('utf-8')
            parsed = html.fromstring(notice)

            try:
                title = parsed.xpath(XPATH_TITLE)[0]
                title = title.replace('\"', '')
                title = title.replace('/', '-')
                summary = parsed.xpath(XPAHT_SUMMARY)[0]
                body = parsed.xpath(XPAHT_BODY)
            except IndexError:
                return

            with open(f'{today}/{title}.txt', 'w', encoding='utf-8') as f:
                f.write(title)
                f.write('\n\n')
                f.write(summary)
                f.write('\n\n')
                for p in body:
                    f.write(p)
                    f.write('\n')
        else:
            raise ValueError(f'Error: {response.status_code}')
    except ValueError as ve:
        raise(ve)


def parse_home():
    try:
        response = requests.get(HOME_URL)
        if response.status_code == 200:
            home = response.content.decode('utf-8')
            parsed = html.fromstring(home)
            links_to_noticies = parsed.xpath(XPATH_LINK_TO_ARTICLE)

            # print(links_to_noticies)

            today = datetime.date.today().strftime('%d-%m-%Y')

            if not os.path.isdir(today):
                os.mkdir(today)

            for link in links_to_noticies:
                parse_notice(link, today)
        else:
            raise ValueError(f'Error: {response.status_code}')
    except ValueError as ve:
        print(ve)


def run():
    parse_home()


if __name__ == "__main__":
    run()
Hola les comparto mi scrip para que haya algo más actual en los comentarios, ya que esto si nos ayuda para ubicarnos o encontrar errores como lo de los h2 que sustituyéndolos por text-fill nos resuelve este problema, gracias a los que nos ayudaron con ese aporte. import requestsimport lxml.html as htmlimport osimport datetime HOME\_URL = 'https://www.larepublica.co/' XPATH\_LINK\_TO\_ARTICLE = '//text-fill\[not(@class)]/a/@href'XPATH\_TITLE = '//div\[@class="mb-auto"]//span/text()'XPATH\_SUMMARY = '//div\[@class="lead"]/p/text()'XPATH\_BODY = '//div\[@class="row article-wrapper"]//p\[not(@class)]/text()' def parse\_notice(link, today):    try:        response = requests.get(link)        if response.status\_code == 200:            notice = response.content.decode('utf-8')            parsed = html.fromstring(notice)             try:                title = parsed.xpath(XPATH\_TITLE)\[0]                title = title.replace('\\"','')                summary = parsed.xpath(XPATH\_SUMMARY)\[0]                body = parsed.xpath(XPATH\_BODY)            except IndexError:                return                        with open(f'{today}/{title}.txt', 'w', encoding='utf-8') as f:                f.write(title)                f.write('\n\n')                f.write(summary)                f.write('\n\n')                for p in body:                    f.write(p)                    f.write('\n')        else:            raise ValueError(f'Error: {response.status\_code}')    except ValueError as ve:        print(ve) def parse\_home():    try:        response = requests.get(HOME\_URL)        if response.status\_code == 200:            home = response.content.decode('utf-8')            parsed = html.fromstring(home)            links\_to\_notices = parsed.xpath(XPATH\_LINK\_TO\_ARTICLE)            # print(links\_to\_notices)             today = datetime.date.today().strftime('%d-%m-%Y')            if not os.path.isdir(today):                os.mkdir(today)             for link in links\_to\_notices:                parse\_notice(link, today)        else:            raise ValueError(f'Error:{response,status\_code}')    except ValueError as ve:        print(ve) def run():    parse\_home() if \_\_name\_\_ == '\_\_main\_\_':    run()

En mi caso en algunos casos me reconoció el h2 en los títulos de la noticia. Así lo resolví.

                try:
                    title = html_Notice.xpath(xpath_Title)[0]                    
                except IndexError:
                    title = html_Notice.xpath(xpath_Title_h2)[0]

Como el titulo venia con espacios al principio y al final así lo resolvi.

title = title.replace('\"', '').strip()

.strip(), elimina espacios blancos en los textos.

Excelente curso me ha gustado mucho y he aprendido como recolectar información de internet para luego procesarla con pandas o otro método y poder sacar información valiosa.

Este es un script un poquito mas reutilizable basado en clases

import requests
import lxml.html as html
import os
import datetime

# Constantes
HOME_URL = 'https://www.larepublica.co/'
XPATH_LINK_TO_ARTICLE = '//div[@id="vue-container"]//a/@href'
XPATH_TITLE = '//h2/span/text()'
XPATH_SUMMARY = '//div[@class="lead"]/p/text()'
XPATH_BODY = '//div[@class="html-content"]/p[not(@class)]/text()'


class ScraperNewspaper:
    today = datetime.date.today().strftime('%d-%m-%Y')
    title = ''

    def write_log(self, message):
        with open('./log.txt', 'a', encoding='utf-8') as f:
            f.write(str(message))
            f.write('\n')

    def parse_notice_link(self, home_url, xpath_link_to_article):
        try:
            response = requests.get(home_url)
            if response.status_code == 200:
                home = response.content.decode('utf-8')
                parsed = html.fromstring(home)
                # open('./test.html', 'wb').write(response.content)
                links_to_notices = parsed.xpath(xpath_link_to_article)
                # print(len(links_to_notices))
                return links_to_notices
            else:
                raise ValueError(f'Error: {response.status_code}')

        except ValueError as ve:
            self.write_log(ve)
            print(ve)
            return (ve)
        except ConnectionRefusedError as cre:
            self.write_log(cre)
            print(cre)

    def len_links(self, links_to_notices):
        print(len(links_to_notices))
        return len(links_to_notices)

    def parse_notice(self, link, xpath_title, xpath_body, xpath_summary='', xpath_image=''):

        try:
            response = requests.get(link)
            if response.status_code == 200:
                notice = response.content.decode('utf-8')
                parsed = html.fromstring(notice)
                try:
                    self.title = title = parsed.xpath(xpath_title)[0]
                    title = title.replace('\"', '')
                    summary = [parsed.xpath(xpath_summary)[
                        0] if xpath_summary else '']
                    image = [parsed.xpath(xpath_image)[0]
                             if xpath_image else '']
                    body = parsed.xpath(xpath_body)
                    return title, body, summary, image
                except IndexError:
                    return "***Error***", "***Error***", "***Error***", "***Error***"

            else:
                raise ValueError(f'Error: {response.status_code}')
        except ValueError as ve:
            self.write_log(ve)
            print(ve)
            return (f"{ve}", "***Error***", "***Error***", "***Error***")
        except ConnectionRefusedError as cre:
            self.write_log(cre)
            print(cre)

    def write_notice_file(self, title, body, summary, image, today):
        try:
            title = str(title)
            summary = str(summary)
            image = str(image)
            with open(f'{today}/{title}.txt', 'w', encoding='utf-8') as f:
                f.write(title)
                if summary:
                    f.write('\n\n')
                    f.write(summary)
                if image:
                    f.write('\n\n')
                    f.write(image)
                f.write('\n\n')
                for p in body:
                    f.write(p)
                    f.write('\n')
        except Exception as te:
            self.write_log("Error de esritura"+str(te))
            print("Error de esritura"+str(te))
            return 0

    def mkdir(self, today):
        if not os.path.isdir(today):
            os.mkdir(today)

    @staticmethod
    def run():
        print('Iniciando proceso de scraping')
        scraper = ScraperNewspaper()
        links_to_notices = scraper.parse_notice_link(
            HOME_URL, XPATH_LINK_TO_ARTICLE)
        scraper.len_links(links_to_notices)
        scraper.mkdir(scraper.today)
        for link in links_to_notices:
            title, body, summary, image = scraper.parse_notice(
                link, XPATH_TITLE, XPATH_BODY, XPATH_SUMMARY)
            scraper.write_notice_file(
                title, body, summary, image, scraper.today)
        print('Proceso terminado')


if __name__ == '__main__':
    ScraperNewspaper.run()

Aca dejo mi codigo para un dettallle que no se si el profe facundo se dio de cuenta pero dentro de los body el codigo no reconoce los textos que esten modificado como en negritas esto se soluciona modificando el xpath del body y usando p.strip()

import requests
import lxml.html as html
import os
import datetime

HOME_URL = 'https://www.larepublica.co/'

XPATH_LINK_TO_ARTICLE = '//text-fill[not(@class)]/a/@href'
XPATH_TITLE = '//div/h2/span/text()'
XPATH_SUMMARY = '//div[@class="lead"]/p/text()'
XPATH_BODY = '//div[@class="html-content"]/p//text()'  # Ajuste en la expresión XPath

def parse_notice(link, today):
    try:
        response = requests.get(link)
        if response.status_code ==200:
            notice= response.content.decode('utf-8')
            parsed= html.fromstring(notice)
            
            try:
                title= parsed.xpath(XPATH_TITLE)[0]
                title= title.replace('\"','')
                summary = parsed.xpath(XPATH_SUMMARY)[0]
                body = parsed.xpath(XPATH_BODY)
            except IndexError:
                return
            
            with open(f'{today}/{title}.txt', 'w', encoding='utf-8') as f:
                f.write(title)
                f.write('\n\n')
                f.write(summary)
                f.write('\n\n')
                for p in body:
                    f.write(p.strip())  # Utilizar strip() para eliminar espacios en blanco alrededor de cada párrafo
                    f.write('\n')
        else:
            raise ValueError(f'Error:{response.status_code}')
    except ValueError as ve:
        print(ve)

def parse_home():
    try:
        response = requests.get(HOME_URL)
        if response.status_code == 200:
            home = response.content.decode('utf-8')
            parsed = html.fromstring(home)
            links_to_notices = parsed.xpath(XPATH_LINK_TO_ARTICLE)
            # print(links_to_notices)

            today = datetime.date.today().strftime('%d-%m-%Y')
            if not os.path.isdir(today):
                os.mkdir(today)

            for link in links_to_notices:
                parse_notice(link, today)
        else:
            raise ValueError(f'Error:{response.status_code}')
    except ValueError as ve:
        print(ve)

def run():
    parse_home()

if __name__ == '__main__':
    run()```

Aqui la clase es mas de Python que de webscraping, ahh! web scraping con python!!

En la linea 16 es un Error poner unicammente el link porque recordemos que esa variable es una lista de hrefs, es dicir; no es la URL completa, no entiendo como funciono entonces?

Esta es una versión del scrapper del curso pero utilizando hilos para optimizar la velocidad del programa, solo extrae links y titulo de noticias pero sin problema puede usarse para extraer más información 😃

import requests
import lxml.html as html
import os
import datetime
import threading

LINK_EXPRESSION = '//a[@class="globoeconomiaSect"]/@href'
TITLE = "//title/text()"

def save_data(link,hoy):
    try:
        response = requests.get(link)
        if response.status_code == 200:
            try:
                parsed = html.fromstring(response.content.decode('utf-8')).xpath(TITLE)[0].replace('"','').replace("'",'').replace('https://www','').replace('#','').replace('|','')
            except IndexError:
                return
            finally:
                with open(f'{hoy}/{parsed}.txt', 'w+', encoding='utf-8') as noticia:
                    noticia.write(parsed + ' ==> ' + link + '\n')
        else:
            raise ValueError(f'Ocurrió un error durante la conexión a este sitio: {response.status_code}')
    except ValueError as ve:
        print(ve)

def get_links(url):
    try:
        parse = html.fromstring(requests.get(url).content.decode('utf-8')).xpath(LINK_EXPRESSION)
        if len(parse) > 0:
            hoy = datetime.datetime.now().strftime('%d-%m-%y')
            if not os.path.isdir(hoy):
                os.mkdir(hoy)
            for link in parse:
                thread = threading.Thread(target=save_data, args=(link, hoy,))
                thread.start()
        else:
            raise ValueError(f'No se encontraron links en {url}')
    except ValueError as ve:
        print(ve)

def run():
    get_links('https://www.larepublica.co/')

run()

5/3/2023
En mi caso al ejecutar scraper me arroja un error debido a que el título contenia caracteres especiales, lo solucioné con import re quedando ese apartado del código de la siguiente forma:

        try:
            title = parsed.xpath(XPATH_TITLE)[0]
            print(title)
           ** title = re.sub(r'[^\w\s-]', '', title).strip()   
            title = re.sub(r'[-\s]+', '_', title)**
            summary = parsed.xpath(XPATH_SUMMARY)[0]
            body = parsed.xpath(XPATH_BODY)
        except IndexError:
            return

Les dejo los Xpath a diciembre del 2022:

XPATH_LINK_TO_ARTICLE = '//h2/a/@href’
XPATH_TITLE = '//div[@class= “col-8 order-2 d-flex flex-column”]/div/h2/span/text()'
XPATH_SUMMARY = '//div[@class= “lead”]/p/text()'
XPATH_BODY = ‘//div[@class= “html-content”]/p/text()’

Ojo, los Xpath son correctos pero esta el problema del h2 del titulo que no permite grabar los archivos. Veremos

Para los que les sale el siguiente error:

AttributeError: type object 'datetime.time' has no attribute 'today'

Reemplacen “time” por datetime.

Les va a quedar así:

today = datetime.datetime.today().strftime('%d,%m,%Y')

Si no les crea los archivos puede ser por el error en el titulo, a mi me sirvio poner en XPATH_TITLE = ‘//div[@class=“mb-auto”]/text-fill/span/text()’

Code on January - 2022

import requests
import lxml.html as html
import os
import datetime

HOME_URL = 'https://www.larepublica.co/'
XPATH_LINK_TO_ARTICULE = '//div[@class = "V_Title"]/text-fill/a/@href'
XPATH_TITLE = '//div[@class="mb-auto"]/text-fill/span/text()'
XPATH_SUMMARY = '//div[@class="lead"]/p/text()'
XPATH_BODY = '//div[@class="html-content"]/p[not(@class)]/text()'

def parce_notice(link, today):
    try:
        response = requests.get(link)
        if response.status_code == 200:
            notice = response.content.decode('utf-8')
            parsed = html.fromstring(notice)
            
            try:
                title = parsed.xpath(XPATH_TITLE)[0]
                title = title.replace('\"', '')
                summary = parsed.xpath(XPATH_SUMMARY)[0]
                body = parsed.xpath(XPATH_BODY)
            except IndexError:
                return
            
            with open(f'{today}/{title}.txt', 'w', encoding='utf-8') as f:
                f.write(title)
                f.write('\n\n')
                f.write(summary)
                f.write('\n\n')
                for p in body:
                    f.write(p)
                    f.write('\n')
        else:
            raise ValueError(f'Error: {response.status_code}' )
    except ValueError as ve:
        print(ve)


def parse_home():
    try: 
        response = requests.get(HOME_URL)
        if response.status_code == 200:
            home = response.content.decode('utf-8')
            parsed = html.fromstring(home)
            links_to_notices = parsed.xpath(XPATH_LINK_TO_ARTICULE)
            #print(links_to_notices)
            
            today = datetime.date.today().strftime('%d-%m-%Y')
            if not os.path.isdir(today):
                os.mkdir(today)
                
            for link in links_to_notices:
                parce_notice(link, today)
        else:
            raise ValueError(f'Error: {response.status_code}')
    except ValueError as ve:
        print(ve)
        

def run():
    parse_home()
    
if __name__ == '__main__':
    run()

👾

Hola les paso mi codigo con otra pagina de un diario de argentina que tiene la particularidad que el link donde se alojan los titulos para ingresar a la noticia esta incompleto con lo cual cree una variable para agregar ese dato a los links y poder tomar las noticias

import requests
import lxml.html as html
import os 
import datetime

from requests.models import Response



HOME_URL = 'https://www.infobae.com/'


XPATH_LINK_TO_ARTICLE = '//a[@class="cst_ctn"]/@href'
XPATH_TITLE = '//h1[@class="article-headline"]/text()'
XPATH_SUMMARY = '//h2[@class="article-subheadline"]/text()'
XPATH_BODY = '//div[@class="nd-body-article"]/p[@class="paragraph"]/text()'
root = 'https://www.infobae.com'




def parse_notice(link, today):
    try:
        response = requests.get(link)
        if response.status_code == 200:
            
            notice = response.content.decode('utf-8')
            parsed = html.fromstring(notice)

            try:
                title = parsed.xpath(XPATH_TITLE)[0]
                print(title)
                title = title.replace('\"', "") 
                title = title.replace('?', "") 
                title = title.replace('¿', "")
                summary = parsed.xpath(XPATH_SUMMARY)[0]
                body = parsed.xpath(XPATH_BODY)
            except IndexError: # puede que hay noticias que no tienen summary o algo entonces con esto salgo 
                print("as")
                return

            with open(f'{today}/{title}.txt', 'w', encoding='utf-8') as f:
                f.write(title)
                f.write('\n\n')
                f.write(summary)
                f.write('\n\n')
                for p in body:
                    f.write(p)
                    f.write('\n')
            
        else:
            raise ValueError(f'Error: {response.status_code}')
    except ValueError as ve:
        print(ve)


def parse_home():
    try:
        response = requests.get(HOME_URL)
        if response.status_code == 200: #para saber el estado de la pagina con .status_code
            home = response.content.decode('utf-8') # response.content responde el documnto html de la respuesta y decode modifica los caracteres para que no de error
            parsed = html.fromstring(home) #esta lina toma el contenido de html lo transforma en un documento especial para usar xpath  
            links_to_notices = parsed.xpath(XPATH_LINK_TO_ARTICLE) #obtiene una lista de todo el resultadod de aplicar xpath
            links_to_notices =[root + x for x in links_to_notices]
            #print(len(links_to_notices))
            #print(links_to_notices)
            today = datetime.date.today().strftime('%d-%m-%Y') #te nos trae la fecha today la de hoy, con strftime nos da el formato de como la queremos
            if not os.path.isdir(today): #estoy preguntando si no exite os.path nos trae un T o F si esta today si no esta la creamos
                os.mkdir(today)

            for link in links_to_notices: # a partit de cada link entro en cada nota y extraigo la info               
                
                parse_notice(link, today)    

        else:
            raise ValueError(f'Error: {response.status_code}')
    except ValueError as ve:
        print(ve)


def run():
    parse_home()
    

if __name__ == "__main__":
    run()

Hola a todos,

Cuando copio el código que muestra Facundo en la clase, no me crea los archivos. Coloque print en varios puntos y encontre que las expresiones title, summary y body no me arrojan ningun valor (ninguna de las tres). Les dejo el codigo para ver si ustedes pueden encontrar porque esta fallando, yo no pude encontrar la falla

import requests
import lxml.html as html
import os #sirve para crear carpetas con el dia de la fecha
import datetime # sirve para traer la fecha de hoy



HOME_URL ='https://www.larepublica.co/'
XPATH_LINK_TO_ARTICLE= '//h2[@class= "headline"]/a/@href'
XPATH_TITLE= '//div[@class= "mb-auto"]/h2/span[1]/text()'
XPATH_SUMMARY= '//div[@class="lead"]/p/text()'
XPATH_BODY= '//div[@class="html-content"]/p[not(@class)]/text()'


def parse_notice(link,today):
    
    try:
        response= requests.get(link)
        
        if response.status_code == 200:
            notice=response.content.decode('utf-8')
            parsed= html.fromstring(notice)
            
            try:
                title= parsed.xpath(XPATH_TITLE)[0]
                print(title)
                #saco las posibles comillas de las cadenas del titutlo
                title=title.replace('\"','')
                #[0] es para traer siempre el primer valor que es el que buscamos
                summary= parsed.xpath(XPATH_SUMMARY)[0]
                print(summary)
                body= parsed.xpath(XPATH_BODY)
                print(body)

            except IndexError:
                return

            #with manejador contextual, si se cierra por alguna circunstacia,
            #este pad me mantiene todo de manera segura
            
            #fx open me permite abrir archivos
            #open(nombre de archivo o ruta, tipo wrm, codificacion siemrpe utf 8)
            with open(f'{today}/{title}.txt', 'w', encoding= 'utf-8') as f:
                f.write(title)
                f.write('\n\n')
                f.write(summary)
                f.write('\n\n')
                for p in body:
                    f.write(p)
                    f.write('\n')
        else:
            #raise para lanzar errores
            raise ValueError(f'Error: {response.status_code}')
    
    except ValueError as ve:
        print(ve)


def parse_home():
    #salvamos los errores que van a ocurrir
    try:
        #traigo la pagina que yo quiero, en este caso la republica
        response= requests.get(HOME_URL)
        #print(response) 200 then we are going well
        #if status_code(look status code in lesson 1 PLATZI 200 ----> ok)
        if response.status_code == 200:
            #solo traigo los links, response.content= devuelve el html
            #decode es un metodo que ayuda a trasnformar caracteres especiales en algo para python
            home= response.content.decode('utf-8')
            #tomamos el html y lo llevamos a un doc especial para hacer xpath
            parsed= html.fromstring(home)
            links_to_notices= parsed.xpath(XPATH_LINK_TO_ARTICLE)
            #print(links_to_notices)

            #datetime para fechas, datye para traer fechas,today
            #strftime convierte en cadena de caracteres
            today=datetime.date.today().strftime('%d-%m-%Y')
           #os.paths.isdir trae true or false dependiendo si en la arpeta que estoy existe o no
            if not os.path.isdir(today):
                os.mkdir(today)
            
            for link in links_to_notices:
                parse_notice(link,today)
                

        else:
            #raise= para elevar un error
            raise ValueError(f'Error: {response.status_code}')

    except ValueError as ve:
        print(ve)
        

def run():
    parse_home()


if __name__ == '__main__':
    run()

Que curso más hermoso, gracias Facundo.

Les comparto mi scraper, yo lo hice con el sitio de noticias el País México. Le puse los nombres a los archivos como los maneja el link, porque había muchos problemas con los títulos de las noticias:

import requests
import lxml.html as html
import os
import datetime
import re

# LINK DE LA PÁGINA
HOME_URL = "https://elpais.com"
MEXICO = "/mexico"

"""
Códigos XPath
"""
XPATH_LINK_TO_ARTICLE = '//h2[@class="c_h headline | color_gray_ultra_dark ' + \
       'font_secondary width_full  headline_md c_h__md "]/a/@href'
XPATH_TITLE = '//h1[@class="a_t | font_secondary color_gray_ultra_dark "]'+ \
       '/text()'
XPATH_SUMMARY = '//h2[@class="a_st font_secondary color_gray_dark "]/text()'
XPATH_BODY = '//div[@class="a_b article_body | color_gray_dark"]/p/text()'
XPATH_DATE = '//a[@class="a_ti"]/text()'
XPATH_PLACE = '//span[@class="a_pl | capitalize  color_black"]/text()'

def extract_name_from_link(link):
    result = re.search(r'\d+/(.*)+.html', link)
    string2 = result.group()
    string2 = string2[3:]
    string2 = string2[:-5]
    return string2

#Función para sacar noticias.
def parse_notice(link, today):
    try:
        response = requests.get(link)
        if response.status_code == 200:
            notice = response.content.decode('utf-8')
            parsed = html.fromstring(notice)

            try:
                title = parsed.xpath(XPATH_TITLE)[0]
                title = title.replace('\"', '')
                title = title.replace('|', '')
                summary = parsed.xpath(XPATH_SUMMARY)[0]
                body = parsed.xpath(XPATH_BODY)
                date = parsed.xpath(XPATH_DATE)[0]
                place = parsed.xpath(XPATH_PLACE)[0]
            except IndexError:
                return

            with open(f'data/{today}/{extract_name_from_link(link)}.txt', 'w', encoding='utf-8') as f:
                f.write(title)
                f.write('\n\n')
                f.write(date)
                f.write('\n\n')
                f.write(place)
                f.write('\n\n')
                f.write(summary)
                f.write('\n\n')
                for p in body:
                    f.write(p)
                    f.write('\n\n')

        else:
            raise ValueError(f'Error: {response.status_code}')

    except ValueError as ve:
        print(ve)


#Función para traer links.
def parse_home():
    try:
        response = requests.get(HOME_URL + MEXICO)
        if response.status_code == 200:
            home = response.content.decode('utf-8')
            parsed = html.fromstring(home)
            links_to_notices = parsed.xpath(XPATH_LINK_TO_ARTICLE)
            #print(links_to_notices)

            #Fecha en de hoy en strings
            today = datetime.date.today().strftime('%d-%m-%Y')
            #Para ver si la carpeta exista.
            if not os.path.isdir(today):
                os.mkdir('data/' + today)
            
            for link in links_to_notices:
                if link.startswith('https'):
                    parse_notice(link, today)
                else:
                    parse_notice(HOME_URL + link, today)

        
        else:
            raise ValueError(f'Error: {response.status_code}')
    except ValueError as ve:
        print(ve)



def run():
    parse_home()  

if __name__ == '__main__':
    run()



A dia de hoy use estos xpath para conseguir 41 noticias sustanciales

XPATH_LINK_TO_ARTICLE = (
    '//div/a[contains(@href, "www.larepublica.co")]/@href'
)
XPATH_TITLE = '//div[@class="mb-auto"]//a/text()'
XPATH_SUMMARY = '//div[@class="lead"]/p/text()'
XPATH_BODY = '//div[@class="html-content"]/p[not(@class)]/text()'```

![](

hola, a mi no me genera los archivos, solo crea la carpeta, alguien podria ayudarme?

Por si les salen links duplicados en su scrapeo, encontré una forma de quitar los repetidos utilizando numpy (np), se me hizo una solucion rápida y sencilla para no batallar si scrapean de más jaja

import numpy as np
lista_sin_repetidos = np.unique(lista_de_elementos_scrapeados)

Yo no pude utilizar el diario la república pero use uno de mi país y me paso algo bien chistoso. Seguí todos los pasos y me lanzaba error la consola y al final después de una hora me di cuenta que era porque en una noticia habían símbolos que no reconocía como el «» al final con un title.replace se soluciono XD

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import requests
import lxml.html as html
import os
import datetime

HOME_URL = 'https://www.lacuarta.com/'

XPATH_LINK_TO_ARTICLE = '//figcaption[@class="m-top-15"]/h4/a/@href'

XPATH_TITTLE = '//div[@class="main-wrapper"]/h1/text()'

XPATH_SUMMARY = '//h2[@class="text-center"]/text()'

XPATH_BODY = '//div[@class="col-md-11 col-lg-11 col-md-offset-1 col-lg-offset-1 nota-interior-tx p-top-30"]/p[not(@class)]/text()'


def parse_notice(link, today):
    try:
        response = requests.get(link)
        if response.status_code == 200:
            notice = response.content.decode('utf-8')

            parsed = html.fromstring(notice)

            try:
                title = parsed.xpath(XPATH_TITTLE)[0]
                title = title.replace('\"','')
                title = title.replace('«','')
                title = title.replace('»','')
                title = title.replace(':','')
                summary = parsed.xpath(XPATH_SUMMARY)[0]
                body = parsed.xpath(XPATH_BODY)

            except IndexError:
                return

            with open(f'{today}/{title}.txt', 'w', encoding='utf-8') as f:
                f.write(title)
                f.write('\n\n')
                f.write(summary)
                f.write('\n\n')

                for p in body:
                    f.write(p)
                    f.write('\n')

        else:
            raise ValueError(f'Error: {response.status_code}')
                
    except ValueError as ve:
        print(ve)


def parse_home():
    try:
        response = requests.get(HOME_URL)
        if response.status_code == 200:
            home = response.content.decode('utf-8')
            parsed = html.fromstring(home)
            links_to_notices = parsed.xpath(XPATH_LINK_TO_ARTICLE)
            #print(links_to_notices)

            today = datetime.date.today().strftime('%d-%m-%Y')

            if not os.path.isdir(today):
                os.mkdir(today)

            for link in links_to_notices:

                parse_notice(link, today)
            

        else:
            raise ValueError(f'Error: {response.status_code}')
    except ValueError as ve:
        print(ve)



def run():
    parse_home()


if __name__ == '__main__':
    run()

Que increíble, cada punto del código explicado muy bien.

Creo que algo que le vendria super bien al proyecto seria usar threads para aumentar el performance :’)

alguien sabe como arreglar el “UnboundLocalError: local variable ‘title’ referenced before assignment”? :c

SI lo hago con Mac, ¿Cómo sería el with open? ya que f’ no lo toma

En Perú

import requests
import lxml.html as html 
import os, datetime


HOME_URL = 'https://www.larepublica.pe/'

XPATH_LINK_TO_LAST_NEWS = '//div[@class="wrapper_items"]/ul/li/a/@href'
XPATH_TITLE = '//h1[@class="DefaultTitle"]/text()'
XPATH_SUMMARY = '//h2[@class="DefaultSubtitle"]/text()'
XPATH_BODY = '//div[@class="page-internal-content"]/section/p/text()'


def parse_notice(link, today):
    try:
        response = requests.get(HOME_URL + link)
        if response.status_code == 200:
            notice = response.content.decode('utf-8')
            parsed = html.fromstring(notice)
            try:
                title = parsed.xpath(XPATH_TITLE)[0]
                title = title.replace('\"','')
                summary = parsed.xpath(XPATH_SUMMARY)[0]
                body = parsed.xpath(XPATH_BODY)
            except IndexError as ie:
                return
            with open(f'{today}/{title}.txt', 'w', encoding='utf-8') as f:
                f.write(title)
                f.write('\n\n')
                f.write(summary)
                f.write('\n\n')
                for p in body:
                    f.write(p)
                    f.write('\n')
        else:
            pass
    except ValueError as ve:
        pass

def parse_home():
    try:
        response = requests.get(HOME_URL)
        if response.status_code == 200:
            home = response.content.decode('utf-8')
            parsed = html.fromstring(home)
            links_to_notices = parsed.xpath(XPATH_LINK_TO_LAST_NEWS)
            # for i in links_to_notices:
            #     print(i)
            today = datetime.date.today().strftime('%d-%m-%Y')
            if not os.path.isdir(today):
                os.mkdir(today)
            for link in links_to_notices:
                parse_notice(link, today)
        else:
            raise ValueError(f'Error: {response.status_code}')
    except ValueError as ve:
        print(ve)

def run():
    parse_home()

if __name__ == "__main__":
    run()

Increible lo que se puede hacer.

import requests
import lxml.html as html
import os
import datetime

HOME_URL = 'https://www.larepublica.co/'

XPATH_LINK_TO_ARTICLE = '//h2[@class="headline"]/a/@href'
XPATH_TITLE = '//h1[@class="headline"]/a/text()'
XPATH_SUMMARY = '//div[@class="lead"]/p/text()'
XPATH_BODY = '//div[@class="articleWrapper  "]/p[not(@class)]/text()'

def parse_notice(link, today):
    try:
        response = requests.get(link)
        if response.status_code == 200:
            notice = response.content.decode('utf-8')
            parsed = html.fromstring(notice)

            try:
                title = parsed.xpath(XPATH_TITLE)[0]
                title = title.replace('\"', '')
                summary = parsed.xpath(XPATH_SUMMARY)[0]
                body = parsed.xpath(XPATH_BODY)
            except IndexError:
                return 

            with open(f'{today}/{title}.txt', 'w', encoding='utf-8') as f:
                f.write(title)
                f.write('\n\n')
                f.write(summary)
                f.write('\n\n')
                for p in body:
                    f.write(p)
                    f.write('\n')

        else:
            raise ValueError(f'Error: {response.status_code}')
    except ValueError as ve:
        print(ve)

def parse_home():
    try:
        response = requests.get(HOME_URL)
        if response.status_code == 200:
            home = response.content.decode('utf-8')
            parsed = html.fromstring(home)
            links_to_notices = parsed.xpath(XPATH_LINK_TO_ARTICLE) 
            # print(links_to_notices)

            today = datetime.date.today().strftime('%d-%m-%Y')
            if not os.path.isdir(today):
                os.mkdir(today)

            for link in links_to_notices:
                parse_notice(link, today)

        else:
            raise ValueError(f'Erro: {response.status_code}')
    except ValueError as ve:
        print(ve)

def run():
    parse_home()

if __name__ == '__main__':
    run()```

Scraper del El Espectador v.1
https://www.elespectador.com/

import requests
import lxml.html as html
import os
import datetime

BASE_URL = 'https://www.elespectador.com'
HOME_URL = 'https://www.elespectador.com/noticias/'
XPATH_LINKS = '//h2/a/@href'
XPATH_TITLE = '//div[contains(@class,"field--name-title")][1]/h1/text()'
XPATH_BODY = '//div[contains(@class,"field--name-field-teaser")][1]//p/text()'


def parse_notice(link, today):
    try:
        response = requests.get(BASE_URL+link)
        if response.status_code==200:
            notice = response.content.decode('utf-8');
            parsed = html.fromstring(notice)
            try:
                title = parsed.xpath(XPATH_TITLE)[0]
                title = title.replace('\"','')
                content = parsed.xpath(XPATH_BODY)[0]
            except IndexError:
                return

            with open(f'{today}/{title}.txt','w', encoding='utf-8') as f:
                f.write(title)
                f.write('\n\n')
                f.write(content)
                f.write('\n\n')
        else:
            raise ValueError(f'Error: {response.status_code}')
    except ValueError as ve:
        print(ve)


def parse_home():
    try:
        response = requests.get(HOME_URL)
        if response.status_code==200:
            home = response.content.decode('utf-8');
            parsed = html.fromstring(home)
            links = parsed.xpath(XPATH_LINKS)
            #print(links)
            today = datetime.date.today().strftime('%d-%m-%y')
            if not os.path.isdir(today):
                os.mkdir(today)
            
            for link in links:
                parse_notice(link,today)

        else:
            raise ValueError(f'Error: {response.status_code}')
    except ValueError as ve:
        print(ve)

def run():
    parse_home()

if __name__ == "__main__":
    run()

Espectacular este curso

Fenomeno !! crack

![](

La consola me deja el siguiente error: ![](

Hice pequeñas modificaciones.
Cambiar la ruta para guardar los .txt dentro de la carpeta data. Para que el .gitignore pueda ignorar todo el contenido scrapeado.
Una función para hacer el response al parse, para no repetir código, ya que en ambas funciones se escribe dos veces buena parte del código.

Pueden haber más cosas por hacer, creo que lo tomaré como base para un NLP, ya de ahí le hago más mejoras. Excelente curso.

Este es mi código:
https://github.com/israelyance/scraper-xpath-larepublica.co

Actualmente la pagina cambio su estructura.

XPATH_LINK_TO_ARTICLE = '//h2[not(@class)]/a/@href' 
XPATH_TITLE_ARTICLE = '//h2[not(@class)]/a/text()' 
XPATH_SUMMARY_ARTICLE = '//div[@class="lead"]/p/text()' 
XPATH_BODY_ARTICLE = '//div[@class="html-content "]/p[not(@class)]/text()'

esas modificaciones tuve que hacer para por lo menos crear unas noticias
si haces un print en esta parte del código

for link in links_to_notice: 
    parse_notice(link, today) 
    #print(f'fecha: {today} link: {link}')

y compruebas la estructura en los links que imprime sabrás como reformular los xpath para que no ingrese al return y solo cree la carpeta con la fecha

Saludos

No puedo obtener los titulos, el xpath funciona perfectamente en la consola de chrome pero el programa no lo trae y tampoco arroja ningun error, se termina de ejecutar y me devuelve la carpeta con la fecha vacia, les dejo mi codigo por si me pueden ayudar.

import requests
import lxml.html as html
import os
import datetime

HOME_URL = 'https://www.larepublica.co/'
XPATH_LINK_TO_ARTICLE = '//div/h2/a/@href'
XPATH_TITLE = '//div[@class="mb-auto"]/h2/a/text()'
XPATH_SUMMARY = '//div[@class="lead"]/p/text()'
XPATH_BODY = '//div[@class="html-content"]/p[not(@class)]/text()'


def parse_new(link, today):
    try:
        response = requests.get(link)
        if response.status_code == 200:
            new = response.content.decode('utf-8')
            parsed2 = html.fromstring(new)

            try:
                title = parsed2.xpath(XPATH_TITLE)[0]
                
                title = title.replace('\"', '')
                title = title.replace('?', '')
                title = title.replace('¿', '')
                title = title.replace('!', '')
                title = title.replace('¡', '')
                title = title.replace(':', '')
                title = title.replace('"', '')
                summary = parsed2.xpath(XPATH_SUMMARY)[0]
                body = parsed2.xpath(XPATH_BODY)
            except IndexError:
                return

            with open(f'.{today}/{title}.txt', 'w', encoding='utf-8') as f:
                f.write(title)
                f.write('\n\n')
                f.write(summary)
                f.write('\n\n')
                for p in body:
                    f.write(p)
                    f.write('\n')


        else:
            raise ValueError(f'Error: {response.status_code}')
    except ValueError as ve:
        print(ve)


def parse_home():
    try:
        response = requests.get(HOME_URL)
        if response.status_code == 200:

            home = response.content.decode('utf-8')
            parsed = html.fromstring(home)
            links_to_news = parsed.xpath(XPATH_LINK_TO_ARTICLE)

            today = datetime.date.today().strftime('%d-%m-%Y')
            if not os.path.isdir(today): #pregunto si ya existe una carpeta con la fecha de hoy
                os.mkdir(today)
            
            for link in links_to_news:
                parse_new(link, today)

        else:
            raise ValueError(f'Error: {response.status_code}')
            
    except ValueError as ve:
        print(ve)



def run():
    parse_home()

if __name__ == "__main__":
    run()```

que práctica tan buena , el secreto está eb sacar bien las expresiones 😀

Me encantó todo lo que aprendí en el curso, es súper útil 🙌 :hea

Pregunta 2 del archivo de Notion.

Les comparto mi codigo para la pagina de infobae.

import requests
import lxml.html as html
import os
import datetime
import time

HOME_URL = 'https://www.infobae.com'
XPATH_LINK_TO_ARTICLE = '//a[@data-pb-field="headlines.basic"]/@href'
XPATH_TITLE = '//div[@class="row"]/header/h1/text()'
XPATH_SUMMARY = '//div[@class="row"]/header/span[@class="subheadline"]/text()'
XPATH_BODY = '//div[@id="article-content"]/div[@class="row pb-content-type-text"]//text()'


def parse_notice(link, today):
    '''Funcion que guarda los titulos, subtitulos y cuerpos de una noticia en un archivo'''
    try:
        response = requests.get(HOME_URL + link)
        if response.status_code == 200:
            notice = response.content.decode('utf-8')
            parsed = html.fromstring(notice)

            try:
                title = parsed.xpath(XPATH_TITLE)[0]
                title = title.replace('\"', '')
                title = title.replace('?', '')
                title = title.replace('¿', '')
                title = title.replace('!', '')
                title = title.replace('¡', '')
                title = title.replace(':', '')
                title = title.replace('"', '')
                summary = parsed.xpath(XPATH_SUMMARY)[0]
                body = parsed.xpath(XPATH_BODY)
                for p in body :
                    p = p.strip(' \n')
            except IndexError:
                return
            
            with open(f'{today}/{title}.txt', 'w', encoding='utf-8') as f:
                f.write(title)
                f.write('\n\n')
                f.write(summary)
                f.write('\n\n')
                for p in body:
                    if not p.isspace():
                        f.write(p)
                        if p.endswith('.') or p.endswith(':'):
                            f.write('\n')
        else:
            raise ValueError(f'Error: {response.status_code}')
    except ValueError as ve:
        print(ve)
    except requests.exceptions.ConnectionError:
        time.sleep(0.1)



def parse_home():
    try:
        response = requests.get(HOME_URL)
        if response.status_code == 200:
            '''Traemos el documento html de la pagina como tipo string'''
            home = response.content.decode('utf-8')
            '''Convertimos el documento string a un documento tipo html al cual poder realizarle xpath'''
            parsed = html.fromstring(home)
            '''Creo una lista de links hacia las noticias'''
            links_to_notices = parsed.xpath(XPATH_LINK_TO_ARTICLE)
            '''Creamos una carpeta donde guardar las noticias de la fecha'''
            today = datetime.date.today().strftime('%d-%m-%Y')
            if not os.path.isdir(today):
                os.mkdir(today)
            for link in links_to_notices:
                parse_notice(link, today)
        else:
            raise ValueError(f'Error: {response.status_code}')
    except ValueError as ve:
        print(ve)

def run():
    parse_home()

if __name__ == "__main__":
    run()

A fecha 01-05-21 tuve varios problemas para hacer que el script funcione, pero con la ayuda de varios aportes he logrado conseguir generar los archivos.

Comparto el script por si a alguien le sirve. Disculpen los comentarios , no se nada de phyton, aprendi lo basico para este curso.

Facundo tiene un nivel de programación increíble. Muy buen curso. También gracias a la comunidad de Platzi por compartir la solución a ciertos errores 😃

Buen día
Veo que todos tuvimos el inconveniente al utilizar el tag h2 y fue cambiando por text-fill.

El día que realice esta practica tuve el inconveniente que había una noticia con un titular largo y a la hora de crear el archivo tenia un error. Por lo que corte el title y lo coloque para que tuviera un tamaño máximo de 80 caracteres.

#Extraer el titulo, el resumen y el cuerpo
title = parsed.xpath(XPATH_TITLE)[0]
                
#Eliminar las comillas de los titulos y acortar los titulos
title = title.replace('\"','')
title = title[0:80]
summary = parsed.xpath(XPATH_SUMMARY)[0]
body = parsed.xpath(XPATH_BODY)

Hola alguno me podria ayudar, saben si hay una forma de traer no los links de hoy sino todos los historicos de este mismo periódico ??

Termine el proyecto 😄

Le añadí unos fstring para darle un pequeño formato a los documentos txt.

Ahora que tenemos los links de las noticias vamos a crear un script con la finalidad de ir a cada link y extraer el titulo, el resumen y el cuerpo. Para ello necesitaremos dos librerias que vienen instaladas en el core de Python, OS y datetima. El modulo OS lo vamos a utilizar para crear una carpeta con la fecha de hoy, mientras que datetime lo vamos a utilizar para traer la fecha de hoy.

El paso numero 1 consiste en crear una variable que contenga el nombre de la carpeta donde vamos a guardar todas las noticias, es decir, la fecha de hoy. Para ello usamos el metodo today() de la funcion date del modulo datetime, nos quedaria algo tal que asi: datetime.data.today(). Esto nos trae la fecha que quedara guardado en un objeto, sin embargo, nosotros no queremos un objeto, nosotros queremos uns string con el formato (dia/mes/año), es por ello que añadimos: strftime(’%d-%m.%Y), y si lo juntamos todo queda esto:

today = datetime.date.today().strftime('%d-%m-&Y') 

y cuadno lo añadimos a la funcion parse_home() queda esto:

import requests
from lxml import html 
import os 
import datetime

HOME_URL = 'https://www.larepublica.co'

XPATH_LINK_TO_ARTICLE = '//div[@class="V_Title"]/text-fill/a/@href'
XPATH_LINK_TO_TITLE = '//div[@class="mb-auto"]/text-fill/span/text()'
XPATH_LINK_TO_SUMMARY = '//div[@class="lead"]/p/text()'
XPATH_LINK_TO_BODY = '//div[@class="html-content"]/p[not(@class)]/text()'

def pearse_home():
    
    try:
      response = requests.get(HOME_URL)
      if response.status_code == 200:
        home = response.content.decode('rtf-8')

        parsed = html.fromstring(home)

        links_to_notices = parsed.xpath(XPATH_LINK_TO_ARTICLE)
      else:
        raise ValueError(f'ERROR: {response.status_code}')

    except ValueError as ve:
      print(ve)
    today = datetime.date.today().strftime('%d-%m-%y')

def run():
  pearse_home()

if __name__ == '__main__':
  run()

El segundo paso consiste en crear una carpeta que tenga como nombre la fecha de hoy simepre y cuando esta carpeta no exista ya. Para conseguir esto crearemos un if statement que creara la carpeta solo si esta no existe ya y ejecutara una funcion que nos permitira acceder a cada noticia y extraer el titulo, resumen y cuerpo.

Nuestro if statement quedara asi:

#os.path.isdir(today) nos retorna un booleano cuyo valor dependera de si hay o no una carpeta que tenga como nombre la fecha de hoy
if not os.path.isdir(today):
  #en caso de que dicha carpeta no exista, crearemos una
  os.makdir(today)

  #ahora vamos a ejecutar una funcion que nos permite obtener el titulo, el resumen y el cuerpo de cada noticia, para despues guardarlo en la carpeta con el nombre de la fecha de hoy.
  for link in links_to_notices:
    parsed_notice(link, today) 

Una vez juntamos esto con el paso anterior nos queda lo siguiente:

import requests
from lxml import html 
import os 
import datetime

HOME_URL = 'https://www.larepublica.co'

XPATH_LINK_TO_ARTICLE = '//div[@class="V_Title"]/text-fill/a/@href'
XPATH_LINK_TO_TITLE = '//div[@class="mb-auto"]/text-fill/span/text()'
XPATH_LINK_TO_SUMMARY = '//div[@class="lead"]/p/text()'
XPATH_LINK_TO_BODY = '//div[@class="html-content"]/p[not(@class)]/text()'

def pearse_home():
    
    try:
      response = requests.get(HOME_URL)
      if response.status_code == 200:
        home = response.content.decode('rtf-8')

        parsed = html.fromstring(home)

        links_to_notices = parsed.xpath(XPATH_LINK_TO_ARTICLE)
      else:
        raise ValueError(f'ERROR: {response.status_code}')

    except ValueError as ve:
      print(ve)
    today = datetime.date.today().strftime('%d-%m-%y')

    if not os.path.isdir(today):
      os.mkdir(today)

      for link in links_to_notices:
        parsed_notice(link, today)

def run():
  pearse_home()

if __name__ == '__main__':
  run()

Genial, ahora en el tercer paso solo nos falta crear la funcion parsed_notice() para obtener la informacion que queriamos. Dentro de esta funcion vamos a crear un bloque try para protegernos en caso de que cuando hagomos la peticion al servidor este nos devuelva un status_code distinto a 200 (no todo ok).

#la funcion recibe como parametros el link a cada noticia y la fehca de hoy para poder guardar lo informacion extraida la carpeta que tiene ese nombre.
def parsed_notice(link, today):
try:
  #solicito respuesta al link de la noticia
  response = request.get(link)
  if resonse.status_code == 200:
    pass
  else:
    raise ValueError(f'ERROR: {response.status_code}')
except valueError as ve:
  print(ve)

Una vez ya hemos protegido nuestro codigo nos falta desarrollar la logica del programa si la solicitud al servidor no nos da ningun error:

def parsed_notice(link, today):
try:
  response = request.get(link)
  if resonse.status_code == 200:
    #convertimos el contenido de la noticia en un lenguaje que python pueda enternder
    notice = response.content.decode('utf-8)
    #lo convertimos a un html con superpoderes en el que podamos usar expresiones XPath
    parsed = html.fromstring(notice)
  else:
    raise ValueError(f'ERROR: {response.status_code}')
except valueError as ve:
  print(ve)

Como ya tenemos el contenido de una peticion al servidor en un html en el que podemos aplicar XPath tenemos que tener lo siguiente en cuenta: cuando aplico XPath a este html con superpoderes(parsed) es una lista de elementos. Entonces, cuadno aplico XPath a ese documento para obtener el titulo, el resultado es una lista cuyo primer elemento es el titulo (index=0). Lo mismo con el resumen, cuando aplico XPath me devuleve una lista cuyo primer elemento es el resumen (index=0), sin embargo, no todas las noticias tienen resumen, con lo cual, puede que me de un IndexError. Para prevenir esto uso un bloque try. Finalmente, con el cuerpo no usare indices puesto que el cuerpo esta constituido por varios parrafos y yo los quiero a todos. La funcion quedaria asi:

def parsed_notice(link, today):
try:
  response = request.get(link)
  if resonse.status_code == 200:
    notice = response.content.decode('utf-8)
    parsed = html.fromstring(notice)
    try:
      title = parsed.xpath(XPATH_TO_TITLE)[0]
      #el nombre del archivo que contenga la noticia va a tener como nombre el titulo, con lo cual, si el titulo de alguna noticia tiene comillas windows no nos dejara poner ese nombre. Es por debemos eliminar las noticias. En esta pregunta del foro se explica mejor porque elminamos las comillas https://platzi.com/comentario/2198698/
      title = title.replace('\"','')
      summary = parsed.xpath(XPATH_TO_SUMMARY)[0]
      body = parsed.xpath(XPATH_TO_BODY)
    except IndexError:#en caso de que halla error retornamos la funcion
      return 
  else:
    raise ValueError(f'ERROR: {response.status_code}')
except valueError as ve:
  print(ve)

Ahora lo que tenemos que hacer es guardar toda la informacion que hemos obtenido y guardarla en un archivo de texto, para ello hacemos lo siguiente:

def parsed_notice(link, today):
try:
  response = request.get(link)
  if resonse.status_code == 200:
    notice = response.content.decode('utf-8)
    parsed = html.fromstring(notice)
    try:
      title = parsed.xpath(XPATH_TO_TITLE)[0]
      title = title.replace('\"','')
      summary = parsed.xpath(XPATH_TO_SUMMARY)[0]
      body = parsed.xpath(XPATH_TO_BODY)
    except IndexError:
      return
    #En esta line de codigo lo que hacemos es abrir la carpeta que tiene como nombre la fecha de hoy y creamos un documento con el nombre del titulo de cada noticia. 'w' indica que estamos en modo escritura y encoding convierte el arhcivo en algo que python pueda entender. Finalmente,  todo eso lo guardamos con el nombre f
    with open(f'{today}/{title}.txt', 'w', encoding='utf-8') as f: 
      #ahora lo que tenemos que hacer es en el archivo de texto que acabamos de crear escribir el titulo, el resumen y el cuerpo
      #escribimos el titulo
      f.write(title)
      f.write('\n\n')
      f.write(summary)
      f.write('\n\n')
      #como el body esta formado por distintos parrafos necesito un loop para escribir todos los parrafos
      for parrafo in body:
        f.write(parrafo)
        f.write('\n\n')
  else:
    raise ValueError(f'ERROR: {response.status_code}')
except valueError as ve:
  print(ve)

El script final queda asi:

import requests
from lxml import html 
import os 
import datetime

HOME_URL = 'https://www.larepublica.co'

XPATH_LINK_TO_ARTICLE = '//div[@class="V_Title"]/text-fill/a/@href'
XPATH_LINK_TO_TITLE = '//div[@class="mb-auto"]/text-fill/span/text()'
XPATH_LINK_TO_SUMMARY = '//div[@class="lead"]/p/text()'
XPATH_LINK_TO_BODY = '//div[@class="html-content"]/p[not(@class)]/text()'


def parsed_notice(link, today):
try:
  response = request.get(link)
  if resonse.status_code == 200:
    notice = response.content.decode('utf-8)
    parsed = html.fromstring(notice)
    try:
      title = parsed.xpath(XPATH_TO_TITLE)[0]
      title = title.replace('\"','')
      summary = parsed.xpath(XPATH_TO_SUMMARY)[0]
      body = parsed.xpath(XPATH_TO_BODY)
    except IndexError:
      return
    with open(f'{today}/{title}.txt', 'w', encoding='utf-8') as f: 
      f.write(title)
      f.write('\n\n')
      f.write(summary)
      f.write('\n\n')
      for parrafo in body:
        f.write(parrafo)
        f.write('\n\n')
  else:
    raise ValueError(f'ERROR: {response.status_code}')
except valueError as ve:
  print(ve)


def pearse_home():
    
    try:
      response = requests.get(HOME_URL)
      if response.status_code == 200:
        home = response.content.decode('rtf-8')

        parsed = html.fromstring(home)

        links_to_notices = parsed.xpath(XPATH_LINK_TO_ARTICLE)
      else:
        raise ValueError(f'ERROR: {response.status_code}')

    except ValueError as ve:
      print(ve)
    today = datetime.date.today().strftime('%d-%m-%y')

    if not os.path.isdir(today):
      os.mkdir(today)

      for link in links_to_notices:
        parsed_notice(link, today)

def run():
  pearse_home()

if __name__ == '__main__':
  run()

La recomendación es hacer print() para ir debuggeando los paths que buscamos, ya que cambia el nombre de algunas etiquetas cuando vemos el código html en la terminal, así que es mejor corroborar si la etiqueta que encontramos con el inspector de Chrome es la misma que aparece en la terminal. En este link se explica mas a detalle. Click aquí

Gracias

Podemos obtener la fecha así también

today = datetime.now().strftime('%d-%m-%Y')

💡No olviden enviarlo a GitHub y mostrarlo como una habilidad más.

Amigos, a alguien más le aparece el siguiente error:
no module named: "requests"
aún ya habiendo instalado el módulo?
Para verificar las librerías instaladas las verifico con el comando: pip list y en efecto me aparece requests instalado. Lo mismo me pasa con lxml.
A la hora de correr el código me crea el directorio pero no me crea ningún archivo.
Ya probé aparte el código de crear y escribir archivos y lo ejecuto perfectamente.

Tuve también el problema de las H2 y lista vacía, ademas de algunos caracteres especiales dejo mi solución

import requests
import lxml.html as html
import os
import datetime

HOME_URL = 'https://www.larepublica.co/'

XPATH_LINK_TO_ARTICLE = '//div[@class="V_Title"]/a/@href'
XPATH_TITLE = '//div[@class="mb-auto"]/text-fill/a/text()'
XPATH_SUMMARY = '//div[@class="lead"]/p/text()'
XPATH_BODY = '//div[@class="html-content"]/p[not(@class)]/text()'


def parse_notice(link, today):

    try:
        response=requests.get(link)
        if response.status_code==200:
            # html from notice 
            notice = response.content.decode('utf-8')
            parsed = html.fromstring(notice)
            try:
                title = parsed.xpath(XPATH_TITLE)[0]
                title = title.replace('\n','').replace('?','').replace('/"','').replace('¿','').replace('/','').replace('\"','')
                summary = parsed.xpath(XPATH_SUMMARY)[0]
                body = parsed.xpath(XPATH_BODY)
            except IndexError as ev:
                print(f'ERR:{ev}')
                return

            with  open(f'{today}/{title}.txt','w',encoding='utf-8') as f:
                f.write(title)
                f.write('\n\n')
                f.write(summary)
                f.write('\n\n')
                for p in body:
                    f.write(p)
                    f.write('\n')
        else:
            raise ValueError(f'ERROR: {response.status_code}')
    except ValueError as ve:
        print(f'ERROR {ve}')

def parse_home():
    try:
        response = requests.get(HOME_URL)
        if(response.status_code==200):
            home = response.content.decode('utf-8')
            # convert response to xpath can undertand
            parsed  = html.fromstring(home)
            links_to_notices = parsed.xpath(XPATH_LINK_TO_ARTICLE)
            # print(links_to_notices)

            today  = datetime.date.today().strftime('%d-%m-%Y')
            #If not exist a folder with the name Today
            if not os.path.isdir(today):
                os.mkdir(today)
            for link in links_to_notices:
                parse_notice(link,today)

        else:
            raise ValueError(f'ERROR:{response.status_code}')
    except ValueError as ve:
        print(f'ERROR:{ve}')

def run():
    parse_home()

if __name__=="__main__":
    run()

Vamos!!

import requests
import lxml.html as html
import os #Create directory for saving
import datetime


HOME_URL = 'https://www.larepublica.co/'

XPATH_LINK_TO_ARTICLE = '//div[@class="news V_Title_Img"]/a/@href'
XPATH_TITLE = '//h1[@class="globoeconomiaSect"]/i/text()'
XPATH_SUMMARY= '//div[@class="lead"]/p/text()'
XPATH_DESCRIPTION = '//div[@class="html-content"]/p[not(@class)]/text()'


def parse_notice(link,today):
  try:
    response = requests.get(link)
    if response.status_code ==200:
      notice = response.content.decode('utf-8') 
      parsed= html.fromstring(notice)

      try:
        title = parsed.xpath(XPATH_TITLE)[0]
        title= title.replace('\"','') #DELETE COMILLAS SIMPLES
        summary = parsed.xpath(XPATH_SUMMARY)[0]
        description = parsed.xpath(XPATH_DESCRIPTION)

      except IndexError:
        return 

      with open(f'{today}/{title}.txt', 'w', encoding='utf-8') as f:
        f.write(title)
        f.write('\n\n')
        f.write(summary)
        f.write('\n\n')
        for p in description:
          f.write(p)
          f.write('\n')


    else:
      raise ValueError(f'Error:{response.status_code}')
  except ValueError as ve:
    print(ve)


def parse_home():
  try:
    response = requests.get(HOME_URL)
    if response.status_code ==200:
      home = response.content.decode("utf-8") #Python can read and save
      parsed = html.fromstring(home) # transform in a special document where i can use xpath
      links_to_notices = parsed.xpath(XPATH_LINK_TO_ARTICLE)
      # print(links_to_notices)

      today = datetime.date.today().strftime('%d-%m-%Y')

      if not os.path.isdir(today): #If it does not exist a dir with today
        os.mkdir(today)

      for link in links_to_notices:
        parse_notice(link,today) #Each link i will execute a function that will take information such as title, summary, description.

    else: 
      raise ValueError(f"Error: {response.status_code}")
  except ValueError as ve:
    print(ve)

def run():
  parse_home()


if __name__ == "__main__":
    run()

¡Buen curso! Falto que se guardara en una base de datos y esta se actualizara cada día con nuevas noticias en un sitio web.