# nprSource.py is an implementation of the abstract Source object. from dataclasses import dataclass from collections import namedtuple from typing import List, Tuple, Any from gazpacho import Soup, get from source import Source, Summary import streamlit as st stub = namedtuple('npr_stub',[ 'link','hed','entities', 'source']) stub.__doc__ = f""" • A namedtuple to represent an unscraped news article. • link is the extension of the article. Added to the source's source_url it is used to retrieve the full article and data. • hed is the headline ('hed' is journalism jargon, as is 'dek' for 'subheader') • entities is the list of entity names discovered in this headline, each entity representing one cluster the article is in. • source is a reference to the Source object that created the stub. """ @dataclass class NPRLite(Source): """Implementation of abstract Source class that retrieves via webscraping at text.npr.org/1001""" # Creates the initial namedtuple that holds the hed, link, # and identified entities for each article. # Chosen articles will have their data stored in a Summary object. def retrieve_cluster_data(self, limit=None) -> List[namedtuple]: #print("retrieving NPR article stub") """Creates article stubs for articles listed on text.npr.org""" # Scrape NPR for headlines and links soup = Soup(get(self.source_url)) # extract each headline npr_hed = [i.text for i in soup.find('div', {'class': 'topic-container'}).find('ul').find('a')] # links scraped are just the extension to the site's base link. npr_links = [i.attrs['href'] for i in soup.find('div', {'class': 'topic-container'}).find('ul').find('a')] # limit amount of data being returned for clustering if limit is not None: npr_hed = npr_hed[:limit] npr_links = npr_links[:limit] # Create stubs with heds and links # Test: do the headlines and links zipped together lineup correctly? article_tuples = [stub(i[0], i[1], [], self) for i in zip(npr_links, npr_hed)] #print(f"Number of npr articles: {len(npr_hed)}") return article_tuples, len(npr_hed) # Returns None if article is only 1 line. def retrieve_article(self, indata: stub) -> Tuple[str, List[Tuple[str, Any]]]: """Retrieves article data from text.npr.org subhead if exists, date, author(s), and whole text""" st.write(f"""Retrieving article from:\n\t{self.source_url[:-5] + indata.link}\n""") container = Soup(get(self.source_url[:-5] + indata.link)) text_container = container.find('div', {'class': "paragraphs-container"}).find('p') if isinstance(text_container, Soup): return None, None whole_text = ''.join([art.strip() for art in text_container]) story_head = container.find('div', {'class':'story-head'}) auth_and_date = [i.text for i in story_head.find('p')] author = auth_and_date[0] story_date = auth_and_date[1] author = author[3:] # return whole text and data for summary return whole_text, [ self, indata.entities, indata.link, indata.hed, None, story_date, [author], len(whole_text.split(' ')), ] @dataclass class CNNText(Source): """Implementation of abstract Source class that retrieves via webscraping at lite.cnn.com""" # Creates the initial namedtuple that holds the hed, link, # and identified entities for each article. # Chosen articles will have their data stored in a Summary object. def retrieve_cluster_data(self, limit=None) -> List[namedtuple]: """Creates a stub for each article listed on lite.cnn.com""" soup = Soup(get(self.source_url)) # Scrape NPR for headlines and links cnn_heds = [i.text for i in soup.find('div', {'class': 'afe4286c'}).find('a')] cnn_links = [i.attrs['href'] for i in soup.find('div', {'class': 'afe4286c'}).find('a')] # limit amount of data returned for clustering if limit is not None: cnn_heds = cnn_heds[:limit] cnn_links = cnn_links[:limit] # Take this next line out of this function and place it where this data is used. article_tuples = [stub(i[0], i[1], [], self) for i in zip(cnn_links, cnn_heds) if 'Opinion' not in i[1] and 'Analysis' not in i[1]] return article_tuples, len(cnn_heds) # Returns None if article is only 1 line. def retrieve_article(self, indata: stub) -> Tuple[str, List[Tuple[str, Any]]]: """Retrieves article data from lite.cnn.com: subhead if exists, date, author(s), and whole text""" #print(f"""Retrieving article from:\n\t{self.source_url + indata.link}\n""") st.write(f"""Retrieving article from:\n\t{self.source_url + indata.link}\n""") repeat = 0 good = False while repeat < 2 and not good: try: container = Soup(get(self.source_url + indata.link)) good = True except Exception as e: print(f"Error:\n{e}") print(f"Problem url: \n\t{self.source_url + indata.link}") repeat += 1 if good: story_container = container.find('div', {'class': 'afe4286c'}) #print(story_container) author = story_container.find('p',{'id':'byline'}).text story_date = story_container.find('p',{'id':'published datetime'}).text[9:] #if isinstance(story_container, Soup): # return None, None scp = story_container.find('p')[4:] whole_text = ''.join([i.text for i in scp if i.text is not None]) article_data = [ self, indata.entities, indata.link, indata.hed, None, story_date, [author], len(whole_text.split(' ')), ] else: whole_text = None article_data = None # return whole text and data for summary return whole_text, article_data