Repository: mariostoev/finviz (language: python)

Issue:
The screener result list is not complete nor stable
Run the following example from README.md. The `len(stock_list)` is always consistent with finviz UI but the actual stocks from the for loop is not incomplete and always changing.


```
from finviz.screener import Screener

filters = [
    'sh_avgvol_o100', # average daily volume > 100k shares
    'sh_curvol_o100', # current daily volume > 100k shares
    'sh_float_u50', # floating stocks < 50m shares
    'sh_price_u3' # current price is under $3
]
stock_list = Screener(filters=filters, table='Performance')  # Get the ownership table

# Print the table into the console
print(len(stock_list))

i = 1
for stock in stock_list:
    ticker = stock['Ticker']
    # print(f'{i} {ticker}')
    i = i + 1
print(i)
```
Not getting full results list using the screener
Hi, I'm trying to use this module to pull all tickers for the NYSE and am running into an issue where I cannot access all the results returned. I have the code below setup to query finviz and get a result set back. It appears to have the right number of results at first, len(stock_list_nyse) returns the same number I see on finviz, but when I try to export to CSV or loop through and print each result I only get between 100 and 160 results (it varies each time). Any ideas what's causing this? Do I need finviz elite in order to see more results?


```
filters = ['exch_nyse']  
stock_list_nyse = Screener(filters=filters, table='Overview', order='Ticker') 

stock_list_nyse.to_csv("stock_list_nyse.csv")

print('Results: ', len(stock_list_nyse))
i = 0
for stock in stock_list_nyse:  # Loop through 10th - 20th stocks
    i = i+1
    print(i, ' - ', stock['Ticker'],' - ', stock['Sector']) 
```

The screener result list is not complete nor stable
Run the following example from README.md. The `len(stock_list)` is always consistent with finviz UI but the actual stocks from the for loop is not incomplete and always changing.


```
from finviz.screener import Screener

filters = [
    'sh_avgvol_o100', # average daily volume > 100k shares
    'sh_curvol_o100', # current daily volume > 100k shares
    'sh_float_u50', # floating stocks < 50m shares
    'sh_price_u3' # current price is under $3
]
stock_list = Screener(filters=filters, table='Performance')  # Get the ownership table

# Print the table into the console
print(len(stock_list))

i = 1
for stock in stock_list:
    ticker = stock['Ticker']
    # print(f'{i} {ticker}')
    i = i + 1
print(i)
```


Discussion / hints:

Fix patch:
diff --git a/.gitignore b/.gitignore
index 040c836..d87d952 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,9 @@ __pycache__/
 # C extensions
 *.so
 
+# IDE
+.idea/
+
 # Distribution / packaging
 .Python
 env/
diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml
deleted file mode 100644
index 85774ff..0000000
--- a/.idea/dataSources.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
-  <component name="DataSourceManagerImpl" format="xml" multifile-model="true">
-    <data-source source="LOCAL" name="identifier.sqlite" uuid="bc3e6325-bfe6-49ab-bfbc-235ea53db4ba">
-      <driver-ref>sqlite.xerial</driver-ref>
-      <synchronize>true</synchronize>
-      <jdbc-driver>org.sqlite.JDBC</jdbc-driver>
-      <jdbc-url>jdbc:sqlite:identifier.sqlite</jdbc-url>
-      <driver-properties>
-        <property name="enable_load_extension" value="true" />
-      </driver-properties>
-    </data-source>
-    <data-source source="LOCAL" name="screener" uuid="64d289fd-1dee-4fd8-9365-05659a78a897">
-      <driver-ref>sqlite.xerial</driver-ref>
-      <synchronize>true</synchronize>
-      <jdbc-driver>org.sqlite.JDBC</jdbc-driver>
-      <jdbc-url>jdbc:sqlite:$PROJECT_DIR$/screener.sqlite</jdbc-url>
-      <driver-properties>
-        <property name="enable_load_extension" value="true" />
-      </driver-properties>
-    </data-source>
-  </component>
-</project>
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
deleted file mode 100644
index 146ab09..0000000
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<component name="InspectionProjectProfileManager">
-  <profile version="1.0">
-    <option name="myName" value="Project Default" />
-    <inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
-      <option name="processCode" value="true" />
-      <option name="processLiterals" value="true" />
-      <option name="processComments" value="true" />
-    </inspection_tool>
-  </profile>
-</component>
\ No newline at end of file
diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml
deleted file mode 100644
index c0e01ca..0000000
--- a/.idea/sqldialects.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
-  <component name="SqlDialectMappings">
-    <file url="PROJECT" dialect="SQLite" />
-  </component>
-</project>
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 94a25f7..0000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
-  <component name="VcsDirectoryMappings">
-    <mapping directory="$PROJECT_DIR$" vcs="Git" />
-  </component>
-</project>
\ No newline at end of file
diff --git a/example.py b/example.py
index d12e0d7..fde060a 100644
--- a/example.py
+++ b/example.py
@@ -5,20 +5,19 @@
 # Get dict of available filters
 # filters dict contains the corresponding filter tags
 filters = Screener.load_filter_dict()
-some_filters = [filters['PEG']['Under 1'],
-                filters['Exchange']['AMEX']]
-stock_list = Screener(filters=some_filters, order='ticker')
+some_filters = [filters["PEG"]["Under 1"], filters["Exchange"]["AMEX"]]
+stock_list = Screener(filters=some_filters, order="ticker")
 
 # Use raw filter tags in a list
 # filters = ['geo_usa']
-filters = ['idx_sp500']  # Shows companies in the S&P500
+filters = ["idx_sp500"]  # Shows companies in the S&P500
 print("Filtering stocks..")
-stock_list = Screener(filters=filters, order='ticker')
+stock_list = Screener(filters=filters, order="ticker")
 print("Parsing every stock..")
 stock_list.get_ticker_details()
 
 # Export the screener results to CSV file
-stock_list.to_csv('sp500.csv')
+stock_list.to_csv("sp500.csv")
 
 # Create a SQLite database
 # stock_list.to_sqlite('sp500.sqlite')
diff --git a/finviz/__init__.py b/finviz/__init__.py
index d893fff..a486231 100644
--- a/finviz/__init__.py
+++ b/finviz/__init__.py
@@ -1,9 +1,4 @@
-from finviz.main_func import (
-    get_all_news,
-    get_analyst_price_targets,
-    get_insider,
-    get_news,
-    get_stock
-)
+from finviz.main_func import (get_all_news, get_analyst_price_targets,
+                              get_insider, get_news, get_stock)
 from finviz.portfolio import Portfolio
 from finviz.screener import Screener
diff --git a/finviz/__pycache__/public.cpython-33.pyc b/finviz/__pycache__/public.cpython-33.pyc
deleted file mode 100644
index f922784..0000000
Binary files a/finviz/__pycache__/public.cpython-33.pyc and /dev/null differ
diff --git a/finviz/helper_functions/display_functions.py b/finviz/helper_functions/display_functions.py
index 6dbaf5d..89d42f8 100644
--- a/finviz/helper_functions/display_functions.py
+++ b/finviz/helper_functions/display_functions.py
@@ -2,10 +2,10 @@ def create_table_string(table_list):
     """ Used to create a readable representation of a table. """
 
     col_size = [max(map(len, col)) for col in zip(*table_list)]
-    format_str = ' | '.join([f"{{:<{i}}}" for i in col_size])
+    format_str = " | ".join([f"{{:<{i}}}" for i in col_size])
     table_list.insert(1, ['-' * i for i in col_size])
 
-    table_string = ''
+    table_string = ""
     for item in table_list:
         table_string += format_str.format(*item) + '\n'
 
