Как разобрать содержимое ответа из вызова Python requests

PythonBeginner
Практиковаться сейчас

Введение

Библиотека Python requests — это мощный инструмент для взаимодействия с веб-сервисами и API. В этом руководстве вы узнаете, как отправлять HTTP-запросы и анализировать данные ответов с помощью Python. К концу этой лабораторной работы вы сможете извлекать ценную информацию из различных типов ответов API, что позволит вам создавать приложения, управляемые данными, и автоматизировать веб-взаимодействия.

Установка библиотеки Requests и выполнение базового запроса

На этом первом шаге мы установим библиотеку Python requests и выполним наш первый HTTP-запрос для получения данных из общедоступного API.

Установка Requests

Библиотека requests — это сторонний пакет, который необходимо установить с помощью pip, установщика пакетов Python. Давайте начнем с его установки:

pip install requests

Вы должны увидеть вывод, подтверждающий, что requests успешно установлена.

Выполнение вашего первого HTTP-запроса

Теперь давайте создадим файл Python для выполнения простого HTTP-запроса. В WebIDE создайте новый файл с именем basic_request.py в каталоге /home/labex/project.

Добавьте следующий код в файл:

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}")

Этот код выполняет GET-запрос к образцу конечной точки API и выводит информацию об ответе.

Понимание объекта 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: Словарь заголовков ответа

При работе с веб-запросами эти атрибуты помогают вам понять ответ сервера и обработать его соответствующим образом.

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.

Разбор содержимого HTML с помощью BeautifulSoup

При работе с веб-данными вы часто будете сталкиваться с ответами HTML. Для разбора HTML библиотека BeautifulSoup в Python — отличный инструмент. На этом шаге мы узнаем, как извлекать информацию из ответов 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

Вы должны увидеть вывод, показывающий информацию о первых трех цитатах, включая текст цитаты, автора и теги.

Создание простого веб-скребка

Давайте объединим все вместе, чтобы создать простой веб-скребок, который извлекает структурированные данные с веб-страницы. Создайте файл с именем 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

Вы должны увидеть вывод, показывающий информацию о цитатах, найденных на первой странице, и цитаты будут сохранены в файл JSON с именем quotes.json.

Проверьте файл JSON, чтобы увидеть структурированные данные:

cat quotes.json

Файл должен содержать массив JSON-объектов цитат, каждый из которых имеет свойства text, author и tags.

Работа с бинарным содержимым ответа

До сих пор мы фокусировались на текстовых ответах, таких как 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

Вы увидите, как полоса прогресса обновляется по мере загрузки файла. Обратите внимание, что это загружает файл размером 100 МБ, что может занять некоторое время в зависимости от скорости вашего соединения.

Чтобы отменить загрузку, вы можете нажать 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-страницы.

Понимание заголовков ответов имеет решающее значение для многих задач веб-разработки, таких как:

  • Определение типов и размеров файлов перед загрузкой
  • Реализация возобновляемых загрузок с помощью range requests (запросы диапазона)
  • Проверка политик кэширования и сроков истечения срока действия
  • Обработка перенаправлений и аутентификации

Резюме

В этой лабораторной работе вы узнали, как работать с библиотекой Python requests для взаимодействия с веб-сервисами и API. Теперь у вас есть навыки:

  1. Выполнять HTTP-запросы и обрабатывать коды состояния и ошибки ответов
  2. Разбирать данные JSON из ответов API
  3. Извлекать информацию из HTML-контента с помощью BeautifulSoup
  4. Загружать и обрабатывать бинарное содержимое, такое как изображения и файлы
  5. Работать с заголовками ответов и метаданными

Эти навыки составляют основу для многих приложений Python, включая веб-скрейпинг (web scraping), интеграцию с API, сбор данных и автоматизацию. Теперь вы можете создавать приложения, которые взаимодействуют с веб-сервисами, извлекают полезную информацию с веб-сайтов и обрабатывают различные типы веб-контента.

Чтобы продолжить обучение, вы можете изучить:

  • Методы аутентификации для доступа к защищенным API
  • Работу с более сложными API, требующими определенных заголовков или форматов запросов
  • Создание полноценного проекта веб-скрейпинга, который собирает и анализирует данные
  • Создание приложения Python, которое интегрируется с несколькими API

Помните, что при скрейпинге веб-сайтов или использовании API важно проверять условия предоставления услуг и соблюдать ограничения скорости, чтобы избежать блокировки.