alexpdr

scraper actual

Jun 14th, 2020
2,424
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 4.81 KB | None | 0 0
  1. """
  2. File contains Scraper class with corresponding methods
  3. """
  4. from typing import Union, List, Optional
  5. from logging import Logger, getLogger
  6. from datetime import datetime, tzinfo
  7. from time import mktime
  8. import asyncio
  9. from pytz import timezone
  10. import validators
  11. import feedparser
  12. from aiohttp import ClientSession, ClientResponseError
  13.  
  14.  
  15. class Scraper:
  16.     """
  17.    Class containing scraping methods
  18.    """
  19.  
  20.     _logger: Logger
  21.     _default_url: str
  22.     _default_response: dict
  23.     _timezone: tzinfo
  24.  
  25.     def __init__(self):
  26.         self._logger = getLogger(__name__)
  27.         self._default_url = "http://www.nu.nl/rss/Algemeen"
  28.         self._default_response = {
  29.             "title": "Not Found",
  30.             "description": "Bad or malformed url",
  31.             "last_entry_update": datetime.utcnow(),
  32.             "items": [],
  33.         }
  34.         self._timezone = timezone('Europe/Amsterdam')
  35.         self._logger.debug("Scraping tool initialized")
  36.  
  37.     async def _scrape(self, session, url) -> Union[str, None]:
  38.         """Internal method running sessions to scrape the supplied URL"""
  39.         try:  # To scrape :)
  40.             async with session.get(url, raise_for_status=True) as response:
  41.                 return await response.text()
  42.  
  43.         except ClientResponseError:  # When there is no website available
  44.             self._logger.exception("Could not reach: %s", url)
  45.  
  46.     async def _parse(
  47.             self,
  48.             url: Optional[str] = None,
  49.             newer_than: Optional[datetime] = None
  50.     ) -> Union[List[dict], None]:
  51.         """Internal method calling _scrape() and parsign it's contents"""
  52.         url = url or self._default_url
  53.         if not validators.url(url):
  54.             return self._default_response
  55.  
  56.         async with ClientSession() as session:
  57.             self._logger.debug("Attempting to scrape: '%s' ...'", url)
  58.             content: str = await self._scrape(session, url)
  59.  
  60.             # Parse data & create a formatted payload to return
  61.             data = feedparser.parse(content)
  62.             last_update = datetime.fromtimestamp(
  63.                 mktime(data.feed.updated_parsed)
  64.             ).astimezone(self._timezone)
  65.  
  66.             payload: dict = {
  67.                 "title": data.feed.title,
  68.                 "description": data.feed.description,
  69.                 "last_updated": last_update,
  70.                 "items": [],
  71.             }
  72.  
  73.             for entry in data.entries:  # Apeend the data we want
  74.                 published = datetime.fromtimestamp(
  75.                     mktime(entry.published_parsed)
  76.                 ).astimezone(self._timezone)
  77.                 if newer_than:
  78.                     statement = published >= newer_than
  79.                     if statement is False:
  80.                         continue
  81.                 try:
  82.  
  83.                     payload["items"].append(
  84.                         {
  85.                             "id": entry.id,
  86.                             "title": entry.title,
  87.                             "summary": entry.summary,
  88.                             "authors": entry.authors,
  89.                             "links": entry.links,
  90.                             "published": published,
  91.                         }
  92.                     )
  93.                     self._logger.debug("Added entry with id: %s", entry.id)
  94.  
  95.                 except AttributeError:
  96.                     self._logger.error("Could not parse feed entry")
  97.  
  98.             # Return payload
  99.             self._logger.debug("Scraping session completed.")
  100.             return payload
  101.  
  102.     def scrape(
  103.             self,
  104.             url: Optional[str] = None,
  105.             newer_than: Optional[datetime] = None
  106.     ) -> dict:
  107.         """
  108.        Method scrapes the supplied url for information.
  109.  
  110.        Argument(s):
  111.            url:        (str)       (optional) the url to scrape from
  112.                                    (default)  class url
  113.            newer_than  (datetime)  (optional) minimum item publish time
  114.  
  115.        Returns a list of feed items
  116.  
  117.        Format: {
  118.            'id': entry.id,
  119.            'title': entry.title,
  120.            'summary': entry.summary,
  121.            'authors': entry.authors,
  122.            'links': entry.links
  123.        }
  124.        """
  125.         loop = asyncio.get_event_loop()
  126.         return loop.run_until_complete(
  127.             self._parse(url=url, newer_than=newer_than)
  128.         )
  129.  
  130.     def get_default_url(self) -> str:
  131.         """Returns the default url"""
  132.         return self._default_url
  133.  
  134.     def get_default_response(self) -> dict:
  135.         """Returns the default response"""
  136.         return self._default_response
  137.  
  138.     def get_class_logger(self) -> Logger:
  139.         """Returns the class logger"""
  140.         return self._logger
  141.  
  142.     def get_time_zone(self) -> tzinfo:
  143.         """Returns the class logger"""
  144.         return self._timezone
Advertisement
Add Comment
Please, Sign In to add comment