diff --git a/finviz/helper_functions/error_handling.py b/finviz/helper_functions/error_handling.py
index ba3cc53..96c56d8 100644
--- a/finviz/helper_functions/error_handling.py
+++ b/finviz/helper_functions/error_handling.py
@@ -5,55 +5,58 @@ class NoResults(Exception):
     """ Raise when there are no results found. """
 
     def __init__(self, query):
-        super(NoResults, self).__init__(f'No results found for query: {query}')
+        super(NoResults, self).__init__(f"No results found for query: {query}")
 
 
 class InvalidTableType(Exception):
     """ Raise when the given table type is invalid. """
 
     def __init__(self, arg):
-        super(InvalidTableType, self).__init__(
-            f'Invalid table type called: {arg}')
+        super(InvalidTableType, self).__init__(f"Invalid table type called: {arg}")
 
 
 class TooManyRequests(Exception):
     """ Raise when HTTP request fails because too many requests were sent to FinViz at once. """
 
     def __init__(self, arg):
-        super(TooManyRequests, self).__init__(
-            f'Too many HTTP requests at once: {arg}')
+        super(TooManyRequests, self).__init__(f"Too many HTTP requests at once: {arg}")
 
 
 class InvalidPortfolioID(Exception):
-    """ Rasie when the given portfolio id is invalid. """
+    """ Raise when the given portfolio id is invalid. """
 
     def __int__(self, portfolio_id):
         super(InvalidPortfolioID, self).__init__(
-            f'Invalid portfolio with ID: {portfolio_id}')
+            f"Invalid portfolio with ID: {portfolio_id}"
+        )
 
 
-class UnexistingPortfolioName(Exception):
-    """ Raise when the given portfolio name is unexisting. """
+class NonexistentPortfolioName(Exception):
+    """ Raise when the given portfolio name is nonexistent. """
 
     def __init__(self, name):
-        super(UnexistingPortfolioName, self).__init__(
-            f'Unexisting portfolio with name: {name}')
+        super(NonexistentPortfolioName, self).__init__(
+            f"Nonexistent portfolio with name: {name}"
+        )
 
 
 class NoPortfolio(Exception):
     """ Raise when the user has not created a portfolio. """
 
     def __int__(self, func_name):
-        super(NoPortfolio, self).__init__("Function ({func_name}) cannot be called because "
-                                          "there is no existing portfolio.")
+        super(NoPortfolio, self).__init__(
+            "Function ({func_name}) cannot be called because "
+            "there is no existing portfolio."
+        )
 
 
 class InvalidTicker(Exception):
-    """ Raise when the given ticker is nonexistant or unavailable on FinViz.  """
+    """ Raise when the given ticker is nonexistent or unavailable on FinViz.  """
 
     def __init__(self, ticker):
         super(InvalidTicker, self).__init__(
-            f'Unable to find {ticker} since it is non-existent or unavailable on FinViz.')
+            f"Unable to find {ticker} since it is non-existent or unavailable on FinViz."
+        )
 
 
 class ConnectionTimeout(Exception):
diff --git a/finviz/helper_functions/request_functions.py b/finviz/helper_functions/request_functions.py
index afc273d..7c06bd8 100644
--- a/finviz/helper_functions/request_functions.py
+++ b/finviz/helper_functions/request_functions.py
@@ -9,10 +9,8 @@
 from user_agent import generate_user_agent
 
 from finviz.config import connection_settings
-from finviz.helper_functions.error_handling import (
-    ConnectionTimeout,
-    TooManyRequests
-)
+from finviz.helper_functions.error_handling import (ConnectionTimeout,
+                                                    TooManyRequests)
 
 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
 
@@ -25,9 +23,19 @@ def http_request_get(url, session=None, payload=None, parse=True):
 
     try:
         if session:
-            content = session.get(url, params=payload, verify=False, headers={'User-Agent': generate_user_agent()})
+            content = session.get(
+                url,
+                params=payload,
+                verify=False,
+                headers={"User-Agent": generate_user_agent()},
+            )
         else:
-            content = requests.get(url, params=payload, verify=False, headers={'User-Agent': generate_user_agent()})
+            content = requests.get(
+                url,
+                params=payload,
+                verify=False,
+                headers={"User-Agent": generate_user_agent()},
+            )
 
         content.raise_for_status()  # Raise HTTPError for bad requests (4xx or 5xx)
         if parse:
@@ -38,16 +46,27 @@ def http_request_get(url, session=None, payload=None, parse=True):
         raise ConnectionTimeout(url)
 
 
-def sequential_data_scrape(scrape_func: Callable, urls: List[str], delay: float = 3, *args, **kwargs) -> List[Dict]:
+def sequential_data_scrape(
+    scrape_func: Callable, urls: List[str], delay: float = 0.5, *args, **kwargs
+) -> List[Dict]:
     data = []
+    delay_multiplier = 1.0
 
     for url in urls:
         try:
-            response = requests.get(url, headers={"User-Agent": generate_user_agent()})
-            if response.text == "Too many requests.":
-                raise TooManyRequests(f"URL: {url}, Response HTML: {response.text}")
+            while True:
+                response = requests.get(
+                    url, headers={"User-Agent": generate_user_agent()}
+                )
+                if response.text == "Too many requests.":
+                    time.sleep(delay * delay_multiplier)
+                    delay_multiplier *= 1.5
+                    continue
+                else:
+                    delay_multiplier = 1.0
+                    break
             kwargs["URL"] = url
-            data.append(scrape_func(response.text, *args, **kwargs))
+            data.append(scrape_func(response, *args, **kwargs))
             time.sleep(delay)
         except Exception as exc:
             raise exc
