Created
October 20, 2020 21:33
-
-
Save hlecuanda/d497b9927291d431417fb90f9d7df5cb to your computer and use it in GitHub Desktop.
Python Requests Rapidamente.ipynb
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"nbformat": 4, | |
"nbformat_minor": 0, | |
"metadata": { | |
"colab": { | |
"name": "Python Requests Rapidamente.ipynb", | |
"provenance": [], | |
"collapsed_sections": [], | |
"toc_visible": true, | |
"authorship_tag": "ABX9TyM5JSeAO7Z/Dy5SeOGVhPFd", | |
"include_colab_link": true | |
}, | |
"kernelspec": { | |
"name": "python3", | |
"display_name": "Python 3" | |
} | |
}, | |
"cells": [ | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "view-in-github", | |
"colab_type": "text" | |
}, | |
"source": [ | |
"<a href=\"https://colab.research.google.com/gist/hlecuanda/d497b9927291d431417fb90f9d7df5cb/python-requests-rapidamente.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "kRW1R5IsbLgY" | |
}, | |
"source": [ | |
"# Introducción rápida al uso de la librería `requests`\n", | |
"\n", | |
"Aquí veremos de manera concisa la forma correcta de invocar un request simple a un api HTTP, limpiando nuestras conecciones, y validando haber recibido una respuesta correcta del servidor antes de procesarla. " | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "Qm3Rgrh_3OEC" | |
}, | |
"source": [ | |
"## setup basico. \n", | |
"\n", | |
"Importamos algunas librerias y la linida impresora para ver nuestros objetos" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "Rd3LrQgibJyx", | |
"outputId": "87aaf1de-bafe-44c4-e91b-8ebe096ce5a7", | |
"colab": { | |
"base_uri": "https://localhost:8080/", | |
"height": 34 | |
} | |
}, | |
"source": [ | |
"import requests\n", | |
"import json\n", | |
"import pprint\n", | |
"\n", | |
"\n", | |
"# una linda impresora con indentacion, maxima profundiad 3 \n", | |
"# no-compacta\n", | |
"pp = pprint.PrettyPrinter(indent=2,depth=3,compact=False)\n" | |
], | |
"execution_count": 48, | |
"outputs": [ | |
{ | |
"output_type": "stream", | |
"text": [ | |
"<Response [200]>\n" | |
], | |
"name": "stdout" | |
} | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "Iaa1gZ0j3jwA" | |
}, | |
"source": [ | |
"## El ejemplo mas básico de uso de la libreria requests\n", | |
"\n", | |
"Hacemos un `get request` a un api que solo responde con tu propio IP en un objeto json simple (el codigo del servidor lo puedes ver al final)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "BtBFkvKa3hWK", | |
"outputId": "3782ccc4-0f97-4c54-ee7b-7d3b279f7fde", | |
"colab": { | |
"base_uri": "https://localhost:8080/", | |
"height": 34 | |
} | |
}, | |
"source": [ | |
"\n", | |
"# un api estupido que responde con tu propio IP en un mensaje json\n", | |
"url = 'https://mezaops.appspot.com/knock/'\n", | |
"r = requests.get(url)\n", | |
"\n", | |
"pp.pprint(r)" | |
], | |
"execution_count": 90, | |
"outputs": [ | |
{ | |
"output_type": "stream", | |
"text": [ | |
"<Response [200]>\n" | |
], | |
"name": "stdout" | |
} | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "fgnMuiCVdZXT", | |
"outputId": "b0a3f82c-f9a4-4fc9-e4da-ad33f814965f", | |
"colab": { | |
"base_uri": "https://localhost:8080/", | |
"height": 34 | |
} | |
}, | |
"source": [ | |
"pp.pprint(r.content) " | |
], | |
"execution_count": 49, | |
"outputs": [ | |
{ | |
"output_type": "stream", | |
"text": [ | |
"b'{\\n \"ip\": \"35.225.45.29\"\\n}\\n'\n" | |
], | |
"name": "stdout" | |
} | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "eq2nwHFWepsW" | |
}, | |
"source": [ | |
"aqui `r.content` es una propiedad del objeto `r` definido por `requests` y contiene un `bytearray` con la respuesta del servidor, que en este caso es un objeto json, con una sola propiedad, \"ip\" cuyo valor es tu propio IP\n", | |
"\n", | |
"el `bytearray` lo debemos convertir a un objeto python para poder usarlo. requests usa `bytearray` para el contenido de la respuesta porque es el tipo mas generico en el que cabe cualquier respuesa, ya sea texto puro, o bits brutos binarios de una imagen. solo tu sabes que es lo que esperas de el servidor. \n", | |
"\n", | |
"para poder usarlo, lo pasamos por `json.loads` que automaticamente detecta el tipo texto y el encoding `utf8` que es el default para el tipo mime `application/json`" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "dVQWGw5VdyXT", | |
"outputId": "471917de-3198-40ff-df29-5232b3533107", | |
"colab": { | |
"base_uri": "https://localhost:8080/", | |
"height": 122 | |
} | |
}, | |
"source": [ | |
"# eso es json en bruto, para poderlo usar, pasalo por json.loads() \n", | |
"# loads quiere decir \"load string\"\n", | |
"\n", | |
"response_content_object = json.loads(r.content)\n", | |
"\n", | |
"print('el objeto diccionario completo:')\n", | |
"pp.pprint(response_content_object) # response_obect es un diccionario con un solo elemento: 'ip'.\n", | |
"\n", | |
"print('\\n')\n", | |
"print('el elemento \"ip\" en el diccionario:')\n", | |
"pp.pprint(response_content_object['ip'])\n" | |
], | |
"execution_count": 50, | |
"outputs": [ | |
{ | |
"output_type": "stream", | |
"text": [ | |
"el objeto diccionario completo:\n", | |
"{'ip': '35.225.45.29'}\n", | |
"\n", | |
"\n", | |
"el elemento \"ip\" en el diccionario:\n", | |
"'35.225.45.29'\n" | |
], | |
"name": "stdout" | |
} | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "xL2zWHEk4BEg" | |
}, | |
"source": [ | |
"## Otras propiedades de los objetos `request`" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "H05KcZ5cgbRV" | |
}, | |
"source": [ | |
"El objeto respuesta `r` contiene otras propiedades que puedes usar en el codigo para validad si has recibido una respuesta valida del servidor, notablemente el codigo de respuesta en los encabezados:" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "MAIIs-pQgom3", | |
"outputId": "0fc857ee-d381-46ea-86c4-a3f2b97c1efe", | |
"colab": { | |
"base_uri": "https://localhost:8080/", | |
"height": 34 | |
} | |
}, | |
"source": [ | |
"pp.pprint(r.status_code)" | |
], | |
"execution_count": 51, | |
"outputs": [ | |
{ | |
"output_type": "stream", | |
"text": [ | |
"200\n" | |
], | |
"name": "stdout" | |
} | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "4AhyyjMVpjmp" | |
}, | |
"source": [ | |
"Una lista de todas las propiedades contenidas en el objeto `r`" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "RhXx2jU6hS53", | |
"outputId": "05c95a8f-0637-4889-f1b5-80a47338410b", | |
"colab": { | |
"base_uri": "https://localhost:8080/", | |
"height": 316 | |
} | |
}, | |
"source": [ | |
"properties = [ prop for prop in dir(r) if '_' not in prop ]\n", | |
"\n", | |
"pp.pprint(properties)" | |
], | |
"execution_count": 52, | |
"outputs": [ | |
{ | |
"output_type": "stream", | |
"text": [ | |
"[ 'close',\n", | |
" 'connection',\n", | |
" 'content',\n", | |
" 'cookies',\n", | |
" 'elapsed',\n", | |
" 'encoding',\n", | |
" 'headers',\n", | |
" 'history',\n", | |
" 'json',\n", | |
" 'links',\n", | |
" 'next',\n", | |
" 'ok',\n", | |
" 'raw',\n", | |
" 'reason',\n", | |
" 'request',\n", | |
" 'text',\n", | |
" 'url']\n" | |
], | |
"name": "stdout" | |
} | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "ZHrzpUsrsAPQ" | |
}, | |
"source": [ | |
"Veamos que es lo que hay en todo el objeto 'r' que nos pueda servir para validar la respuesta antes de procesarla, evitando un error o excepcion en caso de no recibir respuesta adecuada\n" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "o01wXo7nsjQy", | |
"outputId": "2c38508b-15f4-4bcb-e465-6712c1d6023a", | |
"colab": { | |
"base_uri": "https://localhost:8080/", | |
"height": 1000 | |
} | |
}, | |
"source": [ | |
"for p in properties:\n", | |
" print('Property:>>{}<<'.format(p))\n", | |
" print('With Value:{}'.format(r.__getattribute__(p)))\n", | |
" print('-----------------------\\n')" | |
], | |
"execution_count": 78, | |
"outputs": [ | |
{ | |
"output_type": "stream", | |
"text": [ | |
"Property:>>close<<\n", | |
"With Value:<bound method Response.close of <Response [200]>>\n", | |
"-----------------------\n", | |
"\n", | |
"Property:>>connection<<\n", | |
"With Value:<requests.adapters.HTTPAdapter object at 0x7f20537aa9e8>\n", | |
"-----------------------\n", | |
"\n", | |
"Property:>>content<<\n", | |
"With Value:b'{\\n \"ip\": \"35.225.45.29\"\\n}\\n'\n", | |
"-----------------------\n", | |
"\n", | |
"Property:>>cookies<<\n", | |
"With Value:<RequestsCookieJar[]>\n", | |
"-----------------------\n", | |
"\n", | |
"Property:>>elapsed<<\n", | |
"With Value:0:00:00.035224\n", | |
"-----------------------\n", | |
"\n", | |
"Property:>>encoding<<\n", | |
"With Value:None\n", | |
"-----------------------\n", | |
"\n", | |
"Property:>>headers<<\n", | |
"With Value:{'Content-Type': 'application/json', 'X-Cloud-Trace-Context': 'f673bb912acce55214567f4485fdda2e;o=1', 'Date': 'Tue, 20 Oct 2020 20:20:40 GMT', 'Server': 'Google Frontend', 'Content-Length': '29'}\n", | |
"-----------------------\n", | |
"\n", | |
"Property:>>history<<\n", | |
"With Value:[]\n", | |
"-----------------------\n", | |
"\n", | |
"Property:>>json<<\n", | |
"With Value:<bound method Response.json of <Response [200]>>\n", | |
"-----------------------\n", | |
"\n", | |
"Property:>>links<<\n", | |
"With Value:{}\n", | |
"-----------------------\n", | |
"\n", | |
"Property:>>next<<\n", | |
"With Value:None\n", | |
"-----------------------\n", | |
"\n", | |
"Property:>>ok<<\n", | |
"With Value:True\n", | |
"-----------------------\n", | |
"\n", | |
"Property:>>raw<<\n", | |
"With Value:<urllib3.response.HTTPResponse object at 0x7f20537aaf60>\n", | |
"-----------------------\n", | |
"\n", | |
"Property:>>reason<<\n", | |
"With Value:OK\n", | |
"-----------------------\n", | |
"\n", | |
"Property:>>request<<\n", | |
"With Value:<PreparedRequest [GET]>\n", | |
"-----------------------\n", | |
"\n", | |
"Property:>>text<<\n", | |
"With Value:{\n", | |
" \"ip\": \"35.225.45.29\"\n", | |
"}\n", | |
"\n", | |
"-----------------------\n", | |
"\n", | |
"Property:>>url<<\n", | |
"With Value:https://mezaops.appspot.com/knock/\n", | |
"-----------------------\n", | |
"\n" | |
], | |
"name": "stdout" | |
} | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "Qa3zNuHYuzhv" | |
}, | |
"source": [ | |
"podemos ver que `requests` amablemente provee un objeto `json` ya procesado que podemos usar directamente.\n", | |
"\n", | |
"tambien nos provee un objeto `ok` con valor `True` que podemos usar para determinar si el request fue exitoso con un bonito if asi:\n", | |
"\n", | |
"```\n", | |
"if r.ok:\n", | |
" # hacemos algo c on r\n", | |
"else:\n", | |
" # procesams el error\n", | |
"```\n", | |
"\n", | |
"tenemos tambien `r.reason` entonces, muy utilmente podemos decir\n", | |
"```\n", | |
"if r.ok:\n", | |
" # hacemos algo c on r\n", | |
"else:\n", | |
" print('request fallo porque {}'.format(r.reason))\n", | |
"```\n", | |
"\n", | |
"nota tambien que tenemos un metodo `r.close()` lo que nos dice que el request deja abierta una coneccion http al servidor que podemos reusar, y que debemos cerrar al terminar, \n", | |
"\n", | |
"y que tenemos un metodo `r.next()` que hace de nuestro objeto `r`` lindo iterable!! omg, que util! \n", | |
"\n", | |
"podriamos usar algo asi:\n", | |
"\n", | |
"```\n", | |
"for x in r:\n", | |
" print(x.content)\n", | |
"```\n", | |
"\n", | |
"esto es para inspecionar conecciones redireccionadas (algunos apis aprovechan ese tipo de conecciones para generar listas de resultaos)\n", | |
"\n" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "gqDsD6Ty4Zny" | |
}, | |
"source": [ | |
"## Codigo generico para un request simple, con verificacion de status y manejo de errores en la coneccion" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "pXAaIP1X4VZd" | |
}, | |
"source": [ | |
"Podemos ver que el objeto respuesta que nos ofrece la libreria `requests` contiene una multitud de objetos utiles para la validacion de una respuesta del servidor. \n", | |
"\n", | |
"Hay que notar tambien, dada la presencia de un metodo `r.close()` es que la conección esta abierta, y debemos cerrarla cuando ya no sea necesaria. (es el caso análogo a cerrar un archivo despues de escribir en el) para esto usamos un administrador de contextos, de la misma manera que se hace al abrir y cerrar archivos.\n", | |
"\n", | |
"Finalmente, nuestro código lo podemos escribir de la siguiente manera:\n", | |
"\n" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "4bMYRD7muy9C", | |
"outputId": "958eec5d-4baf-4634-c855-bacdd2e61894", | |
"colab": { | |
"base_uri": "https://localhost:8080/", | |
"height": 87 | |
} | |
}, | |
"source": [ | |
"from contextlib import closing # contextos para cerrar conecciones http\n", | |
"\n", | |
"url = 'https://mezaops.appspot.com/knock/'\n", | |
"\n", | |
"with closing(requests.get(url)) as newreq: # al terminar de usar newreq, la coneccion se cerrara\n", | |
" if newreq.ok: # tenemos una respuesta valida\n", | |
" print('Request exitoso') # se lo decimos al usuario\n", | |
" print('Respuesta:')\n", | |
" pp.pprint(newreq.json()) # ya tenemos un objeto json procesado \n", | |
" print('http status code: {}'.format(newreq.status_code))\n", | |
"\n", | |
" else: # Algo sucedio mal. probablemente en el servidor\n", | |
" print('Request a {} fallo por esta razon; {}'.format(\n", | |
" newreq.url,newreq.reason)) # le avisamos al usuario que ocurrio mal\n", | |
" print('http status code: {}'.format(newreq.status_code))\n", | |
"\n" | |
], | |
"execution_count": 88, | |
"outputs": [ | |
{ | |
"output_type": "stream", | |
"text": [ | |
"Request exitoso\n", | |
"Respuesta:\n", | |
"{'ip': '35.225.45.29'}\n", | |
"http status code: 200\n" | |
], | |
"name": "stdout" | |
} | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "Za8nY39Dz-la" | |
}, | |
"source": [ | |
"El servidor responde con error 404 cuando pides un url que no es `/knock/`, veamos como se comporta: " | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "uCTps3Co0enP", | |
"outputId": "dcb79ef7-1165-42cb-cb65-e10e05c5b160", | |
"colab": { | |
"base_uri": "https://localhost:8080/", | |
"height": 52 | |
} | |
}, | |
"source": [ | |
"url = 'https://mezaops.appspot.com/knick/' # \"knick\", no \"knock\"\n", | |
"\n", | |
"with closing(requests.get(url)) as newreq: # al terminar de usar newreq, la coneccion se cerrara\n", | |
" if newreq.ok: # tenemos una respuesta valida\n", | |
" print('Request exitoso') # se lo decimos al usuario\n", | |
" print('Respuesta:')\n", | |
" pp.pprint(newreq.json()) # ya tenemos un objeto json procesado \n", | |
" print('http status code: {}'.format(newreq.status_code))\n", | |
"\n", | |
" else: # Algo sucedio mal. probablemente en el servidor\n", | |
" print('Request a {} fallo por esta razon; \"{}\"'.format(\n", | |
" newreq.url,newreq.reason)) # le avisamos al usuario que ocurrio mal\n", | |
" print('http status code: {}'.format(newreq.status_code))" | |
], | |
"execution_count": 86, | |
"outputs": [ | |
{ | |
"output_type": "stream", | |
"text": [ | |
"Request a https://mezaops.appspot.com/knick/ fallo por esta razon; \"Not Found\"\n", | |
"http status code: 404\n" | |
], | |
"name": "stdout" | |
} | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "neU5uiT_1wjM" | |
}, | |
"source": [ | |
"# Código fuente del servidor\n", | |
"\n", | |
"\n", | |
"El siguiente es el codigo corriendo en el servidor (Google app engine con python 2.7, usando Flask) (lleva como 4 años ahi jajaj ya tengo que actualizarlo porque py2 ya no fifa)\n", | |
"\n", | |
"\n", | |
"```python\n", | |
"\n", | |
"\"\"\" Knocker - knock to trigger an open firewall port\n", | |
"\n", | |
" %his module implements a simple service infroming the\n", | |
" caller of his public IP address as seen from the cloud\n", | |
"\n", | |
"\"\"\"\n", | |
"from flask import Flask\n", | |
"from werkzeug.wrappers import Response\n", | |
"\n", | |
"app = Flask(__name__)\n", | |
"\n", | |
"from flask import request\n", | |
"\n", | |
"@app.route('/knock/', methods=['GET', 'POST'])\n", | |
"def whatismyip():\n", | |
" \"\"\"Return the callers IP plain and simple\"\"\"\n", | |
" return Response('Data: %s' % request.remote.addr)\n", | |
"\n", | |
"@app.errorhandler(404)\n", | |
"def page_not_found(e):\n", | |
" \"\"\"Return a custom 404 error.\"\"\"\n", | |
" return 'Sorry, nothing at this URL.', 404\n", | |
"\n", | |
"```" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "NgCHGxpl7F3N" | |
}, | |
"source": [ | |
"-Hector Lecuanda ([email protected])" | |
] | |
} | |
] | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment