Aún no tienes acceso a esta clase

Crea una cuenta y continúa viendo este curso

Persistiendo la información "scrapeada"

20/38

Aportes 88

Preguntas 19

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad? Crea una cuenta o inicia sesión.

Lo bueno: Me funcionó!
Lo malo: Realmente esta difícil, ni sé cómo salió

Con el pasar del curso vi muchos comentarios de personas que no pudieron avanzar el curso pues no pudieron generar su csv, por lo que dejo mi repositorio en gihub con los archivos .csv que están en esta carpeta.

Espero que les sirva y que no se hayan rendido en este punto del curso.
¡Ánimo, pandas es más fácil!

En nuevo titulo " Persistiendo hasta lograrlo"
Es mi 5ta vez que intento todo el procedimiento y al fin lo he logrado. No te desanimes si no lo puedes lograr, intentalo, ve los comentarios de otros companeros que de seguro tuvieron tu mismo problema.
La sensacion es realmente satisfactoria.

Hunieras abierto el csv para ver que realmente te ha funcionado

¿a alguien más le crea el archivo csv solo con estos datos (body,title) en la fila A1?

Eso me pone

Que importante ha sido los comentarios de ustedes. Es muy gratificante que se enriquezca esta comunidad. Este modulo fue complejo, pero gracias a sus comentarios he podido entender este poder en Python. Muchas gracias!!!

Trabajando con Windows10, utilizo el prompt de anaconda y , codeo con VSC y siguiendo las instrucciones de la clase, realice la practica, pero la gran ayuda la encuentro en los los aportes y comentarios que hacen los compañeros de curso, alli encuentro solucion a inconvenientes (por incompatibilidad o semantica que tengo en mi codigo). porque al no estar familiarizado con la programacion encuentro unos temas de dificil seguimiento y conceptos nuevos. Pero, repaso los videos para asimilar,… con motivacion para seguir adelante.

Fue particularmente complicado de seguir este modulo, raro en David 😦

Ayuda, alguien sabe el error

import argparse
import logging
import re
import datetime
import csv
logging.basicConfig(level=logging.INFO)

from requests.exceptions import  HTTPError
from urllib3.exceptions import MaxRetryError
import news_page_objects as news
from common import config

logger = logging.getLogger(__name__)
is_well_formed_url = re.compile(r'^https?://.+/.+$') # https://example.com/hello ()
is_root_path = re.compile(r'^/.+$') # /some-text



def _build_links(host, link):
    """Se hace una reconstrucción de los links, ya que aveces llegan relativos"""
    if is_well_formed_url.match(link):
        return link
    elif is_root_path.match(link):
        return f'{host}{link}'
    else:
        return f'{host}/{link}'



def _fetch_article(new_site_uid, host, link):
    """ Se comienza a screpear los articulos, y a mirar si tienen cuerpo"""

    logger.info('Empezar buscando artículo en {}'.format(link))

    article = None
    try:
        article = news.ArticlePage(new_site_uid, _build_links(host, link))
    except (HTTPError, MaxRetryError) as e:
        logger.warning('Error mientras buscaba el articulo', exc_info=False)

    if article and not article.body:
        logger.warning('Articulo invalido, no hay articulo')
        return None

    return article



def _news_scraper(new_site_uid):
    """Aquí inicia el scraper, trayendo las url"""
    host = config()['new_sites'][new_site_uid]['url']

    logging.info(' Iniciando scraper para {}'.format(host))
    logging.info('Finding links in homepage...')

    homepage = news.HomePage(new_site_uid, host)

    articles=[]
    for link in homepage.article_links:
        article = _fetch_article(new_site_uid, host, link)


        if article: # si exisite
            logger.info('Se logró')
            articles.append(article)
            print(article.title)

    _save_articles(new_site_uid, articles)

def _save_articles(new_site_uid, articles):
    """se guarda el articulo, con la fecha que si hizo el scraping """

    now = datetime.datetime.now()

    csv_headers = list(filter(lambda property: not property.startswith('_'), dir(articles[0])))
    out_file_name =f'{new_site_uid}_{now.srtftime("%y_%m_%d")}_aticles.csv'
     # csv_headers=['body', 'title']

    with open(out_file_name, mode='w+') as f:
        writer = csv.writer(f)
        writer.writerow(csv_headers)

        for article in articles:
            row = [str(getattr(article, prop)) for prop in csv_headers]
            writer.writerow(row)




if __name__ == '__main__':
    parse = argparse.ArgumentParser()

    news_site_choice = list(config()['new_sites'].keys())
    parse.add_argument('nuevo_sitio',
                        help = 'El sitio que quieres scrapear',
                        type = str,
                        choices = news_site_choice)
    arg = parse.parse_args()
    _news_scraper(arg.nuevo_sitio)```


csv_headers = list(filter(lambda property: not property.startswith(’_’), dir(articles[0])))
IndexError: list index out of range```

Me sucedia que me estaba trayendo solo el primer parrafo de cada articulo, modifique la clase que lee el body así:

    @property
    def body(self):
        result = self._select(self._queries['article_body'])

        texto = " "
        if len(result)>0:
            for i in result:
                texto += " " + i.text        
        return texto

Hola, a todos los que les aparece el archivo CSV creado en blanco, quitenle el BREAK que le coloco el instructor y ejecuten su programa, se demora un par de minutos, y a la final obtendrán el dataset.

