如何解析 Python requests 调用中的响应内容

PythonBeginner
立即练习

介绍

Python requests 库是一个强大的工具,用于与 Web 服务和 API 交互。在本教程中,你将学习如何使用 Python 发送 HTTP 请求并解析响应数据。通过完成这个实验,你将能够从不同类型的 API 响应中提取有价值的信息,从而构建数据驱动的应用程序并自动化 Web 交互。

安装 Requests 库并发出基本请求

在第一步中,我们将安装 Python requests 库,并发出我们的第一个 HTTP 请求,从公共 API 检索数据。

安装 Requests

requests 库是一个第三方包,需要使用 pip(Python 的包安装程序)进行安装。让我们从安装它开始:

pip install requests

你应该看到输出,确认 requests 已成功安装。

发出你的第一个 HTTP 请求

现在,让我们创建一个 Python 文件来发出一个简单的 HTTP 请求。在 WebIDE 中,在 /home/labex/project 目录下创建一个名为 basic_request.py 的新文件。

将以下代码添加到文件中:

import requests

## Make a GET request to a public API
response = requests.get("https://jsonplaceholder.typicode.com/todos/1")

## Print the status code
print(f"Status code: {response.status_code}")

## Print the raw response content
print("\nRaw response content:")
print(response.text)

## Print the response headers
print("\nResponse headers:")
for header, value in response.headers.items():
    print(f"{header}: {value}")

这段代码向一个示例 API 端点发出一个 GET 请求,并打印有关响应的信息。

理解 Response 对象

让我们运行代码,看看我们得到了什么信息。在终端中,运行:

python basic_request.py

你应该看到类似这样的输出:

Status code: 200

Raw response content:
{
  "userId": 1,
  "id": 1,
  "title": "delectus aut autem",
  "completed": false
}

Response headers:
Date: Mon, 01 Jan 2023 12:00:00 GMT
Content-Type: application/json; charset=utf-8
...

response 对象包含几个重要的属性:

  • status_code:HTTP 状态码(200 表示成功)
  • text:响应内容,作为字符串
  • headers:一个响应头部的字典

在使用 Web 请求时,这些属性可以帮助你理解服务器的响应并适当地处理它。

HTTP 状态码

HTTP 状态码指示请求是成功还是失败:

  • 2xx(如 200):成功
  • 3xx(如 301):重定向
  • 4xx(如 404):客户端错误
  • 5xx(如 500):服务器错误

让我们修改我们的代码来检查一个成功的响应。创建一个名为 check_status.py 的新文件,内容如下:

import requests

try:
    ## Make a GET request to a valid URL
    response = requests.get("https://jsonplaceholder.typicode.com/todos/1")

    ## Check if the request was successful
    if response.status_code == 200:
        print("Request successful!")
    else:
        print(f"Request failed with status code: {response.status_code}")

    ## Try an invalid URL
    invalid_response = requests.get("https://jsonplaceholder.typicode.com/invalid")
    print(f"Invalid URL status code: {invalid_response.status_code}")

except requests.exceptions.RequestException as e:
    print(f"An error occurred: {e}")

运行这段代码,看看不同的 URL 如何返回不同的状态码:

python check_status.py

你应该看到有效的 URL 返回状态码 200,而无效的 URL 返回 404 状态码。

解析 JSON 响应数据

许多现代 API 以 JSON(JavaScript Object Notation)格式返回数据。在这一步中,你将学习如何解析 JSON 响应并在 Python 中使用数据。

理解 JSON

JSON 是一种轻量级的数据交换格式,易于人类阅读和编写,也易于机器解析和生成。它基于键值对,类似于 Python 字典。

这是一个 JSON 对象的示例:

{
  "name": "John Doe",
  "age": 30,
  "email": "john@example.com",
  "is_active": true,
  "hobbies": ["reading", "swimming", "cycling"]
}

解析 JSON 响应

requests 库使用 .json() 方法可以轻松解析 JSON 响应。让我们创建一个名为 parse_json.py 的新文件,并添加以下代码:

import requests