@@ -58,22 +77,32 @@ def sequential_data_scrape(scrape_func: Callable, urls: List[str], delay: float
 class Connector:
     """ Used to make asynchronous HTTP requests. """
 
-    def __init__(self, scrape_function: Callable, urls: List[str], *args, css_select: bool = False):
+    def __init__(
+        self,
+        scrape_function: Callable,
+        urls: List[str],
+        *args,
+        css_select: bool = False
+    ):
         self.scrape_function = scrape_function
         self.urls = urls
         self.arguments = args
         self.css_select = css_select
         self.data = []
 
-    async def __http_request__async(self, url: str, session: aiohttp.ClientSession, user_agent: str):
+    async def __http_request__async(
+        self, url: str, session: aiohttp.ClientSession, user_agent: str
+    ):
         """ Sends asynchronous http request to URL address and scrapes the webpage. """
 
         try:
-            async with session.get(url, headers={'User-Agent': user_agent}) as response:
+            async with session.get(url, headers={"User-Agent": user_agent}) as response:
                 page_html = await response.read()
 
                 if self.css_select:
-                    return self.scrape_function(html.fromstring(page_html), *self.arguments)
+                    return self.scrape_function(
+                        html.fromstring(page_html), *self.arguments
+                    )
                 return self.scrape_function(page_html, *self.arguments)
         except (asyncio.TimeoutError, requests.exceptions.Timeout):
             raise ConnectionTimeout(url)
@@ -82,13 +111,15 @@ async def __async_scraper(self):
         """ Adds a URL's into a list of tasks and requests their response asynchronously. """
 
         async_tasks = []
-        conn = aiohttp.TCPConnector(limit_per_host=connection_settings['CONCURRENT_CONNECTIONS'])
-        timeout = aiohttp.ClientTimeout(total=connection_settings['CONNECTION_TIMEOUT'])
+        conn = aiohttp.TCPConnector(
+            limit_per_host=connection_settings["CONCURRENT_CONNECTIONS"]
+        )
+        timeout = aiohttp.ClientTimeout(total=connection_settings["CONNECTION_TIMEOUT"])
         user_agent = generate_user_agent()
 
-        async with aiohttp.ClientSession(connector=conn,
-                                         timeout=timeout,
-                                         headers={'User-Agent': user_agent}) as session:
+        async with aiohttp.ClientSession(
+            connector=conn, timeout=timeout, headers={"User-Agent": user_agent}
+        ) as session:
             for url in self.urls:
                 async_tasks.append(self.__http_request__async(url, session, user_agent))
 
diff --git a/finviz/helper_functions/save_data.py b/finviz/helper_functions/save_data.py
index c549b6d..34c5458 100644
--- a/finviz/helper_functions/save_data.py
+++ b/finviz/helper_functions/save_data.py
@@ -11,7 +11,10 @@ def create_connection(sqlite_file):
         conn = sqlite3.connect(sqlite_file)
         return conn
     except sqlite3.Error as error:
-        raise ("An error has occurred while connecting to the database: ", error.args[0])
+        raise (
+            "An error has occurred while connecting to the database: ",
+            error.args[0],
+        )
 
 
 def __write_csv_to_stream(stream, headers, data):
@@ -22,8 +25,8 @@ def __write_csv_to_stream(stream, headers, data):
     dict_writer.writerows(data)
 
 
-def export_to_csv(headers, data, filename=None, mode='w', newline=''):
-    """ Exports the generated table into a CSV file if a file is mentioned.
+def export_to_csv(headers, data, filename=None, mode="w", newline=""):
+    """Exports the generated table into a CSV file if a file is mentioned.
     Returns the CSV table as a string if no file is mentioned."""
 
     if filename:
@@ -45,7 +48,7 @@ def export_to_db(headers, data, filename):
 
     for field in headers:
 
-        field_cleaned = re.sub(r'[^\w\s]', '', field)
+        field_cleaned = re.sub(r"[^\w\s]", "", field)
         field_cleaned = field_cleaned.replace(" ", "")
         field_list += field_cleaned + " TEXT, "
 
@@ -57,7 +60,7 @@ def export_to_db(headers, data, filename):
         insert_fields = "("
         for field, value in row.items():
 
-            insert_fields += "\"" + value + "\", "
+            insert_fields += '"' + value + '", '
 
         inserts += insert_fields[:-2] + "), "
 
diff --git a/finviz/helper_functions/scraper_functions.py b/finviz/helper_functions/scraper_functions.py
index ac8b773..ce808cf 100644
--- a/finviz/helper_functions/scraper_functions.py
+++ b/finviz/helper_functions/scraper_functions.py
@@ -1,12 +1,13 @@
 import datetime
 import os
 
+import requests
 from lxml import etree, html
 
 
-def get_table(page_html, headers, rows=None, **kwargs):
+def get_table(page_html: requests.Response, headers, rows=None, **kwargs):
     """ Private function used to return table data inside a list of dictionaries. """
-    page_parsed = html.fromstring(page_html)
+    page_parsed = html.fromstring(page_html.text)
     # When we call this method from Portfolio we don't fill the rows argument.
     # Conversely, we always fill the rows argument when we call this method from Screener.
     # Also, in the portfolio page, we don't need the last row - it's redundant.
@@ -16,7 +17,10 @@ def get_table(page_html, headers, rows=None, **kwargs):
     data_sets = []
     # Select the HTML of the rows and append each column text to a list
     # Skip the first element ([1:]), since it's the headers (we already have it as a constant)
-    all_rows = [column.xpath('td//text()') for column in page_parsed.cssselect('tr[valign="top"]')[1:rows + 1]]
+    all_rows = [
+        column.xpath("td//text()")
+        for column in page_parsed.cssselect('tr[valign="top"]')[1: rows + 1]
+    ]
 
     # If rows is different from -2, this function is called from Screener
     if rows != -2:
@@ -35,7 +39,9 @@ def get_total_rows(page_content):
     """ Returns the total number of rows(results). """
 
     total_element = page_content.cssselect('td[width="140"]')
-    total_number = etree.tostring(total_element[0]).decode('utf-8').split('</b>')[1].split()[0]
+    total_number = (
+        etree.tostring(total_element[0]).decode("utf-8").split("</b>")[1].split()[0]
+    )
 
     try:
         return int(total_number)
@@ -46,78 +52,102 @@ def get_total_rows(page_content):
 def get_page_urls(page_content, rows, url):
     """ Returns a list containing all of the page URL addresses. """
 
-    total_pages = int([i.text.split('/')[1] for i in page_content.cssselect('option[value="1"]')][0])
+    total_pages = int(
+        [i.text.split("/")[1] for i in page_content.cssselect('option[value="1"]')][0]
+    )
     urls = []
 
     for page_number in range(1, total_pages + 1):
-
         sequence = 1 + (page_number - 1) * 20
 
         if sequence - 20 <= rows < sequence:
             break
-        else:
-            urls.append(url + f'&r={str(sequence)}')
+        urls.append(url + f"&r={str(sequence)}")
 
     return urls
 
 
-def download_chart_image(page_content, url):
-    """ Downloads a .jpg image of a chart into the "charts" folder. """
+def download_chart_image(page_content: requests.Response, **kwargs):
+    """ Downloads a .png image of a chart into the "charts" folder. """
 
-    file_name = url.split('t=')[1] + '.jpg'
+    file_name = kwargs["URL"].split("t=")[1] + ".png"
 
-    if not os.path.exists('charts'):
-        os.mkdir('charts')
+    if not os.path.exists("charts"):
+        os.mkdir("charts")
 
-    with open(os.path.join('charts', file_name), 'wb') as handle:
-        handle.write(page_content)
+    with open(os.path.join("charts", file_name), "wb") as handle:
+        handle.write(page_content.content)
 
 
-def get_analyst_price_targets_for_export(ticker=None, page_content=None, last_ratings=5):
+def get_analyst_price_targets_for_export(
+        ticker=None, page_content=None, last_ratings=5
+):
     analyst_price_targets = []
 
     try:
         table = page_content.cssselect('table[class="fullview-ratings-outer"]')[0]
-        ratings_list = [row.xpath('td//text()') for row in table]
-        ratings_list = [[val for val in row if val != '\n'] for row in ratings_list]  # remove new line entries
-
-        headers = ['ticker', 'date', 'category', 'analyst', 'rating', 'price_from', 'price_to']  # header names
+        ratings_list = [row.xpath("td//text()") for row in table]
+        ratings_list = [
+            [val for val in row if val != "\n"] for row in ratings_list
+        ]  # remove new line entries
+
+        headers = [
+            "ticker",
+            "date",
+            "category",
+            "analyst",
+            "rating",
+            "price_from",
+            "price_to",
+        ]  # header names
         count = 0
 
         for row in ratings_list:
             if count == last_ratings:
                 break
 
-            price_from, price_to = 0, 0  # default values for len(row) == 4 , that is there is NO price information
+            price_from, price_to = (
+                0,
+                0,
+            )  # default values for len(row) == 4 , that is there is NO price information
             if len(row) == 5:
-                strings = row[4].split('→')
+                strings = row[4].split("→")
                 if len(strings) == 1:
-                    price_to = strings[0].strip(' ').strip('$')   # if only ONE price is avalable then it is 'price_to' value
+                    price_to = (
+                        strings[0].strip(" ").strip("$")
+                    )  # if only ONE price is available then it is 'price_to' value
                 else:
-                    price_from = strings[0].strip(' ').strip('$')  # both '_from' & '_to' prices available
-                    price_to = strings[1].strip(' ').strip('$')
-
-            elements = [ ticker ]
-            elements.append(datetime.datetime.strptime(row[0], '%b-%d-%y').strftime('%Y-%m-%d')) # convert date format
+                    price_from = (
+                        strings[0].strip(" ").strip("$")
+                    )  # both '_from' & '_to' prices available
+                    price_to = strings[1].strip(" ").strip("$")
+
+            elements = [
+                ticker,
+                datetime.datetime.strptime(row[0], "%b-%d-%y").strftime("%Y-%m-%d"),
+            ]
             elements.extend(row[1:3])
-            elements.append(row[3].replace('→', '->'))
+            elements.append(row[3].replace("→", "->"))
             elements.append(price_from)
             elements.append(price_to)
             data = dict(zip(headers, elements))
             analyst_price_targets.append(data)
             count += 1
-    except Exception as exc:
-        # print(f"-> Exception: {exc} parsing analysts' ratings for ticker {ticker}")
+    except Exception:
         pass
 
     return analyst_price_targets
 
 
-def download_ticker_details(page_content, **kwargs):
+def download_ticker_details(page_content: requests.Response, **kwargs):
     data = {}
-    ticker = kwargs["URL"].split('=')[1]
-    page_parsed = html.fromstring(page_content)
-    all_rows = [row.xpath('td//text()') for row in page_parsed.cssselect('tr[class="table-dark-row"]')]
+    ticker = kwargs["URL"].split("=")[1]
+    page_parsed = html.fromstring(page_content.text)
+
+    all_rows = [
+        row.xpath("td//text()")
+        for row in page_parsed.cssselect('tr[class="table-dark-row"]')
+    ]
 
     for row in all_rows:
         for column in range(0, 11):
@@ -127,5 +157,4 @@ def download_ticker_details(page_content, **kwargs):
     if len(data) == 0:
         print(f"-> Unable to parse page for ticker: {ticker}")
 
-    return {ticker: [data, get_analyst_price_targets_for_export(ticker, page_content)]}
-
+    return {ticker: [data, get_analyst_price_targets_for_export(ticker, page_parsed)]}
diff --git a/finviz/main_func.py b/finviz/main_func.py
index b005565..b17d709 100644
--- a/finviz/main_func.py
+++ b/finviz/main_func.py
@@ -3,9 +3,9 @@
 from finviz.helper_functions.request_functions import http_request_get
 from finviz.helper_functions.scraper_functions import get_table
 
-STOCK_URL = 'https://finviz.com/quote.ashx'
-NEWS_URL = 'https://finviz.com/news.ashx'
-CRYPTO_URL = 'https://finviz.com/crypto_performance.ashx'
+STOCK_URL = "https://finviz.com/quote.ashx"
+NEWS_URL = "https://finviz.com/news.ashx"
+CRYPTO_URL = "https://finviz.com/crypto_performance.ashx"
 STOCK_PAGE = {}
 
 
@@ -14,7 +14,8 @@ def get_page(ticker):
 
     if ticker not in STOCK_PAGE:
         STOCK_PAGE[ticker], _ = http_request_get(
-            url=STOCK_URL, payload={'t': ticker}, parse=True)
+            url=STOCK_URL, payload={"t": ticker}, parse=True
+        )
 
 
 def get_stock(ticker):
@@ -30,12 +31,14 @@ def get_stock(ticker):
     page_parsed = STOCK_PAGE[ticker]
 
     title = page_parsed.cssselect('table[class="fullview-title"]')[0]
-    keys = ['Company', 'Sector', 'Industry', 'Country']
+    keys = ["Company", "Sector", "Industry", "Country"]
     fields = [f.text_content() for f in title.cssselect('a[class="tab-link"]')]
     data = dict(zip(keys, fields))
 
-    all_rows = [row.xpath('td//text()')
-                for row in page_parsed.cssselect('tr[class="table-dark-row"]')]
+    all_rows = [
+        row.xpath("td//text()")
+        for row in page_parsed.cssselect('tr[class="table-dark-row"]')
+    ]
 
     for row in all_rows:
         for column in range(0, 11, 2):
@@ -55,8 +58,8 @@ def get_insider(ticker):
     get_page(ticker)
     page_parsed = STOCK_PAGE[ticker]
     table = page_parsed.cssselect('table[class="body-table"]')[0]
-    headers = table[0].xpath('td//text()')
-    data = [dict(zip(headers, row.xpath('td//text()'))) for row in table[1:]]
+    headers = table[0].xpath("td//text()")
+    data = [dict(zip(headers, row.xpath("td//text()"))) for row in table[1:]]
 
     return data
 
@@ -72,27 +75,22 @@ def get_news(ticker):
     get_page(ticker)
     page_parsed = STOCK_PAGE[ticker]
     all_news = page_parsed.cssselect('a[class="tab-link-news"]')
-    headlines = [row.xpath('text()')[0] for row in all_news]
-    urls = [row.get('href') for row in all_news]
+    headlines = [row.xpath("text()")[0] for row in all_news]
+    urls = [row.get("href") for row in all_news]
 
     return list(zip(headlines, urls))
 
 
-def get_all_news(ticker):
+def get_all_news():
     """
     Returns a list of sets containing time, headline and url
-
     :return: list
     """
 
-    get_page(ticker)
-    page_parsed = STOCK_PAGE[ticker]
-    all_dates = [row.text_content()
-                 for row in page_parsed.cssselect('td[class="nn-date"]')]
-    all_headlines = [row.text_content()
-                     for row in page_parsed.cssselect('a[class="nn-tab-link"]')]
-    all_links = [row.get('href')
-                 for row in page_parsed.cssselect('a[class="nn-tab-link"]')]
+    page_parsed, _ = http_request_get(url=NEWS_URL, parse=True)
+    all_dates = [row.text_content() for row in page_parsed.cssselect('td[class="nn-date"]')]
+    all_headlines = [row.text_content() for row in page_parsed.cssselect('a[class="nn-tab-link"]')]
+    all_links = [row.get('href') for row in page_parsed.cssselect('a[class="nn-tab-link"]')]
 
     return list(zip(all_dates, all_headlines, all_links))
 
@@ -106,8 +104,7 @@ def get_crypto(pair):
 
     page_parsed, _ = http_request_get(url=CRYPTO_URL, parse=True)
     page_html, _ = http_request_get(url=CRYPTO_URL, parse=False)
-    crypto_headers = page_parsed.cssselect('tr[valign="middle"]')[
-        0].xpath('td//text()')
+    crypto_headers = page_parsed.cssselect('tr[valign="middle"]')[0].xpath("td//text()")
     crypto_table_data = get_table(page_html, crypto_headers)
 
     return crypto_table_data[pair]
@@ -118,6 +115,7 @@ def get_analyst_price_targets(ticker, last_ratings=5):
     Returns a list of dictionaries containing all analyst ratings and Price targets
      - if any of 'price_from' or 'price_to' are not available in the DATA, then those values are set to default 0
     :param ticker: stock symbol
+    :param last_ratings: most recent ratings to pull
     :return: list
     """
 
@@ -126,14 +124,20 @@ def get_analyst_price_targets(ticker, last_ratings=5):
     try:
         get_page(ticker)
         page_parsed = STOCK_PAGE[ticker]
-        table = page_parsed.cssselect(
-            'table[class="fullview-ratings-outer"]')[0]
-        ratings_list = [row.xpath('td//text()') for row in table]
-        ratings_list = [[val for val in row if val != '\n']
-                        for row in ratings_list]  # remove new line entries
-
-        headers = ['date', 'category', 'analyst', 'rating',
-                   'price_from', 'price_to']  # header names
+        table = page_parsed.cssselect('table[class="fullview-ratings-outer"]')[0]
+        ratings_list = [row.xpath("td//text()") for row in table]
+        ratings_list = [
+            [val for val in row if val != "\n"] for row in ratings_list
+        ]  # remove new line entries
+
+        headers = [
+            "date",
+            "category",
+            "analyst",
+            "rating",
+            "price_from",
+            "price_to",
+        ]  # header names
         count = 0
 
         for row in ratings_list:
@@ -143,37 +147,38 @@ def get_analyst_price_targets(ticker, last_ratings=5):
             price_from, price_to = 0, 0
             if len(row) == 5:
 
-                strings = row[4].split('→')
+                strings = row[4].split("→")
                 # print(strings)
                 if len(strings) == 1:
                     # if only ONE price is avalable then it is 'price_to' value
-                    price_to = strings[0].strip(' ').strip('$')
+                    price_to = strings[0].strip(" ").strip("$")
                 else:
                     # both '_from' & '_to' prices available
-                    price_from = strings[0].strip(' ').strip('$')
-                    price_to = strings[1].strip(' ').strip('$')
+                    price_from = strings[0].strip(" ").strip("$")
+                    price_to = strings[1].strip(" ").strip("$")
             # only take first 4 elements, discard last element if exists
             elements = row[:4]
-            elements.append(datetime.datetime.strptime(
-                row[0], '%b-%d-%y').strftime('%Y-%m-%d'))  # convert date format
+            elements.append(
+                datetime.datetime.strptime(row[0], "%b-%d-%y").strftime("%Y-%m-%d")
+            )  # convert date format
             elements.extend(row[1:3])
-            elements.append(row[3].replace('→', '->'))
+            elements.append(row[3].replace("→", "->"))
             elements.append(price_from)
             elements.append(price_to)
 
             data = {
-                'date': elements[0],
-                'category':elements[1], 
-                'analyst': elements[2],
-                'rating':elements[3],
-                'price_from':float(price_from),
-                'price_to':float(price_to)
+                "date": elements[0],
+                "category": elements[1],
+                "analyst": elements[2],
+                "rating": elements[3],
+                "price_from": float(price_from),
+                "price_to": float(price_to),
             }
-            
+
             analyst_price_targets.append(data)
             count += 1
     except Exception as e:
-        #print("-> Exception: %s parsing analysts' ratings for ticker %s" % (str(e), ticker))
+        # print("-> Exception: %s parsing analysts' ratings for ticker %s" % (str(e), ticker))
         pass
 
     return analyst_price_targets
diff --git a/finviz/portfolio.py b/finviz/portfolio.py
index dc29036..e979954 100644
--- a/finviz/portfolio.py
+++ b/finviz/portfolio.py
@@ -5,25 +5,32 @@
 from user_agent import generate_user_agent
 
 from finviz.helper_functions.display_functions import create_table_string
-from finviz.helper_functions.error_handling import (
-    InvalidPortfolioID,
-    InvalidTicker,
-    UnexistingPortfolioName
-)
+from finviz.helper_functions.error_handling import (InvalidPortfolioID,
+                                                    InvalidTicker,
+                                                    NonexistentPortfolioName)
 from finviz.helper_functions.request_functions import http_request_get
 from finviz.helper_functions.scraper_functions import get_table
 
-LOGIN_URL = 'https://finviz.com/login_submit.ashx'
-PRICE_REQUEST_URL = 'https://finviz.com/request_quote.ashx'
-PORTFOLIO_URL = 'https://finviz.com/portfolio.ashx'
-PORTFOLIO_SUBMIT_URL = 'https://finviz.com/portfolio_submit.ashx'
+LOGIN_URL = "https://finviz.com/login_submit.ashx"
+PRICE_REQUEST_URL = "https://finviz.com/request_quote.ashx"
+PORTFOLIO_URL = "https://finviz.com/portfolio.ashx"
+PORTFOLIO_SUBMIT_URL = "https://finviz.com/portfolio_submit.ashx"
 PORTFOLIO_DIGIT_COUNT = 9  # Portfolio ID is always 9 digits
 PORTFOLIO_HEADERS = [
-    'No.', 'Ticker', 'Company',
-    'Price', 'Change%', 'Volume',
-    'Transaction', 'Date', 'Shares',
-    'Cost', 'Market Value', 'Gain$',
-    'Gain%', 'Change$'
+    "No.",
+    "Ticker",
+    "Company",
+    "Price",
+    "Change%",
+    "Volume",
+    "Transaction",
+    "Date",
+    "Shares",
+    "Cost",
+    "Market Value",
+    "Gain$",
+    "Gain%",
+    "Change$",
 ]
 
 
@@ -35,25 +42,25 @@ def __init__(self, email, password, portfolio=None):
         Logs in to FinViz and send a GET request to the portfolio.
         """
 
-        payload = {
-            'email': email,
-            'password': password
-        }
+        payload = {"email": email, "password": password}
 
         # Create a session and log in by sending a POST request
         self._session = requests.session()
-        auth_response = self._session.post(LOGIN_URL, data=payload, headers={'User-Agent': generate_user_agent()})
+        auth_response = self._session.post(
+            LOGIN_URL, data=payload, headers={"User-Agent": generate_user_agent()}
+        )
 
         if not auth_response.ok:  # If the post request wasn't successful
             auth_response.raise_for_status()
 
         # Get the parsed HTML and the URL of the base portfolio page
         self._page_content, self.portfolio_url = http_request_get(
-            url=PORTFOLIO_URL, session=self._session, parse=False)
+            url=PORTFOLIO_URL, session=self._session, parse=False
+        )
 
         # If the user has not created a portfolio it redirects the request to <url>?v=2)
         self.created = True
-        if self.portfolio_url == f'{PORTFOLIO_URL}?v=2':
+        if self.portfolio_url == f"{PORTFOLIO_URL}?v=2":
             self.created = False
 
         if self.created:
@@ -68,7 +75,7 @@ def __str__(self):
         table_list = [PORTFOLIO_HEADERS]
 
         for row in self.data:
-            table_list.append([row[col] or '' for col in PORTFOLIO_HEADERS])
+            table_list.append([row[col] or "" for col in PORTFOLIO_HEADERS])
 
         return create_table_string(table_list)
 
@@ -88,39 +95,40 @@ def create_portfolio(self, name, file, drop_invalid_ticker=False):
         """
 
         data = {
-            'portfolio_id': '0',
-            'portfolio_name': name,
+            "portfolio_id": "0",
+            "portfolio_name": name,
         }
 
-        with open(file, 'r') as infile:
+        with open(file, "r") as infile:
             reader = csv.reader(infile)
             next(reader, None)  # Skip the headers
 
             for row_number, row in enumerate(reader, 0):
                 row_number_string = str(row_number)
-                data['ticker' + row_number_string] = row[0]
-                data['transaction' + row_number_string] = row[1]
-                data['date' + row_number_string] = row[2]
-                data['shares' + row_number_string] = row[3]
+                data["ticker" + row_number_string] = row[0]
+                data["transaction" + row_number_string] = row[1]
+                data["date" + row_number_string] = row[2]
+                data["shares" + row_number_string] = row[3]
 
                 try:
                     # empty string is no price, so try get today's price
-                    assert (data['price' + row_number_string] != '')
-                    data['price' + row_number_string] = row[4]
+                    assert data["price" + row_number_string] != ""
+                    data["price" + row_number_string] = row[4]
                 except (IndexError, KeyError):
                     current_price_page, _ = http_request_get(
-                        PRICE_REQUEST_URL, payload={'t': row[0]}, parse=True)
+                        PRICE_REQUEST_URL, payload={"t": row[0]}, parse=True
+                    )
 
                     # if price not available on finviz don't upload that ticker to portfolio
-                    if current_price_page.text == 'NA':
+                    if current_price_page.text == "NA":
                         if not drop_invalid_ticker:
                             raise InvalidTicker(row[0])
-                        del data['ticker' + row_number_string]
-                        del data['transaction' + row_number_string]
-                        del data['date' + row_number_string]
-                        del data['shares' + row_number_string]
+                        del data["ticker" + row_number_string]
+                        del data["transaction" + row_number_string]
+                        del data["date" + row_number_string]
+                        del data["shares" + row_number_string]
                     else:
-                        data['price' + row_number_string] = current_price_page.text
+                        data["price" + row_number_string] = current_price_page.text
         self._session.post(PORTFOLIO_SUBMIT_URL, data=data)
 
     def __get_portfolio_url(self, portfolio_name):
@@ -132,15 +140,21 @@ def __get_portfolio_url(self, portfolio_name):
             if not len(str(portfolio_name)) == PORTFOLIO_DIGIT_COUNT:
                 raise InvalidPortfolioID(portfolio_name)
             else:
-                return http_request_get(url=f"{PORTFOLIO_URL}?pid={portfolio_name}",
-                                        session=self._session,
-                                        parse=False)
+                return http_request_get(
+                    url=f"{PORTFOLIO_URL}?pid={portfolio_name}",
+                    session=self._session,
+                    parse=False,
+                )
         else:  # else the user has passed a name
             # We remove the first element, since it's redundant
