Библиотека Python urllib3

В данной статье мы опишем библиотеку языка Python urllib3. Мы расскажем, как получать данные, публиковать их и передавать в потоковом режиме, работать с JSON и использовать редирект.

Протокол передачи гипертекста (HTTP) — это прикладной протокол для распределенных, совместимых с гипермедиа, информационных систем. HTTP является основой основ передачи данных для World Wide Web.

Что такое urllib3

Библиотека urllib3 — это мощный HTTP-клиент на Python c простым для понимания и продуманным кодом. Она поддерживает безопасность потоков, пул соединений, проверку SSL / TLS на стороне клиента, загрузку файлов с многокомпонентным кодированием, создание повторных запросов и работу с редиректом HTTP, архивирование и разархивирование, а также прокси для HTTP и SOCKS.

Установка библиотеки при помощи пакетного менеджера pip:

$ pip install urllib3

Проверка версии библиотеки

Для начала мы выведем на экран версию библиотеки urllib3.

# version.py
#!/usr/bin/env python3

import urllib3
rint(urllib3.__version__)

Эта несложная программа позволяет нам узнать версию установленной нами библиотеки urllib3:

1.24.1

Проверка состояния запроса

HTTP-коды состояния показывают нам, был ли HTTP-запрос успешно обработан, и если нет, то по какой причине. Коды ответов делятся на пять групп:

  • информационные (100–199)
  • успешные (200–299)
  • редирект (300–399)
  • ошибка клиента (400–499)
  • ошибка сервера (500–599)
# status.py
#!/usr/bin/env python3

import urllib3

http = urllib3.PoolManager()
url = 'http://webcode.me'
resp = http.request('GET', url)
print(resp.status)

В этом примере мы генерируем GET-запрос на сайт webcode.me и выводим на экран код ответа от сайта.

Для начала мы создаем менеджер соединений при помощи функции urllib3.PoolManager() и сохраняем его в переменную http. Он обрабатывает все детали пула соединений и обеспечивает безопасность потоков.
Далее мы записываем в переменную url строку с адресом сайта ('http://webcode.me').
Затем мы шлем на этот адрес GET-запрос при помощи метода request() и сохраняем результат в переменную resp. После этого выводим статус запроса на экран:

200

Код ответа 200 означает, что запрос прошел успешно.

GET-запросы в urllib3

В протоколе HTTP метод GET запрашивает представление страницы по адресу, определенному в запросе .

# get_request.py
#!/usr/bin/env python3

import urllib3

http = urllib3.PoolManager()
url = 'http://webcode.me'
resp = http.request('GET', url)
print(resp.data.decode('utf-8'))

В данном примере, как и в предыдущем, мы посылаем GET-запрос по адресу webcode.me. Но сейчас мы выводим на экран не код ответа, а сам результат запроса, то есть html-код запрошенной страницы.

Генерируем GET-запрос и записываем результат в переменную resp. Далее мы берем данные из этой переменной, декодируем их и выводим на экран:
print(resp.data.decode('utf-8')). В результате получаем:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>My html page</title>
</head>
<body>

    <p>
        Today is a beautiful day. We go swimming and fishing.
    </p>

    <p>
         Hello there. How are you?
    </p>

</body>

HEAD-запросы в urllib3

HEAD-запрос полностью идентичен GET-запросу, за исключением того, что в нем не запрашивается тело страницы. Обычно он используется для извлечения метаданных.

head_request.py
#!/usr/bin/env python3

import urllib3

http = urllib3.PoolManager()

url = 'http://webcode.me'
resp = http.request('HEAD', url)

print(resp.headers['Server'])
print(resp.headers['Date'])
print(resp.headers['Content-Type'])
print(resp.headers['Last-Modified'])

В данном примере мы создаем HEAD-запрос на сайт webcode.me. Переменная, содержащая объект ответа, представляет собой словарь, из которого можно извлечь различные данные:

print(resp.headers['Server'])
print(resp.headers['Date'])
print(resp.headers['Content-Type'])
print(resp.headers['Last-Modified'])

Результат будет следующим:

nginx/1.6.2
Thu, 20 Feb 2020 14:35:14 GMT
text/html
Sat, 20 Jul 2019 11:49:25 GMT

Из этого ответа можно заключить, что сервер, на котором работает этот сайт, — nginx, а тип контента — html-код.

HTTPS-запросы

Библиотека Urllib3 обеспечивает проверку TLS / SSL на стороне клиента. Но для этого нам нужно предварительно установить библиотеку certifi. Она представляет собой тщательно отобранную коллекцию корневых сертификатов для проверки достоверности SSL-сертификатов при проверке подлинности хостов TLS. Эта библиотека была выделена в отдельный модуль из известного проекта Requests (который также является очень известной библиотекой для протокола HTTP)

$ pip install certifi

