Шаг 204.
Python: сборник рецептов.
Сети и веб-программирование. Взаимодействие с HTTP-сервисами в роли клиента

    На этом шаге мы рассмотрим способы решения этой задачи.

Задача

    Вам нужно обращаться к различным веб-сервисам через HTTP в роли клиента. Например, скачивать данные или взаимодействовать с REST API.

Решение

    Для простых задач обычно достаточно модуля urllib.request. Например, чтобы послать простой GET-запрос удаленному сервису, сделайте так:

from urllib import request, parse

# Базовый URL, к которому обращаемся
url = 'http://httpbin.org/get'

# Словарь параметров запроса (если они есть)
parms = {
   'name1': 'value1',
   'name2': 'value2'
}

# Кодируем строку запроса
querystring = parse.urlencode(parms)

# Делаем GET-запрос и читаем ответ
u = request.urlopen(url + '?' + querystring)
resp = u.read()

    Если вам нужно послать параметры в теле запроса, используя метод POST, закодируйте их и предоставьте в качестве необязательных аргументов в urlopen():

from urllib import request, parse

# Базовый URL, к которому обращаемся
url = 'http://httpbin.org/get'

# Словарь параметров запроса (если они есть)
parms = {
   'name1': 'value1',
   'name2': 'value2'
}

# Кодируем строку запроса
querystring = parse.urlencode(parms)

# Делаем POST-запрос и читаем ответ
u = request.urlopen(url, querystring.encode('ascii'))
resp = u.read()

    Если вам нужно предоставить какие-то кастомные HTTP-заголовки в исходящем запросе (например, измененное поле User-Agent), нужно создать содержащий их значения словарь, а также экземпляр Request - и передать его в urlopen():

from urllib import request, parse
.  .  .
#  Дополнительные заголовки 
headers = {
    'User-agent': 'none/ofyourbusiness',
    'Spam': 'Eggs'
}

req = request.Request(url, querystring.encode('ascii'), headers=headers)

#  Делаем запрос и читаем ответ 
u = request.urlopen(req)
resp = u.read()

    Если ваше взаимодействие с сервисом сложнее, вам стоит обратить внимание на библиотеку requests. Например, вот эквивалентный предыдущим операциям код на requests:

import requests

# Базовый URL, к которому обращаемся 
url = 'http://httpbin.org/post'

# Словарь параметров запроса (если они есть)
parms = {
   'name1': 'value1',
   'name2': 'value2'
}

# Дополнительные заголовки
headers = {
    'User-agent': 'none/ofyourbusiness',
    'Spam': 'Eggs'
}

resp = requests.post(url, data=parms, headers=headers)

# Раскодированный текст, возвращенный запросом
text = resp.text

    Стоит отметить, как requests возвращает полученное содержимое ответа на запрос. Как показано выше, атрибут resp.text предоставляет декодированный в Unicode текст запроса. Однако если вы обратитесь к resp.content, то получите не текст, а сырое бинарное содержимое. С другой стороны, если вы обратитесь к resp.json, то получите содержимое ответа в формате JSON.

    Вот пример использования requests для создания запроса HEAD и извлечения нескольких полей данных заголовка из ответа:

import requests

resp = requests.head('http://www.python.org/index.html')

status = resp.status_code
last_modified = resp.headers['last-modified']
content_type = resp.headers['content-type']
content_length = resp.headers['content-length']

    Вот пример, который показывает, как можно залогиниться в Python Package Index, используя базовую аутентификацию:

import requests

resp = requests.get('http://pypi.python.org/pypi?:action=login', auth=('user', 'password'))

    Вот пример передачи HTTP-куки из одного запроса следующему:

import requests

# Первый запрос
resp1 = requests.get(url)
. . .
# Второй запрос с cookie, полученными при первом запросе
resp2 = requests.get(url, cookies=resp1.cookies)

    Последний пример - загрузка данных на сервер:

import requests

url = 'http://httpbin.org/post'
files = {'file': ('data.csv', open('data.csv', 'rb'))}

r = requests.post(url, files=files)


Обсуждение

    Для простого клиентского HTTP-кода встроенного модуля urllib обычно хватает. Однако если вам нужно что-то помимо простых POST- и GET-запросов, вы не можете полагаться на его функциональность. В этом случае лучше подойдут сторонние модули, такие как requests.

    Например, если вы решите полностью опираться только на стандартную библиотеку вместо библиотеки типа requests, вам придется реализовать ваш код с использованием низкоуровневого модуля http.client. Например, этот код выполняет HEAD-запрос:

from http.client import HTTPConnection
from urllib import parse

c = HTTPConnection('www.python.org', 80)
c.request('HEAD', '/index.html')
resp = c.getresponse()

print('Status', resp.status)
for name, value in resp.getheaders():
    print(name, value)

    А если вам нужно написать код, использующий прокси, аутентификацию, куки и другие подобные моменты, использовать urllib неудобно, а код получится многословным. Например, вот пример кода, который производит авторизацию в Python Package Index:

import urllib.request

auth = urllib.request.HTTPBasicAuthHandler()
auth.add_password('pypi', 'http://pypi.python.org', 'username', 'password')
opener = urllib.request.build_opener(auth)

r = urllib.request.Request('http://pypi.python.org/pypi?:action=login')
u = opener.open(r)
resp = u.read()

# Далее вы можете обращаться к другим страницам, используя opener
.  .  .

    Если честно, все это проще сделать с помощью requests. Тестирование клиентского HTTP-кода в процессе разработки часто может быть непростым из-за большого количества неочевидных деталей, о которых нужно думать (например, куки, авторизация, заголовки, кодировки и т. п.). Если вам нужно тестировать код, посмотрите на сервис httpbin.

    Этот сайт получает запросы и "эхом" отсылает информацию обратно в форме JSON-ответа. Вот интерактивный сеанс:

>>> import requests
>>> r = requests.get('http://httpbin.org/get?name=Dave&n=37', 
         headers = {'User-agent': 'goaway/1.0'})
>>> resp = r.json 
>>> resp['headers']
{'User-Agent': 'goaway/1.0', 'Content-Length': '', 'Content-Type': '',
'Accept-Encoding': 'gzip, deflate, compress', 'Connection':
'keep-alive', 'Host': 'httpbin.org', 'Accept': '*/*'}
>>> resp['args']
{'name': 'Dave', 'n': '37'}
>>>

    Работать с сайтом типа httpbin часто предпочтительнее, чем экспериментировать с реальным сайтом, особенно в том случае, если существует риск отключения аккаунта после трех провалившихся попыток залогиниться (не пробуйте учиться писать клиента с HTTP-аутентификацией путем отправки запросов на сайт вашего банка).

    Хотя мы здесь это и не обсудили, requests поддерживает намного более продвинутые клиентские протоколы HTTP, такие как OAuth. Рекомендуем вам обратиться к замечательной документации requests.

    На следующем шаге мы рассмотрим создание TCP-сервера.




Предыдущий шаг Содержание Следующий шаг