-            for portfolio in html.fromstring(self._page_content).cssselect('option')[1:]:
+            for portfolio in html.fromstring(self._page_content).cssselect("option")[
+                1:
+            ]:
                 if portfolio.text == portfolio_name:
-                    return http_request_get(url=f"{PORTFOLIO_URL}?pid={portfolio.get('value')}",
-                                            session=self._session,
-                                            parse=False)
+                    return http_request_get(
+                        url=f"{PORTFOLIO_URL}?pid={portfolio.get('value')}",
+                        session=self._session,
+                        parse=False,
+                    )
             # Raise Non-existing PortfolioName if none of the names match
-            raise UnexistingPortfolioName(portfolio_name)
+            raise NonexistentPortfolioName(portfolio_name)
diff --git a/finviz/screener.py b/finviz/screener.py
index 2fe869f..0766da8 100644
--- a/finviz/screener.py
+++ b/finviz/screener.py
@@ -9,21 +9,18 @@
 import finviz.helper_functions.scraper_functions as scrape
 from finviz.helper_functions.display_functions import create_table_string
 from finviz.helper_functions.error_handling import InvalidTableType, NoResults
-from finviz.helper_functions.request_functions import (
-    Connector,
-    http_request_get,
-    sequential_data_scrape
-)
+from finviz.helper_functions.request_functions import (http_request_get,
+                                                       sequential_data_scrape)
 from finviz.helper_functions.save_data import export_to_csv, export_to_db
 
 TABLE_TYPES = {
-    'Overview': '111',
-    'Valuation': '121',
-    'Ownership': '131',
-    'Performance': '141',
-    'Custom': '152',
-    'Financial': '161',
-    'Technical': '171'
+    "Overview": "111",
+    "Valuation": "121",
+    "Ownership": "131",
+    "Performance": "141",
+    "Custom": "152",
+    "Financial": "161",
+    "Technical": "171",
 }
 
 
@@ -43,24 +40,34 @@ def init_from_url(cls, url, rows=None):
 
         split_query = urlparse_qs(urlparse(url).query)
 
-        tickers = split_query['t'][0].split(',') if 't' in split_query else None
-        filters = split_query['f'][0].split(',') if 'f' in split_query else None
-        custom = split_query['c'][0].split(',') if 'c' in split_query else None
-        order = split_query['o'][0] if 'o' in split_query else ''
-        signal = split_query['s'][0] if 's' in split_query else ''
+        tickers = split_query["t"][0].split(",") if "t" in split_query else None
+        filters = split_query["f"][0].split(",") if "f" in split_query else None
+        custom = split_query["c"][0].split(",") if "c" in split_query else None
+        order = split_query["o"][0] if "o" in split_query else ""
+        signal = split_query["s"][0] if "s" in split_query else ""
 
-        table = 'Overview'
-        if 'v' in split_query:
+        table = "Overview"
+        if "v" in split_query:
             table_numbers_types = {v: k for k, v in TABLE_TYPES.items()}
-            table_number_string = split_query['v'][0][0:3]
+            table_number_string = split_query["v"][0][0:3]
             try:
                 table = table_numbers_types[table_number_string]
             except KeyError:
-                raise InvalidTableType(split_query['v'][0])
+                raise InvalidTableType(split_query["v"][0])
 
         return cls(tickers, filters, rows, order, signal, table, custom)
 
-    def __init__(self, tickers=None, filters=None, rows=None, order='', signal='', table=None, custom=None, delay=0.5):
+    def __init__(
+        self,
+        tickers=None,
+        filters=None,
+        rows=None,
+        order="",
+        signal="",
+        table=None,
+        custom=None,
+        delay=0.5,
+    ):
         """
         Initializes all variables to its values
 
@@ -93,18 +100,20 @@ def __init__(self, tickers=None, filters=None, rows=None, order='', signal='', t
             self._filters = filters
 
         if table is None:
-            self._table = 'Overview'
+            self._table = "Overview"
         else:
             self._table = self.__check_table(table)
 
         if custom is None:
             self._custom = []
         else:
-            self._table = '152'
+            self._table = "152"
             self._custom = custom
 
-            if '0' not in self._custom:  # 0 (No.) is required for the sequence algorithm to work
-                self._custom = ['0'] + self._custom
+            if (
+                "0" not in self._custom
+            ):  # 0 (No.) is required for the sequence algorithm to work
+                self._custom = ["0"] + self._custom
 
         self._rows = rows
         self._order = order
@@ -114,7 +123,16 @@ def __init__(self, tickers=None, filters=None, rows=None, order='', signal='', t
         self.analysis = []
         self.data = self.__search_screener()
 
-    def __call__(self, tickers=None, filters=None, rows=None, order='', signal='', table=None, custom=None):
+    def __call__(
+        self,
+        tickers=None,
+        filters=None,
+        rows=None,
+        order="",
+        signal="",
+        table=None,
+        custom=None,
+    ):
         """
         Adds more filters to the screener. Example usage:
 
@@ -157,20 +175,22 @@ def __str__(self):
         table_list = [self.headers]
 
         for row in self.data:
-            table_list.append([row[col] or '' for col in self.headers])
+            table_list.append([row[col] or "" for col in self.headers])
 
         return create_table_string(table_list)
 
     def __repr__(self):
         """ Returns a string representation of the parameter's values. """
 
-        values = f'tickers: {tuple(self._tickers)}\n' \
-                 f'filters: {tuple(self._filters)}\n' \
-                 f'rows: {self._rows}\n' \
-                 f'order: {self._order}\n' \
-                 f'signal: {self._signal}\n' \
-                 f'table: {self._table}\n' \
-                 f'table: {self._custom}'
+        values = (
+            f"tickers: {tuple(self._tickers)}\n"
+            f"filters: {tuple(self._filters)}\n"
+            f"rows: {self._rows}\n"
+            f"order: {self._order}\n"
+            f"signal: {self._signal}\n"
+            f"table: {self._table}\n"
+            f"table: {self._custom}"
+        )
 
         return values
 
@@ -205,38 +225,40 @@ def load_filter_dict(reload=True):
 
         # Get location of filter.json
         json_directory = pathlib.Path(__file__).parent
-        json_file = pathlib.Path.joinpath(json_directory, 'filters.json')
+        json_file = pathlib.Path.joinpath(json_directory, "filters.json")
 
         # Reload the filters JSON file if present and requested
         if reload and json_file.is_file():
