{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Online Data\n",
"\n",
"The internet is probably the most common place to seek information. In fact, every homepage you browse and every article you read is data and, as long as there is no pay wall or other security feature, free to you. Most of the data in such form is unordered and cannot easily be analysed quantitatively, like text or video clips (especially text usually does carry a lot of information). Beside this unstructured data, there are files for free to download, e.g. in a .csv format. Yet, all this has to be done by hand: either click some 'download' button to acquire a file or, even worse, copy a text online and paste it into a document.\n",
"\n",
"In the next sections, we will at first briefly introduce some basic elements of online communication and then present different ways to access data online from our computer without loading and storing it manually (we will write code manually, though).\n",
"\n",
"## Website basics \n",
"\n",
"### HTTP \n",
"\n",
"The **H**yper**T**ext **T**ransfer **P**rotocol is used for online communication in the world wide web (www). The basic operations are *requests* and *response*. In the www, when trying to open a website, you are actually sending a request to a server asking to provide the information stored under that web address. The server will then hopefully respond to your request by sending the respective information to your machine which then will be rendered in your browser. This response information does contain additional meta data, like the time of the request, status/error codes, etc which we will see later. An extension is **https** (**s**ecure).\n",
"\n",
"### URL\n",
"\n",
"The **U**niform **R**esource **L**ocator is the address your request is telling the server to look for information. It consists of several elements, ```http://www.uni-passau.de```:\n",
"\n",
"- the protocol: usually ```http``` or ```https``` in the www, followed by a colon and doube slash ```://```\n",
"\n",
"- the hostname: ```www.uni-passau.de``` \n",
"\n",
"- a path/query: appended with ```/``` or ```?``` to the hostname\n",
"\n",
"To know about these three separate parts enables us to automate navigation in the www using python.\n",
"\n",
"### HTML\n",
"\n",
"Information in the www is usually found in the **H**yper**T**ext **M**arkup **L**anguage for web browsers. HTML uses **tags** to structure hierarchical data, telling the browser how to display the single elements. Tags appear in pairs and are enclosed in angle brackets ```
```: paragraph, simple text information\n", "\n", "- ```
\n", " | length | \n", "height | \n", "
---|---|---|
obj_1 | \n", "19 | \n", "55 | \n", "
obj_2 | \n", "22 | \n", "64 | \n", "
\n", " | length | \n", "height | \n", "
---|---|---|
obj_1 | \n", "19 | \n", "55 | \n", "
obj_2 | \n", "22 | \n", "64 | \n", "
The University of Passau (Universit\\u00e4t Passau in German) is a public research university located in Passau, Lower Bavaria, Germany. Founded in 1973, it is the youngest university in Bavaria and consequently has the most modern campus in the state. Nevertheless, its roots as the Institute for Catholic Studies dates back to the early 17th century.\\n
Today it is home to four faculties and 39 different undergraduate and postgraduate degree programmes.
\"\n", " }\n", " }\n", " }\n", "}\n" ] } ], "source": [ "print(json.dumps(response.json(), indent=4))" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "title: University of Passau\n", "\n", "summary:The University of Passau (Universität Passau in German) is a public research university located in Passau, Lower Bavaria, Germany. Founded in 1973, it is the youngest university in Bavaria and consequently has the most modern campus in the state. Nevertheless, its roots as the Institute for Catholic Studies dates back to the early 17th century.\n", "
Today it is home to four faculties and 39 different undergraduate and postgraduate degree programmes.
\n" ] } ], "source": [ "# we can now navigate through the nested dictionaries\n", "title = response.json()['query']['pages']['409091']['title']\n", "print('title: ',title)\n", "summary = response.json()['query']['pages']['409091']['extract']\n", "print('\\nsummary:', summary)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Since the Text is still in html, as can be seen from tags, we are not yet finished. To convert html to normal text, one way is by using [**BeautifulSoup**.](https://pypi.org/project/beautifulsoup4/)\\\n", "Let's import it from the ```bs4``` package and transform the html." ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The University of Passau (Universität Passau in German) is a public research university located in Passau, Lower Bavaria, Germany. Founded in 1973, it is the youngest university in Bavaria and consequently has the most modern campus in the state. Nevertheless, its roots as the Institute for Catholic Studies dates back to the early 17th century.\n", "Today it is home to four faculties and 39 different undergraduate and postgraduate degree programmes.\n" ] } ], "source": [ "from bs4 import BeautifulSoup\n", "\n", "# first instantiate object, use 'lxml' parser (installation necessary) \n", "my_soup = BeautifulSoup(summary, 'lxml')\n", "\n", "# extract the text, i.e. remove tags\n", "print(my_soup.text)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Website content\n", "BeautifulSoup can not only extract text from html, but, in analogy to the json ```dumps()``` method from before, print html files more readable. We will leverage BeautifulSoup's functions now to extract again the same information, but now from the website directly, i.e. without an API.\n" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": [ "# first, send request - get response from ordinary url\n", "response = re.get('https://en.wikipedia.org/wiki/University_of_Passau')\n", "\n", "# second, extract content from the response (html)\n", "html = response.content\n", "\n", "# third, instantiate BS onject\n", "soup = BeautifulSoup(html)\n", "\n", "# if necessary look at a formatted version of the html\n", "# print(soup.prettify()) # -> very long output" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "The University of Passau (Universität Passau in German) is a public research university located in Passau, Lower Bavaria, Germany. Founded in 1973, it is the youngest university in Bavaria and consequently has the most modern campus in the state. Nevertheless, its roots as the Institute for Catholic Studies dates back to the early 17th century.\n", "Today it is home to four faculties and 39 different undergraduate and postgraduate degree programmes.\n" ] } ], "source": [ "# finally print the text attribute from the created object\n", "print(soup.text[588:1037]) # only part of document for shorter output " ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'The University of Passau (Universität Passau in German) is a public research university located in Passau, Lower Bavaria, Germany. Founded in 1973, it is the youngest university in Bavaria and consequently has the most modern campus in the state. Nevertheless, its roots as the Institute for Catholic Studies dates back to the early 17th century.\\n'" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "soup.find('p').getText()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can see, that we loaded the whole website as raw text. One can immediately see that it is not obvious how to extract the wanted summary only. We will later see some ways to extract text and leverage repeated structure across a website for its different pages to extract text sections like this." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Back to APIs\n", "\n", "Now, the API we will mostly use in this course is from [financialmodelingprep.com](https://financialmodelingprep.com/developer/docs/), providing financial and company data. In contrast to the Wikipedia API, a registration by e-mail is required. Please register to get your API key.\n", "\n", "To access the data, the key must be submitted in the request. We can use the params statement as before, handing it the respective statements.\n", "\n", "(Note that the API key is already saved in \"api_key\")" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "tags": [ "remove-cell" ] }, "outputs": [], "source": [ "api_key = 'my_key'\n", "api_key = '44c50c7a71efa92e8dac68f5902ea0ec'" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [], "source": [ "# define components of the request\n", "base_url = 'https://financialmodelingprep.com/api/v3/'\n", "filing = 'profile'\n", "stock = 'GOOG'\n", "params = {'apikey': api_key}\n", "\n", "# send request and store reponse\n", "response = re.get(base_url+filing+'/'+stock, params=params)" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[{'symbol': 'GOOG',\n", " 'price': 101.38,\n", " 'beta': 1.099484,\n", " 'volAvg': 23226134,\n", " 'mktCap': 1316794269696,\n", " 'lastDiv': 0.0,\n", " 'range': '95.27-152.1',\n", " 'changes': -0.01000214,\n", " 'companyName': 'Alphabet Inc.',\n", " 'currency': 'USD',\n", " 'cik': '0001652044',\n", " 'isin': 'US02079K1079',\n", " 'cusip': '02079K107',\n", " 'exchange': 'NASDAQ Global Select',\n", " 'exchangeShortName': 'NASDAQ',\n", " 'industry': 'Internet Content & Information',\n", " 'website': 'https://www.abc.xyz',\n", " 'description': 'Alphabet Inc. provides various products and platforms in the United States, Europe, the Middle East, Africa, the Asia-Pacific, Canada, and Latin America. It operates through Google Services, Google Cloud, and Other Bets segments. The Google Services segment offers products and services, including ads, Android, Chrome, hardware, Gmail, Google Drive, Google Maps, Google Photos, Google Play, Search, and YouTube. It is also involved in the sale of apps and in-app purchases and digital content in the Google Play store; and Fitbit wearable devices, Google Nest home products, Pixel phones, and other devices, as well as in the provision of YouTube non-advertising services. The Google Cloud segment offers infrastructure, platform, and other services; Google Workspace that include cloud-based collaboration tools for enterprises, such as Gmail, Docs, Drive, Calendar, and Meet; and other services for enterprise customers. The Other Bets segment sells health technology and internet services. The company was founded in 1998 and is headquartered in Mountain View, California.',\n", " 'ceo': 'Mr. Sundar Pichai',\n", " 'sector': 'Communication Services',\n", " 'country': 'US',\n", " 'fullTimeEmployees': '174014',\n", " 'phone': '650-253-0000',\n", " 'address': '1600 Amphitheatre Parkway',\n", " 'city': 'Mountain View',\n", " 'state': 'CA',\n", " 'zip': '94043',\n", " 'dcfDiff': 30.96,\n", " 'dcf': 132.35,\n", " 'image': 'https://financialmodelingprep.com/image-stock/GOOG.png',\n", " 'ipoDate': '2004-08-19',\n", " 'defaultImage': False,\n", " 'isEtf': False,\n", " 'isActivelyTrading': True,\n", " 'isAdr': False,\n", " 'isFund': False}]" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Without further processing, the json is already quite well structured to read\n", "response.json()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can also load symbols from all companies listed in the Dow Jones Index and then load historical stock price data from those companies using the afore acquired symbols." ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "200\n" ] } ], "source": [ "filing = 'dowjones_constituent'\n", "response = re.get(base_url+filing, params=params)\n", "print(response.status_code)" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " symbol name sector \\\n", "0 CRM Salesforce.Com Inc Technology \n", "1 WBA Walgreens Boots Alliance Inc Healthcare \n", "2 V Visa Inc Financial Services \n", "\n", " subSector headQuarter dateFirstAdded cik \\\n", "0 Technology San Francisco, CALIFORNIA 2020-08-31 0001108524 \n", "1 Healthcare Deerfield, ILLINOIS 2018-06-26 0001618921 \n", "2 Financial Services San Francisco, CALIFORNIA 2013-09-23 0001403161 \n", "\n", " founded \n", "0 2004-06-23 \n", "1 2014-12-31 \n", "2 2008-03-19 \n" ] } ], "source": [ "dji_df = pd.DataFrame.from_dict(response.json())\n", "print(dji_df.head(3))" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['CRM', 'WBA', 'V', 'NKE', 'UNH', 'TRV', 'VZ', 'INTC', 'WMT', 'JNJ', 'DIS', 'MCD', 'JPM', 'CAT', 'BA', 'AMGN', 'DOW', 'AAPL', 'GS', 'CSCO', 'MSFT', 'HD', 'PG', 'MRK', 'IBM', 'HON', 'KO', 'CVX', 'AXP', 'MMM']\n" ] } ], "source": [ "# extract symbol column and convert to list\n", "dji_symbols = dji_df.symbol.tolist()\n", "print(dji_symbols)" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [], "source": [ "# set the filing (from documentation)\n", "filing = 'historical-price-full'\n", "\n", "# create empty dataframe (before loop!) to collect data for every company in loop\n", "dji_hist = pd.DataFrame()\n", "\n", "# loop over first 5 symbols in the list\n", "for symbol in dji_symbols[:5]:\n", " \n", " # request data for every symbol \n", " response = re.get(base_url+filing+'/'+symbol, params=params)\n", " # break the loop if a response error occurs\n", " if response.status_code != 200:\n", " print('Error! Aborted')\n", " break\n", " # convert the response to json to temporary dataframe\n", " temp_df = pd.DataFrame.from_dict(response.json()['historical'], orient='columns')\n", " # add column with respective symbol\n", " temp_df['symbol'] = symbol\n", " # append temporary dataframe to collect dataframe \n", " dji_hist = pd.concat([dji_hist, temp_df], ignore_index=True)\n", " # delete temporary dataframe before next iteration\n", " del temp_df" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "AxesSubplot(0.125,0.11;0.775x0.77)\n" ] }, { "data": { "image/png": "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NmyYxo4dqwULFmjz5s0aMGCAYmNj1adPH0lSmzZt1LNnTw0ZMkTr1q3T6tWrlZWVpX79+ik2NlaSdOeddyo4OFgZGRnaunWr5s6dq6lTp2r48OH+fkgAAKAW8vvLVS+88IL+8pe/6MEHH1RZWZliY2N13333KTs7214zcuRIHTlyREOHDlV5ebmuvfZa5eXlKTQ01F4ze/ZsZWVl6frrr1dgYKD69u2rnJwce394eLiWLFmizMxMtW/fXs2bN1d2drbPuXQAAEDd5ffz5NQmnCcHcA7nyQHwazl2nhwAAICagMgBAABGInIAAICRiBwAAGAkIgcAABiJyAEAAEYicgAAgJGIHAAAYCQiBwAAGInIAQAARiJyAACAkYgcAABgJCIHAAAYicgBAABGInIAAICRiBwAAGAkIgcAABiJyAEAAEYicgAAgJGIHAAAYCQiBwAAGInIAQAARiJyAACAkYgcAABgJCIHAAAYicgBAABGInIAAICRiBwAAGAkIgcAABiJyAEAAEYicgAAgJGIHAAAYCQiBwAAGInIAQAARiJyAACAkYgcAABgJCIHAAAYicgBAABGInIAAICRiBwAAGAkIgcAABiJyAEAAEYicgAAgJGIHAAAYCQiBwAAGInIAQAARiJyAACAkYgcAABgJCIHAAAYicgBAABGInIAAICRiBwAAGAkIgcAABiJyAEAAEYicgAAgJGIHAAAYCQiBwAAGInIAQAARiJyAACAkYgcAABgpCqJnG+++UZ33XWXmjVrprCwMCUmJmrDhg32fsuylJ2drZiYGIWFhSklJUWff/65z20cOHBA6enpcrlcioiIUEZGhg4fPuyz5tNPP1WXLl0UGhqquLg4TZgwoSoeDgAAqIX8HjkHDx5U586dVb9+fX300Uf67LPPNGnSJDVp0sReM2HCBOXk5GjGjBkqLi5Ww4YNlZqaqmPHjtlr0tPTtXXrVuXn52vhwoUqLCzU0KFD7f1er1c9evRQixYtVFJSookTJ2rMmDF65ZVX/P2QAABALRRgWZblzxscNWqUVq9erZUrV551v2VZio2N1WOPPabHH39ckuTxeBQVFaXc3Fz169dP27ZtU0JCgtavX68OHTpIkvLy8nTDDTfo66+/VmxsrKZPn64///nPcrvdCg4Otu/7vffe0/bt289pVq/Xq/DwcHk8HrlcLj88egDn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VHVXYGCg3G63HTmNGzfWJ598ogsvvFDS95ETExOjiooKJ8esE5KTk32ur1q1Su3bt1dYWJi9LSAgQMuWLavu0eq8Xbt26fXXX9cbb7wht9utnj17asGCBU6PZbyysjLl5ubq73//uzwej/r3768777xTSUlJ+uSTT5SQkOD0iNWCyKlFNmzYoJkzZ+qtt95SfHy8Bg0apPT0dDVp0sTp0eqkc4kcjuQ444fPBZx15MgRzZ49W6NHj1Z5eTl/J6pBWFiYbr31Vt111136wx/+oMDA79+dUr9+/ToVObwnpxbp0KGDpk+frm+//VbDhw/X/PnzdcEFF6hfv37Kz893ejwA8FFYWKh77rlH0dHRGjFihG655RatXr3a6bHqhBYtWmjVqlUqLCzUzp07nR7HMUROLRQaGqq77rpLS5cu1ZYtW1RWVqaePXvqwIEDTo9WpwQEBOjQoUPyer3yeDwKCAjQ4cOH5fV67QtQ1+zbt0/PPvusLr30UnXv3l27du1STk6O9u3bp1dffVWdOnVyesQ6Yfv27XrzzTf17bff6uqrr1b79u31/PPPS/r+3666gperaqmvv/5aubm5ys3N1XfffacBAwZo7NixqlevVn+xfK0SGBjo84+FZVlnvc6h+erHy1XO6NWrl/71r3+pefPmGjBggAYNGqRWrVo5PVadd/jwYf3jH//Q66+/rrVr16pbt26688471adPH5133nlOj1eliJxa5MSJE5o/f75mzpyplStXqlevXho0aJB69ep1zp+8gv8UFBSc07pu3bpV8ST49NNPfa5fc801mjdvni644AKf7VdccUV1jlXn/PGPf1RGRoZ69+7Nv0k11LZt2/Taa6/pzTff1IEDB3Ty5EmnR6pSRE4t0qxZMzVu3FgDBw7U3Xffbb/h9YdcLlc1T1Y3HTp0SI0bN/7ZNQUFBURONag8qna2f84qt3NUDfj/Tp06pQULFuiWW25xepQqReTUIpXvjpfO/poq/5BXr+7du2vx4sUKCQk56/6CggL17t2bM79Wg6+++uqc1rVo0aKKJwFqLsuytHz5ch09elTXXHNNnfhkLm/gqEWWL1/u9Ag4w//+7//q9ttv1/z5830CVPr+UyVpaWm69957HZqubiFeAF/l5eV65JFHtHHjRnXq1EmTJk3SDTfcoDVr1kiSIiMjtWTJEuNfwuVIDvAr7du3T126dFHnzp313//93/b2lStXKi0tTXfffbemTZvm4IR1x549e85p3e9+97sqngSoGQYPHqzCwkINHDhQH3zwgQIDA2VZlqZMmaLAwECNHDlSjRo10gcffOD0qFWKyDHIxo0blZ2drYULFzo9Sp3x73//W126dNFtt92mqVOnatWqVerVq5fS09M1Y8YMp8erM374SbdKZ37iLSAgQKdOnaru0QBHnH/++ZozZ466deumb775RnFxcVq2bJm6d+8uSVq3bp3++Mc/yu12OztoFePlqlpm8eLFys/PV3BwsAYPHqwLL7xQ27dv16hRo/TBBx8oNTXV6RHrlIsuukh5eXnq3r27PB6P5s+fr/79+xM41ezjjz8+63bLsvTWW28pJydHjRo1quapAOeUlpbq0ksvlfR98ISGhtrfqyd9f1Rz//79To1XbYicWmTmzJkaMmSImjZtqoMHD+q1117T5MmT9dBDD+mOO+7Qli1b1KZNG6fHrDMqT/bXsmVLzZ49WzfffLP69OmjiRMn+pwIkE+7Vb22bdv+aNu//vUvjRo1Sjt37tTIkSP12GOPOTAZ4IyKigqfj/EHBQX5HO2sKycEJHJqkalTp2r8+PEaMWKE3n33Xd1222166aWXtHnz5h+dDwRVLyIi4kcn/5s3b57efvtt+zqfdqt+Gzdu1BNPPKGVK1dq8ODB+vDDD3/ydAuAyV577TX7COapU6eUm5ur5s2bS1Kd+dQn78mpRRo2bKitW7eqZcuWsixLISEhWr58uTp37uz0aHXSihUrzum/hjhPTvX497//rT/96U969913dfvtt2vs2LGc8Rh1VsuWLc/p36fdu3dXwzTO4UhOLXL06FE1aNBA0veHGkNCQhQTE+PwVHVX5Rv44LwHH3xQM2fOVHJysjZs2KB27do5PRLgqOXLlys+Pt7pMRzHkZxaJDAwUGPHjrUPPz7xxBMaMWKEffix0sMPP+zEeHXOT32i50x8oqd6BAYGKjQ0VK1bt/7ZdRs3bqymiQBnBQYGqkWLFkpOTtZ1112n5ORknX/++U6PVe2InFrkXA4/BgQE6Isvvqimieq2999//yf3FRUVKScnRxUVFTp27Fg1TlU3jRkz5pwOzT/11FPVMA3gvBUrVtiX4uJinThxQhdeeKEdPMnJyYqKinJ6zCpH5AB+tGPHDvvj/Onp6XrmmWc4Gy8ARx07dkxr1qyxo2fdunU6efKkWrdura1btzo9XpUK/M9LUFMsW7ZMCQkJPh9PruTxeHTZZZdp5cqVDkyGffv2aciQIUpMTNSpU6e0adMmzZo1i8CpJk2aNFHTpk1/dImPj1dqaqry8/OdHhFwTGhoqK677jo9+eSTevrpp/Xwww+rUaNG2r59u9OjVTneeFyLTJkyRUOGDDnreVfCw8N13333afLkyerSpYsD09VNHo9Hzz77rF544QW1a9dOS5cu5c/fAVOmTDnr9vLycpWUlKh379565513dOONN1bvYICDTpw4obVr12r58uX2y1ZxcXHq2rWrXnzxxTrxyU9erqpFWrRooby8vJ884d/27dvVo0ePc/4eH/w2EyZM0Pjx4xUdHa1nn31WN910k9Mj4SdMnjxZ77zzjv3lhIDprrvuOhUXFys+Pl7dunVTly5d1K1btzr3iVwipxYJDQ3Vli1bdPHFF591/65du5SYmKijR49W82R1U2BgoMLCwpSSkuJzZtEf+uc//1mNU+Fsdu7cqU6dOunAgQNOjwJUi/r16ysmJkZ9+vRR9+7d1a1bNzVr1szpsaodL1fVIueff/7PRs6nn35a5yrdSQMGDKgzp0av7Y4fP67g4GCnxwCqTXl5uVauXKkVK1Zo/Pjx6t+/vy699FJ169bNjp7zzjvP6TGrHEdyapGHHnpIK1as0Pr16xUaGuqz7+jRo/r973+v5ORk5eTkODQhUDMNGzZM27dvV15entOjAI44dOiQVq1aZb8/55NPPtEll1yiLVu2OD1alSJyapHS0lJdddVVCgoKUlZWllq1aiXp+/fiTJs2TadPn9bGjRvrxLkPgDMNHz78rNs9Ho82btyonTt3qrCwUO3bt6/myYCaoaKiQuvXr9fy5cu1fPlyrVq1SseOHTP+u/WInFrmq6++0gMPPKDFixer8qkLCAhQamqqpk2bxmm8USclJyefdbvL5VKrVq30wAMP8HcDdUpFRYU2bNigFStWaPny5Vq9erWOHDmi888/3z4ZYHJysvGnuSByaqmDBw9q165dsixLl1xyiZo0aeL0SACAGsLlcunIkSOKjo62g6Z79+666KKLnB6tWhE5AAAY5uWXX1ZycrIuvfRSp0dxFJEDAACMxNc6AAAAIxE5AADASEQOAAAwEpEDAACMROQAqDNatmz5k99Yfq7GjBmjdu3a+WUeAFWLyAEAAEYicgAAgJGIHACOeeedd5SYmKiwsDA1a9ZMKSkpKigoUP369eV2u33WDhs2TF26dJEk5ebmKiIiQgsXLlSrVq3UoEED3Xrrrfruu+80a9YstWzZUk2aNNHDDz/8o+/mOXTokPr376+GDRvq/PPP17Rp03z279mzRzfddJMaNWokl8ul22+/XaWlpVX7BwGgShA5ABzx7bffqn///ho0aJC2bdumFStW6JZbblH79u114YUX6o033rDXnjx5UrNnz9agQYPsbd99951ycnL01ltvKS8vTytWrNDNN9+sDz/8UB9++KHeeOMNvfzyy3rnnXd87nfixIlq27atPv74Y40aNUqPPPKI8vPzJX3/fT833XSTDhw4oIKCAuXn5+uLL77QHXfcUT1/KAD8ywIAB5SUlFiSrC+//PJH+8aPH2+1adPGvv7uu+9ajRo1sg4fPmxZlmW9/vrrliRr165d9pr77rvPatCggXXo0CF7W2pqqnXffffZ11u0aGH17NnT577uuOMOq1evXpZlWdaSJUusoKAga8+ePfb+rVu3WpKsdevWWZZlWU899ZTVtm3b3/DIAVQXjuQAcETbtm11/fXXKzExUbfddpteffVVHTx4UJJ0zz33aNeuXVq7dq2k71+euv3229WwYUP75xs0aODzZYNRUVFq2bKlGjVq5LOtrKzM536TkpJ+dH3btm2SpG3btikuLk5xcXH2/oSEBEVERNhrANQeRA4ARwQFBSk/P18fffSREhIS9MILL6hVq1bavXu3IiMjdeONN+r1119XaWmpPvroI5+XqiSpfv36PtcDAgLOuq2ioqLKHwuAmonIAeCYgIAAde7cWU8//bQ+/vhjBQcHa/78+ZKkwYMHa+7cuXrllVd00UUXqXPnzn65z8qjQ2deb9OmjSSpTZs22rt3r/bu3Wvv/+yzz1ReXq6EhAS/3D+A6lPP6QEA1E3FxcVaunSpevToocjISBUXF2v//v12cKSmpsrlcmns2LF65pln/Ha/q1ev1oQJE9SnTx/l5+fr7bff1qJFiyRJKSkpSkxMVHp6uqZMmaJTp07pwQcfVLdu3dShQwe/zQCgenAkB4AjXC6XCgsLdcMNN+jSSy/Vk08+qUmTJqlXr16SpMDAQN1zzz06ffq0BgwY4Lf7feyxx7RhwwZdeeWVGjt2rCZPnqzU1FRJ3x9Zev/999WkSRN17dpVKSkpuvDCCzV37ly/3T+A6hNgWZbl9BAAcDYZGRnav3+/FixY4PQoAGohXq4CUON4PB5t3rxZc+bMIXAA/GpEDoAa56abbtK6det0//336w9/+IPT4wCopXi5CgAAGIk3HgMAACMROQAAwEhEDgAAMBKRAwAAjETkAAAAIxE5AADASEQOAAAwEpEDAACM9H93P+ruEUg1lAAAAABJRU5ErkJggg==\n", "text/plain": [ "