{ "cells": [ { "cell_type": "markdown", "id": "83b2077c", "metadata": {}, "source": [ "---\n", "title: \"Working with APIs in Python\"\n", "description: \"Learn to fetch, process, and analyze data from web APIs using Python's requests library\"\n", "date: 2025-01-27\n", "lastmod: 2025-01-27\n", "author: \"Zer0-Mistakes Team\"\n", "layout: notebook\n", "difficulty: beginner\n", "tags: [python, api, requests, json, web-scraping]\n", "categories: [Notebooks, Tutorials]\n", "toc: true\n", "comments: true\n", "---\n", "\n", "# Working with APIs in Python\n", "\n", "Learn to fetch and process data from web APIs using Python's `requests` library. This tutorial covers HTTP methods, JSON parsing, error handling, and working with real public APIs.\n", "\n", "**What you'll learn:**\n", "- Making HTTP GET and POST requests\n", "- Parsing JSON responses into Python objects\n", "- Error handling and retry strategies\n", "- Working with query parameters and headers\n", "- Processing API data with Pandas" ] }, { "cell_type": "markdown", "id": "99ea236b", "metadata": {}, "source": [ "## Setup and Imports" ] }, { "cell_type": "code", "execution_count": 1, "id": "ff43c49e", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "āœ… Libraries imported successfully!\n", "Requests version: 2.32.5\n" ] } ], "source": [ "# Import required libraries\n", "import requests\n", "import json\n", "import pandas as pd\n", "from datetime import datetime\n", "\n", "print(\"āœ… Libraries imported successfully!\")\n", "print(f\"Requests version: {requests.__version__}\")" ] }, { "cell_type": "markdown", "id": "750a4a21", "metadata": {}, "source": [ "## Basic GET Request\n", "\n", "Let's start by fetching data from a simple, public API:" ] }, { "cell_type": "code", "execution_count": null, "id": "31ecda7e", "metadata": {}, "outputs": [], "source": [ "# Make a simple GET request to JSONPlaceholder (free fake API)\n", "url = \"https://jsonplaceholder.typicode.com/posts/1\"\n", "response = requests.get(url)\n", "\n", "print(\"🌐 Basic GET Request\")\n", "print(\"=\" * 50)\n", "print(f\"URL: {url}\")\n", "print(f\"Status Code: {response.status_code}\")\n", "print(f\"Response Time: {response.elapsed.total_seconds():.3f}s\")\n", "\n", "# Parse JSON response\n", "data = response.json()\n", "print(f\"\\nšŸ“¦ Response Data:\")\n", "print(f\" User ID: {data['userId']}\")\n", "print(f\" Post ID: {data['id']}\")\n", "print(f\" Title: {data['title'][:50]}...\")\n", "print(f\" Body: {data['body'][:100]}...\")" ] }, { "cell_type": "markdown", "id": "e17ffbff", "metadata": {}, "source": [ "## Query Parameters\n", "\n", "Pass parameters to filter API responses:" ] }, { "cell_type": "code", "execution_count": null, "id": "4eb20e46", "metadata": {}, "outputs": [], "source": [ "# Use query parameters to filter posts by user\n", "base_url = \"https://jsonplaceholder.typicode.com/posts\"\n", "params = {\"userId\": 1} # Get posts from user 1 only\n", "\n", "response = requests.get(base_url, params=params)\n", "posts = response.json()\n", "\n", "print(f\"šŸ“ Posts by User 1:\")\n", "print(f\"Total posts: {len(posts)}\\n\")\n", "\n", "# Display first 5 posts\n", "for post in posts[:5]:\n", " print(f\" [{post['id']}] {post['title'][:60]}...\")" ] }, { "cell_type": "markdown", "id": "1566a3d2", "metadata": {}, "source": [ "## Error Handling\n", "\n", "Properly handle API errors and edge cases:" ] }, { "cell_type": "code", "execution_count": null, "id": "6846bd0c", "metadata": {}, "outputs": [], "source": [ "def safe_api_request(url, params=None, timeout=10):\n", " \"\"\"Make an API request with proper error handling\"\"\"\n", " try:\n", " response = requests.get(url, params=params, timeout=timeout)\n", " response.raise_for_status() # Raises HTTPError for bad status codes\n", " return {\"success\": True, \"data\": response.json(), \"status\": response.status_code}\n", " except requests.exceptions.Timeout:\n", " return {\"success\": False, \"error\": \"Request timed out\", \"status\": None}\n", " except requests.exceptions.HTTPError as e:\n", " return {\"success\": False, \"error\": f\"HTTP Error: {e}\", \"status\": response.status_code}\n", " except requests.exceptions.RequestException as e:\n", " return {\"success\": False, \"error\": f\"Request failed: {e}\", \"status\": None}\n", " except json.JSONDecodeError:\n", " return {\"success\": False, \"error\": \"Invalid JSON response\", \"status\": response.status_code}\n", "\n", "# Test with valid URL\n", "print(\"šŸ”’ Safe API Requests with Error Handling\")\n", "print(\"=\" * 60)\n", "\n", "# Test 1: Valid request\n", "result = safe_api_request(\"https://jsonplaceholder.typicode.com/posts/1\")\n", "print(f\"\\nāœ… Valid request:\")\n", "print(f\" Success: {result['success']}\")\n", "print(f\" Status: {result['status']}\")\n", "\n", "# Test 2: Invalid endpoint (404)\n", "result = safe_api_request(\"https://jsonplaceholder.typicode.com/posts/99999\")\n", "print(f\"\\nāš ļø Non-existent resource:\")\n", "print(f\" Success: {result['success']}\")\n", "print(f\" Status: {result['status']}\")\n", "\n", "# Test 3: Invalid domain\n", "result = safe_api_request(\"https://this-domain-does-not-exist-12345.com/api\", timeout=3)\n", "print(f\"\\nāŒ Invalid domain:\")\n", "print(f\" Success: {result['success']}\")\n", "print(f\" Error: {result['error'][:50]}...\")" ] }, { "cell_type": "markdown", "id": "8b16b137", "metadata": {}, "source": [ "## Working with Public APIs: GitHub\n", "\n", "Fetch data from GitHub's public API (no authentication required for basic requests):" ] }, { "cell_type": "code", "execution_count": null, "id": "b2dda126", "metadata": {}, "outputs": [], "source": [ "# Fetch repository information from GitHub API\n", "repo_url = \"https://api.github.com/repos/jekyll/jekyll\"\n", "headers = {\"Accept\": \"application/vnd.github.v3+json\"}\n", "\n", "response = requests.get(repo_url, headers=headers)\n", "repo = response.json()\n", "\n", "print(\"šŸ™ GitHub Repository Info: Jekyll/Jekyll\")\n", "print(\"=\" * 60)\n", "print(f\"\\nšŸ“Œ Repository Details:\")\n", "print(f\" Name: {repo['full_name']}\")\n", "print(f\" Description: {repo['description'][:70]}...\")\n", "print(f\" ⭐ Stars: {repo['stargazers_count']:,}\")\n", "print(f\" šŸ“ Forks: {repo['forks_count']:,}\")\n", "print(f\" šŸ‘ļø Watchers: {repo['watchers_count']:,}\")\n", "print(f\" šŸ“ Open Issues: {repo['open_issues_count']:,}\")\n", "print(f\" šŸ“„ License: {repo.get('license', {}).get('name', 'N/A')}\")\n", "print(f\" šŸ”§ Language: {repo['language']}\")\n", "print(f\" šŸ“… Created: {repo['created_at'][:10]}\")\n", "print(f\" šŸ“… Last Updated: {repo['updated_at'][:10]}\")" ] }, { "cell_type": "markdown", "id": "9da3210f", "metadata": {}, "source": [ "## Converting API Data to DataFrame\n", "\n", "Process API responses into Pandas DataFrames for analysis:" ] }, { "cell_type": "code", "execution_count": null, "id": "1d055567", "metadata": {}, "outputs": [], "source": [ "# Fetch multiple users and create a DataFrame\n", "users_url = \"https://jsonplaceholder.typicode.com/users\"\n", "response = requests.get(users_url)\n", "users = response.json()\n", "\n", "# Convert to DataFrame\n", "df = pd.DataFrame(users)\n", "\n", "# Extract nested data (address)\n", "df['city'] = df['address'].apply(lambda x: x['city'])\n", "df['company_name'] = df['company'].apply(lambda x: x['name'])\n", "\n", "# Select relevant columns\n", "df_clean = df[['id', 'name', 'username', 'email', 'city', 'company_name']]\n", "\n", "print(\"šŸ‘„ Users Data as DataFrame:\")\n", "print(f\"Shape: {df_clean.shape}\\n\")\n", "df_clean" ] }, { "cell_type": "markdown", "id": "7ae7477e", "metadata": {}, "source": [ "## POST Request: Creating Data\n", "\n", "Send data to an API using POST requests:" ] }, { "cell_type": "code", "execution_count": null, "id": "01eba6fa", "metadata": {}, "outputs": [], "source": [ "# Create a new post using POST request\n", "post_url = \"https://jsonplaceholder.typicode.com/posts\"\n", "\n", "# Data to send\n", "new_post = {\n", " \"title\": \"Learning Python APIs\",\n", " \"body\": \"This tutorial teaches you how to work with REST APIs in Python using the requests library.\",\n", " \"userId\": 1\n", "}\n", "\n", "# Send POST request\n", "response = requests.post(\n", " post_url,\n", " json=new_post, # Automatically serializes to JSON and sets Content-Type\n", " headers={\"Content-Type\": \"application/json\"}\n", ")\n", "\n", "print(\"šŸ“® POST Request - Create New Resource\")\n", "print(\"=\" * 60)\n", "print(f\"Status Code: {response.status_code}\")\n", "print(f\"Response Time: {response.elapsed.total_seconds():.3f}s\")\n", "\n", "created = response.json()\n", "print(f\"\\nāœ… Created Post:\")\n", "print(f\" ID: {created['id']} (assigned by server)\")\n", "print(f\" Title: {created['title']}\")\n", "print(f\" User ID: {created['userId']}\")" ] }, { "cell_type": "markdown", "id": "9a116105", "metadata": {}, "source": [ "## Summary Statistics from API Data" ] }, { "cell_type": "code", "execution_count": null, "id": "2eccc57a", "metadata": {}, "outputs": [], "source": [ "# Fetch all posts and analyze\n", "all_posts_url = \"https://jsonplaceholder.typicode.com/posts\"\n", "response = requests.get(all_posts_url)\n", "all_posts = response.json()\n", "\n", "# Create DataFrame and analyze\n", "posts_df = pd.DataFrame(all_posts)\n", "posts_df['title_length'] = posts_df['title'].str.len()\n", "posts_df['body_length'] = posts_df['body'].str.len()\n", "\n", "print(\"šŸ“Š API DATA ANALYSIS SUMMARY\")\n", "print(\"=\" * 60)\n", "\n", "print(f\"\\nšŸ“‹ Dataset Overview:\")\n", "print(f\" Total Posts: {len(posts_df)}\")\n", "print(f\" Unique Users: {posts_df['userId'].nunique()}\")\n", "\n", "print(f\"\\nšŸ“ Content Statistics:\")\n", "print(f\" Avg Title Length: {posts_df['title_length'].mean():.1f} characters\")\n", "print(f\" Avg Body Length: {posts_df['body_length'].mean():.1f} characters\")\n", "print(f\" Shortest Post: {posts_df['body_length'].min()} chars\")\n", "print(f\" Longest Post: {posts_df['body_length'].max()} chars\")\n", "\n", "print(f\"\\nšŸ‘¤ Posts per User:\")\n", "user_posts = posts_df.groupby('userId').size()\n", "print(f\" Min: {user_posts.min()} posts\")\n", "print(f\" Max: {user_posts.max()} posts\")\n", "print(f\" Avg: {user_posts.mean():.1f} posts\")\n", "\n", "print(\"\\n\" + \"=\" * 60)" ] }, { "cell_type": "markdown", "id": "c7a114d6", "metadata": {}, "source": [ "## Next Steps\n", "\n", "This tutorial covered the basics of working with APIs in Python. To continue learning:\n", "\n", "1. **Analyze your data** - Check out the [Pandas Data Analysis](/notebooks/pandas-data-analysis/) tutorial\n", "2. **Visualize API data** - See the [Matplotlib Visualization](/notebooks/matplotlib-visualization/) tutorial\n", "3. **Statistical analysis** - Learn more in the [Python Statistics](/notebooks/python-statistics/) tutorial\n", "\n", "**Key Takeaways:**\n", "- Use `requests.get()` for fetching data, `requests.post()` for sending data\n", "- Always handle errors with try/except blocks\n", "- Set timeouts to prevent hanging requests\n", "- Use `response.json()` to parse JSON responses\n", "- Convert API data to DataFrames for powerful analysis\n", "\n", "**Useful Public APIs to Practice With:**\n", "- [JSONPlaceholder](https://jsonplaceholder.typicode.com/) - Fake REST API\n", "- [GitHub API](https://docs.github.com/en/rest) - Repository data\n", "- [Open Weather Map](https://openweathermap.org/api) - Weather data\n", "- [REST Countries](https://restcountries.com/) - Country information" ] } ], "metadata": { "kernelspec": { "display_name": ".venv", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.14.0" } }, "nbformat": 4, "nbformat_minor": 5 }