diff --git a/CHANGES b/CHANGES index 1a2a6bc..fcf8842 100644 --- a/CHANGES +++ b/CHANGES @@ -56,4 +56,6 @@ 1.16 07/17/2023 -- Minor code cleanup in test_yahoofinancials.py. 1.16 07/17/2023 -- Fixed HTTP 401 error when executing get_current_price() as reported in #152. 1.17 10/30/2023 -- Added a fix for 404 error reported in #157. -1.18 12/12/2023 -- Merged in branch from shaunpatterson to fix #164. \ No newline at end of file +1.18 12/09/2023 -- Merged in branch from shaunpatterson to fix #164. +1.19 12/12/2023 -- Refactored session management system to handle cookie and crumbs better. +1.19 12/12/2023 -- Added fixes for #167, #166, #160. diff --git a/README.rst b/README.rst index 58eb860..b0468fa 100644 --- a/README.rst +++ b/README.rst @@ -16,9 +16,9 @@ A python module that returns stock, cryptocurrency, forex, mutual fund, commodit .. image:: https://static.pepy.tech/badge/yahoofinancials/week :target: https://pepy.tech/project/yahoofinancials -Current Version: v1.18 +Current Version: v1.19 -Version Released: 12/09/2023 +Version Released: 12/12/2023 Report any bugs by opening an issue here: https://github.com/JECSand/yahoofinancials/issues @@ -48,7 +48,6 @@ A powerful financial data module used for pulling both fundamental and technical Installation ------------- - yahoofinancials runs on Python 3.7, 3.8, 3.9, 3.10, and 3.11. -- This package depends on pytz & requests to work. 1. Installation using pip: diff --git a/setup.py b/setup.py index 12fe761..dd8aff8 100644 --- a/setup.py +++ b/setup.py @@ -10,19 +10,24 @@ setup( name='yahoofinancials', - version='1.18', + version='1.19', description='A powerful financial data module used for pulling both fundamental and technical data from Yahoo Finance', long_description=long_description, url='https://github.com/JECSand/yahoofinancials', - download_url='https://github.com/JECSand/yahoofinancials/archive/1.18.tar.gz', + download_url='https://github.com/JECSand/yahoofinancials/archive/1.19.tar.gz', author='Connor Sanders', author_email='connor@exceleri.com', license='MIT', keywords=['finance data', 'stocks', 'commodities', 'cryptocurrencies', 'currencies', 'forex', 'yahoo finance'], packages=['yahoofinancials'], install_requires=[ - "pytz", - "requests>=2.26", + "pytz>=2022.5", + "requests>=2.31", + "appdirs>=1.4.4", + "frozendict>=2.3.4", + "peewee>=3.16.2", + "beautifulsoup4>=4.11.1", + "lxml>=4.9.1", ], classifiers=[ 'Development Status :: 5 - Production/Stable', diff --git a/test/test_yahoofinancials.py b/test/test_yahoofinancials.py index f5456ff..6597474 100644 --- a/test/test_yahoofinancials.py +++ b/test/test_yahoofinancials.py @@ -1,5 +1,5 @@ -# YahooFinancials Unit Tests v1.18 -# Version Released: 12/09/2023 +# YahooFinancials Unit Tests v1.19 +# Version Released: 12/12/2023 # Author: Connor Sanders # Tested on Python 3.7, 3.8, 3.9, 3.10, and 3.11 # Copyright (c) 2023 Connor Sanders @@ -107,66 +107,85 @@ def test_yf_concurrency(self): def test_yf_module_methods(self): # 10 Day Average Daily Volume - if isinstance(self.test_yf_stock_single.get_ten_day_avg_daily_volume(), int): + out = self.test_yf_stock_single.get_ten_day_avg_daily_volume() + if isinstance(out, int): self.assertEqual(True, True) else: self.assertEqual(False, True) # Stock Current Price - if isinstance(self.test_yf_stock_single.get_current_price(), float): + out = self.test_yf_stock_single.get_current_price() + if isinstance(out, float): self.assertEqual(True, True) else: self.assertEqual(False, True) + # Stock Net Income - if isinstance(self.test_yf_stock_single.get_net_income(), float): + out = self.test_yf_stock_single.get_net_income() + if isinstance(out, float): self.assertEqual(True, True) else: self.assertEqual(False, True) # Stock Financial Data - if self.test_yf_stock_single.get_financial_data().get("C").get("financialCurrency") == "USD": + out = self.test_yf_stock_single.get_financial_data() + if out.get("C").get("financialCurrency") == "USD": self.assertEqual(True, True) else: self.assertEqual(False, True) # Stock Profile Data - if self.test_yf_stock_single.get_stock_profile_data().get("C").get("sector") == "Financial Services": + out = self.test_yf_stock_single.get_stock_profile_data() + if out.get("C").get("sector") == "Financial Services": self.assertEqual(True, True) else: self.assertEqual(False, True) # Stock Summary Data - if self.test_yf_stock_single.get_summary_data().get("C").get("currency") == "USD": + out = self.test_yf_stock_single.get_summary_data() + if out.get("C").get("currency") == "USD": self.assertEqual(True, True) else: self.assertEqual(False, True) # Stock Price Data - if self.test_yf_stock_single.get_stock_price_data().get("C").get("exchangeName") == "NYSE": + out = self.test_yf_stock_single.get_stock_price_data() + if out.get("C").get("exchangeName") == "NYSE": self.assertEqual(True, True) else: self.assertEqual(False, True) # Stock Key Statistics - if isinstance(self.test_yf_stock_single.get_key_statistics_data().get("C").get("forwardPE"), float): + out = self.test_yf_stock_single.get_key_statistics_data() + if isinstance(out.get("C").get("forwardPE"), float): self.assertEqual(True, True) else: self.assertEqual(False, True) # Stock ESG SCORES - if self.test_yf_stock_single.get_esg_score_data().get("C").get("peerGroup") == "Banks": + out = self.test_yf_stock_single.get_esg_score_data() + if out.get("C").get("peerGroup") == "Banks": self.assertEqual(True, True) else: self.assertEqual(False, True) # Treasuries - if isinstance(self.test_yf_treasuries_single.get_current_price(), float): + out = self.test_yf_stock_single.get_current_price() + if isinstance(out, float): self.assertEqual(True, True) else: self.assertEqual(False, True) # Stock Earnings data check - if isinstance(self.test_yf_stock_single.get_stock_earnings_data().get("C").get("earningsChart").get("quarterly")[0].get("actual"), float): + out = self.test_yf_stock_single.get_stock_earnings_data() + if isinstance(out.get("C").get("earningsChart").get("quarterly")[0].get("actual"), float): + self.assertEqual(True, True) + else: + self.assertEqual(False, True) + + # Stock Data + out = self.test_yf_stock_single.get_stock_data() + if out.get("C").get("sector") == "Financial Services": self.assertEqual(True, True) else: self.assertEqual(False, True) diff --git a/yahoofinancials/cache.py b/yahoofinancials/cache.py new file mode 100644 index 0000000..fb9ed94 --- /dev/null +++ b/yahoofinancials/cache.py @@ -0,0 +1,397 @@ +import logging +import peewee as _peewee +from threading import Lock +import os as _os +import appdirs as _ad +import atexit as _atexit +import datetime as _datetime +import pickle as _pkl + +_cache_init_lock = Lock() + + +# -------------- +# TimeZone cache +# -------------- + +class _TzCacheException(Exception): + pass + + +class _TzCacheDummy: + """Dummy cache to use if tz cache is disabled""" + + def lookup(self, tkr): + return None + + def store(self, tkr, tz): + pass + + @property + def tz_db(self): + return None + + +class _TzCacheManager: + _tz_cache = None + + @classmethod + def get_tz_cache(cls): + if cls._tz_cache is None: + with _cache_init_lock: + cls._initialise() + return cls._tz_cache + + @classmethod + def _initialise(cls, cache_dir=None): + cls._tz_cache = _TzCache() + + +class _TzDBManager: + _db = None + _cache_dir = _os.path.join(_ad.user_cache_dir(), "py-yfinance") + + @classmethod + def get_database(cls): + if cls._db is None: + cls._initialise() + return cls._db + + @classmethod + def close_db(cls): + if cls._db is not None: + try: + cls._db.close() + except Exception: + # Must discard exceptions because Python trying to quit. + pass + + @classmethod + def _initialise(cls, cache_dir=None): + if cache_dir is not None: + cls._cache_dir = cache_dir + + if not _os.path.isdir(cls._cache_dir): + try: + _os.makedirs(cls._cache_dir) + except OSError as err: + raise _TzCacheException( + f"yahoofinancials: Error creating TzCache folder: '{cls._cache_dir}' reason: {err}") + elif not (_os.access(cls._cache_dir, _os.R_OK) and _os.access(cls._cache_dir, _os.W_OK)): + raise _TzCacheException(f"yahoofinancials: Cannot read and write in TzCache folder: '{cls._cache_dir}'") + + cls._db = _peewee.SqliteDatabase( + _os.path.join(cls._cache_dir, 'tkr-tz.db'), + pragmas={'journal_mode': 'wal', 'cache_size': -64} + ) + + old_cache_file_path = _os.path.join(cls._cache_dir, "tkr-tz.csv") + if _os.path.isfile(old_cache_file_path): + _os.remove(old_cache_file_path) + + @classmethod + def set_location(cls, new_cache_dir): + if cls._db is not None: + cls._db.close() + cls._db = None + cls._cache_dir = new_cache_dir + + @classmethod + def get_location(cls): + return cls._cache_dir + + +# close DB when Python exists +_atexit.register(_TzDBManager.close_db) + +tz_db_proxy = _peewee.Proxy() + + +class _KV(_peewee.Model): + key = _peewee.CharField(primary_key=True) + value = _peewee.CharField(null=True) + + class Meta: + database = tz_db_proxy + without_rowid = True + + +class _TzCache: + def __init__(self): + self.initialised = -1 + self.db = None + self.dummy = False + + def get_db(self): + if self.db is not None: + return self.db + + try: + self.db = _TzDBManager.get_database() + except _TzCacheException as err: + logging.info(f"yahoofinancials: Failed to create TzCache, reason: {err}. " + "TzCache will not be used. " + "Tip: You can direct cache to use a different location with 'set_tz_cache_location(" + "mylocation)'") + self.dummy = True + return None + return self.db + + def initialise(self): + if self.initialised != -1: + return + + db = self.get_db() + if db is None: + self.initialised = 0 # failure + return + + db.connect() + tz_db_proxy.initialize(db) + db.create_tables([_KV]) + self.initialised = 1 # success + + def lookup(self, key): + if self.dummy: + return None + + if self.initialised == -1: + self.initialise() + + if self.initialised == 0: # failure + return None + + try: + return _KV.get(_KV.key == key).value + except _KV.DoesNotExist: + return None + + def store(self, key, value): + if self.dummy: + return + + if self.initialised == -1: + self.initialise() + + if self.initialised == 0: # failure + return + + db = self.get_db() + if db is None: + return + try: + if value is None: + q = _KV.delete().where(_KV.key == key) + q.execute() + return + with db.atomic(): + _KV.insert(key=key, value=value).execute() + except _peewee.IntegrityError: + # Integrity error means the key already exists. Try updating the key. + old_value = self.lookup(key) + if old_value != value: + logging.debug(f"yahoofinancials: Value for key {key} changed from {old_value} to {value}.") + with db.atomic(): + q = _KV.update(value=value).where(_KV.key == key) + q.execute() + + +def get_tz_cache(): + return _TzCacheManager.get_tz_cache() + + +def set_tz_cache_location(cache_dir: str): + """ + Sets the path to create the "py-yfinance" cache folder in. + Useful if the default folder returned by "appdir.user_cache_dir()" is not writable. + Must be called before cache is used (that is, before fetching tickers). + :param cache_dir: Path to use for caches + :return: None + """ + _TzDBManager.set_location(cache_dir) + + +# -------------- +# Cookie cache +# -------------- + +class _CookieCacheException(Exception): + pass + + +class _CookieCacheDummy: + """Dummy cache to use if Cookie cache is disabled""" + + def lookup(self, tkr): + return None + + def store(self, tkr, Cookie): + pass + + @property + def Cookie_db(self): + return None + + +class _CookieCacheManager: + _Cookie_cache = None + + @classmethod + def get_cookie_cache(cls): + if cls._Cookie_cache is None: + with _cache_init_lock: + cls._initialise() + return cls._Cookie_cache + + @classmethod + def _initialise(cls, cache_dir=None): + cls._Cookie_cache = _CookieCache() + + +class _CookieDBManager: + _db = None + _cache_dir = _os.path.join(_ad.user_cache_dir(), "py-yfinance") + + @classmethod + def get_database(cls): + if cls._db is None: + cls._initialise() + return cls._db + + @classmethod + def close_db(cls): + if cls._db is not None: + try: + cls._db.close() + except Exception: + # Must discard exceptions because Python trying to quit. + pass + + @classmethod + def _initialise(cls, cache_dir=None): + if cache_dir is not None: + cls._cache_dir = cache_dir + + if not _os.path.isdir(cls._cache_dir): + try: + _os.makedirs(cls._cache_dir) + except OSError as err: + raise _CookieCacheException( + f"yahoofinancials: Error creating CookieCache folder: '{cls._cache_dir}' reason: {err}") + elif not (_os.access(cls._cache_dir, _os.R_OK) and _os.access(cls._cache_dir, _os.W_OK)): + raise _CookieCacheException( + f"yahoofinancials: Cannot read and write in CookieCache folder: '{cls._cache_dir}'") + + cls._db = _peewee.SqliteDatabase( + _os.path.join(cls._cache_dir, 'cookies.db'), + pragmas={'journal_mode': 'wal', 'cache_size': -64} + ) + + @classmethod + def set_location(cls, new_cache_dir): + if cls._db is not None: + cls._db.close() + cls._db = None + cls._cache_dir = new_cache_dir + + @classmethod + def get_location(cls): + return cls._cache_dir + + +# close DB when Python exists +_atexit.register(_CookieDBManager.close_db) + +Cookie_db_proxy = _peewee.Proxy() + + +class _CookieSchema(_peewee.Model): + strategy = _peewee.CharField(primary_key=True) + fetch_date = _peewee.DateTimeField(default=_datetime.datetime.now) + + # Which cookie type depends on strategy + cookie_bytes = _peewee.BlobField() + + class Meta: + database = Cookie_db_proxy + without_rowid = True + + +class _CookieCache: + def __init__(self): + self.initialised = -1 + self.db = None + self.dummy = False + + def get_db(self): + if self.db is not None: + return self.db + + try: + self.db = _CookieDBManager.get_database() + except _CookieCacheException as err: + logging.info(f"yahoofinancials: Failed to create CookieCache, reason: {err}. " + "CookieCache will not be used. " + "Tip: You can direct cache to use a different location with 'set_tz_cache_location(" + "mylocation)'") + self.dummy = True + return None + return self.db + + def initialise(self): + if self.initialised != -1: + return + db = self.get_db() + if db is None: + self.initialised = 0 # failure + return + db.connect() + Cookie_db_proxy.initialize(db) + db.create_tables([_CookieSchema]) + self.initialised = 1 # success + + def lookup(self, strategy): + if self.dummy: + return None + if self.initialised == -1: + self.initialise() + if self.initialised == 0: # failure + return None + try: + data = _CookieSchema.get(_CookieSchema.strategy == strategy) + cookie = _pkl.loads(data.cookie_bytes) + return {'cookie': cookie, 'age': _datetime.datetime.now() - data.fetch_date} + except _CookieSchema.DoesNotExist: + return None + + def store(self, strategy, cookie): + if self.dummy: + return + if self.initialised == -1: + self.initialise() + if self.initialised == 0: # failure + return + db = self.get_db() + if db is None: + return + try: + q = _CookieSchema.delete().where(_CookieSchema.strategy == strategy) + q.execute() + if cookie is None: + return + with db.atomic(): + cookie_pkl = _pkl.dumps(cookie, _pkl.HIGHEST_PROTOCOL) + _CookieSchema.insert(strategy=strategy, cookie_bytes=cookie_pkl).execute() + except _peewee.IntegrityError: + raise + # # Integrity error means the strategy already exists. Try updating the strategy. + # old_value = self.lookup(strategy) + # if old_value != cookie: + # get_yf_logger().debug(f"cookie for strategy {strategy} changed from {old_value} to {cookie}.") + # with db.atomic(): + # q = _CookieSchema.update(cookie=cookie).where(_CookieSchema.strategy == strategy) + # q.execute() + + +def get_cookie_cache(): + return _CookieCacheManager.get_cookie_cache() diff --git a/yahoofinancials/etl.py b/yahoofinancials/etl.py index beb0506..e31ceca 100644 --- a/yahoofinancials/etl.py +++ b/yahoofinancials/etl.py @@ -7,19 +7,15 @@ from json import loads from multiprocessing import Pool import pytz -import requests as requests from yahoofinancials.maps import COUNTRY_MAP, REQUEST_MAP, USER_AGENTS -from yahoofinancials.sessions import _init_session +from yahoofinancials.sessions import SessionManager from yahoofinancials.utils import remove_prefix, get_request_config, get_request_category # track the last get timestamp to add a minimum delay between gets - be nice! _lastget = 0 -# logger = log_to_stderr(logging.DEBUG) - - # Custom Exception class to handle custom error class ManagedException(Exception): pass @@ -27,7 +23,6 @@ class ManagedException(Exception): # Class used to get data from urls class UrlOpener: - request_headers = { "accept": "*/*", "accept-encoding": "gzip, deflate, br", @@ -41,16 +36,16 @@ class UrlOpener: user_agent = random.choice(USER_AGENTS) request_headers["User-Agent"] = user_agent - def __init__(self, session=None): - self._session = session or requests + def __init__(self, session): + self._session_manager = SessionManager(session=session) def open(self, url, request_headers=None, params=None, proxy=None, timeout=30): - response = self._session.get( + response = self._session_manager.cache_get( url=url, params=params, - proxies=proxy, + proxy=proxy, timeout=timeout, - headers=request_headers or self.request_headers + user_agent_headers=request_headers or self.request_headers ) return response @@ -66,8 +61,8 @@ def __init__(self, ticker, **kwargs): self.max_workers = kwargs.get("max_workers", 8) self.timeout = kwargs.get("timeout", 30) self.proxies = kwargs.get("proxies") + self.session = kwargs.pop("session", None) self._cache = {} - self.session, self.crumb = _init_session(kwargs.pop("session", None), **kwargs) # Minimum interval between Yahoo Finance requests for this instance _MIN_INTERVAL = 7 @@ -186,8 +181,6 @@ def _request_handler(self, url, res_field=""): # Try to open the URL up to 10 times sleeping random time if something goes wrong cur_url = url max_retry = 10 - if 'quoteSummary' in cur_url: - cur_url += "&crumb=" + self.crumb for i in range(0, max_retry): response = urlopener.open(cur_url, proxy=self._get_proxy(), timeout=self.timeout) if response.status_code != 200: @@ -409,7 +402,7 @@ def _get_api_data(self, api_url, tries=0): cur_url = cur_url.replace("query2.", "query1.") elif 'query1.' in cur_url: cur_url = cur_url.replace("query1.", "query2.") - urlopener = UrlOpener() + urlopener = UrlOpener(self.session) response = urlopener.open(cur_url, proxy=self._get_proxy(), timeout=self.timeout) if response.status_code == 200: res_content = response.text @@ -561,6 +554,10 @@ def get_time_code(self, time_interval): # Public Method to get stock data def get_stock_data(self, statement_type='income', tech_type='', report_name='', hist_obj={}): data = {} + if statement_type == 'income' and tech_type == '' and report_name == '': + statement_type = 'profile' + tech_type = 'assetProfile' + report_name = 'assetProfile' if isinstance(self.ticker, str): dict_ent = self._create_dict_ent(self.ticker, statement_type, tech_type, report_name, hist_obj) data.update(dict_ent) @@ -582,8 +579,8 @@ def get_stock_data(self, statement_type='income', tech_type='', report_name='', dict_ent = self._create_dict_ent(tick, statement_type, tech_type, report_name, hist_obj) data.update(dict_ent) except ManagedException: - logging.warning("yahoofinancials ticker: %s error getting %s - %s\n\tContinuing extraction...", - str(tick), statement_type, str(ManagedException)) + logging.info("yahoofinancials ticker: %s error getting %s - %s\n\tContinuing extraction...", + str(tick), statement_type, str(ManagedException)) continue return data diff --git a/yahoofinancials/maps.py b/yahoofinancials/maps.py index 9110dbd..e028ac8 100644 --- a/yahoofinancials/maps.py +++ b/yahoofinancials/maps.py @@ -2349,7 +2349,7 @@ REQUEST_MAP = { "quoteSummary": { - "path": "https://query2.finance.yahoo.com/v10/finance/quoteSummary/{symbol}", + "path": "https://query1.finance.yahoo.com/v10/finance/quoteSummary/{symbol}", "response_field": "quoteSummary", "request": { "formatted": {"required": False, "default": False}, @@ -2361,7 +2361,7 @@ }, }, "fundamentals": { - "path": "https://query2.finance.yahoo.com/ws/fundamentals-timeseries/v1/finance/timeseries/{symbol}", + "path": "https://query1.finance.yahoo.com/ws/fundamentals-timeseries/v1/finance/timeseries/{symbol}", "response_field": "timeseries", "request": { "period1": {"required": True, "default": 493590046}, diff --git a/yahoofinancials/sessions.py b/yahoofinancials/sessions.py index 5fcb38e..ac6256e 100644 --- a/yahoofinancials/sessions.py +++ b/yahoofinancials/sessions.py @@ -1,173 +1,378 @@ -import random -from urllib3.util import Retry -from requests import Session -from requests.adapters import HTTPAdapter -from requests.exceptions import ConnectionError, RetryError - +import functools +from functools import lru_cache +import requests as requests +from bs4 import BeautifulSoup +import datetime +import logging +from frozendict import frozendict +import threading +from . import cache DEFAULT_TIMEOUT = 5 +cache_maxsize = 64 + + +def lru_cache_freezeargs(func): + """ + Decorator transforms mutable dictionary and list arguments into immutable types + Needed so lru_cache can cache method calls what has dict or list arguments. + """ + + @functools.wraps(func) + def wrapped(*args, **kwargs): + args = tuple([frozendict(arg) if isinstance(arg, dict) else arg for arg in args]) + kwargs = {k: frozendict(v) if isinstance(v, dict) else v for k, v in kwargs.items()} + args = tuple([tuple(arg) if isinstance(arg, list) else arg for arg in args]) + kwargs = {k: tuple(v) if isinstance(v, list) else v for k, v in kwargs.items()} + return func(*args, **kwargs) + # copy over the lru_cache extra methods to this wrapper to be able to access them + # after this decorator has been applied + wrapped.cache_info = func.cache_info + wrapped.cache_clear = func.cache_clear + return wrapped + + +class SingletonMeta(type): + """ + Metaclass that creates a Singleton instance. + """ + _instances = {} + _lock = threading.Lock() + + def __call__(cls, *args, **kwargs): + with cls._lock: + if cls not in cls._instances: + instance = super().__call__(*args, **kwargs) + cls._instances[cls] = instance + else: + cls._instances[cls]._set_session(*args, **kwargs) + return cls._instances[cls] + + +class SessionManager(metaclass=SingletonMeta): + """ + Have one place to retrieve data from Yahoo API in order to ease caching and speed up operations. + Singleton means one session one cookie shared by all threads. + """ + user_agent_headers = { + 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36' + } + + def __init__(self, session=None): + self._session = session or requests.Session() + try: + self._session.cache + except AttributeError: + # Not caching + self._session_is_caching = False + else: + # Is caching. This is annoying. + # Can't simply use a non-caching session to fetch cookie & crumb, + # because then the caching-session won't have cookie. + self._session_is_caching = True + from requests_cache import DO_NOT_CACHE + self._expire_after = DO_NOT_CACHE + self._crumb = None + self._cookie = None + if self._session_is_caching and self._cookie is None: + logging.warning( + "yahoofinancials: cookie & crumb does not work well with requests_cache. Am experimenting with " + "'expire_after=DO_NOT_CACHE', but you need to help stress-test." + ) + # Default to using 'basic' strategy + self._cookie_strategy = 'basic' + # If it fails, then fallback method is 'csrf' + # self._cookie_strategy = 'csrf' + self._cookie_lock = threading.Lock() + + def _set_session(self, session): + if session is None: + return + with self._cookie_lock: + self._session = session + + def _set_cookie_strategy(self, strategy, have_lock=False): + if strategy == self._cookie_strategy: + return + if not have_lock: + self._cookie_lock.acquire() + + try: + if self._cookie_strategy == 'csrf': + logging.info(f'yahoofinancials: toggling cookie strategy {self._cookie_strategy} -> basic') + self._session.cookies.clear() + self._cookie_strategy = 'basic' + else: + logging.info(f'yahoofinancials: toggling cookie strategy {self._cookie_strategy} -> csrf') + self._cookie_strategy = 'csrf' + self._cookie = None + self._crumb = None + except Exception: + self._cookie_lock.release() + raise + + if not have_lock: + self._cookie_lock.release() + + def _save_session_cookies(self): + try: + cache.get_cookie_cache().store('csrf', self._session.cookies) + except Exception: + return False + return True + + def _load_session_cookies(self): + cookie_dict = cache.get_cookie_cache().lookup('csrf') + if cookie_dict is None: + return False + # Periodically refresh, 24 hours seems fair. + if cookie_dict['age'] > datetime.timedelta(days=1): + return False + self._session.cookies.update(cookie_dict['cookie']) + logging.info('yahoofinancials: loaded persistent cookie') + + def _save_cookie_basic(self, cookie): + try: + cache.get_cookie_cache().store('basic', cookie) + except Exception: + return False + return True + + def _load_cookie_basic(self): + cookie_dict = cache.get_cookie_cache().lookup('basic') + if cookie_dict is None: + return None + # Periodically refresh, 24 hours seems fair. + if cookie_dict['age'] > datetime.timedelta(days=1): + return None + logging.info('yahoofinancials: loaded persistent cookie') + return cookie_dict['cookie'] + + def _get_cookie_basic(self, proxy=None, timeout=30): + if self._cookie is not None: + logging.info('yahoofinancials: reusing cookie') + return self._cookie + + self._cookie = self._load_cookie_basic() + if self._cookie is not None: + return self._cookie + + # To avoid infinite recursion, do NOT use self.get() + # - 'allow_redirects' copied from @psychoz971 solution - does it help USA? + response = self._session.get( + url='https://finance.yahoo.com', + headers=self.user_agent_headers, + proxies=proxy, + timeout=timeout, + allow_redirects=True) + + if not response.cookies: + logging.info("yahoofinancials: response.cookies = None") + return None + self._cookie = list(response.cookies)[0] + if self._cookie == '': + logging.info("yahoofinancials: list(response.cookies)[0] = ''") + return None + self._save_cookie_basic(self._cookie) + logging.info(f"yahoofinancials: fetched basic cookie = {self._cookie}") + return self._cookie + + def _get_crumb_basic(self, proxy=None, timeout=30): + if self._crumb is not None: + logging.info('yahoofinancials: reusing crumb') + return self._crumb + + cookie = self._get_cookie_basic() + if cookie is None: + return None + + # - 'allow_redirects' copied from @psychoz971 solution - does it help USA? + get_args = { + 'url': "https://query1.finance.yahoo.com/v1/test/getcrumb", + 'headers': self.user_agent_headers, + 'cookies': {cookie.name: cookie.value}, + 'proxies': proxy, + 'timeout': timeout, + 'allow_redirects': True + } + if self._session_is_caching: + get_args['expire_after'] = self._expire_after + crumb_response = self._session.get(**get_args) + else: + crumb_response = self._session.get(**get_args) + self._crumb = crumb_response.text + if self._crumb is None or '' in self._crumb: + logging.info("yahoofinancials: didn't receive crumb") + return None + + logging.info(f"yahoofinancials: crumb = '{self._crumb}'") + return self._crumb + + def _get_cookie_and_crumb_basic(self, proxy, timeout): + cookie = self._get_cookie_basic(proxy, timeout) + crumb = self._get_crumb_basic(proxy, timeout) + return cookie, crumb + + def _get_cookie_csrf(self, proxy, timeout): + if self._cookie is not None: + logging.info('yahoofinancials: reusing cookie') + return True + + elif self._load_session_cookies(): + logging.info('yahoofinancials: reusing persistent cookie') + self._cookie = True + return True + + base_args = { + 'headers': self.user_agent_headers, + 'proxies': proxy, + 'timeout': timeout} + + get_args = {**base_args, 'url': 'https://guce.yahoo.com/consent'} + if self._session_is_caching: + get_args['expire_after'] = self._expire_after + response = self._session.get(**get_args) + else: + response = self._session.get(**get_args) + + soup = BeautifulSoup(response.content, 'html.parser') + csrfTokenInput = soup.find('input', attrs={'name': 'csrfToken'}) + if csrfTokenInput is None: + logging.info('yahoofinancials: Failed to find "csrfToken" in response') + return False + csrfToken = csrfTokenInput['value'] + logging.info(f'csrfToken = {csrfToken}') + sessionIdInput = soup.find('input', attrs={'name': 'sessionId'}) + sessionId = sessionIdInput['value'] + logging.info(f"sessionId='{sessionId}") + + originalDoneUrl = 'https://finance.yahoo.com/' + namespace = 'yahoo' + data = { + 'agree': ['agree', 'agree'], + 'consentUUID': 'default', + 'sessionId': sessionId, + 'csrfToken': csrfToken, + 'originalDoneUrl': originalDoneUrl, + 'namespace': namespace, + } + post_args = {**base_args, + 'url': f'https://consent.yahoo.com/v2/collectConsent?sessionId={sessionId}', + 'data': data} + get_args = {**base_args, + 'url': f'https://guce.yahoo.com/copyConsent?sessionId={sessionId}', + 'data': data} + if self._session_is_caching: + post_args['expire_after'] = self._expire_after + get_args['expire_after'] = self._expire_after + self._session.post(**post_args) + self._session.get(**get_args) + else: + self._session.post(**post_args) + self._session.get(**get_args) + self._cookie = True + self._save_session_cookies() + return True + + def _get_crumb_csrf(self, proxy=None, timeout=30): + if self._crumb is not None: + logging.info('yahoofinancials: reusing crumb') + return self._crumb + if not self._get_cookie_csrf(proxy, timeout): + # This cookie stored in session + return None + get_args = { + 'url': 'https://query2.finance.yahoo.com/v1/test/getcrumb', + 'headers': self.user_agent_headers, + 'proxies': proxy, + 'timeout': timeout} + if self._session_is_caching: + get_args['expire_after'] = self._expire_after + r = self._session.get(**get_args) + else: + r = self._session.get(**get_args) + self._crumb = r.text + if self._crumb is None or '' in self._crumb or self._crumb == '': + logging.info("yahoofinancials: didn't receive crumb") + return None + logging.info(f"yahoofinancials: crumb = '{self._crumb}'") + return self._crumb + + def _get_cookie_and_crumb(self, proxy=None, timeout=30): + cookie, crumb, strategy = None, None, None + logging.info(f"yahoofinancials: cookie_mode = '{self._cookie_strategy}'") + with self._cookie_lock: + if self._cookie_strategy == 'csrf': + crumb = self._get_crumb_csrf() + if crumb is None: + # Fail + self._set_cookie_strategy('basic', have_lock=True) + cookie, crumb = self._get_cookie_and_crumb_basic(proxy, timeout) + else: + # Fallback strategy + cookie, crumb = self._get_cookie_and_crumb_basic(proxy, timeout) + if cookie is None or crumb is None: + # Fail + self._set_cookie_strategy('csrf', have_lock=True) + crumb = self._get_crumb_csrf() + strategy = self._cookie_strategy + return cookie, crumb, strategy + + def get(self, url, user_agent_headers=None, params=None, proxy=None, timeout=30): + # Important: treat input arguments as immutable. + proxy = self._get_proxy(proxy) + if params is None: + params = {} + if 'crumb' in params: + raise Exception("yahoofinancials: Don't manually add 'crumb' to params dict, let sessions.py handle it") + cookie, crumb, strategy = self._get_cookie_and_crumb() + if crumb is not None: + crumbs = {'crumb': crumb} + else: + crumbs = {} + if strategy == 'basic' and cookie is not None: + # Basic cookie strategy adds cookie to GET parameters + cookies = {cookie.name: cookie.value} + else: + cookies = None + request_args = { + 'url': url, + 'params': {**params, **crumbs}, + 'cookies': cookies, + 'proxies': proxy, + 'timeout': timeout, + 'headers': user_agent_headers or self.user_agent_headers + } + response = self._session.get(**request_args) + if response.status_code >= 400: + # Retry with other cookie strategy + if strategy == 'basic': + self._set_cookie_strategy('csrf') + else: + self._set_cookie_strategy('basic') + cookie, crumb, strategy = self._get_cookie_and_crumb(proxy, timeout) + request_args['params']['crumb'] = crumb + if strategy == 'basic': + request_args['cookies'] = {cookie.name: cookie.value} + response = self._session.get(**request_args) + + return response -HEADERS = [ - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36 Edg/89.0.774.76', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 'sec-ch-ua': 'Microsoft Edge;v="89", "Chromium";v="89", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en-US,es;q=0.6'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36 Edg/86.0.622.69', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'sec-ch-ua': 'Microsoft Edge;v="86", "Chromium";v="86", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en-US,en;q=0.9,es;q=0.8'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 'sec-ch-ua': 'Google Chrome;v="90", "Chromium";v="90", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate', 'accept-language': 'en-US,en;q=0.9,it;q=0.8'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 'sec-ch-ua': 'Google Chrome;v="90", "Chromium";v="90", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en-US,es;q=0.8'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.75 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 'sec-ch-ua': '" Not A;Brand";v="99", "Chromium";v="104", "Opera";v="90"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': '"Windows"', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate, br', 'accept-language': 'en'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'sec-ch-ua': 'Google Chrome;v="84", "Chromium";v="84", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'macOS', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en-US,en;q=0.9,de;q=0.5'}, - {'upgrade-insecure-requests': '1x', 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 'sec-ch-ua': 'Google Chrome;v="72", "Chromium";v="72", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate', 'accept-language': 'en-US,it;q=0.7'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'sec-ch-ua': 'Google Chrome;v="85", "Chromium";v="85", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate', 'accept-language': 'en-US,es;q=0.6'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'sec-ch-ua': 'Google Chrome;v="89", "Chromium";v="89", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'macOS', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate', 'accept-language': 'en-US,es;q=0.7'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'sec-ch-ua': 'Google Chrome;v="86", "Chromium";v="86", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en-US,de;q=0.5'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36 Edg/90.0.818.46', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'sec-ch-ua': 'Microsoft Edge;v="90", "Chromium";v="90", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en-US,it;q=0.8'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'sec-ch-ua': 'Google Chrome;v="89", "Chromium";v="89", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en-US,it;q=0.6'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36 Edg/88.0.705.74', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'sec-ch-ua': 'Microsoft Edge;v="88", "Chromium";v="88", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en-US,en;q=0.9,es;q=0.5'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'sec-ch-ua': 'Google Chrome;v="83", "Chromium";v="83", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'macOS', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 'sec-ch-ua': 'Google Chrome;v="84", "Chromium";v="84", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'macOS', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate', 'accept-language': 'en-US,de;q=0.9'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'sec-ch-ua': 'Google Chrome;v="89", "Chromium";v="89", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en-US,en;q=0.9,fr;q=0.5'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'sec-ch-ua': 'Google Chrome;v="90", "Chromium";v="90", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'macOS', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en-US,es;q=0.8'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'sec-ch-ua': 'Google Chrome;v="90", "Chromium";v="90", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en-US,es;q=0.5'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'sec-ch-ua': 'Google Chrome;v="89", "Chromium";v="89", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'macOS', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en-US,de;q=0.7'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36 Edg/88.0.705.81', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 'sec-ch-ua': 'Microsoft Edge;v="88", "Chromium";v="88", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en-US,it;q=0.6'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 'sec-ch-ua': 'Google Chrome;v="90", "Chromium";v="90", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en-US,en;q=0.9,fr;q=0.5'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'sec-ch-ua': 'Google Chrome;v="84", "Chromium";v="84", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate', 'accept-language': 'en-US,en;q=0.9,es;q=0.5'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'sec-ch-ua': 'Google Chrome;v="84", "Chromium";v="84", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'macOS', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate', 'accept-language': 'en-US'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36 Edg/83.0.478.37', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'sec-ch-ua': 'Microsoft Edge;v="83", "Chromium";v="83", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate', 'accept-language': 'en-US,de;q=0.8'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'sec-ch-ua': 'Google Chrome;v="87", "Chromium";v="87", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en-US,en;q=0.8'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36 Edg/88.0.705.81', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'sec-ch-ua': 'Microsoft Edge;v="88", "Chromium";v="88", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en-US,it;q=0.6'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 'sec-ch-ua': 'Google Chrome;v="63", "Chromium";v="63", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'sec-ch-ua': 'Google Chrome;v="90", "Chromium";v="90", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en-US'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'sec-ch-ua': 'Google Chrome;v="80", "Chromium";v="80", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en-US,fr;q=0.7'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.192 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 'sec-ch-ua': 'Google Chrome;v="88", "Chromium";v="88", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'macOS', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en-US,en;q=0.9,de;q=0.8'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; ) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 'sec-ch-ua': 'Google Chrome;v="83", "Chromium";v="83", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate', 'accept-language': 'en-US,es;q=0.6'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'sec-ch-ua': 'Google Chrome;v="88", "Chromium";v="88", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate', 'accept-language': 'en-US,it;q=0.5'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 'sec-ch-ua': 'Google Chrome;v="90", "Chromium";v="90", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en-US,en;q=0.9,fr;q=0.8'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.72 Safari/537.36 Edg/90.0.818.42', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'sec-ch-ua': 'Microsoft Edge;v="90", "Chromium";v="90", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate', 'accept-language': 'en-US,en;q=0.8'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36 Edg/89.0.774.57', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 'sec-ch-ua': 'Microsoft Edge;v="89", "Chromium";v="89", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate', 'accept-language': 'en-US'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 'sec-ch-ua': 'Google Chrome;v="70", "Chromium";v="70", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate', 'accept-language': 'en-US,en;q=0.8'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 'sec-ch-ua': 'Google Chrome;v="80", "Chromium";v="80", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate', 'accept-language': 'en-US,fr;q=0.5'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36 Edg/90.0.818.46', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 'sec-ch-ua': 'Microsoft Edge;v="90", "Chromium";v="90", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en-US,it;q=0.6'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'sec-ch-ua': 'Google Chrome;v="86", "Chromium";v="86", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'macOS', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate', 'accept-language': 'en-US,en;q=0.7'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 'sec-ch-ua': 'Google Chrome;v="80", "Chromium";v="80", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en-US,fr;q=0.5'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.67 Safari/537.36 Edg/87.0.664.52', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'sec-ch-ua': 'Microsoft Edge;v="87", "Chromium";v="87", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate', 'accept-language': 'en-US,en;q=0.9,es;q=0.5'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'sec-ch-ua': 'Google Chrome;v="90", "Chromium";v="90", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en-US,en;q=0.9,de;q=0.5'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'sec-ch-ua': 'Google Chrome;v="86", "Chromium";v="86", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate', 'accept-language': 'en-US,en;q=0.9,it;q=0.5'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36 Edg/83.0.478.37', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 'sec-ch-ua': 'Microsoft Edge;v="83", "Chromium";v="83", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate', 'accept-language': 'en-US,de;q=0.9'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 'sec-ch-ua': 'Google Chrome;v="85", "Chromium";v="85", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'macOS', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate', 'accept-language': 'en-US'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'sec-ch-ua': 'Google Chrome;v="55", "Chromium";v="55", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en-US,en;q=0.6'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36 Edge/15.15063', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'sec-ch-ua': 'Google Chrome;v="95", "Chromium";v="95", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate', 'accept-language': 'en-US,it;q=0.9'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36 Edg/89.0.774.68', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'sec-ch-ua': 'Microsoft Edge;v="89", "Chromium";v="89", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate', 'accept-language': 'en-US,es;q=0.6'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 'sec-ch-ua': 'Google Chrome;v="86", "Chromium";v="86", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate', 'accept-language': 'en-US,fr;q=0.8'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'sec-ch-ua': 'Google Chrome;v="83", "Chromium";v="83", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36 Edg/90.0.818.46', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'sec-ch-ua': 'Microsoft Edge;v="90", "Chromium";v="90", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; ) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 'sec-ch-ua': 'Google Chrome;v="83", "Chromium";v="83", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en-US,en;q=0.9,de;q=0.5'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36 Edg/89.0.774.50', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'sec-ch-ua': 'Microsoft Edge;v="89", "Chromium";v="89", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate', 'accept-language': 'en-US,fr;q=0.8'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'sec-ch-ua': 'Google Chrome;v="85", "Chromium";v="85", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en-US,de;q=0.7'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 'sec-ch-ua': 'Google Chrome;v="87", "Chromium";v="87", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 'sec-ch-ua': 'Google Chrome;v="84", "Chromium";v="84", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'macOS', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en-US,en;q=0.9,it;q=0.8'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 'sec-ch-ua': 'Google Chrome;v="89", "Chromium";v="89", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en-US'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'sec-ch-ua': 'Google Chrome;v="89", "Chromium";v="89", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'macOS', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate', 'accept-language': 'en-US'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36 Edge/15.15063', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'sec-ch-ua': 'Google Chrome;v="95", "Chromium";v="95", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate', 'accept-language': 'en-US,en;q=0.5'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'sec-ch-ua': 'Google Chrome;v="84", "Chromium";v="84", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate', 'accept-language': 'en-US,en;q=0.9,it;q=0.5'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'sec-ch-ua': 'Google Chrome;v="89", "Chromium";v="89", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'macOS', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en-US,it;q=0.9'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'sec-ch-ua': 'Google Chrome;v="90", "Chromium";v="90", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'macOS', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate', 'accept-language': 'en-US,en;q=0.9,de;q=0.8'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'sec-ch-ua': '" Not A;Brand";v="99", "Chromium";v="104", "Opera";v="90"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': '"Windows"', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate, br', 'accept-language': 'en'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36 Edg/88.0.705.74', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 'sec-ch-ua': 'Microsoft Edge;v="88", "Chromium";v="88", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate', 'accept-language': 'en-US,en;q=0.9,es;q=0.8'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 'sec-ch-ua': '" Not A;Brand";v="99", "Chromium";v="100", "Google Chrome";v="100"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': '"Windows"', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate, br', 'accept-language': 'en-US,fr;q=0.7'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'sec-ch-ua': 'Google Chrome;v="84", "Chromium";v="84", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'macOS', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate', 'accept-language': 'en-US,es;q=0.7'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.106 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 'sec-ch-ua': 'Google Chrome;v="80", "Chromium";v="80", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate', 'accept-language': 'en'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 'sec-ch-ua': 'Google Chrome;v="86", "Chromium";v="86", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'macOS', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en-US,de;q=0.6'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'sec-ch-ua': 'Google Chrome;v="88", "Chromium";v="88", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'macOS', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate', 'accept-language': 'en-US,en;q=0.9,de;q=0.5'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 'sec-ch-ua': 'Google Chrome;v="83", "Chromium";v="83", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate', 'accept-language': 'en-US,de;q=0.7'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.136 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 'sec-ch-ua': 'Google Chrome;v="79", "Chromium";v="79", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate', 'accept-language': 'en-US,en;q=0.6'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 'sec-ch-ua': 'Google Chrome;v="83", "Chromium";v="83", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate', 'accept-language': 'en-US'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 'sec-ch-ua': 'Google Chrome;v="84", "Chromium";v="84", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'macOS', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en-US,en;q=0.9,fr;q=0.8'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 'sec-ch-ua': 'Google Chrome;v="89", "Chromium";v="89", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate', 'accept-language': 'en-US,de;q=0.7'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 'sec-ch-ua': 'Google Chrome;v="84", "Chromium";v="84", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'macOS', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate', 'accept-language': 'en-US,en;q=0.5'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 'sec-ch-ua': 'Google Chrome;v="89", "Chromium";v="89", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate', 'accept-language': 'en-US,es;q=0.8'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'sec-ch-ua': 'Google Chrome;v="84", "Chromium";v="84", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en-US,en;q=0.7'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'sec-ch-ua': 'Google Chrome;v="90", "Chromium";v="90", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate', 'accept-language': 'en-US,it;q=0.6'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'sec-ch-ua': 'Google Chrome;v="90", "Chromium";v="90", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en-US,it;q=0.8'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36 Edg/85.0.564.70', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 'sec-ch-ua': 'Microsoft Edge;v="85", "Chromium";v="85", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en-US,en;q=0.9,de;q=0.5'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.67 Safari/537.36 Edg/87.0.664.47', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'sec-ch-ua': 'Microsoft Edge;v="87", "Chromium";v="87", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36 Edg/90.0.818.56', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'sec-ch-ua': 'Microsoft Edge;v="90", "Chromium";v="90", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en-US,it;q=0.7'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'sec-ch-ua': 'Google Chrome;v="84", "Chromium";v="84", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate', 'accept-language': 'en-US,en;q=0.9,fr;q=0.8'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'sec-ch-ua': 'Google Chrome;v="88", "Chromium";v="88", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en-US,fr;q=0.8'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36 Edg/90.0.818.49', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 'sec-ch-ua': 'Microsoft Edge;v="90", "Chromium";v="90", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en-US,de;q=0.6'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'sec-ch-ua': 'Google Chrome;v="83", "Chromium";v="83", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en-US,es;q=0.8'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'sec-ch-ua': 'Google Chrome;v="86", "Chromium";v="86", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'macOS', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate', 'accept-language': 'en-US,es;q=0.8'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36 Edg/87.0.664.75', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'sec-ch-ua': 'Microsoft Edge;v="87", "Chromium";v="87", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en-US,de;q=0.8'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.72 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 'sec-ch-ua': 'Google Chrome;v="89", "Chromium";v="89", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate', 'accept-language': 'en-US'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'sec-ch-ua': 'Google Chrome;v="86", "Chromium";v="86", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'macOS', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en-US,it;q=0.7'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.67 Safari/537.36 Edg/87.0.664.52', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'sec-ch-ua': 'Microsoft Edge;v="87", "Chromium";v="87", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en-US,it;q=0.9'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 'sec-ch-ua': 'Google Chrome;v="89", "Chromium";v="89", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en-US'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36 Edg/85.0.564.70', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 'sec-ch-ua': 'Microsoft Edge;v="85", "Chromium";v="85", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate', 'accept-language': 'en-US,en;q=0.9,fr;q=0.5'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.75 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'sec-ch-ua': 'Google Chrome;v="73", "Chromium";v="73", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate', 'accept-language': 'en-US,es;q=0.8'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36 Edg/90.0.818.49', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'sec-ch-ua': 'Microsoft Edge;v="90", "Chromium";v="90", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate', 'accept-language': 'en-US,it;q=0.9'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36 Edg/89.0.774.68', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'sec-ch-ua': 'Microsoft Edge;v="89", "Chromium";v="89", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate', 'accept-language': 'en-US,it;q=0.9'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36 Edg/86.0.622.69', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'sec-ch-ua': 'Microsoft Edge;v="86", "Chromium";v="86", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en-US,fr;q=0.5'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36 Edg/84.0.522.63', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 'sec-ch-ua': 'Microsoft Edge;v="84", "Chromium";v="84", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate', 'accept-language': 'en-US,fr;q=0.8'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'sec-ch-ua': 'Google Chrome;v="72", "Chromium";v="72", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip, deflate', 'accept-language': 'en-US,it;q=0.7'}, - {'upgrade-insecure-requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'sec-ch-ua': 'Google Chrome;v="81", "Chromium";v="81", ";Not A Brand";v="99"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': 'Windows', 'sec-fetch-site': 'none', 'sec-fetch-mod': '', 'sec-fetch-user': '?1', 'accept-encoding': 'gzip', 'accept-language': 'en-US,en;q=0.7'}, -] - - -class TimeoutHTTPAdapter(HTTPAdapter): - def __init__(self, *args, **kwargs): - self.timeout = DEFAULT_TIMEOUT - if "timeout" in kwargs: - self.timeout = kwargs["timeout"] - del kwargs["timeout"] - super(TimeoutHTTPAdapter, self).__init__(*args, **kwargs) - - def send(self, request, **kwargs): - timeout = kwargs.get("timeout") - if timeout is None: - kwargs["timeout"] = self.timeout - return super(TimeoutHTTPAdapter, self).send(request, **kwargs) - - -def setup_session_with_cookies_and_crumb(session: Session): - headers = {**random.choice(HEADERS)} - session.headers = headers - try: - response = session.get('https://finance.yahoo.com') - except Exception: - return session, None - else: - session.cookies = response.cookies - crumb = _get_crumb(session) - return session, crumb - - -def _get_crumb(session): - try: - response = session.get('https://query2.finance.yahoo.com/v1/test/getcrumb') - return response.text - except (ConnectionError, RetryError) as e: - # Cookies most likely not set in previous request - return None - - -def _init_session(session=None, **kwargs): - crumb = None - if session is None: - session = Session() - if kwargs.get("proxies"): - session.proxies = kwargs.get("proxies") - retries = Retry( - total=kwargs.get("retry", 5), - backoff_factor=kwargs.get("backoff_factor", 0.3), - status_forcelist=kwargs.get("status_forcelist", [429, 500, 502, 503, 504]), - ) - if kwargs.get("verify") is not None: - session.verify = kwargs.get("verify") - session.mount( - "https://", - TimeoutHTTPAdapter( - max_retries=retries, timeout=kwargs.get("timeout", DEFAULT_TIMEOUT) - ), - ) - session, crumb = setup_session_with_cookies_and_crumb(session) - return session, crumb + @lru_cache_freezeargs + @lru_cache(maxsize=cache_maxsize) + def cache_get(self, url, user_agent_headers=None, params=None, proxy=None, timeout=30): + return self.get(url, user_agent_headers, params, proxy, timeout) + def _get_proxy(self, proxy): + # setup proxy in requests format + if proxy is not None: + if isinstance(proxy, (dict, frozendict)) and "https" in proxy: + proxy = proxy["https"] + proxy = {"https": proxy} + return proxy + def get_raw_json(self, url, user_agent_headers=None, params=None, proxy=None, timeout=30): + logging.info(f'yahoofinancials: get_raw_json(): {url}') + response = self.get(url, user_agent_headers=user_agent_headers, params=params, proxy=proxy, timeout=timeout) + response.raise_for_status() + return response.json() diff --git a/yahoofinancials/yf.py b/yahoofinancials/yf.py index a08391e..d13ca81 100644 --- a/yahoofinancials/yf.py +++ b/yahoofinancials/yf.py @@ -1,12 +1,12 @@ """ ============================== The Yahoo Financials Module -Version: 1.18 +Version: 1.19 ============================== Author: Connor Sanders Email: jecsand@pm.me -Version Released: 12/09/2023 +Version Released: 12/12/2023 Tested on Python 3.7, 3.8, 3.9, 3.10, and 3.11 Copyright (c) 2023 Connor Sanders @@ -44,7 +44,7 @@ from yahoofinancials.calcs import num_shares_outstanding, eps from yahoofinancials.etl import YahooFinanceETL -__version__ = "1.17" +__version__ = "1.19" __author__ = "Connor Sanders"