## Make a request to a GitHub API endpoint that returns JSON data
response = requests.get("https://api.github.com/users/python")

## Check if the request was successful
if response.status_code == 200:
    ## Parse the JSON response
    data = response.json()

    ## Print the parsed data
    print("Parsed JSON data:")
    print(f"Username: {data['login']}")
    print(f"Name: {data.get('name', 'Not provided')}")
    print(f"Followers: {data['followers']}")
    print(f"Public repositories: {data['public_repos']}")

    ## Print the type to verify it's a Python dictionary
    print(f"\nType of parsed data: {type(data)}")

    ## Access nested data
    print("\nAccessing specific elements:")
    print(f"Avatar URL: {data['avatar_url']}")
else:
    print(f"Request failed with status code: {response.status_code}")

运行此脚本,查看 JSON 数据如何被解析成 Python 字典:

python parse_json.py

你应该看到输出,显示有关 GitHub 用户的信息,包括他们的用户名、关注者数量和存储库数量。

处理数据列表

许多 API 返回对象列表。让我们看看如何处理这种响应。创建一个名为 json_list.py 的文件,内容如下:

import requests

## Make a request to an API that returns a list of posts
response = requests.get("https://jsonplaceholder.typicode.com/posts")

## Check if the request was successful
if response.status_code == 200:
    ## Parse the JSON response (this will be a list of posts)
    posts = response.json()

    ## Print the total number of posts
    print(f"Total posts: {len(posts)}")

    ## Print details of the first 3 posts
    print("\nFirst 3 posts:")
    for i, post in enumerate(posts[:3], 1):
        print(f"\nPost #{i}")
        print(f"User ID: {post['userId']}")
        print(f"Post ID: {post['id']}")
        print(f"Title: {post['title']}")
        print(f"Body: {post['body'][:50]}...")  ## Print just the beginning of the body
else:
    print(f"Request failed with status code: {response.status_code}")

运行此脚本,查看如何处理 JSON 对象列表:

python json_list.py

你应该看到有关前三个帖子的信息,包括它们的标题和内容的开头。

使用 JSON 解析进行错误处理

有时,响应可能不包含有效的 JSON 数据。让我们看看如何优雅地处理这种情况。创建一个名为 json_error.py 的文件,其中包含以下代码:

import requests
import json

def get_and_parse_json(url):
    try:
        ## Make the request
        response = requests.get(url)

        ## Check if the request was successful
        response.raise_for_status()

        ## Try to parse the JSON
        try:
            data = response.json()
            return data
        except json.JSONDecodeError:
            print(f"Response from {url} is not valid JSON")
            print(f"Raw response: {response.text[:100]}...")  ## Print part of the raw response
            return None

    except requests.exceptions.HTTPError as e:
        print(f"HTTP error: {e}")
    except requests.exceptions.RequestException as e:
        print(f"Request error: {e}")

    return None

## Test with a valid JSON endpoint
json_data = get_and_parse_json("https://jsonplaceholder.typicode.com/posts/1")
if json_data:
    print("\nValid JSON response:")
    print(f"Title: {json_data['title']}")

## Test with a non-JSON endpoint
html_data = get_and_parse_json("https://www.example.com")
if html_data:
    print("\nThis should not print as example.com returns HTML, not JSON")
else:
    print("\nAs expected, could not parse HTML as JSON")

运行此脚本,查看如何处理不同类型的响应:

python json_error.py

你应该看到代码成功地处理了有效的 JSON 响应和非 JSON 响应。

使用 BeautifulSoup 解析 HTML 内容

在使用 Web 数据时,你经常会遇到 HTML 响应。对于解析 HTML,Python 的 BeautifulSoup 库是一个极好的工具。在这一步中,我们将学习如何从 HTML 响应中提取信息。

安装 BeautifulSoup

首先,让我们安装 BeautifulSoup 及其 HTML 解析器:

pip install beautifulsoup4

基本 HTML 解析

让我们创建一个名为 parse_html.py 的文件来获取和解析一个网页:

import requests
from bs4 import BeautifulSoup

## Make a request to a webpage
url = "https://www.example.com"
response = requests.get(url)

## Check if the request was successful
if response.status_code == 200:
    ## Parse the HTML content
    soup = BeautifulSoup(response.text, 'html.parser')

    ## Extract the page title
    title = soup.title.text
    print(f"Page title: {title}")

    ## Extract all paragraphs
    paragraphs = soup.find_all('p')
    print(f"\nNumber of paragraphs: {len(paragraphs)}")

    ## Print the text of the first paragraph
    if paragraphs:
        print(f"\nFirst paragraph text: {paragraphs[0].text.strip()}")

    ## Extract all links
    links = soup.find_all('a')
    print(f"\nNumber of links: {len(links)}")

    ## Print the href attribute of the first link
    if links:
        print(f"First link href: {links[0].get('href')}")

else:
    print(f"Request failed with status code: {response.status_code}")

运行此脚本,查看如何从 HTML 页面中提取基本信息:

python parse_html.py

你应该看到输出,显示页面标题、段落数量、第一个段落的文本、链接数量以及第一个链接的 URL。

查找特定元素

现在,让我们看看如何使用 CSS 选择器查找特定元素。创建一个名为 html_selectors.py 的文件:

import requests
from bs4 import BeautifulSoup

## Make a request to a webpage with more complex structure
url = "https://quotes.toscrape.com/"
response = requests.get(url)

## Check if the request was successful
if response.status_code == 200:
    ## Parse the HTML content
    soup = BeautifulSoup(response.text, 'html.parser')

    ## Find all quote elements
    quote_elements = soup.select('.quote')
    print(f"Number of quotes found: {len(quote_elements)}")

    ## Process the first 3 quotes
    print("\nFirst 3 quotes:")
    for i, quote_element in enumerate(quote_elements[:3], 1):
        ## Extract the quote text
        text = quote_element.select_one('.text').text

        ## Extract the author
        author = quote_element.select_one('.author').text

        ## Extract the tags
        tags = [tag.text for tag in quote_element.select('.tag')]

        ## Print the information
        print(f"\nQuote #{i}")
        print(f"Text: {text}")
        print(f"Author: {author}")
        print(f"Tags: {', '.join(tags)}")

else:
    print(f"Request failed with status code: {response.status_code}")

运行此脚本,查看如何使用 CSS 选择器提取特定元素:

python html_selectors.py

你应该看到输出,显示有关前三个引用的信息,包括引用文本、作者和标签。

构建一个简单的 Web 抓取器

让我们把所有内容放在一起,构建一个简单的 Web 抓取器,从网页中提取结构化数据。创建一个名为 quotes_scraper.py 的文件:

import requests
from bs4 import BeautifulSoup
import json
import os

def scrape_quotes_page(url):
    ## Make a request to the webpage
    response = requests.get(url)

    ## Check if the request was successful
    if response.status_code != 200:
        print(f"Request failed with status code: {response.status_code}")
        return None

    ## Parse the HTML content
    soup = BeautifulSoup(response.text, 'html.parser')

    ## Extract all quotes
    quotes = []
    for quote_element in soup.select('.quote'):
        ## Extract the quote text
        text = quote_element.select_one('.text').text.strip('"')

        ## Extract the author
        author = quote_element.select_one('.author').text

        ## Extract the tags
        tags = [tag.text for tag in quote_element.select('.tag')]

        ## Add the quote to our list
        quotes.append({
            'text': text,
            'author': author,
            'tags': tags
        })

    ## Check if there's a next page
    next_page = soup.select_one('.next a')
    next_page_url = None
    if next_page:
        next_page_url = 'https://quotes.toscrape.com' + next_page['href']

    return {
        'quotes': quotes,
        'next_page': next_page_url
    }

## Scrape the first page
result = scrape_quotes_page('https://quotes.toscrape.com/')

if result:
    ## Print information about the quotes found
    quotes = result['quotes']
    print(f"Found {len(quotes)} quotes on the first page")

    ## Print the first 2 quotes
    print("\nFirst 2 quotes:")
    for i, quote in enumerate(quotes[:2], 1):
        print(f"\nQuote #{i}")
        print(f"Text: {quote['text']}")
        print(f"Author: {quote['author']}")
        print(f"Tags: {', '.join(quote['tags'])}")

    ## Save the quotes to a JSON file
    output_dir = '/home/labex/project'
    with open(os.path.join(output_dir, 'quotes.json'), 'w') as f:
        json.dump(quotes, f, indent=2)

    print(f"\nSaved {len(quotes)} quotes to {output_dir}/quotes.json")

    ## Print information about the next page
    if result['next_page']:
        print(f"\nNext page URL: {result['next_page']}")
    else:
        print("\nNo next page available")

运行此脚本以从网站抓取引用:

python quotes_scraper.py

你应该看到输出,显示有关在第一页上找到的引用的信息,并且这些引用将被保存到名为 quotes.json 的 JSON 文件中。

检查 JSON 文件以查看结构化数据:

cat quotes.json

该文件应包含一个 JSON 数组,其中包含引用对象,每个对象都具有文本、作者和标签属性。

处理二进制响应内容

到目前为止,我们主要关注基于文本的响应,例如 JSON 和 HTML。但是,requests 库也可以处理二进制内容,例如图像、PDF 和其他文件。在这一步中,我们将学习如何下载和处理二进制内容。

下载图像

让我们从下载图像开始。创建一个名为 download_image.py 的文件:

import requests
import os

## URL of an image to download
image_url = "https://httpbin.org/image/jpeg"

## Make a request to get the image
response = requests.get(image_url)

## Check if the request was successful
if response.status_code == 200:
    ## Get the content type
    content_type = response.headers.get('Content-Type', '')
    print(f"Content-Type: {content_type}")

    ## Check if the content is an image
    if 'image' in content_type:
        ## Create a directory to save the image if it doesn't exist
        output_dir = '/home/labex/project/downloads'
        os.makedirs(output_dir, exist_ok=True)

        ## Save the image to a file
        image_path = os.path.join(output_dir, 'sample_image.jpg')
        with open(image_path, 'wb') as f:
            f.write(response.content)

        ## Print information about the saved image
        print(f"Image saved to: {image_path}")
        print(f"Image size: {len(response.content)} bytes")
    else:
        print("The response does not contain an image")
else:
    print(f"Request failed with status code: {response.status_code}")

运行此脚本以下载图像:

python download_image.py

你应该看到输出,确认图像已下载并保存到 /home/labex/project/downloads/sample_image.jpg

使用进度下载文件

下载大文件时,显示进度指示器会很有用。让我们创建一个显示下载进度的脚本。创建一个名为 download_with_progress.py 的文件:

import requests
import os
import sys

def download_file(url, filename):
    ## Make a request to get the file
    ## Stream the response to handle large files efficiently
    response = requests.get(url, stream=True)

    ## Check if the request was successful
    if response.status_code != 200:
        print(f"Request failed with status code: {response.status_code}")
        return False

    ## Get the total file size if available
    total_size = int(response.headers.get('Content-Length', 0))
    if total_size:
        print(f"Total file size: {total_size/1024:.2f} KB")
    else:
        print("Content-Length header not found. Unable to determine file size.")

    ## Create a directory to save the file if it doesn't exist
    os.makedirs(os.path.dirname(filename), exist_ok=True)

    ## Download the file in chunks and show progress
    print(f"Downloading {url} to {filename}...")

    ## Initialize variables for progress tracking
    downloaded = 0
    chunk_size = 8192  ## 8 KB chunks

    ## Open the file for writing
    with open(filename, 'wb') as f:
        ## Iterate through the response chunks
        for chunk in response.iter_content(chunk_size=chunk_size):
            if chunk:  ## Filter out keep-alive chunks
                f.write(chunk)
                downloaded += len(chunk)

                ## Calculate and display progress
                if total_size:
                    percent = downloaded * 100 / total_size
                    sys.stdout.write(f"\rProgress: {percent:.1f}% ({downloaded/1024:.1f} KB)")
                    sys.stdout.flush()
                else:
                    sys.stdout.write(f"\rDownloaded: {downloaded/1024:.1f} KB")
                    sys.stdout.flush()

    ## Print a newline to ensure the next output starts on a new line
    print()

    return True