Desde la terminal puedes utilizar cat para ver el archivo o si solo quieres ver la estructura y unas cuantas filas utiliza head(-n # El numero de filas que deseas ver)

head -n 5 eluniversal_2018_11_04_articles.csv```

el programa funciona, pero me arroja este error despues de buscar varios links

('Received response with content-encoding: gzip, but failed to decode it.', error('Error -3 while decompressing data: incorrect header check'))

Hola.
Les comparto el proyecto tal cual lo tengo hasta el momento, dentro de mi GitHub.
Hay razón en decir que es complicado, yo todavía no termino de comprender todo lo que he realizado jeje, pero esto sirve para dos cosas:

  1. Tener algo funcional para los siguientes pasos del curso
  2. Regresar cuantas veces sea necesario y todo lo escrito se comprenda

Ojalá les sirva.

Saludos!

https://tuit.es/dpnH9

Un proyecto personal que hice con lo aprendido hasta el momento (Web Scrapping desde el celular!!!):
https://www.youtube.com/watch?v=VAYsMyKsP88&feature=youtu.be

Este curso vuela mente, que increíble crear mi primer dataset

Se logra el ejercicio. Después de creado el archivo .csv, se deben organizar los datos en Excel para poderlos visualizar. También se pueden visualizar en un archivo de texto .txt.

Los datos se guardan en orden alfabético? body, title, com podria cambiarlos a title, body

Excelente, ahora a analizar

La verdad este modulo es de los dificiles, aqui quisiera preguntar si ¿es mejor usar las etiquetas CSS para ubicar la informacion o si es mejor con Xpath?
honestamente despues de hacer los cursos con xpath se me hace mucho mas sencillo de esa manera. Obviamente David es un master en html por que ahi empezo por eso usa las etiquetas CSS. Ustedes que prefieren???

Vaya, hasta uno en las dichosas noticias se encuentra hasta con hipervínculos encapsulados en eqtiquetas, que son me imagino los comerciales o anuncions en el universal. Es otra cosa a tener en cuenta cuando se escrapea o rasga un sitio de noticias. Como manejar toda esa compleja estrucura de diseño interna la cual uno tiene que averiguar y no creo que un simple archivo YAML (para mi ya obsoleto) pueda con toda la complejidad. Casi que termina uno escribiendo un WebScraper para cada página a rasgar. Y hasta ahora me di cuenta de eso! A arreglar mas código!

Este proceso de scranping si que ha resultado un desafío para mi, el problema que tengo es que al hacer el proceso final con “eluniversal” no tengo problema me genera el scraping y el archivo csv, pero con las otras webs que estoy utilizando me sale el error** IndexError: list index out of range**

les copio pantallazo del error

este es el mismo proceso con la página de el tiempo

pero con el universal si funciona sin problema, alguien del grupo o de Platzi podría ayudarme por favor a identificar que ajustes debería realizar, estos son los códigos de mis archivos

archivo common.py

import yaml

__config = None

def config():
    """
    """

    global __config

    if not __config:
        with open('config.yaml', mode='r') as f:
            __config = yaml.safe_load(f)

    return __config

archivo config.yaml

news_sites:
   eluniversal:
     url: http://www.eluniversal.com.mx
     queries:
        homepage_article_links: '.field-content a'   
        article_body: '.field-name-body'
        article_title: '.pane-content h1'
   
   elpais:
     url: https://elpais.com
     queries:
        homepage_article_links: '.headline_md a'
        article_body: '.articulo-cuerpo'
        article_title: '.articulo-titulo'

   eltiempo:
     url: https://eltiempo.com
     queries:
        homepage_article_links: '.title-container a' 
        article_body: '.field-name-body'
        article_title: '.pane-content h1'
  

archivo news_page_objects.py

import bs4
import requests

from common import config 


class NewsPage:

    def __init__(self, news_site_uid, url):
        self._config = config() ['news_sites'][news_site_uid]
        self._queries = self._config['queries']
        self._html = None
        self._url = url
        self._visit(url)


    def _select(self, query_string):
        return self._html.select(query_string)

    def _visit(self, url):
        response = requests.get(url)

        response.raise_for_status()

        self._html = bs4.BeautifulSoup(response.text, 'html.parser')

class HomePage(NewsPage):

    def __init__(self, news_site_uid, url):
       super().__init__(news_site_uid, url)

    @property
    def article_links(self):
        link_list = []
        for link in self._select(self._queries['homepage_article_links']):
            if link and link.has_attr('href'):
                link_list.append(link)

        return set(link['href'] for link in link_list)

class ArticlePage(NewsPage):

    def __init__(self, news_site_uid, url):
       super().__init__(news_site_uid, url)

    @property
    def body(self):
        result = self._select(self._queries['article_body'])

        return result[0].text if len(result) else ''
    

    @property
    def title(self):
        result = self._select(self._queries['article_title'])
        return result[0].text if len(result) else ''

  
    @property
    def url(self):
        return self._url

     

archivo main.py


import argparse
import logging
import csv
import datetime

logging.basicConfig(level=logging.INFO)
import re

from requests.exceptions import HTTPError

from urllib3.exceptions import MaxRetryError

import news_page_objects as news
from common import config

is_well_formed_url= re.compile(r'^https?://.+/.+$') # i.e. https://www.somesite.com/something
is_root_path = re.compile(r'^/.+$') # i.e. /some-text
logger = logging.getLogger(__name__)


def _news_scraper(news_site_uid):
    host = config()['news_sites'][news_site_uid]['url']

    logging.info('Beginning scraper for {}'.format(host))
    logging.info('Finding links in homepage...')

    homepage = news.HomePage(news_site_uid, host)

    articles = []

    for link in homepage.article_links:
        article = _fetch_article(news_site_uid, host, link)

        if article:
            logger.info('Article fetched!')
            articles.append(article)

        _save_articles(news_site_uid, articles)
#creamos una funcion auxiliar que va a guardar los articulos y la llamaremos _save_articles y la crearemos con 2 parametros




#definimos la funcion para guardar los articulos


def _save_articles(news_site_uid, articles):

#utilizaremos un nuevo modulo datetime para saber cuando hacemos el scraping, se debe importar inicialmente datetime asi como csv 

    now = datetime.datetime.now().strftime('%Y_%m_%d')
   
#creamos el nombre del archivo que se llamara "out_file_name" definimos la estructura del nombre despues del =

    out_file_name = '{news_site_uid}_{datetime}_articles.csv'.format(
        news_site_uid=news_site_uid,
        datetime=now)

#vamos a generar los headers del cvs para lo cual se declara la variable, cambio el codigo del profesor ya que me genera error lo dejo en las notas de onenote

    csv_headers = list(filter(lambda property: not property.startswith('_'), dir(articles[0])))


#ahora vamos a guardar los articulos, en el modo le colocamos escritura y si no existe que se cree con el +, y lo colocamos como file "as f"

    with open (out_file_name, mode = 'w+', encoding = "utf-8") as f: #? Crea el archivo.
        writer = csv.writer(f)     
        writer.writerow(csv_headers) #? Escribe las cabeceras de los archivos.

#guardar todos nuestros articulos

        for article in articles:
            row = [str(getattr(article, prop)) for prop in csv_headers]
            writer.writerow(row)


       

def _fetch_article(news_site_uid, host, link):
    logger.info('Start fetching article at {}'.format(link))

    article = None
    try:
        article = news.ArticlePage(news_site_uid, _build_link(host, link))
    except (HTTPError, MaxRetryError) as e:
        logger.warning('Error while fetching article!', exc_info=False)

    if article and not article.body:
        logger.warning('Invalid article. There is no body.')
        return None

    return article


def _build_link(host, link):
    if is_well_formed_url.match(link):
        return link
    elif is_root_path.match(link):
        return'{}{}'.format(host, link)
    else:
        return'{host}/{uri}'.format(host=host, uri=link)


if __name__ == '__main__':
    parser = argparse.ArgumentParser()

    news_site_choices = list(config()['news_sites'].keys())
    parser.add_argument('news_site',
                        help='The news site that you want to scrape',
                        type=str,
                        choices=news_site_choices)

    args = parser.parse_args()
    _news_scraper(args.news_site)


Agradezco la retroalimentación y ayuda

Falta el archivo main en los archivos… ¿por qué esta incompleto y por qué son diferentes los archivos que suben a lo que se ve en la clase? no logro comprender eso

Aqui les comparto la Estructura de ‘EL TIEMPO’ para que tambien la puedan Scrapear!
eltiempo:
url: https://www.eltiempo.com
queries:
homepage_article_links: '.title-container a’
article_body: '.contenido’
article_title: ‘.titulo’

csv_headers = list(filter(
    lambda x: not x.startswith('_'), dir(articles[0])
))

No debería utilizar la palabra reservada property

Excelenta clase, vamos dandole en W10

El tema ya esta en alto nivel, se usa mac y es posible realizarlo en linux por su similitud. Para usuarios de windows tienen que buscar las equivalencias de los comandos porque sino les van a surgir demasiados errores.-

Revisando videos posteriores noté que aquí no nos muestran en el video cuando guardan la url del articulo tambíen, les sugiero que revisen el código news_page_objects.py del compañero @Eduardo y prestenle atención a la linea 10 y al final, más adelante vamos a usar también la url

por favor quisiera entender todo lo que hizo David en estos módulos para scrapear. Que cursos debo tomar a parte de python para entender lo que hizo . Todos esos términos me parecen extraños y seguro eventualmente lo sabre pero quisiera acelerar eso con sus recomendaciones .

Soy yo, o el codigo que escribe esta cambiado levemente en los archivos de descarga.

Hola, así lo hice para el caso de ELTIEMPO:

Pero solamente me trae el primer párrafo que encuentra y ya ¿Alguien sabe cómo solucionarlo?

Se genera un archivo CSV con solamente el header (body, title, url). No se supone que tiene que llenarlo con los datos Scrapeados de la pagina web?

Comparto los apuntes y el proyecto de este curso.

https://github.com/francomanca93/ingenieria-de-datos/tree/master

Si a laguien le salión problema de encoding…

deben pasar el encoding

with open(out_file_name, mode='w+', encoding='utf-8') as f:

tus muertos

Se me estaba presentando el siguiente error

UnicodeEncodeError: 'charmap' codec can't encode characters

para solucionarlo se debe usar encoding="utf-8" en la función open() así:

with open(out_file_name, mode='w+', encoding="utf-8") as f:

David, es la tercera vez que veo este curso, y viejo una chimba, muchas gracias.

Buen curso estimado David! Ingeniería del Software aplicado a la ciencia de datos

No es meramente programación.









David se ve que es buenísimo codiando, pero como profesor no es muy bueno además es cansado ver su interfaz (y no pone un workflow universal) y no se entiende muy bien de lo que se esta haciendo y eso que acabo de tomar cómo 5 cursos de scrapear .

Mientras realizaba la prueba con la web del pais ocurrió una falla de decodificacion en el método _visit debido a que la pagina venia comprimida con gzip y estaba mal formateada. Para solucionar implementé un try/except:

Hola,

al inicio David había dicho que con requests no se podía manejar javascript. Aparte de requests he usado selenium para extraer información de la web. Sin embargo, en un scraper que estoy tratando de implementar sale la siguiente estructura: javascript:__doPostBack(’ ‘,’ '): cómo la podría manejar. Muchas gracias a David o cualquiera de la comunidad que me pueda ayudar.

He trabajado Web Scraping en Jupyter notebook haciendo uso de las librerías de python (request,bs4) y he guardado la información en un dataframes de pandas lo que me ha parecido magnifico , la pregunta es :
¿Que hacer cuando realizo la petición a una pagina web y esta me retorna la información encriptada ?

Yo tengo un problema.
En la consola me imprime este error:

requests.exceptions.MissingSchema: Invalid URL '/autor-opinion/articulistas/irene-tello-arista': No schema supplied. Perhaps you meant http:///autor-opinion/articulistas/irene-tello-arista?

Me imagino que es por el / al principio de la URL pero no es si lo que está mal es el código o qué onda. Les comparto mi código:

import yaml
import argparse
import logging
import news_page_objects as news
import re
from requests.exceptions import HTTPError
import datetime
import csv
from urllib3.exceptions import MaxRetryError

logging.basicConfig(level=logging.INFO)

from common import config

logger = logging.getLogger(__name__)
is_well_formed_link = re.compile(r'^htts?://.+/.+$') # regular expression
is_root_path = re.compile(r'^/.+$') # regular expression

def _news_scraper(news_site_uid):
    host = config()['news_sites'][news_site_uid]['url']

    logging.info('Begging scrapper for {}'.format(host))
    homepage = news.HomePage(news_site_uid, host)

    articles = []
    for link in homepage.article_links:
        article = _fetch_article(news_site_uid, host, link)

    if article:
        logger.info('Article fetched!!!')
        articles.append(article)

    _save_article(news_site_uid, articles)


def _fetch_article(news_site_uid, host, link):
    logger.info('Start fetching article at {}'.format(link))
    article = None

    try:
        article = news.ArticlePage(news_site_uid, _build_link(host, link))
    except (HTTPError, MaxRetryError) as e:
        logger.warning('Error while fetching the article', exc_info=False)

    if article and not article.body:
        logger.warning('Invalid article, There is no body')
        return None

    return article


def _build_link(host, link):
    if is_well_formed_link:
        return link
    elif is_root_path:
        return '{}{}'.format(host, link)
    else:
        return '{host}/{uri}'.format(host=host, uri=link)


def _save_article(news_site_uid, articles):
    now = datetime.now().strftime('%Y_%b_%dth')
    out_file_name = '{news_site_uid}_{datetime}_articles.csv'.format(
        news_site_uid=news_site_uid,
        datetime=now)
    csv_headers = list(filter(lambda property: not property.startswiht('_'), dir(articles[0])))

    with open('out_file_name', mode='w') as f:
        writer = csv.writer()
        writer.writerow(csv_headers)

    for article in articles:
        row = [str(getattr(article, pop)) for prop in csv_headers]
        witer.writerow(row)

if __name__ == '__main__':
    parser = argparse.ArgumentParser()

    news_sites_choices = list(config()['news_sites'].keys())
    parser.add_argument(
                        'news_site',
                        help='The news site that you wanna add',
                        type=str,
                        choices=news_sites_choices)

    args = parser.parse_args()
    _news_scraper(args.news_site)

Hola buen día, tengo un problema al intentar correr python main.py eluniversal

Me sale este error y no me guarda el archivo .csv

File “/home/david/anaconda3/lib/python3.7/site-packages/requests/sessions.py”, line 165, in resolve_redirects
raise TooManyRedirects(‘Exceeded %s redirects.’ % self.max_redirects, response=resp)
requests.exceptions.TooManyRedirects: Exceeded 30 redirects.

no me genera el archivo csv

Si alguien le sirve comparto codigo y si desean contribuyen

Al probar con otro site, me arroja el error:

In [1]: %run main.py globovision
INFO:root:Beginning Scraper for https://www.globovision.com
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
~\Documents\PAM\development\dengi\main.py in <module>
    100
    101     args = parser.parse_args()
--> 102     _news_scraper(args.new_site)

~\Documents\PAM\development\dengi\main.py in _news_scraper(news_site_uid)
     41             break
     42
---> 43     _save_articles(news_site_uid, articles)
     44
     45 def _save_articles(news_site_uid, articles):

~\Documents\PAM\development\dengi\main.py in _save_articles(news_site_uid, articles)
     49         datetime = now
     50     )
---> 51     csv_headers = list(filter(lambda property: not property.startswith('_'), dir(articles[0])))
     52
     53     with open (out_file_name, mode='w+') as f:

IndexError: list index out of range

Ayuda!

Me funcionó.
Entiendo el concepto general y todo lo de Python, me pierdo en las librerías nuevas, me tocara revisar la documentación. Pasemos al siguiente nivel.

Interesante. Complicado pero es cuestión de tiempo y practica para aplicar todo lo aprendido. Sin embargo, nos abre muchas posibilidades.

El segmento de código

dir(articles[0])

returna la siguiente lista:

['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__',   '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_config', '_html', '_queries', '_select', '_url', '_visit', 'body', 'title', 'url'] 

Uff esto me saco canas, revisar línea por línea y realizar prints por todos lados para verificar el funcionamiento de cada función. pero lo logré !!

No sé lo que estuve leyendo pero estuvo interesante c:

Hola, estuvo como media hora diciendo Article fetched!!, pero luego dio el error UnicodeEncodeError: ‘charmap’ codec can’t encode character ‘\u2764’ in position 2120: character maps to <undefined> y cuando fui a ver el csv, sólo me guardó 17 noticias.

Muy buena practica

interesante!

Excelente clase.

(platzi_data) D:\Dropbox\Curso de Ingeniería de Datos con Python\web_scrapper>conda run python main.py eluniversal
D:\Dropbox\Curso de Ingeniería de Datos con Python\web_scrapper\common.py:11: YAMLLoadWarning: calling yaml.load() without Loader=... is deprecated, as the default Loader is unsafe. Please read https://msg.pyyaml.org/load for full details.
  __config = yaml.load(f)
INFO:root:Beginning scraper for http://www.eluniversal.com.mx
INFO:__main__:Start fetching article at /espectaculos/no-hay-riesgo-de-contagio-con-omar-fayad-derbez
INFO:__main__:Article fetched!!```

Super, todo OK

La verdad no comprendí muy bien el concepto de Headers para crear el CSV , el archivo que resulta tampoco es que sea claro de comprender, no se si es por las paginas web que use, pero me queda mucho más legible el código de guardar así:

    with open(out_file_name, mode='w+') as f:
        writer=csv.writer(f)
        for article in articles:
            body=article.body.strip()
            title=article.title.strip()            
            rows = [[title],[body]]
            writer.writerows(rows)

Explicación de la función getattr:

Get a named attribute from an object; getattr(x, 'y') is equivalent to x.y. When a default argument is given, it is returned when the attribute doesn't exist; without it, an exception is raised in that case.

interesante lo que se obtuvo como resultado final, lo intente con tres paginas diferentes y resultados distintos, esto nos lleva analizar muy bien el código para saber con exactitud la informacion a extraer, como veo se debe profundizar con el curso de Web scraping de Platzi para dominar bien este tema.

Muy buena esta fase del curso! Gracias!

Hola! a dia de hoy 04/05/2020 el scrapper me esta funcionando pero no me esta guardando los datos en un csv, alguien sabe que podria estar pasando?, les dejo el codigo.

import argparse
import datetime
import csv
import logging
import re
logging.basicConfig(level=logging.INFO)

from requests.exceptions import HTTPError
from urllib3.exceptions import MaxRetryError

import news_page_objects as news
from common import config

logger = logging.getLogger(__name__)
is_well_formed_link = re.compile(r'^https?://.+/.+$')
is_root_path = re.compile(r'^/.+$')

def _news_scraper(news_site_uid):
    host = config()['news_sites'][news_site_uid]['url']

    logging.info('Beggining scrapper for {}'.format(host))
    homepage = news.HomePage(news_site_uid, host)

    articles=[]
    for link in homepage.article_links:
        #print(link)
        article = _fetch_article(news_site_uid, host, link)
        if article:
            logger.info('Article fetched')
            articles.append(article)
            #break
            print(article.title)

    print(len(articles))

#""" #Inicio Codigo ADGR
    _saved_articles(news_site_uid, articles)

def _saved_articles(news_site_uid, articles):
    now = datetime.datetime.now().strftime('%Y_%m_%d')
    out_filename = '{news_site_uid}_{datetime}_articles.csv'.format(
        news_site_uid=news_site_uid, 
        datetime = now)
    csv_headers = list(filter(lambda property: not property.startswith('_'), dir(articles[0]))) #Filtro que devuelve un iterador
    
    with open(out_filename, mode ='w+', encoding="utf-8") as f:
        writer = csv.writer(f)
        writer.writerow(csv_headers)

        for article in articles:
            row = [str(getattr(article, prop)) for prop in csv_headers]
            writer.writerow(row)
#"""#Fin de codigo ADGR


def _fetch_article(news_site_uid, host, link):
    logger.info('Start fetching article at {}'.format(link))

    article = None
    try:
        article = news.ArticlePage(news_site_uid, _build_link(host, link))
    except(HTTPError, MaxRetryError) as e:
        logger.warning('Error while fetching the article', exc_info=False)

    if article and not article.body:
        logger.warning('Invalid article, There is no body')
        return None

    return article

def _build_link(host, link):
    if is_well_formed_link.match(link):
        return link
    elif is_root_path.match(link):
        return'{}{}'.format(host, link)
    else:
        return '{host}/{url}'.format(host = host, url=link)


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    
    news_site_choices = list(config()['news_sites'].keys()) #Devuelve un iterador 
    
    parser.add_argument('news_sites',
                        help = 'The news site that you want to scrape',
                        type=str,
                        choices=news_site_choices)

    args = parser.parse_args()
    _news_scraper(args.news_sites)

Estoy usando una hp con windows 8.1 y tuve algunos problemas, así que les indico lo que hice para ejecutarlo bajo windows 8.1, espero funcione para cualquier windows.

1.- Primero instalé python version 3.7.7
2.- Instalé anaconca para version 3.7
3.- Al final de instalar anaconda abrí mi consola de windows con cmd
4.- Buscar la ubicación de un subdirectorio Anaconda3 en mi caso lo puso en C:\ProgramData\Anaconda3 ir con comandos msdos cd C:\ProgramData\Anaconda3\Scripts
5.- ejecutar el archivo por lotes activate.bat (solo escribe activate.bat y enter), te debe aparecer algo como (base) C:\ProgramData\Anaconda3\Scripts>

A partir de ahí, te puedes ir a tu ruta de trabajo (con el comando cd) y ejecutar python main.py eluniversal

Para agregar la url al archivo csv
Hay que modificar el archivo news_page_objects.py
En la clase NewsPage en def init agregar

         self._url = url

En la clase ArticlePage
Agregar la apropiedad url

    @property
    def url(self):
        return self._url

el CSV que se crea me sale en blanco, osea, se crea pero en blanco sin ninguna URL, eso tiene que ser asi o tiene que guardar el unico link que aparece en e, CMD?

Buenas tardes me ayudan por favor llevo varias horas no logro ver el error, en main.py me arroja el siguiente error

File “main.py”, line 87, in <module>
_news_scraper(args.news_site)
File “main.py”, line 40, in _news_scraper
_save_articles(news_site_uid, articles)
File “main.py”, line 45, in save_articles
csv_headers = list(filter(lambda property: not property.startswith(’
’), dir(articles[0])))
IndexError: list index out of range

aqui esta mi codigo
main.py

<import argparse
import logging
import csv
import datetime
logging.basicConfig(level=logging.INFO)
import re
from requests.exceptions import HTTPError
from urllib3.exceptions import MaxRetryError
import news_page_objects as news
from common import config


logger = logging.getLogger(__name__)
is_well_formed_link= re.compile(r'^https?://.+/+$')
is_root_path = re.compile(r'^/.+$')

def _news_scraper(news_site_uid):
          host = config()['news_sites'][news_site_uid]['url']

          articles =[] 
          logging.info('Beginning scraper for {}'.format(host))
          logging.info('Finding links in homePage...')
          homepage= news.HomePage(news_site_uid, host)

          i = 0
               
          for link in homepage.article_links:
             article = _fetch_article(news_site_uid, host, link)

             if article:
                logger.info('article fetched!')
                articles.append(article)
                break
                print (article.title)

          print (len(articles))
             


          _save_articles(news_site_uid, articles)

def _save_articles(news_site_uid, articles):
            now=datetime.datetime.now().strftime('%Y_%m_%d')
            
            csv_headers =  list(filter(lambda property: not property.startswith('_'), dir(articles[0])))
            out_file_name = '{news_site_uid}_{datetime}_aricles.csv'.format(news_site_uid=news_site_uid,datetime=now)           

            with open (out_file_name, mode ='w+') as f:
                writer = csv.writer(f)
                writer.writerow(csv_headers)

                for article in articles:
                    row =[str(getattr(article, prop)) for prop in csv_headers]
                    writer.writerow

def _fetch_article(news_site_uid, host , link):
            logger.info('start fetching article at {}'. format (link))

            article = None
            try:
                article = news.ArticlePage (news_site_uid, _build_link(host,link))
            except (HTTPError, MaxRetryError) as e:
                if article and not article.body:
                    logger.warning ('invalid article. there is no body')
                    return None
                return article

def _build_link(host, link):
   if is_well_formed_link.match(link):
         return link
   elif is_root_path.match(link):
         return '{}{}'. format(host, link)
   else :
        return '{host}/{url}'.format (host = host, url=link)


if __name__ == '__main__':

    parser = argparse.ArgumentParser()
    news_site_choices = list(config()['news_sites'].keys())
    parser.add_argument('news_site',
                        help = ' The news site that you want to scrape',
                        type = str ,
                        choices=news_site_choices)

    args = parser.parse_args()
    _news_scraper(args.news_site)

>

`< pages objects>




```
<import bs4
import requests

from common import config
class NewsPage:

  
  
  def __init__(self,news_site_uid,url):
      self._config = config()['news_sites'][news_site_uid]
      self._queries = self._config['queries']
      self._html =None
      self._url = url

class HomePage (NewsPage):

  def __init__(self, news_site_uid, url):
      super().__init__(news_site_uid,url)

      self._visit(url)

  def _select(self, query_string):
      return self._html.select(query_string)

  def _visit (self,url):
      response = requests.get(url)

      response.raise_for_status()

      self._html = bs4.BeautifulSoup(response.text,'html.parser')


  @property
  def article_links(self):
      link_list = []
      for link in self._select(self._queries['homepage_article_links']):
          if link and link.has_attr('href'):
              link_list.append(link)

      return  set (link['href'] for link in link_list)


class ArticlePage(NewsPage):
  
      def __init__(self, news_site_uid, url):
          super().__init__(news_site_uid,url)


      @property
      def url (self):
        return self._url

      @property
      def body(self):
        result = self._select(self._queries['article_body'])

        texto = " "
        if len(result)>0:
            for i in result:
                texto += " " + i.text        
        return texto
        
      @property
      def title(self):
         result = self._select(selft._queries['article_title'])

         return result[0].text if len (result) else ''

>
```

```

Hola, durante el transcurso de este curso he visto muchos comentarios de personas que han tenido problemas con algunos comandos en windows ya que David esta trabajando en mac.

Les recomiendo que trabajen en una terminal basada en unix como Git Bash. Es mucho mas amigable que el cmd de windows y pueden trabajar tranquilamente con todos los comandos que usa David en el curso como vim, touch, etc…

Inclusive también puede bajar un ambiente de ubuntu de la tienda de windows…

Les recomiendo los cursos de terminal y de git de platzi para profundizar en esto.

Saludos

Me acabo de dar cuenta que muchas de las URLs no sirven porque se elaboran mal. Habrá que ver que pasa con los parsers que tenemos porque la verdad no funcionan bien del todo.

Se logro el ejercicio para los dos diarios, inicialmente el data set parecia vacio pero como dicen otros compañeros la info si esta, yo uso excel, y cuando cambie el color de la letra a negro aparecieron los articulos, pero eso si llegan con muchos espacios, apesar de que coloque como dicen otros compañeros el encoding utf 8 en el page object

a mi me parece que aunque se entiende el procedimiento y la lógica … la codificación es la que me da duro… se que se debe repetir las clases e investigar por aparte… pero si se ve que se necesita una base mejor para este curso…

Paso 5: guardando la info en disco.

1- se crea la funcion _save_articles, es para guardar los documentos en el csv.

2- se importa csv y datetime.

3- se codifica para la manipulacion de archivos.

import argparse
import logging
import news_page_objects as news
import re
import datetime     #se importa este modulo de fechas
import csv          #se importa csv para guardar la informacion
from requests.exceptions import HTTPError
from config_yaml import config

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def _news_scraper(news_site_uid):
    host = config()['news_sites'][news_site_uid]['url']
    logging.info('Comenzanco el scraping en: {}'.format(host))

    homepage = news.HomePage(news_site_uid, host)

    articles = []   #lista de articulos que se encontrarán

    for link in homepage.article_links:
        article = _fetch_article(news_site_uid, host, link)

        if article: #se ejecuta si es que hay un articulo
            logger.info('se encontro el articulo')
            articles.append(article)
            print('titulo = {}'.format(article.title))
            break

    _save_articles(news_site_uid, articles)                 #se inicializa esta funcion que servirá de guardado

    print('cantidad de articulos es de : {}'.format(len(article)))

def _save_articles(news_site_uid, articles):                #guardará en un archivo la informacion
    now = datetime.date.now().strftime('%Y_%m_%d')          #se inicializa el datetime, y se le da formato con string format time
    out_file_name = '{}_{}_articles.csv'.format(news_site_uid, now)
    csv_headers = list(filter(lambda property: not property.startswith('_'), dir(articles[0])))  #se filtra la informacion mediante la funcion filter de dir
    with open(out_file_name, mode= 'w+') as f:                          #se emplea codificacion de manipulacion de archivos
        writer= csv.writer(f)                                             #se inicializa un writer
        writer.writerow(csv_headers)                                     #se pide que escriba en la columna del archivo csv

        for article in articles:
            row = [str(getattr(article, prop))for prop in csv_headers] #row almacena los atributos de article y prop que se encuentran en csv_headers, getattr = es la forma de obtener atributos de una funcion
            writer.writerow(row)                                       #funcion para escribir el row en cada casilla en el archivo csv que retornará

def _fetch_article(news_site_uid, host, link):
    logger.info('esta buscando el archivo en {}'.format(link))
    article = None

    try:
        article = news.ArticlePage(news_site_uid, _build_link(host, link))
    except (HTTPError) as e:
        logger.warning('Error mientras se buscaba el articulo', exc_info= False)

    if article and not article.body:
        logger.warning('articulo invalido, no hay cuerpo en el articulo')
        return None

    return article

is_well_formed_link = re.compile(r'^https?://.+/.+$')
is_root_path = re.compile(r'^/.+$')

def _build_link(host, link):
    if is_well_formed_link.match(link):
        return link
    elif is_root_path.match(link):
        return '{}/{}'.format(host, link)
    else:
        return '{}/{}'.format(host, link)

if __name__ == '__main__':
    parser = argparse.ArgumentParser('siempre debe iniciar el parse así')
    news_site_choices = list(config()['news_sites'].keys())
    parser.add_argument('news_site',
                        help= 'Escoge el sitio el cual quieres buscar',
                        type= str,
                        choices= news_site_choices)

    arg = parser.parse_args()
    _news_scraper(arg.news_site)

La razon por la que los archivos quedan con campos en blanco es que los class del css cambian constantemente y es más, ya el archivo .YAML no sirve por que los class ya tienen espacios. Lo que se debe hacer es utilizar una libreria mas avanzada que BS4 o programaticamente arreglar el código. En mi caso debi leer mucho la documentación de BS4 para lograr que solo funcionara con eluniversal. Yo basicamente extraigo el titulo y el texto del articulo y nada mas, ya limpio para guardar en el CSV.

En windows cuando isntalas anaconda se instala una consola llamada “Anaconda Prompt”, por el cmd y por gitbash no me funciono pero al usar esta consola todo funciono, es muy posible que tu código este bien pero que el ambiente sea el problema.

Ademas estoy usando la versión 3.7.6 de Python

Estoy estancado 😦, La verdad he invertido mucho tiempo en identificar este error pero no se realmente que es, los archivos están idénticos a los que cuelgan en el sistema de archivos, ya verifique el IDE y todos los paquetes, hasta lo corri sin activarlo, por conda y por VS pero siempre me arroja le mismo error, alguien me podría echar una mano pls.

Traceback (most recent call last):
  File "main.py", line 82, in <module>
    news_site_choices = list(config()['news_sites'].keys())
  File "C:\Users\Usuario...\common.py", line 8, in config
    config = yaml.safe_load(f)
  File "C:\Users\Usuario...\yaml\__init__.py", line 162, in safe_load
    return load(stream, SafeLoader)
  File "C:\Users\Usuario...\yaml\__init__.py", line 114, in load
    return loader.get_single_data()
  File "C:\Users\Usuario...\yaml\constructor.py", line 49, in get_single_data
    node = self.get_single_node()
  File "C:\Users\Usuario...\yaml\composer.py", line 36, in get_single_node
    document = self.compose_document()
  File "C:\Users\Usuario...\yaml\composer.py", line 55, in compose_document
    node = self.compose_node(None, None)
  File "C:\Users\Usuario...\yaml\composer.py", line 84, in compose_node
    node = self.compose_mapping_node(anchor)
  File "C:\Users\Usuario...\yaml\composer.py", line 133, in compose_mapping_node
    item_value = self.compose_node(node, item_key)
  File "C:\Users\Usuario...\yaml\composer.py", line 84, in compose_node
    node = self.compose_mapping_node(anchor)
  File "C:\Users\Usuario...\yaml\composer.py", line 133, in compose_mapping_node
    item_value = self.compose_node(node, item_key)
  File "C:\Users\Usuario...\yaml\composer.py", line 84, in compose_node
    node = self.compose_mapping_node(anchor)
  File "C:\Users\Usuario...\yaml\composer.py", line 127, in compose_mapping_node
    while not self.check_event(MappingEndEvent):
  File "C:\Users\Usuario...\yaml\parser.py", line 98, in check_event
    self.current_event = self.state()
  File "C:\Users\Usuario...\yaml\parser.py", line 428, in parse_block_mapping_key
    if self.check_token(KeyToken):
  File "C:\Users\Usuario...\yaml\scanner.py", line 116, in check_token
    self.fetch_more_tokens()
  File "C:\Users\Usuario...\yaml\scanner.py", line 223, in fetch_more_tokens
    return self.fetch_value()
  File "C:\Users\Usuario...\yaml\scanner.py", line 577, in fetch_value
    raise ScannerError(None, None,
yaml.scanner.ScannerError: mapping values are not allowed here
  in "config.yaml", line 6, column 19```

El archivo yaml no me lo reconoce por ningún lado

No está de más agregar la codificación en la escritura a disco

en _save_articles

with open(out_file_name, mode='w+',encoding="utf-8") as f:

Se me estaba rompiendo la ejecución por algunos caracteres.

Les dejo esta lectura sobre el manejo de archivos con Python, si hubiera leído esto cuando hice el curso, se me hubiera hecho todo más fácil, espero les sirva.

https://drive.google.com/file/d/12PEwLO0-glgu0dLH8Wra_CEZJHZ-GxKF/view?usp=sharing

Todo funciono bien con la página del universal incluso en el 2020. Estoy encantada con este curso 😃

import bs4
import requests

from common import config

class NewsPage:

def __init__(self, news_site_uid, url):
    self._config = config() ['news_sites'][news_site_uid]
    self._queries = self._config['queries']
    self._html = None
    self._url = url
    self._visit(url)


def _select(self, query_string):
    return self._html.select(query_string)

def _visit(self, url):
    response = requests.get(url)

    response.raise_for_status()

    self._html = bs4.BeautifulSoup(response.text, 'html.parser')

class HomePage(NewsPage):

def __init__(self, news_site_uid, url):
   super().__init__(news_site_uid, url)

@property
def article_links(self):
    link_list = []
    for link in self._select(self._queries['homepage_article_links']):
        if link and link.has_attr('href'):
            link_list.append(link)

    return set(link['href'] for link in link_list)

class ArticlePage(NewsPage):

def __init__(self, news_site_uid, url):
   super().__init__(news_site_uid, url)

@property
def body(self):
    result = self._select(self._queries['article_body'])

    return result[0].text if len(result) else ''


@property
def title(self):
    result = self._select(self._queries['article_title'])
    return result[0].text if len(result) else ''


@property
def url(self):
    return self._url

Me parece que cada curso en la plataforma debería tener una sección de pre-requisitos para identificar si puedo o no tomar el curso, ya que veo comentarios de personas que están un poco perdidas o que no entienden ciertas cosas por las explicaciones que omite el tutor.
De mi parte, como he trabajado con python me queda claro gran parte de lo que explica, pero seguro que si no tuviera conocimientos de python tendría la misma sensación.
Solo eso, me parece que al inicio falta advertir que conocimientos previos debería tener.

Es difícil y será lo que será, pero yo quedé fascinado. Me encanta.

no es mas facil usar un editor de texto en vez de explicar en la consola?

Excelente,

He leído los comentarios de algunos compañeros, y claro todos tenemos derecho a expresar con respeto nuestra forma de ver y entender las cosas. Muy seguramente muchos o algunos de los que pasamos por estos cursos hemos tenido clases presenciales en la universidad, y el real aprendizaje se da cuando uno practica, investiga y va un mas allá de lo que el profesor expone. En mi opinión es uno de los mejores profesores de Platzi,
PD. A mi me corrió bien el ejercicio propuesto.

Paso 5: guardando la info en disco.

1- se crea la funcion _save_articles, es para guardar los documentos en el csv.

2- se importa csv y datetime.

3- se codifica para la manipulacion de archivos.

import argparse
import logging
import news_page_objects as news
import re
import datetime     #se importa este modulo de fechas
import csv          #se importa csv para guardar la informacion
from requests.exceptions import HTTPError
from config_yaml import config

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def_news_scraper(news_site_uid):
    host = config()['news_sites'][news_site_uid]['url']
    logging.info('Comenzanco el scraping en: {}'.format(host))

    homepage = news.HomePage(news_site_uid, host)

    articles = []   #lista de articulos que se encontrarán

    for link in homepage.article_links:
        article = _fetch_article(news_site_uid, host, link)

        if article: #se ejecuta si es que hay un articulo
            logger.info('se encontro el articulo')
            articles.append(article)
            print('titulo = {}'.format(article.title))
            break

    _save_articles(news_site_uid, articles)                 
#se inicializa esta funcion que servirá de guardado

    print('cantidad de articulos es de : {}'.format(len(article)))

def_save_articles(news_site_uid, articles):#guardará en un archivo la informacion
    now = datetime.date.now().strftime('%Y_%m_%d')          
#se inicializa el datetime, y se le da formato con string format time
    out_file_name = '{}_{}_articles.csv'.format(news_site_uid, now)
    csv_headers = list(filter(lambda property: not property.startswith('_'), dir(articles[0])))  
#se filtra la informacion mediante la funcion filter de dir
    with open(out_file_name, mode= 'w+') as f:                          
#se emplea codificacion de manipulacion de archivos
        writer= csv.writer(f)                                             
#se inicializa un writer
        writer.writerow(csv_headers)                                     
#se pide que escriba en la columna del archivo csv

        for article in articles:
            row = [str(getattr(article, prop))for prop in csv_headers] 
#row almacena los atributos de article y prop que se encuentran 
#en csv_headers, getattr = es la forma de obtener atributos de una funcion
            writer.writerow(row)                                       
#funcion para escribir el row en cada casilla en el archivo csv que retornará

def_fetch_article(news_site_uid, host, link):
    logger.info('esta buscando el archivo en {}'.format(link))
    article = None

    try:
        article = news.ArticlePage(news_site_uid, _build_link(host, link))
    except (HTTPError) as e:
        logger.warning('Error mientras se buscaba el articulo', exc_info= False)

    if article andnot article.body:
        logger.warning('articulo invalido, no hay cuerpo en el articulo')
        returnNone

    return article

is_well_formed_link = re.compile(r'^https?://.+/.+$')
is_root_path = re.compile(r'^/.+$')

def_build_link(host, link):
    if is_well_formed_link.match(link):
        return link
    elif is_root_path.match(link):
        return'{}/{}'.format(host, link)
    else:
        return'{}/{}'.format(host, link)

if __name__ == '__main__':
    parser = argparse.ArgumentParser('siempre debe iniciar el parse así')
    news_site_choices = list(config()['news_sites'].keys())
    parser.add_argument('news_site',
                        help= 'Escoge el sitio el cual quieres buscar',
                        type= str,
                        choices= news_site_choices)

    arg = parser.parse_args()
    _news_scraper(arg.news_site)```