Документация Python неофициальный перевод

urllib2.md

439 строк · 33.7 КБ · обычная страница · сырой текст · скачать

1> **Источник:** https://python-all.ru/2.7/howto/urllib2.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# HOWTO. Получение интернет-ресурсов с помощью urllib289**Автор**1011[Michael Foord](https://python-all.ru/2.7/howto/urllib2.html)1213> **Примечание**14>15> Существует французский перевод более ранней версии этого руководства, доступный по адресу [urllib2 - Le Manuel manquant](https://python-all.ru/2.7/howto/urllib2.html).1617## Введение1819Связанные статьи2021Возможно, вам также будет полезна следующая статья о получении веб-ресурсов с помощью Python:2223- [Базовая аутентификация](https://python-all.ru/2.7/howto/urllib2.html)2425  > Учебник по *базовой аутентификации* с примерами на Python.2627**urllib2** – это модуль Python для получения URL (Uniform Resource Locators). Он предоставляет очень простой интерфейс в виде функции *urlopen*. Эта функция способна получать URL с использованием различных протоколов. Также доступен несколько более сложный интерфейс для обработки распространённых ситуаций, таких как базовая аутентификация, куки, прокси и так далее. Эти возможности реализуются объектами, называемыми обработчиками (handlers) и открывателями (openers).2829urllib2 поддерживает получение URL для многих «схем URL» (они определяются строкой перед `":"` в URL – например, `"ftp"` – это схема URL для `"ftp://python.org/"`) с использованием соответствующих сетевых протоколов (например, FTP, HTTP). Данное руководство сосредоточено на наиболее распространённом случае – HTTP.3031В простых ситуациях *urlopen* очень прост в использовании. Но как только вы столкнётесь с ошибками или нетривиальными случаями при открытии HTTP-URL, потребуется некоторое понимание протокола HTTP. Наиболее полный и авторитетный источник по HTTP – это [**RFC 2616**](https://python-all.ru/2.7/howto/urllib2.html). Это технический документ, не предназначенный для лёгкого чтения. Данное руководство призвано проиллюстрировать использование *urllib2* с достаточными подробностями об HTTP, чтобы помочь вам. Оно не заменяет документацию по [`urllib2`](https://python-all.ru/2.7/library/urllib2.html#module-urllib2), а дополняет её.3233## Загрузка URL3435Самый простой способ использования urllib2 выглядит следующим образом:3637```python38import urllib239response = urllib2.urlopen('http://python.org/')40html = response.read()41```4243Многие случаи использования urllib2 будут столь же простыми (обратите внимание, что вместо URL с ‘http:’ мы могли бы использовать URL, начинающийся с ‘ftp:’, ‘file:’ и т.д.). Однако цель данного руководства – объяснить более сложные случаи, сосредоточившись на HTTP.4445HTTP основан на запросах и ответах: клиент отправляет запросы, а серверы отправляют ответы. urllib2 отражает это с помощью объекта `Request`, который представляет выполняемый HTTP-запрос. В простейшем случае создаётся объект Request, указывающий URL для получения. Вызов `urlopen` с этим объектом Request возвращает объект ответа для запрошенного URL. Этот ответ является файлоподобным объектом, что означает, что вы можете, например, вызвать `.read()` для ответа:4647```python48import urllib24950req = urllib2.Request('http://www.voidspace.org.uk')51response = urllib2.urlopen(req)52the_page = response.read()53```5455Обратите внимание, что urllib2 использует один и тот же интерфейс Request для обработки всех схем URL. Например, можно выполнить FTP-запрос следующим образом:5657```python58req = urllib2.Request('ftp://example.com/')59```6061В случае HTTP объекты Request позволяют сделать ещё две вещи: во-первых, можно передать данные для отправки на сервер. Во-вторых, можно передать серверу дополнительную информацию («метаданные») *о* данных или о самом запросе – эта информация отправляется в виде HTTP-заголовков. Рассмотрим каждую из них по очереди.6263### Данные6465Иногда требуется отправить данные на URL (часто URL будет ссылаться на CGI-скрипт [1](https://python-all.ru/2.7/howto/urllib2.html#id9) или другое веб-приложение). В HTTP это часто делается с помощью так называемого **POST**-запроса. Именно так часто поступает браузер при отправке заполненной HTML-формы. Не все POST-запросы должны исходить из форм: можно использовать POST для передачи произвольных данных в собственное приложение. В типичном случае с HTML-формами данные необходимо закодировать стандартным способом, а затем передать объекту Request в качестве аргумента `data`. Кодирование выполняется с помощью функции из библиотеки `urllib`, *а не* из `urllib2`.6667```python68import urllib69import urllib27071url = 'http://www.someserver.com/cgi-bin/register.cgi'72values = {'name' : 'Michael Foord',73          'location' : 'Northampton',74          'language' : 'Python' }7576data = urllib.urlencode(values)77req = urllib2.Request(url, data)78response = urllib2.urlopen(req)79the_page = response.read()80```8182Обратите внимание, что иногда требуются другие кодировки (например, для загрузки файлов из HTML-форм – подробнее см. [Спецификацию HTML, раздел «Отправка формы»](https://python-all.ru/2.7/howto/urllib2.html)).8384If you do not pass the `data` argument, urllib2 uses a **GET** request. One way in which GET and POST requests differ is that POST requests often have “side-effects”: they change the state of the system in some way (for example by placing an order with the website for a hundredweight of tinned spam to be delivered to your door). Though the HTTP standard makes it clear that POSTs are intended to *always* cause side-effects, and GET requests *never* to cause side-effects, nothing prevents a GET request from having side-effects, nor a POST requests from having no side-effects. Data can also be passed in an HTTP GET request by encoding it in the URL itself.8586Это делается следующим образом:8788```python89>>> import urllib290>>> import urllib91>>> data = {}92>>> data['name'] = 'Somebody Here'93>>> data['location'] = 'Northampton'94>>> data['language'] = 'Python'95>>> url_values = urllib.urlencode(data)96>>> print url_values  # Порядок может отличаться. 97name=Somebody+Here&language=Python&location=Northampton98>>> url = 'http://www.example.com/example.cgi'99>>> full_url = url + '?' + url_values100>>> data = urllib2.urlopen(full_url)101```102103Обратите внимание, что полный URL создаётся добавлением `?` к URL, после которого следуют закодированные значения.104105### Заголовки106107Здесь будет рассмотрен один конкретный HTTP-заголовок, чтобы проиллюстрировать, как добавлять заголовки в HTTP-запросы.108109Некоторые веб-сайты [2](https://python-all.ru/2.7/howto/urllib2.html#id10) не любят, когда их просматривают программы, или отправляют разные версии разным браузерам [3](https://python-all.ru/2.7/howto/urllib2.html#id11). По умолчанию urllib2 идентифицирует себя как `Python-urllib/x.y` (где `x` и `y` – номера старшей и младшей версии дистрибутива Python, например `Python-urllib/2.5`), что может сбить с толку сайт или просто не сработать. Браузер идентифицирует себя через заголовок `User-Agent` [4](https://python-all.ru/2.7/howto/urllib2.html#id12). При создании объекта Request можно передать словарь заголовков. В следующем примере выполняется тот же запрос, что и выше, но клиент идентифицирует себя как версия Internet Explorer [5](https://python-all.ru/2.7/howto/urllib2.html#id13).110111```python112import urllib113import urllib2114115url = 'http://www.someserver.com/cgi-bin/register.cgi'116user_agent = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64)'117values = {'name': 'Michael Foord',118          'location': 'Northampton',119          'language': 'Python' }120headers = {'User-Agent': user_agent}121122data = urllib.urlencode(values)123req = urllib2.Request(url, data, headers)124response = urllib2.urlopen(req)125the_page = response.read()126```127128Ответ также имеет два полезных метода. См. раздел [info и geturl](https://python-all.ru/2.7/howto/urllib2.html#info-and-geturl), который следует после того, как мы рассмотрим, что происходит, когда что-то идёт не так.129130## Обработка исключений131132*urlopen* возбуждает `URLError`, когда не может обработать ответ (хотя, как обычно в Python API, могут также возбуждаться встроенные исключения, такие как [`ValueError`](https://python-all.ru/2.7/library/exceptions.html#exceptions.ValueError), [`TypeError`](https://python-all.ru/2.7/library/exceptions.html#exceptions.TypeError) и т.д.).133134`HTTPError` является подклассом `URLError`, возбуждаемым в конкретном случае HTTP URL.135136### URLError137138Часто URLError возникает из-за отсутствия сетевого соединения (нет маршрута к указанному серверу) или потому что указанный сервер не существует. В этом случае возбуждённое исключение будет иметь атрибут 'reason', который представляет собой кортеж, содержащий код ошибки и текстовое сообщение об ошибке.139140например141142```python143>>> req = urllib2.Request('http://www.pretend_server.org')144>>> try: urllib2.urlopen(req)145... except urllib2.URLError as e:146...    print e.reason   147...148(4, 'getaddrinfo failed')149```150151### HTTPError152153Каждый HTTP-ответ от сервера содержит числовой «код состояния». Иногда код состояния указывает на то, что сервер не может выполнить запрос. Обработчики по умолчанию обработают некоторые из таких ответов (например, если ответ является «перенаправлением», требующим от клиента получить документ по другому URL, urllib2 обработает это автоматически). Для тех случаев, которые не могут быть обработаны, urlopen возбуждает исключение `HTTPError`. Типичные ошибки: «404» (страница не найдена), «403» (запрос запрещён) и «401» (требуется аутентификация).154155Все коды ошибок HTTP перечислены в разделе 10 документа RFC 2616.156157Возбуждённый экземпляр `HTTPError` будет иметь целочисленный атрибут 'code', который соответствует ошибке, отправленной сервером.158159#### Коды ошибок160161Поскольку обработчики по умолчанию обрабатывают перенаправления (коды в диапазоне 300), а коды в диапазоне 100–299 указывают на успех, обычно вы будете видеть только коды ошибок в диапазоне 400–599.162163`BaseHTTPServer.BaseHTTPRequestHandler.responses` – это полезный словарь кодов ответов, который содержит все коды ответов, используемые в RFC 2616. Для удобства словарь приведён здесь.164165```python166# Таблица, сопоставляющая коды ответов с сообщениями; записи имеют167# вид {code: (shortmessage, longmessage)}.168responses = {169    100: ('Continue', 'Request received, please continue'),170    101: ('Switching Protocols',171          'Switching to new protocol; obey Upgrade header'),172173    200: ('OK', 'Request fulfilled, document follows'),174    201: ('Created', 'Document created, URL follows'),175    202: ('Accepted',176          'Request accepted, processing continues off-line'),177    203: ('Non-Authoritative Information', 'Request fulfilled from cache'),178    204: ('No Content', 'Request fulfilled, nothing follows'),179    205: ('Reset Content', 'Clear input form for further input.'),180    206: ('Partial Content', 'Partial content follows.'),181182    300: ('Multiple Choices',183          'Object has several resources -- see URI list'),184    301: ('Moved Permanently', 'Object moved permanently -- see URI list'),185    302: ('Found', 'Object moved temporarily -- see URI list'),186    303: ('See Other', 'Object moved -- see Method and URL list'),187    304: ('Not Modified',188          'Document has not changed since given time'),189    305: ('Use Proxy',190          'You must use proxy specified in Location to access this '191          'resource.'),192    307: ('Temporary Redirect',193          'Object moved temporarily -- see URI list'),194195    400: ('Bad Request',196          'Bad request syntax or unsupported method'),197    401: ('Unauthorized',198          'No permission -- see authorization schemes'),199    402: ('Payment Required',200          'No payment -- see charging schemes'),201    403: ('Forbidden',202          'Request forbidden -- authorization will not help'),203    404: ('Not Found', 'Nothing matches the given URI'),204    405: ('Method Not Allowed',205          'Specified method is invalid for this server.'),206    406: ('Not Acceptable', 'URI not available in preferred format.'),207    407: ('Proxy Authentication Required', 'You must authenticate with '208          'this proxy before proceeding.'),209    408: ('Request Timeout', 'Request timed out; try again later.'),210    409: ('Conflict', 'Request conflict.'),211    410: ('Gone',212          'URI no longer exists and has been permanently removed.'),213    411: ('Length Required', 'Client must specify Content-Length.'),214    412: ('Precondition Failed', 'Precondition in headers is false.'),215    413: ('Request Entity Too Large', 'Entity is too large.'),216    414: ('Request-URI Too Long', 'URI is too long.'),217    415: ('Unsupported Media Type', 'Entity body in unsupported format.'),218    416: ('Requested Range Not Satisfiable',219          'Cannot satisfy request range.'),220    417: ('Expectation Failed',221          'Expect condition could not be satisfied.'),222223    500: ('Internal Server Error', 'Server got itself in trouble'),224    501: ('Not Implemented',225          'Server does not support this operation'),226    502: ('Bad Gateway', 'Invalid responses from another server/proxy.'),227    503: ('Service Unavailable',228          'The server cannot process the request due to a high load'),229    504: ('Gateway Timeout',230          'The gateway server did not receive a timely response'),231    505: ('HTTP Version Not Supported', 'Cannot fulfill request.'),232    }233```234235При возникновении ошибки сервер отвечает, возвращая HTTP-код ошибки *и* страницу с ошибкой. Экземпляр `HTTPError` можно использовать в качестве ответа на возвращённой странице. Это означает, что помимо атрибута ⟦code⟧, у него также есть методы ⟦read⟧, ⟦geturl⟧ и ⟦info⟧.236237```python238>>> req = urllib2.Request('http://www.python.org/fish.html')239>>> try:240...     urllib2.urlopen(req)241... except urllib2.HTTPError as e:242...     print e.code243...     print e.read() 244...245404246<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"247"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">248...249<title>Page Not Found</title>250...251```252253### Резюме254255Итак, если вы хотите быть готовым к `HTTPError` *или* `URLError`, есть два основных подхода. Второй подход предпочтительнее.256257#### Способ 1258259```python260from urllib2 import Request, urlopen, URLError, HTTPError261req = Request(someurl)262try:263    response = urlopen(req)264except HTTPError as e:265    print 'The server couldn\'t fulfill the request.'266    print 'Error code: ', e.code267except URLError as e:268    print 'We failed to reach a server.'269    print 'Reason: ', e.reason270else:271    # всё в порядке272```273274> **Примечание**275>276> `except HTTPError` *должен* идти первым, иначе `except URLError` *также* перехватит `HTTPError`.277278#### Способ 2279280```python281from urllib2 import Request, urlopen, URLError282req = Request(someurl)283try:284    response = urlopen(req)285except URLError as e:286    if hasattr(e, 'reason'):287        print 'We failed to reach a server.'288        print 'Reason: ', e.reason289    elif hasattr(e, 'code'):290        print 'The server couldn\'t fulfill the request.'291        print 'Error code: ', e.code292else:293    # всё в порядке294```295296## info и geturl297298Ответ, возвращаемый urlopen (или экземпляром `HTTPError`), имеет два полезных метода: `info()` и `geturl()`.299300**geturl** – возвращает реальный URL полученной страницы. Это полезно, поскольку `urlopen` (или используемый объект opener) мог выполнить перенаправление. URL полученной страницы может не совпадать с запрошенным URL.301302**info** – возвращает объект, подобный словарю, который описывает полученную страницу, в частности заголовки, отправленные сервером. В настоящее время это экземпляр `httplib.HTTPMessage`.303304Типичные заголовки включают ‘Content-length’, ‘Content-type’ и т.д. Смотрите [Краткий справочник HTTP-заголовков](https://python-all.ru/2.7/howto/urllib2.html) для полезного списка HTTP-заголовков с краткими пояснениями их значения и использования.305306## Открыватели и обработчики307308При получении URL используется открыватель – экземпляр [`urllib2.OpenerDirector`](https://python-all.ru/2.7/library/urllib2.html#urllib2.OpenerDirector), название которого, возможно, сбивает с толку. Обычно используется открыватель по умолчанию – через `urlopen`, – но можно создавать собственные открыватели. Открыватели используют обработчики. Вся «тяжёлая работа» выполняется обработчиками. Каждый обработчик знает, как открывать URL для определённой схемы (http, ftp и т.д.) или как обрабатывать аспект открытия URL, например перенаправления HTTP или HTTP-куки.309310Стоит создавать собственные opener'ы, если нужно получать URL с установленными определёнными обработчиками, например чтобы получить opener, обрабатывающий cookies, или opener, не обрабатывающий перенаправления.311312Чтобы создать opener, создайте экземпляр `OpenerDirector`, а затем вызывайте `.add_handler(some_handler_instance)` несколько раз.313314В качестве альтернативы можно использовать `build_opener` – удобную функцию для создания объектов opener одним вызовом. `build_opener` по умолчанию добавляет несколько обработчиков, но предоставляет быстрый способ добавить ещё и/или переопределить стандартные обработчики.315316Другие типы обработчиков могут обрабатывать прокси, аутентификацию и другие распространённые, но слегка специализированные ситуации.317318`install_opener` можно использовать, чтобы сделать объект `opener` (глобальным) стандартным opener'ом. Это означает, что вызовы `urlopen` будут использовать установленный opener.319320Объекты opener имеют метод `open`, который можно вызывать напрямую для получения URL так же, как функцию `urlopen`: нет необходимости вызывать `install_opener`, кроме как для удобства.321322## Базовая аутентификация323324Для иллюстрации создания и установки обработчика мы будем использовать `HTTPBasicAuthHandler`. Для более подробного обсуждения этой темы – включая объяснение того, как работает базовая аутентификация, – смотрите [Руководство по базовой аутентификации](https://python-all.ru/2.7/howto/urllib2.html).325326Когда требуется аутентификация, сервер отправляет заголовок (а также код ошибки 401) с запросом аутентификации. В нём указывается схема аутентификации и «realm». Заголовок выглядит так: `WWW-Authenticate: SCHEME realm="REALM"`.327328например329330```python331WWW-Authenticate: Basic realm="cPanel Users"332```333334Затем клиент должен повторить запрос, включив в него соответствующие имя и пароль для данной области в качестве заголовка. Это и есть «базовая аутентификация». Чтобы упростить этот процесс, можно создать экземпляр `HTTPBasicAuthHandler` и opener для использования этого обработчика.335336`HTTPBasicAuthHandler` использует объект, называемый менеджером паролей, для обработки соответствия URL и областей паролям и именам пользователей. Если известно, какая область (из заголовка аутентификации, отправленного сервером), то можно использовать `HTTPPasswordMgr`. Часто область не важна. В этом случае удобно использовать `HTTPPasswordMgrWithDefaultRealm`. Это позволяет указать имя пользователя и пароль по умолчанию для URL. Они будут использованы при отсутствии альтернативной комбинации для конкретной области. Это указывается передачей `None` в качестве аргумента realm методу `add_password`.337338URL верхнего уровня – это первый URL, требующий аутентификации. URL «глубже», чем переданный в .add\_password(), также будут соответствовать.339340```python341# создать менеджер паролей342password_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()343344# Добавить имя пользователя и пароль.345# Если бы мы знали realm, мы могли бы использовать его вместо None.346top_level_url = "http://example.com/foo/"347password_mgr.add_password(None, top_level_url, username, password)348349handler = urllib2.HTTPBasicAuthHandler(password_mgr)350351# создать "opener" (экземпляр OpenerDirector)352opener = urllib2.build_opener(handler)353354# использовать opener для получения URL355opener.open(a_url)356357# Установить opener.358# Теперь все вызовы urllib2.urlopen используют наш opener.359urllib2.install_opener(opener)360```361362> **Примечание**363>364> В предыдущем примере мы передали только `HTTPBasicAuthHandler` в `build_opener`. По умолчанию открыватели (openers) имеют обработчики для стандартных ситуаций: `ProxyHandler` (если установлена настройка прокси, например переменная окружения `http_proxy`), `UnknownHandler`, `HTTPHandler`, `HTTPDefaultErrorHandler`, `HTTPRedirectHandler`, `FTPHandler`, `FileHandler`, `HTTPErrorProcessor`.365366`top_level_url` на самом деле *либо* полный URL (включая компонент схемы 'http:' и имя хоста и, опционально, номер порта), например `"http://example.com/"`, *либо* «authority» (т.е. имя хоста, опционально включая номер порта), например `"example.com"` или `"example.com:8080"` (последний пример включает номер порта). Authority, если присутствует, НЕ должен содержать компонент «userinfo» – например `"joe:password@example.com"` некорректно.367368## Прокси369370**urllib2** автоматически определяет настройки прокси и использует их. Это происходит через `ProxyHandler`, который входит в стандартную цепочку обработчиков, когда обнаружена настройка прокси. Обычно это хорошо, но бывают случаи, когда это может быть нежелательно [6](https://python-all.ru/2.7/howto/urllib2.html#id14). Один из способов – настроить собственный `ProxyHandler` без определения прокси. Это делается похожими шагами, что и настройка обработчика [Basic Authentication](https://python-all.ru/2.7/howto/urllib2.html):371372```python373>>> proxy_support = urllib2.ProxyHandler({})374>>> opener = urllib2.build_opener(proxy_support)375>>> urllib2.install_opener(opener)376```377378> **Примечание**379>380> В настоящее время `urllib2` *не* поддерживает получение `https`-адресов через прокси. Однако эту возможность можно включить, расширив urllib2, как показано в рецепте [7](https://python-all.ru/2.7/howto/urllib2.html#id15).381382> **Примечание**383>384> `HTTP_PROXY` будет проигнорирован, если установлена переменная `REQUEST_METHOD`; смотрите документацию по [`getproxies()`](https://python-all.ru/2.7/library/urllib.html#urllib.getproxies).385386## Сокеты и уровни387388Поддержка Python для получения ресурсов из Интернета является многоуровневой. urllib2 использует библиотеку httplib, которая, в свою очередь, использует библиотеку socket.389390Начиная с Python 2.3 можно указать, как долго сокет должен ждать ответа до истечения времени ожидания. Это может быть полезно в приложениях, которым необходимо получать веб-страницы. По умолчанию модуль socket *не имеет тайм-аута* и может зависнуть. В настоящее время тайм-аут сокета не раскрыт на уровнях httplib или urllib2. Однако можно глобально задать тайм-аут по умолчанию для всех сокетов с помощью391392```python393import socket394import urllib2395396# тайм-аут в секундах397timeout = 10398socket.setdefaulttimeout(timeout)399400# этот вызов urllib2.urlopen теперь использует таймаут по умолчанию401# который мы установили в модуле socket402req = urllib2.Request('http://www.voidspace.org.uk')403response = urllib2.urlopen(req)404```405406---407408## Примечания409410Этот документ был проверен и доработан Джоном Ли.411412**[1](https://python-all.ru/2.7/howto/urllib2.html#id1)**413414Введение в протокол CGI см. в [Writing Web Applications in Python](https://python-all.ru/2.7/howto/urllib2.html).415416**[2](https://python-all.ru/2.7/howto/urllib2.html#id2)**417418Google, например.419420**[3](https://python-all.ru/2.7/howto/urllib2.html#id3)**421422Определение браузера (browser sniffing) – очень плохая практика для веб-дизайна: создание сайтов с использованием веб-стандартов гораздо разумнее. К сожалению, многие сайты до сих пор отправляют разные версии разным браузерам.423424**[4](https://python-all.ru/2.7/howto/urllib2.html#id4)**425426Пользовательский агент для MSIE 6 – *'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)'*427428**[5](https://python-all.ru/2.7/howto/urllib2.html#id5)**429430Подробнее о HTTP-заголовках запросов см. [Quick Reference to HTTP Headers](https://python-all.ru/2.7/howto/urllib2.html).431432**[6](https://python-all.ru/2.7/howto/urllib2.html#id7)**433434В моём случае для доступа в интернет на работе приходится использовать прокси. При попытке получить URL-адреса *localhost* через этот прокси они блокируются. Браузер Internet Explorer настроен на использование прокси, и urllib2 использует эту настройку. Чтобы тестировать скрипты с локальным сервером, приходится запрещать urllib2 использовать прокси.435436**[7](https://python-all.ru/2.7/howto/urllib2.html#id8)**437438Открыватель urllib2 для SSL-прокси (метод CONNECT): [ASPN Cookbook Recipe](https://python-all.ru/2.7/howto/urllib2.html).439