-            with open(json_file, 'r') as fp:
+            with open(json_file, "r") as fp:
                 return json.load(fp)
 
         # Get html from main filter page, ft=4 ensures all filters are present
         hdr = {
-            'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11'}
+            "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) "
+            "Chrome/23.0.1271.64 Safari/537.11"
+        }
         url = "https://finviz.com/screener.ashx?ft=4"
         req = urllib.request.Request(url, headers=hdr)
         with urllib.request.urlopen(req) as response:
-            html = response.read().decode('utf-8')
+            html = response.read().decode("utf-8")
 
         # Parse html and locate table we are interested in.
         # Use one of the text values and get the parent table from that
-        bs = BeautifulSoup(html, 'html.parser')
+        bs = BeautifulSoup(html, "html.parser")
         filters_table = None
-        for td in bs.find_all('td'):
-            if td.get_text().strip() == 'Exchange':
-                filters_table = td.find_parent('table')
+        for td in bs.find_all("td"):
+            if td.get_text().strip() == "Exchange":
+                filters_table = td.find_parent("table")
         if filters_table is None:
-            raise Exception('Could not locate filter parameters')
+            raise Exception("Could not locate filter parameters")
 
         # Delete all div tags, we don't need them
-        for div in filters_table.find_all('div'):
+        for div in filters_table.find_all("div"):
             div.decompose()
 
         # Populate dict with filtering options and corresponding filter tags
         filter_dict = {}
-        td_list = filters_table.find_all('td')
+        td_list = filters_table.find_all("td")
 
         for i in range(0, len(td_list) - 2, 2):
             current_dict = {}
@@ -247,14 +269,14 @@ def load_filter_dict(reload=True):
             filter_text = td_list[i].get_text().strip()
 
             # Odd td elements contain the filter tag and options
-            selections = td_list[i + 1].find('select')
-            filter_name = selections.get('data-filter').strip()
+            selections = td_list[i + 1].find("select")
+            filter_name = selections.get("data-filter").strip()
 
             # Store filter options for current filter
-            options = selections.find_all('option', {'value': True})
+            options = selections.find_all("option", {"value": True})
             for opt in options:
                 # Encoded filter string
-                value = opt.get('value').strip()
+                value = opt.get("value").strip()
 
                 # String shown in pull-down menu
                 text = opt.get_text()
@@ -271,16 +293,16 @@ def load_filter_dict(reload=True):
 
         # Save filter dict to finviz directory
         try:
-            with open(json_file, 'w') as fp:
+            with open(json_file, "w") as fp:
                 json.dump(filter_dict, fp)
         except Exception as e:
             print(e)
-            print('Unable to write to file{}'.format(json_file))
+            print("Unable to write to file{}".format(json_file))
 
         return filter_dict
 
     def to_sqlite(self, filename):
-        """ Exports the generated table into a SQLite database.
+        """Exports the generated table into a SQLite database.
 
         :param filename: SQLite database file path
         :type filename: str
@@ -289,23 +311,34 @@ def to_sqlite(self, filename):
         export_to_db(self.headers, self.data, filename)
 
     def to_csv(self, filename: str):
-        """ Exports the generated table into a CSV file.
+        """Exports the generated table into a CSV file.
         Returns a CSV string if filename is None.
 
         :param filename: CSV file path
         :type filename: str
         """
 
-        if filename and filename.endswith('.csv'):
+        if filename and filename.endswith(".csv"):
             filename = filename[:-4]
 
         if len(self.analysis) > 0:
-            export_to_csv(['ticker', 'date', 'category', 'analyst', 'rating', 'price_from', 'price_to'],
-                          self.analysis, f"{filename}-analysts.csv")
+            export_to_csv(
+                [
+                    "ticker",
+                    "date",
+                    "category",
+                    "analyst",
+                    "rating",
+                    "price_from",
+                    "price_to",
+                ],
+                self.analysis,
+                f"{filename}-analysts.csv",
+            )
 
         return export_to_csv(self.headers, self.data, f"{filename}.csv")
 
-    def get_charts(self, period='d', size='l', chart_type='c', ta='1'):
+    def get_charts(self, period="d", size="l", chart_type="c", ta="1"):
         """
         Downloads the charts of all tickers shown by the table.
 
@@ -319,40 +352,38 @@ def get_charts(self, period='d', size='l', chart_type='c', ta='1'):
         :type ta: str
         """
 
-        payload = {
-            'ty': chart_type,
-            'ta': ta,
-            'p': period,
-            's': size
-        }
-
-        base_url = f"https://finviz.com/chart.ashx?{urlencode(payload)}"
-        chart_urls = []
-
-        for row in self.data:
-            chart_urls.append(f"{base_url}&t={row.get('Ticker')}")
+        encoded_payload = urlencode(
+            {"ty": chart_type, "ta": ta, "p": period, "s": size}
+        )
 
-        async_connector = Connector(scrape.download_chart_image, chart_urls)
-        async_connector.run_connector()
+        sequential_data_scrape(
+            scrape.download_chart_image,
+            [
+                f"https://finviz.com/chart.ashx?{encoded_payload}&t={row.get('Ticker')}"
+                for row in self.data
+            ],
+            self._delay,
+        )
 
     def get_ticker_details(self):
         """
         Downloads the details of all tickers shown by the table.
         """
 
-        base_url = 'https://finviz.com/quote.ashx?'
-        ticker_urls = []
-
-        for row in self.data:
-            ticker_urls.append(f"{base_url}&t={row.get('Ticker')}")
-
-        ticker_data = sequential_data_scrape(scrape.download_ticker_details, ticker_urls, self._delay)
+        ticker_data = sequential_data_scrape(
+            scrape.download_ticker_details,
+            [
+                f"https://finviz.com/quote.ashx?&t={row.get('Ticker')}"
+                for row in self.data
+            ],
+            self._delay,
+        )
 
         for entry in ticker_data:
             for key, value in entry.items():
                 for ticker_generic in self.data:
-                    if ticker_generic.get('Ticker') == key:
-                        if 'Sales' not in self.headers:
+                    if ticker_generic.get("Ticker") == key:
+                        if "Sales" not in self.headers:
                             self.headers.extend(list(value[0].keys()))
 
                         ticker_generic.update(value[0])
@@ -369,7 +400,7 @@ def __check_rows(self):
         self._total_rows = scrape.get_total_rows(self._page_content)
 
         if self._total_rows == 0:
-            raise NoResults(self._url.split('?')[1])
+            raise NoResults(self._url.split("?")[1])
         elif self._rows is None or self._rows > self._total_rows:
             return self._total_rows
         else:
@@ -378,24 +409,34 @@ def __check_rows(self):
     def __get_table_headers(self):
         """ Private function used to return table headers. """
 
-        return self._page_content.cssselect('tr[valign="middle"]')[0].xpath('td//text()')
+        return self._page_content.cssselect('tr[valign="middle"]')[0].xpath(
+            "td//text()"
+        )
 
     def __search_screener(self):
         """ Private function used to return data from the FinViz screener. """
 
-        self._page_content, self._url = http_request_get('https://finviz.com/screener.ashx', payload={
-            'v': self._table,
-            't': ','.join(self._tickers),
-            'f': ','.join(self._filters),
-            'o': self._order,
-            's': self._signal,
-            'c': ','.join(self._custom)
-        })
+        self._page_content, self._url = http_request_get(
+            "https://finviz.com/screener.ashx",
+            payload={
+                "v": self._table,
+                "t": ",".join(self._tickers),
+                "f": ",".join(self._filters),
+                "o": self._order,
+                "s": self._signal,
+                "c": ",".join(self._custom),
+            },
+        )
 
         self._rows = self.__check_rows()
         self.headers = self.__get_table_headers()
-        page_urls = scrape.get_page_urls(self._page_content, self._rows, self._url)
-        pages_data = sequential_data_scrape(scrape.get_table, page_urls, self._delay, self.headers, self._rows)
+        pages_data = sequential_data_scrape(
+            scrape.get_table,
+            scrape.get_page_urls(self._page_content, self._rows, self._url),
+            self._delay,
+            self.headers,
+            self._rows,
+        )
 
         data = []
         for page in pages_data:
diff --git a/pyproject.toml b/pyproject.toml
index bd828a8..1bba700 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [tool.pytest.ini_options]
 minversion = "6.0"
-addopts = "-s"
 filterwarnings = [
-    "ignore::DeprecationWarning"
+    "ignore::DeprecationWarning",
+    "ignore::urllib3.exceptions.InsecureRequestWarning",
 ]
diff --git a/requirements-dev.txt b/requirements-dev.txt
new file mode 100644
index 0000000..02420df
--- /dev/null
+++ b/requirements-dev.txt
@@ -0,0 +1,4 @@
+black~=0.10.1
+isort~=5.6.4
+pytest==6.1.1
+python-dateutil~=1.15.0
diff --git a/requirements.txt b/requirements.txt
index c8698cd..9da64c9 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,8 +3,6 @@ beautifulsoup4~=4.9.3
 requests~=2.24.0
 aiohttp~=3.6.2
 lxml~=4.6.1
-pytest==6.1.1
-isort==5.6.4
 urllib3~=1.25.10
 user_agent~=0.1.9
 cssselect~=1.1.0
diff --git a/setup.py b/setup.py
index 6537f80..187d6a3 100644
--- a/setup.py
+++ b/setup.py
@@ -1,32 +1,29 @@
 from distutils.core import setup
 
 setup(
-    name='finviz',
-    packages=[
-        'finviz',
-        'finviz.helper_functions'
-    ],
-    version='1.3.4',
-    license='MIT',
-    description='Unofficial API for finviz.com',
-    author='Mario Stoev',
-    author_email='bg.mstoev@gmail.com',
-    url='https://github.com/mariostoev/finviz',
-    download_url='https://github.com/mariostoev/finviz/archive/1.3.4.tar.gz',
-    keywords=['finviz', 'api', 'screener', 'finviz api', 'charts', 'scraper'],
+    name="finviz",
+    packages=["finviz", "finviz.helper_functions"],
+    version="1.3.4",
+    license="MIT",
+    description="Unofficial API for finviz.com",
+    author="Mario Stoev",
+    author_email="bg.mstoev@gmail.com",
+    url="https://github.com/mariostoev/finviz",
+    download_url="https://github.com/mariostoev/finviz/archive/1.3.4.tar.gz",
+    keywords=["finviz", "api", "screener", "finviz api", "charts", "scraper"],
     install_requires=[
-        'lxml',
-        'requests',
-        'aiohttp',
-        'urllib3',
-        'cssselect',
-        'user_agent'
+        "lxml",
+        "requests",
+        "aiohttp",
+        "urllib3",
+        "cssselect",
+        "user_agent",
     ],
     classifiers=[
-        'Development Status :: 5 - Production/Stable',
-        'Intended Audience :: Developers',
-        'Topic :: Software Development :: Build Tools',
-        'License :: OSI Approved :: MIT License',
-        'Programming Language :: Python :: 3.6',
+        "Development Status :: 5 - Production/Stable",
+        "Intended Audience :: Developers",
+        "Topic :: Software Development :: Build Tools",
+        "License :: OSI Approved :: MIT License",
+        "Programming Language :: Python :: 3.6",
     ],
 )


Test patch:
diff --git a/finviz/tests/test_screener.py b/finviz/tests/test_screener.py
index 2c929a2..3b2b404 100644
--- a/finviz/tests/test_screener.py
+++ b/finviz/tests/test_screener.py
@@ -1,23 +1,105 @@
+from unittest.mock import patch
+
+import lxml
+from dateutil.parser import parse
+
+from finviz.main_func import get_all_news, get_analyst_price_targets
 from finviz.screener import Screener
 
 
 class TestScreener:
-	""" Unit tests for Screener app """
-	def test_get_screener_data_sequential_requests(self):
-		stocks = Screener(filters=['sh_curvol_o300', 'ta_highlow52w_b0to10h', 'ind_stocksonly'])
+    """ Unit tests for Screener app """
+
+    def test_get_screener_data_sequential_requests(self):
+        """ Tests that basic Screener example returns correct number of stocks. """
+        stock_list = Screener(
+            filters=["sh_curvol_o300", "ta_highlow52w_b0to10h", "ind_stocksonly"]
+        )
+
+        count = 0
+        for _ in stock_list:
+            count += 1
+
+        assert len(stock_list) == count
+
+    def test_screener_stability(self):
+        """ Requested in SD-77: https://github.com/mariostoev/finviz/issues/77 """
+        filters = [
+            "sh_avgvol_o100",  # average daily volume > 100k shares
+            "sh_curvol_o100",  # current daily volume > 100k shares
+            "sh_float_u50",  # floating stocks < 50m shares
+            "sh_price_u3",  # current price is under $3
+        ]
+        stock_list = Screener(filters=filters, table="Performance")
+
+        count = 0
+        for _ in stock_list:
+            count += 1
+
+        assert len(stock_list) == count
+
+    def test_get_ticker_details_sequential_requests(self):
+        """ Tests that `get_ticker_details` method returns correct number of ticker details. """
+        stocks = Screener(
+            filters=[
+                "sh_curvol_o300",
+                "ta_highlow52w_b0to10h",
+                "ind_stocksonly",
+                "sh_outstanding_o1000",
+            ]
+        )
+        ticker_details = stocks.get_ticker_details()
+
+        count = 0
+        for _ in ticker_details:
+            count += 1
+
+        assert len(stocks) == count == len(ticker_details)
+
+    @patch("finviz.screener.scrape.download_chart_image")
+    def test_get_charts_sequential_requests(self, patched_download_chart_image):
+        """ Tests that `get_charts` method returns correct number of valid charts. """
+
+        stocks = Screener(
+            filters=[
+                "sh_curvol_o20000",
+                "ta_highlow52w_b0to10h",
+                "ind_stocksonly",
+                "sh_outstanding_o1000",
+            ]
+        )
+
+        count = 0
+        for _ in stocks:
+            count += 1
+
+        stocks.get_charts()
+
+        for call in patched_download_chart_image.call_args_list:
+            assert call.kwargs["URL"]
+            assert call.args[0]
+        assert len(stocks) == count == patched_download_chart_image.call_count
+
+
+def test_get_analyst_price_targets():
+    """ Verifies `get_analyst_price_targets` results' types are valid. """
 
-		count = 0
-		for _ in stocks:
-			count += 1
+    price_target = get_analyst_price_targets("AAPL")[0]
+    assert price_target
 
-		assert len(stocks) == count
+    try:
+        parse(price_target["date"])
+    except ValueError:
+        assert False
 
-	def test_get_ticker_details_sequential_requests(self):
-		stocks = Screener(filters=['sh_curvol_o300', 'ta_highlow52w_b0to10h', 'ind_stocksonly', 'sh_outstanding_o1000'])
-		ticker_details = stocks.get_ticker_details()
+    assert type(price_target["category"]) == lxml.etree._ElementUnicodeResult
+    assert type(price_target["analyst"]) == lxml.etree._ElementUnicodeResult
+    assert type(price_target["rating"]) == lxml.etree._ElementUnicodeResult
+    assert type(price_target.get("price_from", 0.0)) == float
+    assert type(price_target.get("price_to", 0.0)) == float
 
-		count = 0
-		for _ in ticker_details:
-			count += 1
 
-		assert len(stocks) == count == len(ticker_details)
+def test_get_all_news():
+    """ Verifies news results are greater than 0. """
+    news = get_all_news()
+    assert len(news) > 0