При помощи приведенной выше команды мы установили библиотеку certifi.

import certifi

print(certifi.where())

Для ссылки на установленный модуль центра сертификации (CA) мы используем встроенную функцию where().

# status2.py
#!/usr/bin/env python3

import urllib3
import certifi


url = 'https://httpbin.org/anything'

http = urllib3.PoolManager(ca_certs=certifi.where())
resp = http.request('GET', url)

print(resp.status

Чтобы отправить GET- запрос на страницу https://httpbin.org/anything, мы должны передать пакет корневых сертификатов в функцию PoolManager(). Без этого сервер вернет следующее предупреждение: Unverified HTTPS request is being made. Adding certificate verification is strongly advised. («Поступил непроверенный HTTPS-запрос. Крайне рекомендуется добавить сертификат проверки»).

Параметры запроса

Параметры запроса являются частью URL (унифицированного указателя ресурса), который присваивает значения указанным параметрам. Это один из способов отправки данных на сервер назначения.

http://example.com/api/users?name=John%20Doe&occupation=gardener

Параметры запроса указываются после знака '?', а поля разделяются знаком '&'. Специальные символы, например пробелы, кодируются определенным образом. В приведенном выше примере пробелы закодированы значением %20.

# query_params.py
#!/usr/bin/env python3

import urllib3
import certifi

http = urllib3.PoolManager(ca_certs=certifi.where())

payload = {'name': 'Peter', 'age': 23}

url = 'https://httpbin.org/get'
req = http.request('GET', url, fields=payload)

print(req.data.decode('utf-8'))

В данном примере мы послали GET-запрос с некоторыми параметрами на страницу https://httpbin.org/get . Эта страница просто возвращает определенные данные обратно клиенту, включая при этом параметры запроса. Этот сайт вообще создан для тестирования HTTP-запросов.

payload = {'name': 'Peter', 'age': 23}

Параметры запросы были заданы в формате словаря, который был сохранен в переменную payload.

req = http.request('GET', url, fields=payload)

А при помощи необязательного аргумента fields они были переданы в функцию request().

{
  "args": {
    "age": "23",
    "name": "Peter"
  },
  "headers": {
    "Accept-Encoding": "identity",
    "Host": "httpbin.org",
    "X-Amzn-Trace-Id": "Root=1-5e4ea45f-c3c9c721c848f8f81a3129d8"
  },
  "origin": "188.167.251.9",
  "url": "https://httpbin.org/get?name=Peter&age=23"
}

Ответ сайта httpbin.org был дан в виде строки в формате JSON, в начале которой стоят параметры нашего запроса.

POST-запросы

В протоколе HTTP метод POST используется для передачи данных на сервер. Он часто применяется для загрузки файлов или для заполнения веб-форм.

# post_request.py
#!/usr/bin/env python3

import urllib3
import certifi


http = urllib3.PoolManager(ca_certs=certifi.where())

url = 'https://httpbin.org/post'

req = http.request('POST', url, fields={'name': 'John Doe'})
print(req.data.decode('utf-8'))

В данном примере был послан POST-запрос. Его данные были заданы при помощи аргумента fields.

А ответ сервера был таким:

{
  "args": {},
  "data": "",
  "files": {},
  "form": {
    "name": "John Doe"
  },
  ...
  "url": "https://httpbin.org/post"
}

Отправка данных в формате JSON

В запросах, таких как POST или PUT, клиент сообщает серверу, какой тип данных фактически будет отправлен при помощи поля 'Content-Type' в параметре headers.

# send_json.py
#!/usr/bin/env python3

import urllib3
import certifi
import json


http = urllib3.PoolManager(ca_certs=certifi.where())

payload = {'name': 'John Doe'}
encoded_data = json.dumps(payload).encode('utf-8')

resp = http.request(
     'POST',
     'https://httpbin.org/post',
     body=encoded_data,
     headers={'Content-Type': 'application/json'})

data = json.loads(resp.data.decode('utf-8'))['json']
print(data)

В данном примере мы отправили данные в формате JSON.

payload = {'name': 'John Doe'}
encoded_data = json.dumps(payload).encode('utf-8')

Здесь мы перевели данные в формате JSON в двоичный формат.

resp = http.request(
     'POST',
     'https://httpbin.org/post',
     body=encoded_data,
     headers={'Content-Type': 'application/json'})

Тут мы задали тип передаваемых данных в поле 'Content-Type' параметра headers.

data = json.loads(resp.data.decode('utf-8'))['json']
print(data)

И в конце мы декодировали в текстовый формат данные, возвращенные сервером, и вывели их на экран.

Использование двоичных данных

В следующем примере мы загружаем с сервера двоичные данные.

get_binary.py
#!/usr/bin/env python3

import urllib3


http = urllib3.PoolManager()

url = 'http://webcode.me/favicon.ico'
req = http.request('GET', url)

with open('favicon.ico', 'wb') as f:
    f.write(req.data)

В данном примере с сервера загружается маленькая иконка.

with open('favicon.ico', 'wb') as f:
    f.write(req.data)

req.data имеет двоичный формат, так что мы можем записывать его прямо на диск.

Работа с потоковыми данными

Кодирование данных по частям — это механизм передачи потоковых данных, доступный начиная с версии HTTP 1.1. При кодировании с частичной передачей поток данных делится на ряд непересекающихся блоков. Эти блоки отправляются и принимаются независимо друг от друга. Перед отправкой блока всегда посылается его размер в байтах.

Установка параметра preload_content в состояние False означает, что библиотека urllib3 будет производить потоковою передачу данных. Метод stream() выполняет итерации по частям содержимого ответа. При потоковой передаче мы должны вызывать метод release_conn(), который переносит http-соединение обратно в пул соединений, чтобы его можно было использовать повторно.

# streaming.py
#!/usr/bin/env python3

import urllib3
import certifi

url = "https://docs.oracle.com/javase/specs/jls/se8/jls8.pdf"

local_filename = url.split('/')[-1]

http = urllib3.PoolManager(ca_certs=certifi.where())

resp = http.request(
    'GET',
    url,
    preload_content=False)

with open(local_filename, 'wb') as f:

    for chunk in resp.stream(1024):
        f.write(chunk)

resp.release_conn()

В данном примере мы загружаем с сервера PDF-файл.

resp = http.request(
    'GET',
    url,
    preload_content=False)

Задав параметр preload_content=False, мы включаем потоковую передачу данных.

with open(local_filename, 'wb') as f:

    for chunk in resp.stream(1024):
        f.write(chunk)

Мы производим итерацию по блокам данных и записываем их в файл.

resp.release_conn()

В конце мы освобождаем соединение для дальнейшего использования.

Редирект

Редирект переадресовывает пользователей и поисковые системы на разные URL-адреса, отличные от тех, которые они первоначально запрашивали. Чтобы разрешить перенаправление нашего запроса, мы присваиваем параметру redirect значение True.

# redirect.py
#!/usr/bin/env python3

import urllib3
import certifi

http = urllib3.PoolManager(ca_certs=certifi.where())


url = 'https://httpbin.org/redirect-to?url=/'
resp = http.request('GET', url, redirect=True)

print(resp.status)
print(resp.geturl())
print(resp.info())

В данном примере мы создали запрос с разрешением редиректа. А вот результат выполнения нашего запроса:

200
/
HTTPHeaderDict({'Date': 'Fri, 21 Feb 2020 12:49:29 GMT', 'Content-Type': 'text/html; 
charset=utf-8', 'Content-Length': '9593', 'Connection': 'keep-alive', 
'Server': 'gunicorn/19.9.0', 'Access-Control-Allow-Origin': '*', 
'Access-Control-Allow-Credentials': 'true'})

Пример работы библиотеки urllib3 с фреймворком Flask

В данном примере мы пошлем запрос на небольшое веб-приложение, запущенное на сервере flask. Подробнее о фреймворке flask можно прочитать, например, в нашем обзоре.

Для начала установим библиотеку flask:

$ pip install flask

# app.py
#!/usr/bin/env python3

from flask import Flask
from flask import request


app = Flask(__name__)

@app.route('/headers')
def hello():

    ua = request.headers.get('user-agent')
    ka = request.headers.get('connection')

    return f'User agent: {ua}; Connection: {ka}'

Данное приложение на фреймворке flask выполняет только одну операцию: в ответ на запрос оно возвращает пользователю заголовки этого самого запроса.

# send_req.py
#!/usr/bin/env python3

import urllib3


http = urllib3.PoolManager()

url = 'localhost:5000/headers'

headers = urllib3.make_headers(keep_alive=True, user_agent='Python program')
resp = http.request('GET', url, headers=headers)
print(resp.data.decode('utf-8'))

В данном коде мы посылаем запрос нашему серверному приложению на базе библиотеки flask.

url = 'localhost:5000/headers'

По умолчанию, flask сервер запускается на 5000 порте.

headers = urllib3.make_headers(keep_alive=True, user_agent='Python program')

При помощи метода make_headers() мы создаем словарь заголовков.

resp = http.request('GET', url, headers=headers)

Посылаем запрос по заданному ранее URL.

print(resp.data.decode('utf-8'))

Выводим ответ на дисплей.

$ export FLASK_APP=app.py
$ flask run

Таким образом мы запускаем приложение flask.

$ ./send_req.py
User agent: Python program; Connection: keep-alive

А с другого терминала мы запускаем программу запроса и получаем от сервера ответ.

Итак, мы вкратце рассмотрели библиотеку Python urllib3.

Перевод статьи «Python urllib3».