import requestsModule 3 - Day 3
Login to Lab using your credentials. There is a notebook with name 3-3.ipynb already created for you. Open that and use it for today’s training.
Shut down all previous notebooks.
You can access live notes from https://live.arcesium-lab.pipal.in
Web Scraping
requests is third party library which implements functions to do web request from python. it support all four HTTP methods (get, post, put, delete)
pip install requests
url = "https://www.python.org/"
response = requests.get(url)response # response 200 means everything went well!<Response [200]>
url_wrong = "https://thereis.nowebsite.like.this/"
response = requests.get(url_wrong)--------------------------------------------------------------------------- gaierror Traceback (most recent call last) File /opt/tljh/user/lib/python3.10/site-packages/urllib3/connection.py:174, in HTTPConnection._new_conn(self) 173 try: --> 174 conn = connection.create_connection( 175 (self._dns_host, self.port), self.timeout, **extra_kw 176 ) 178 except SocketTimeout: File /opt/tljh/user/lib/python3.10/site-packages/urllib3/util/connection.py:72, in create_connection(address, timeout, source_address, socket_options) 68 return six.raise_from( 69 LocationParseError(u"'%s', label empty or too long" % host), None 70 ) ---> 72 for res in socket.getaddrinfo(host, port, family, socket.SOCK_STREAM): 73 af, socktype, proto, canonname, sa = res File /opt/tljh/user/lib/python3.10/socket.py:955, in getaddrinfo(host, port, family, type, proto, flags) 954 addrlist = [] --> 955 for res in _socket.getaddrinfo(host, port, family, type, proto, flags): 956 af, socktype, proto, canonname, sa = res gaierror: [Errno -2] Name or service not known During handling of the above exception, another exception occurred: NewConnectionError Traceback (most recent call last) File /opt/tljh/user/lib/python3.10/site-packages/urllib3/connectionpool.py:703, in HTTPConnectionPool.urlopen(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, **response_kw) 702 # Make the request on the httplib connection object. --> 703 httplib_response = self._make_request( 704 conn, 705 method, 706 url, 707 timeout=timeout_obj, 708 body=body, 709 headers=headers, 710 chunked=chunked, 711 ) 713 # If we're going to release the connection in ``finally:``, then 714 # the response doesn't need to know about the connection. Otherwise 715 # it will also try to release it and we'll have a double-release 716 # mess. File /opt/tljh/user/lib/python3.10/site-packages/urllib3/connectionpool.py:386, in HTTPConnectionPool._make_request(self, conn, method, url, timeout, chunked, **httplib_request_kw) 385 try: --> 386 self._validate_conn(conn) 387 except (SocketTimeout, BaseSSLError) as e: 388 # Py2 raises this as a BaseSSLError, Py3 raises it as socket timeout. File /opt/tljh/user/lib/python3.10/site-packages/urllib3/connectionpool.py:1042, in HTTPSConnectionPool._validate_conn(self, conn) 1041 if not getattr(conn, "sock", None): # AppEngine might not have `.sock` -> 1042 conn.connect() 1044 if not conn.is_verified: File /opt/tljh/user/lib/python3.10/site-packages/urllib3/connection.py:363, in HTTPSConnection.connect(self) 361 def connect(self): 362 # Add certificate verification --> 363 self.sock = conn = self._new_conn() 364 hostname = self.host File /opt/tljh/user/lib/python3.10/site-packages/urllib3/connection.py:186, in HTTPConnection._new_conn(self) 185 except SocketError as e: --> 186 raise NewConnectionError( 187 self, "Failed to establish a new connection: %s" % e 188 ) 190 return conn NewConnectionError: <urllib3.connection.HTTPSConnection object at 0x7365c5c98760>: Failed to establish a new connection: [Errno -2] Name or service not known During handling of the above exception, another exception occurred: MaxRetryError Traceback (most recent call last) File /opt/tljh/user/lib/python3.10/site-packages/requests/adapters.py:667, in HTTPAdapter.send(self, request, stream, timeout, verify, cert, proxies) 666 try: --> 667 resp = conn.urlopen( 668 method=request.method, 669 url=url, 670 body=request.body, 671 headers=request.headers, 672 redirect=False, 673 assert_same_host=False, 674 preload_content=False, 675 decode_content=False, 676 retries=self.max_retries, 677 timeout=timeout, 678 chunked=chunked, 679 ) 681 except (ProtocolError, OSError) as err: File /opt/tljh/user/lib/python3.10/site-packages/urllib3/connectionpool.py:787, in HTTPConnectionPool.urlopen(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, **response_kw) 785 e = ProtocolError("Connection aborted.", e) --> 787 retries = retries.increment( 788 method, url, error=e, _pool=self, _stacktrace=sys.exc_info()[2] 789 ) 790 retries.sleep() File /opt/tljh/user/lib/python3.10/site-packages/urllib3/util/retry.py:592, in Retry.increment(self, method, url, response, error, _pool, _stacktrace) 591 if new_retry.is_exhausted(): --> 592 raise MaxRetryError(_pool, url, error or ResponseError(cause)) 594 log.debug("Incremented Retry for (url='%s'): %r", url, new_retry) MaxRetryError: HTTPSConnectionPool(host='thereis.nowebsite.like.this', port=443): Max retries exceeded with url: / (Caused by NewConnectionError('<urllib3.connection.HTTPSConnection object at 0x7365c5c98760>: Failed to establish a new connection: [Errno -2] Name or service not known')) During handling of the above exception, another exception occurred: ConnectionError Traceback (most recent call last) Cell In[5], line 2 1 url_wrong = "https://thereis.nowebsite.like.this/" ----> 2 response = requests.get(url_wrong) File /opt/tljh/user/lib/python3.10/site-packages/requests/api.py:73, in get(url, params, **kwargs) 62 def get(url, params=None, **kwargs): 63 r"""Sends a GET request. 64 65 :param url: URL for the new :class:`Request` object. (...) 70 :rtype: requests.Response 71 """ ---> 73 return request("get", url, params=params, **kwargs) File /opt/tljh/user/lib/python3.10/site-packages/requests/api.py:59, in request(method, url, **kwargs) 55 # By using the 'with' statement we are sure the session is closed, thus we 56 # avoid leaving sockets open which can trigger a ResourceWarning in some 57 # cases, and look like a memory leak in others. 58 with sessions.Session() as session: ---> 59 return session.request(method=method, url=url, **kwargs) File /opt/tljh/user/lib/python3.10/site-packages/requests/sessions.py:589, in Session.request(self, method, url, params, data, headers, cookies, files, auth, timeout, allow_redirects, proxies, hooks, stream, verify, cert, json) 584 send_kwargs = { 585 "timeout": timeout, 586 "allow_redirects": allow_redirects, 587 } 588 send_kwargs.update(settings) --> 589 resp = self.send(prep, **send_kwargs) 591 return resp File /opt/tljh/user/lib/python3.10/site-packages/requests/sessions.py:703, in Session.send(self, request, **kwargs) 700 start = preferred_clock() 702 # Send the request --> 703 r = adapter.send(request, **kwargs) 705 # Total elapsed time of the request (approximately) 706 elapsed = preferred_clock() - start File /opt/tljh/user/lib/python3.10/site-packages/requests/adapters.py:700, in HTTPAdapter.send(self, request, stream, timeout, verify, cert, proxies) 696 if isinstance(e.reason, _SSLError): 697 # This branch is for urllib3 v1.22 and later. 698 raise SSLError(e, request=request) --> 700 raise ConnectionError(e, request=request) 702 except ClosedPoolError as e: 703 raise ConnectionError(e, request=request) ConnectionError: HTTPSConnectionPool(host='thereis.nowebsite.like.this', port=443): Max retries exceeded with url: / (Caused by NewConnectionError('<urllib3.connection.HTTPSConnection object at 0x7365c5c98760>: Failed to establish a new connection: [Errno -2] Name or service not known'))
url = "https://www.python.org/"
response = requests.get(url)response<Response [200]>
response.status_code200
print(response.text[:800]) # if website retuns any text or html data<!doctype html>
<!--[if lt IE 7]> <html class="no-js ie6 lt-ie7 lt-ie8 lt-ie9"> <![endif]-->
<!--[if IE 7]> <html class="no-js ie7 lt-ie8 lt-ie9"> <![endif]-->
<!--[if IE 8]> <html class="no-js ie8 lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!--><html class="no-js" lang="en" dir="ltr"> <!--<![endif]-->
<head>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-TF35YF9CVH"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-TF35YF9CVH');
</script>
<!-- Plausible.io analytics -->
<script defer data-domain="python.org" src="https://plausible.io/js/script.js"></script>
data = response.contenttype(data)bytes
xlurl = "https://github.com/vikipedia/python-trainings/raw/master/online_course/source/module2/wallet.xlsx"import requests
r = requests.get(xlurl)r.status_code200
#r.text # this will not have data of excel in proper format! with open("wallet.xlsx", "wb") as f:
f.write(r.content) # this will have binary dataimport osos.getcwd()'/home/jupyter-pipal'
problem
Write a function
downloadwhich will download file from internetr and save in as file on local system.Write a function to
download_notesall these notes from live notes website and save to your local system There is a patter in live notes urlModule 1, day1 => https://live.arcesium-lab.pipal.in/1-1.html Module 1, day2 => https://live.arcesium-lab.pipal.in/1-2.html Module 3, day3 => https://live.arcesium-lab.pipal.in/3-3.html
def geturl(baseurl, module, day):
return f"{baseurl}/" + getfilename(module, day)
def download(url, filename):
r = requests.get(url)
if r.status_code == 200:
with open(filename, "wb") as f:
f.write(r.content)
else:
print(f"Error in downloading {filename}")
def getfilename(module, day):
return f"{module}-{day}.html"
def getfilepath(module, day, folder):
return os.path.join(folder, getfilename(module, day))
def download_notes(baseurl, folder):
os.makedirs(folder, exist_ok=True)
for module in range(1, 3):
for day in range(1, 6):
url = geturl(baseurl, module, day)
filename = getfilepath(module, day, folder)
print(f"Downloading {filename}")
download(url, filename)download_notes("https://live.arcesium-lab.pipal.in/", "livenotes")Downloading livenotes/1-1.html
Downloading livenotes/1-2.html
Downloading livenotes/1-3.html
Downloading livenotes/1-4.html
Downloading livenotes/1-5.html
Downloading livenotes/2-1.html
Downloading livenotes/2-2.html
Downloading livenotes/2-3.html
Downloading livenotes/2-4.html
Downloading livenotes/2-5.html
API
There are some websites which sale data using WEBAPI. it consists of APIURL and documentation of paramaters required to use the API to download data
We will use www.alphavantage.co API
import requests
url = "https://www.alphavantage.co/query"
params = {"function":"TIME_SERIES_INTRADAY",
"symbol":"IBM",
"interval": "5min",
"apikey":"demo"}
r = requests.get(url, params=params)r<Response [200]>
data = r.json()type(data)dict
data.keys()dict_keys(['Meta Data', 'Time Series (5min)'])
data['Meta Data']{'1. Information': 'Intraday (5min) open, high, low, close prices and volume',
'2. Symbol': 'IBM',
'3. Last Refreshed': '2024-08-21 19:55:00',
'4. Interval': '5min',
'5. Output Size': 'Compact',
'6. Time Zone': 'US/Eastern'}
import pandas as pd
pd.DataFrame(data['Time Series (5min)'])| 2024-08-21 19:55:00 | 2024-08-21 19:50:00 | 2024-08-21 19:45:00 | 2024-08-21 19:40:00 | 2024-08-21 19:35:00 | 2024-08-21 19:30:00 | 2024-08-21 19:25:00 | 2024-08-21 19:15:00 | 2024-08-21 19:05:00 | 2024-08-21 19:00:00 | ... | 2024-08-21 11:50:00 | 2024-08-21 11:45:00 | 2024-08-21 11:40:00 | 2024-08-21 11:35:00 | 2024-08-21 11:30:00 | 2024-08-21 11:25:00 | 2024-08-21 11:20:00 | 2024-08-21 11:15:00 | 2024-08-21 11:10:00 | 2024-08-21 11:05:00 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1. open | 197.1900 | 197.1900 | 197.1800 | 197.1100 | 197.2200 | 197.1800 | 197.1800 | 197.1900 | 197.2900 | 197.2100 | ... | 194.8000 | 194.9150 | 194.8100 | 194.7250 | 194.7900 | 194.9640 | 194.8390 | 194.8900 | 194.6300 | 194.5300 |
| 2. high | 197.2200 | 197.1900 | 197.1800 | 197.1800 | 197.2200 | 197.3000 | 197.1800 | 197.1900 | 197.2900 | 197.2900 | ... | 194.8310 | 194.9600 | 194.9200 | 194.8900 | 194.8800 | 195.0200 | 195.0800 | 195.0600 | 194.9600 | 194.7100 |
| 3. low | 197.1800 | 197.1900 | 197.1800 | 197.1100 | 197.1000 | 197.1800 | 197.1800 | 196.9400 | 197.1000 | 197.2100 | ... | 194.7100 | 194.7900 | 194.7400 | 194.7020 | 194.6600 | 194.8000 | 194.8390 | 194.7800 | 194.6000 | 194.4700 |
| 4. close | 197.2200 | 197.1900 | 197.1800 | 197.1800 | 197.1000 | 197.3000 | 197.1800 | 196.9400 | 197.1000 | 197.2900 | ... | 194.8050 | 194.8000 | 194.9150 | 194.8100 | 194.7250 | 194.8250 | 195.0100 | 194.8700 | 194.9500 | 194.6100 |
| 5. volume | 12 | 2 | 1 | 13 | 75 | 24 | 25 | 150 | 13 | 387015 | ... | 25459 | 26803 | 23019 | 14291 | 20691 | 17639 | 12343 | 17589 | 13712 | 12457 |
5 rows × 100 columns
def get_timeseries_data(ticker):
url = "https://www.alphavantage.co/query"
params = {"function":"TIME_SERIES_INTRADAY",
"symbol": ticker,
"interval": "5min",
"apikey":"demo"}
r = requests.get(url, params=params)
timeseries = r.json()['Time Series (5min)']
return pd.DataFrame(timeseries).transpose()get_timeseries_data("IBM")| 1. open | 2. high | 3. low | 4. close | 5. volume | |
|---|---|---|---|---|---|
| 2024-08-21 19:55:00 | 197.1900 | 197.2200 | 197.1800 | 197.2200 | 12 |
| 2024-08-21 19:50:00 | 197.1900 | 197.1900 | 197.1900 | 197.1900 | 2 |
| 2024-08-21 19:45:00 | 197.1800 | 197.1800 | 197.1800 | 197.1800 | 1 |
| 2024-08-21 19:40:00 | 197.1100 | 197.1800 | 197.1100 | 197.1800 | 13 |
| 2024-08-21 19:35:00 | 197.2200 | 197.2200 | 197.1000 | 197.1000 | 75 |
| ... | ... | ... | ... | ... | ... |
| 2024-08-21 11:25:00 | 194.9640 | 195.0200 | 194.8000 | 194.8250 | 17639 |
| 2024-08-21 11:20:00 | 194.8390 | 195.0800 | 194.8390 | 195.0100 | 12343 |
| 2024-08-21 11:15:00 | 194.8900 | 195.0600 | 194.7800 | 194.8700 | 17589 |
| 2024-08-21 11:10:00 | 194.6300 | 194.9600 | 194.6000 | 194.9500 | 13712 |
| 2024-08-21 11:05:00 | 194.5300 | 194.7100 | 194.4700 | 194.6100 | 12457 |
100 rows × 5 columns
get_timeseries_data("MICROSOFT")--------------------------------------------------------------------------- KeyError Traceback (most recent call last) Cell In[37], line 1 ----> 1 get_timeseries_data("MICROSOFT") Cell In[35], line 10, in get_timeseries_data(ticker) 4 params = {"function":"TIME_SERIES_INTRADAY", 5 "symbol": ticker, 6 "interval": "5min", 7 "apikey":"demo"} 9 r = requests.get(url, params=params) ---> 10 timeseries = r.json()['Time Series (5min)'] 11 return pd.DataFrame(timeseries).transpose() KeyError: 'Time Series (5min)'
def get_ticker_name(keyword):
url = f"https://www.alphavantage.co/query?function=SYMBOL_SEARCH&{keyword}=tesco&apikey=demo"
r = requests.get(url)
return r.json()get_ticker_name("microsoft"){'Information': 'The **demo** API key is for demo purposes only. Please claim your free API key at (https://www.alphavantage.co/support/#api-key) to explore our full API offerings. It takes fewer than 20 seconds.'}
def get_ticker_name(keyword, apikey="demo"):
url = f"https://www.alphavantage.co/query?function=SYMBOL_SEARCH&keywords={keyword}&apikey={apikey}"
r = requests.get(url)
return r.json()get_ticker_name("APPLE", "UKVFE0JLE0TBPDEF"){'bestMatches': [{'1. symbol': 'APLE',
'2. name': 'Apple Hospitality REIT Inc',
'3. type': 'Equity',
'4. region': 'United States',
'5. marketOpen': '09:30',
'6. marketClose': '16:00',
'7. timezone': 'UTC-04',
'8. currency': 'USD',
'9. matchScore': '0.8889'},
{'1. symbol': 'AAPL',
'2. name': 'Apple Inc',
'3. type': 'Equity',
'4. region': 'United States',
'5. marketOpen': '09:30',
'6. marketClose': '16:00',
'7. timezone': 'UTC-04',
'8. currency': 'USD',
'9. matchScore': '0.7143'},
{'1. symbol': 'AAPL34.SAO',
'2. name': 'Apple Inc',
'3. type': 'Equity',
'4. region': 'Brazil/Sao Paolo',
'5. marketOpen': '10:00',
'6. marketClose': '17:30',
'7. timezone': 'UTC-03',
'8. currency': 'BRL',
'9. matchScore': '0.7143'},
{'1. symbol': 'APC.DEX',
'2. name': 'Apple Inc',
'3. type': 'Equity',
'4. region': 'XETRA',
'5. marketOpen': '08:00',
'6. marketClose': '20:00',
'7. timezone': 'UTC+02',
'8. currency': 'EUR',
'9. matchScore': '0.7143'},
{'1. symbol': 'APC.FRK',
'2. name': 'Apple Inc',
'3. type': 'Equity',
'4. region': 'Frankfurt',
'5. marketOpen': '08:00',
'6. marketClose': '20:00',
'7. timezone': 'UTC+02',
'8. currency': 'EUR',
'9. matchScore': '0.7143'},
{'1. symbol': 'AGPL',
'2. name': 'Apple Green Holding Inc',
'3. type': 'Equity',
'4. region': 'United States',
'5. marketOpen': '09:30',
'6. marketClose': '16:00',
'7. timezone': 'UTC-04',
'8. currency': 'USD',
'9. matchScore': '0.6667'},
{'1. symbol': '0R2V.LON',
'2. name': 'Apple Inc.',
'3. type': 'Equity',
'4. region': 'United Kingdom',
'5. marketOpen': '08:00',
'6. marketClose': '16:30',
'7. timezone': 'UTC+01',
'8. currency': 'USD',
'9. matchScore': '0.6667'},
{'1. symbol': '500014.BSE',
'2. name': 'Apple Finance Limited',
'3. type': 'Equity',
'4. region': 'India/Bombay',
'5. marketOpen': '09:15',
'6. marketClose': '15:30',
'7. timezone': 'UTC+5.5',
'8. currency': 'INR',
'9. matchScore': '0.3846'},
{'1. symbol': '48T.FRK',
'2. name': 'APPLE HOSPITALITY REIT',
'3. type': 'Equity',
'4. region': 'Frankfurt',
'5. marketOpen': '08:00',
'6. marketClose': '20:00',
'7. timezone': 'UTC+02',
'8. currency': 'EUR',
'9. matchScore': '0.3704'},
{'1. symbol': '603020.SHH',
'2. name': 'Apple Flavor Fragrance Group Company Ltd',
'3. type': 'Equity',
'4. region': 'Shanghai',
'5. marketOpen': '09:30',
'6. marketClose': '15:00',
'7. timezone': 'UTC+08',
'8. currency': 'CNY',
'9. matchScore': '0.2222'}]}
def get_ticker_name(keyword, apikey="demo"):
url = f"https://www.alphavantage.co/query?function=SYMBOL_SEARCH&keywords={keyword}&apikey={apikey}"
r = requests.get(url)
return [ match['1. symbol'] for match in r.json()['bestMatches']]get_ticker_name("APPLE", "UKVFE0JLE0TBPDEF")['APLE',
'AAPL',
'AAPL34.SAO',
'APC.DEX',
'APC.FRK',
'AGPL',
'0R2V.LON',
'500014.BSE',
'48T.FRK',
'603020.SHH']
%load_problem fibs-apiWrite a function fibs to generate fibonacci numbers using fibs API at https://numbers.apps.pipal.in/.
The API takes 3 query parameters a, b and n and generates n fibonacci numbers starting with a and b.
$ curl 'https://numbers.apps.pipal.in/fibs?a=3&b=4&n=10'
3
4
7
11
18
29
47
76
123
199
Write a function fibs that takes three numbers a, b and n as arguments and returns a list of fibonacci numbers given by the API.
>>> fibs(3, 4, 10)
[3, 4, 7, 11, 18, 29, 47, 76, 123, 199]
>>> sum(fibs(3, 4, 10))
517
You can verify your solution using:
%verify_problem fibs-api
# your code here
def fibs(a, b, n):
url = f'https://numbers.apps.pipal.in/fibs?a={a}&b={b}&n={n}'
r = requests.get(url)
return r.text.strip().split("\n")
fibs(1, 1, 10)['1', '1', '2', '3', '5', '8', '13', '21', '34', '55']
def fibs(a, b, n):
url = f'https://numbers.apps.pipal.in/fibs?a={a}&b={b}&n={n}'
r = requests.get(url)
return [int(i) for i in r.text.strip().split("\n")]fibs(1, 1, 10)[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
%verify_problem fibs-apiFound 7 checks
✓ fibs(3, 4, 10)
✓ sum(fibs(3, 4, 10))
✗ fibs(3, 4, 0)
Traceback (most recent call last):
File "/opt/sigma/src/sigma/problems.py", line 262, in run
result = self.do_eval(self.code, env)
File "/opt/sigma/src/sigma/problems.py", line 242, in do_eval
return eval(self.code, env)
File "<string>", line 1, in <module>
File "/tmp/ipykernel_1369338/2104082634.py", line 4, in fibs
return [int(i) for i in r.text.strip().split("\n")]
File "/tmp/ipykernel_1369338/2104082634.py", line 4, in <listcomp>
return [int(i) for i in r.text.strip().split("\n")]
ValueError: invalid literal for int() with base 10: ''
✓ fibs(3, 4, 1)
✓ fibs(3, 4, 2)
✓ fibs(3, 4, 3)
✓ fibs(1, 1, 6)
💥 Oops! Your solution to problem fibs-api is incorrect or incomplete.
fibs(3,4 , 0)--------------------------------------------------------------------------- ValueError Traceback (most recent call last) Cell In[59], line 1 ----> 1 fibs(3,4 , 0) Cell In[56], line 4, in fibs(a, b, n) 2 url = f'https://numbers.apps.pipal.in/fibs?a={a}&b={b}&n={n}' 3 r = requests.get(url) ----> 4 return [int(i) for i in r.text.strip().split("\n")] Cell In[56], line 4, in <listcomp>(.0) 2 url = f'https://numbers.apps.pipal.in/fibs?a={a}&b={b}&n={n}' 3 r = requests.get(url) ----> 4 return [int(i) for i in r.text.strip().split("\n")] ValueError: invalid literal for int() with base 10: ''
Authentication
Simple authentication is done using username and password
%%file passwd.txt
vikrant=secretWriting passwd.txt
def get_auth_details(filepath):
with open(filepath) as f:
return f.read().strip().split("=")
username, password = get_auth_details("passwd.txt")
#print(username, password)
requests.get(url, auth=(username, password))vikrant secret
Kerberos authentication
pip install requests requests-kerberos
from request_kerberos import HTTPKerberosAuth, OPTIONAL
kerberos_auth = HTTPKerberosAuth(mutual_authentication=OPTIONAL)
r = requests.get(url, auth= kerberos_auth)--------------------------------------------------------------------------- ModuleNotFoundError Traceback (most recent call last) Cell In[63], line 1 ----> 1 from request_kerberos import HTTPKerberosAuth, OPTIONAL 2 kerberos_auth = HTTPKerberosAuth(mutual_authentication=OPTIONAL) 3 r = requests.get(url, auth= kerberos_auth) ModuleNotFoundError: No module named 'request_kerberos'