## URL of a file to download
file_url = "https://speed.hetzner.de/100MB.bin"

## Path where the file will be saved
output_path = '/home/labex/project/downloads/test_file.bin'

## Download the file
success = download_file(file_url, output_path)

if success:
    ## Get file stats
    file_size = os.path.getsize(output_path)
    print(f"\nDownload complete!")
    print(f"File saved to: {output_path}")
    print(f"File size: {file_size/1024/1024:.2f} MB")
else:
    print("\nDownload failed.")

运行此脚本以下载文件并跟踪进度:

python download_with_progress.py

你将看到一个进度条,随着文件的下载而更新。请注意,这会下载一个 100MB 的文件,这可能需要一些时间,具体取决于你的连接速度。

要取消下载,你可以按 Ctrl+C。

使用响应标头和元数据

下载文件时,响应标头通常包含有用的元数据。让我们创建一个详细检查响应标头的脚本。创建一个名为 response_headers.py 的文件:

import requests

def check_url(url):
    print(f"\nChecking URL: {url}")

    try:
        ## Make a HEAD request first to get headers without downloading the full content
        head_response = requests.head(url)

        print(f"HEAD request status code: {head_response.status_code}")

        if head_response.status_code == 200:
            ## Print all headers
            print("\nResponse headers:")
            for header, value in head_response.headers.items():
                print(f"  {header}: {value}")

            ## Extract content type and size
            content_type = head_response.headers.get('Content-Type', 'Unknown')
            content_length = head_response.headers.get('Content-Length', 'Unknown')

            print(f"\nContent Type: {content_type}")

            if content_length != 'Unknown':
                size_kb = int(content_length) / 1024
                size_mb = size_kb / 1024

                if size_mb >= 1:
                    print(f"Content Size: {size_mb:.2f} MB")
                else:
                    print(f"Content Size: {size_kb:.2f} KB")
            else:
                print("Content Size: Unknown")

            ## Check if the server supports range requests
            accept_ranges = head_response.headers.get('Accept-Ranges', 'none')
            print(f"Supports range requests: {'Yes' if accept_ranges != 'none' else 'No'}")

        else:
            print(f"HEAD request failed with status code: {head_response.status_code}")

    except requests.exceptions.RequestException as e:
        print(f"Error: {e}")

## Check a few different URLs
check_url("https://httpbin.org/image/jpeg")
check_url("https://speed.hetzner.de/100MB.bin")
check_url("https://example.com")

运行此脚本以查看有关响应标头的详细信息:

python response_headers.py

你将看到输出,显示不同类型内容(包括图像、二进制文件和 HTML 页面)的标头。

理解响应标头对于许多 Web 开发任务至关重要,例如:

  • 在下载之前确定文件类型和大小
  • 使用范围请求实现可恢复的下载
  • 检查缓存策略和过期日期
  • 处理重定向和身份验证

总结

在这个实验中,你已经学习了如何使用 Python requests 库与 Web 服务和 API 交互。你现在具备以下技能:

  1. 发出 HTTP 请求并处理响应状态码和错误
  2. 从 API 响应中解析 JSON 数据
  3. 使用 BeautifulSoup 从 HTML 内容中提取信息
  4. 下载和处理二进制内容,如图像和文件
  5. 使用响应标头和元数据

这些技能构成了许多 Python 应用程序的基础,包括 Web 抓取、API 集成、数据收集和自动化。你现在可以构建与 Web 服务交互、从网站提取有用信息以及处理各种 Web 内容的应用程序。

要继续学习,你可能需要探索:

  • 用于访问受保护 API 的身份验证方法
  • 使用需要特定标头或请求格式的更复杂的 API
  • 构建一个完整的 Web 抓取项目,用于收集和分析数据
  • 创建一个与多个 API 集成的 Python 应用程序

请记住,在抓取网站或使用 API 时,务必检查服务条款并遵守速率限制,以避免被阻止。