Введение в программирование на Python

Сетевые программы

Показывать лекцию целиком

По ссылке youtube выложено видео с русскими титрами.

Хотя большая часть примеров в этой книге до сих пор фокусировалась на чтении файлов и поиске данных в них, есть много других источников информации, и важнейший из них – Интернет.

В этой главе мы попробуем исполнить роль веб-браузера и получить веб-страницу, используя протокол передачи гипертекста HTTP (HyperText Transport Protocol). Затем мы прочитаем содержимое страницы и осуществим его разбор.

23.1. Протокол передачи гипертекста – HTTP

Сетевой протокол, на котором основана вся мощь Интернет, на самом деле совсем простой, и его поддержка встроена в Питон – это так называемый механизм сокетов, пользуясь которым, совсем несложно устанавливать сетевые соединения и получать данные из сети в программах Питона.

Сокет в основном аналогичен файлу, за тем исключением, что он обеспечивает двустороннее соединение между двумя программами.

Вы можете как читать, так и записывать данные по одному и тому же сокету. Если вы что-то пишете в сокет, то эти данные передаются по сети парному приложению, работающему на другом конце сокета. Если вы читаете из сокета, то получаете данные, которые парное приложение послало вам. Но если вы пытаетесь прочесть данные, когда парное приложение на другом конце сокета еще ничего не послало – вы попросту ничего не делаете и ждёте. Если приложения на обоих концах сокета ждут прихода данных, не посылая ничего, то они будут ждать очень долго.

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

Существует множество документов, описывающих подобные сетевые протоколы. Протокол передачи гипертекста приводится в следующем документе: .

Это длинный и сложный 176-страничный документ с множеством деталей. Если вам интересно, можете прочитать его. Но если прочитать лишь страницу 36 стандарта RFC2616, вы найдете описание синтаксиса запроса GET. Более точно, вы прочитаете, что для получения документа от веб-сервера нужно установить соединение с сервером по адресу и порту 80 и передать ему текстовую строку вида:

GET http://www.py4inf.com/code/romeo.txt
HTTP/1.0
  

Здесь второй параметр – это адрес запрашиваемой веб-страницы; затем нужно послать также пустую строку. Веб-сервер отвечает на запрос, посылая некоторую служебную ("заголовочную") информацию о запрашиваемом документе, пустую строку и затем содержимое документа.

23.2. Самый простой в мире веб-браузер

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

import socket
mysock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
mysock.connect(('www.py4inf.com', 80))
mysock.send('GET http://www.py4inf.com/code/romeo.txt HTTP/1.0\n\n')

while True:
data = mysock.recv(512)
if ( len(data) < 1 ) :
break
print data
mysock.close()
  

Сначала программа устанавливает соединение по порту 80 с сервером на узле . Поскольку наша программа исполняет роль "веб-браузера", протокол HTTP предписывает послать команду GET и затем пустую строку.

После посылки пустой строки следует цикл, который принимает данные из сокета порциями по 512 символов и печатает их до тех пор, пока не останется непрочитанных данных (т.е. функция recv() не вернёт пустую строку).

На выходе программы получаем:

HTTP/1.1 200 OK
Date: Sun, 14 Mar 2010 23:52:41 GMT
Server: Apache
Last-Modified: Tue, 29 Dec 2009 01:31:22 GMT
ETag: "143c1b33-a7-4b395bea"
Accept-Ranges: bytes
Content-Length: 167
Connection: close
Content-Type: text/plain
But soft what light through yonder window breaks
It is the east and Juliet is the sun
Arise fair sun and kill the envious moon
Who is already sick and pale with grief
  

Вывод программы начинается с заголовочной информации, которую сервер посылает для описания документа. Например, заголовочная строка Content-Type указывает, что это простой текстовый документ (text/plain). После того, как сервер пересылает нам всю заголовочную информацию, он посылает пустую строку, отделяющую служебное описание документа от его содержимого, и дальше пересылает текст, содержащийся в файле romeo.txt.

Этот пример показывает, как устанавливается низкоуровневое соединение с помощью сокетов. Сокеты можно использовать для обмена с веб-сервером, сервером электронной почты и многими другими типами серверов. Всё, что требуется – это разыскать документ, который описывает протокол обмена, и записать программный код, пересылающий и принимающий данные в соответствии с этим протоколом. Однако, поскольку самый часто используемый протокол – это HTTP (протокол Всемирной паутины), Питон имеет специальную библиотеку, обеспечивающую поддержку протокола HTTP для получения документов и данных через Интернет.

23.3. Получение веб-страниц с использованием библиотеки urllib

Библиотека urllib максимально упрощает получение веб-страниц и обработку их содержимого в программах Питона. Используя urllib, мы работаем с веб-страницами почти так же, как с файлами. Нужно всего лишь указать, какую веб-страницу мы хотим получить, дальше уже сама библиотека urllib отрабатывает все детали протокола HTTP.

Эквивалентный код для чтения файла romeo.txt из сети с помощью urllib записывается следующим образом:

import urllib
fhand = urllib.urlopen('http://www.py4inf.com/code/romeo.txt')
for line in fhand:
print line.strip()
  

После того, как веб-страница открыта с помощью метода urllib.urlopen, можно работать с ней как с файлом, читая ее содержимое в цикле for. На выходе программа выдает только содержимое файла. Заголовочная информация также пересылается, но код библиотеки urllib проглатывает заголовки и возвращает нам только данные:

But soft what light through yonder window breaks
It is the east and Juliet is the sun
Arise fair sun and kill the envious moon
Who is already sick and pale with grief
  

Для примера можно написать программу, получающую содержимое файла romeo.txt и подсчитывающую частоту каждого слова в тексте:

import urllib
counts = dict()
fhand = urllib.urlopen('http://www.py4inf.com/code/romeo.txt')
for line in fhand:
words = line.split()
for word in words:
counts[word] = counts.get(word,0) + 1
print counts
  

Еще раз: как только мы открыли веб-страницу, можно читать ее точно так же, как и локальный файл на компьютере.

23.4. Разбор HTML и извлечение информации из веб-страниц (Web scraping)

Очень часто возможности библиотеки urllib Питона используются при извлечении информации во всемирной паутине (Web scraping). Мы пишем программу, которая притворяется веб-браузером, получает Интернет-страницы и затем анализирует их содержимое, находя в них нужные текстовые шаблоны.

Например, поисковая машина, подобная Google, начинает свою работу с какой-нибудь веб-страницы, извлекает из нее ссылки на другие страницы, получает по сети содержимое этих страниц, извлекает из них ссылки и так далее. Используя подобную технику, пауки Google прокладывают свой путь практически через все страницы в сети. Google принимает частоту найденных подобным образом ссылок на конкретную веб-страницу как меру "важности" этой страницы, определяющую, насколько высоко она будет помещена в ответах на поисковые запросы.

23.5. Разбор HTML-страниц с помощью регулярных выражений

Один из самых простых способов анализа HTML-страниц – использование регулярных выражений для поиска и извлечения подстрок, соответствующих определенным шаблонам. Рассмотрим простейшую веб-страницу:

<h1>The First Page</h1>
<p>
If you like, you can switch to the
<a href="http://www.dr-chuck.com/page2.htm">
Second Page</a>.
</p>

Можно построить регулярное выражение для нахождения и извлечения ссылок из приведенного выше текста:

href="http://.+?"

Это регулярное выражение соответсвует подстрокам, начинающимся с фрагмента "href="http://", за которым следует один или несколько произвольных символов ".+?" и далее закрывающая двойная кавычка. Вопросительный знак после плюса в ".+?" указывает, что сопоставление подстроки шаблону должно происходить в "нежадном" режиме вместо используемого по умолчанию "жадного". При "нежадном" сопоставлении мы пытаемся найти максимально короткую строку, соответствующую шаблону, при "жадном" – максимально длинную. Нужно еще добавить круглые скобки в наше регулярное выражение, чтобы указать, какую часть сопоставленной шаблону строки мы хотим извлечь. В результате получим следующую программу:

import urllib
import re
url = raw_input('Enter - ')
html = urllib.urlopen(url).read()
links = re.findall('href="(http://.*?)"', html)
for link in links:
print link

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

В результате работы программы получим:

python urlregex.py
Enter - http://www.dr-chuck.com/page1.htm
http://www.dr-chuck.com/page2.htm
python urlregex.py
Enter - http://www.py4inf.com/book.htm
http://www.greenteapress.com/thinkpython/thinkpython.html
http://allendowney.com/
http://www.py4inf.com/code
http://www.lib.umich.edu/espresso-book-machine
http:// www.py4inf.com/py4inf-slides.zip
  

Регулярные выражения прекрасно работают, когда HTML-текст правильно и предсказуемо отформатирован. Но, поскольку в сети огромное количество не вполне корректных страниц (формат HTML не является строгим), программа, основанная лишь на регулярных выражениях, либо пропускает некоторые допустимые ссылки, либо выдает неправильные данные. Эта проблема может быть решена путем использования библиотеки для надежного разбора HTML.

23.6. Разбор HTML-страниц с помощью библиотеки BeautifulSoup

В Питоне есть несколько библиотек, помогающих при разборе HTML-текста и извлечении данных из веб-страниц. Каждая из этих библиотек имеет свои преимущества и недостатки.

Вы можете выбрать одну из них, основываясь на своих потребностях. Например, задача разбора HTML-текста и извлечения ссылок из него легко решается с помощью библиотеки BeautifulSoup. Вы можете скачать и установить эту библиотеку с адреса . Можно даже не устанавливая библиотеку, просто поместить файл BeautifulSoup.py в тот же каталог, что и ваше приложение.

Несмотря на то, что HTML-документ выглядит как XML и некоторые страницы тщательно сконструированы так, чтобы удовлетворять строгим правилам XML, большая часть HTML-страниц сформирована неправильно в том смысле, что XML-парсер отвергает подобные страницы целиком как некорректные. Но библиотека BeautifulSoup терпимо относится даже к очень неряшливым страницам и позволяет извлекать из них нужную информацию.

Мы будем использовать библиотеку urllib, чтобы читать веб-страницы, и затем библиотеку BeautifulSoup, чтобы извлекать тексты ссылок (т.е. аттрибут href) из тегов <a> (anchor).

import urllib
from BeautifulSoup import *
url = raw_input('Enter - ')
html = urllib.urlopen(url).read()
soup = BeautifulSoup(html)

# Retrieve all of the anchor tags
tags = soup('a')
for tag in tags:
print tag.get('href', None)
  

Программа предлагает ввести веб-адрес, открывает веб-страницу, читает ее содержимое и передает данные парсеру BeautifulSoup, а затем извлекает все теги <a> и выводит атрибут href (т.е. гипертекстовую ссылку) для каждого тега.

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

python urllinks.py
Enter - http://www.dr-chuck.com/page1.htm
http://www.dr-chuck.com/page2.htm
python urllinks.py
Enter - http://www.py4inf.com/book.htm
http://www.greenteapress.com/thinkpython/thinkpython.html
http://allendowney.com/
http://www.si502.com/
http://www.lib.umich.edu/espresso-book-machine
http://www.py4inf.com/code
http://www.pythonlearn.com/
  

Можно использовать BeautifulSoup для извлечения различных частей каждого тега, как в следующей программе:

import urllib
from BeautifulSoup import *
url = raw_input('Enter - ')
html = urllib.urlopen(url).read()
soup = BeautifulSoup(html)
# Retrieve all of the anchor tags
tags = soup('a')
for tag in tags:
# Look at the parts of a tag
print 'TAG:',tag
print 'URL:',tag.get('href', None)
print 'Content:',tag.contents[0]
print 'Attrs:',tag.attrs
  

В результате получаем:

python urllink2.py

Enter - http://www.dr-chuck.com/page1.htm
TAG: <a href="http://www.dr-chuck.com/page2.htm">
Second Page</a>
URL: http://www.dr-chuck.com/page2.htm
Content: [u'\nSecond Page']
Attrs: [(u'href', u'http://www.dr-chuck.com/page2.htm')]
  

Эти примеры лишь отчасти демонстрируют силу библиотеки .

23.7. Чтение бинарных файлов с помощью urllib

Часто нужно получить из сети нетекстовый (бинарный) файл, например, изображение или видео-файл. Данные в таких файлах обычно не используются для печати, но с помощью urllib нетрудно просто скопировать содержимое сетевого файла на жесткий диск.

Открываем URL и используем метод read для скачивания всего содержимого документа в строковую переменную img, затем записываем эту информацию в локальный файл:

img = urllib.urlopen('http://www.py4inf.com/cover.jpg').read()
fhand = open('cover.jpg', 'w')
fhand.write(img)
fhand.close()
  

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

Это работает, когда размер сетевого файла меньше, чем объем памяти вашего компьютера. Однако при чтении огромного аудио- или видео-файла программа может привести к отказу или в лучшем случае начнет работать крайне медленно, когда физическая память компьютера будет исчерпана.

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

import urllib
img = urllib.urlopen('http://www.py4inf.com/cover.jpg')
fhand = open('cover.jpg', 'w')
size = 0
while True:
info = img.read(100000)
if len(info) < 1 : break
size = size + len(info)
fhand.write(info)
print size,'characters copied.'
fhand.close()
  

В этом примере мы читаем за один раз блок размером в 100000 байтов и записываем его файл cover.jpg перед чтением следующей порции данных из сети. После запуска программа выдает:

python curl2.py
568248 characters copied.
  

Если у вас Unix- или Macintosh-компьютер, то, скорее всего, в операционной системе есть команда, которая выполняет описанную выше операцию:

curl -O http://www.py4inf.com/cover.jpg

Название команды curl является сокращением от "copy URL". Поэтому файлы с программами Питона из последних двух примеров называны curl1.py и curl2.py – они делают то же самое, что и команда curl. Также имеется программа curl3.py, которая решает ту же задачу чуть более эффективно – ее тоже можно использовать в качестве образца при написании собственных программ.

23.8. Глоссарий

BeautifulSoup Название происходит от распространенного выражения "tag soup" – суп, смесь из тегов, которое употребляется для плохо сформированных HTML-документов. Библиотека BeautifulSoup – "Прекрасный суп", по-видимому, хорошо разбирается в подобном супе. – прим. перев. : библиотека Питона для разбора HTML-документов и извлечения данных из них, которая, подобно большинству браузеров, принимает даже плохо сформированные HTML-документы. Код библиотеки

Порт: число, которое обычно определяет, с каким именно приложением вы общаетесь, когда устанавливается соединение с сервером через сокет. Например, передача данных во всемирной паутине обычно использует порт 80, электронная почта – порт 25.

Scrape – извлечение, дословно "выскабливание" данных: процесс, при котором сетевая программа, притворяясь веб-браузером, получает по сети веб-страницы и затем анализирует их содержимое. Часто подобные программы следуют по ссылкам, содержащимся в прочитанной странице, чтобы перейти к следующей странице и таким образом пройти всю сеть, например, социальную.

Сокет: сетевое соединение между двумя приложениями, при котором эти приложения могут посылать и принимать данные в обоих направлениях.

Паук (spider): способ работы сетевой поисковой машины, которая считывает страницу, затем все страницы, на которые она ссылается, и так далее, пока в конце-концов не будут пройдены почти все страницы в Интернет. Используется при построении поисковых систем.

23.9. Упражнения

Упражнение 23.1.

Измените использующую механизм сокетов программу socket1.py так, чтобы она сначала запрашивала у пользователя адрес веб-страницы (URL) и затем считывала её. Можно использовать разделитель '/' и строковый метод split для того, чтобы разбить URL на компоненты и извлечь адрес сетевого узла, который необходим при установке соединения через сокет. Добавьте проверку ошибок, используя конструкцию try-except, для обработки ситуации, когда пользователь ввел неправильно сформированный или несуществующий URL.

Упражнение 23.2.

Измените программу, использующую механизм сокетов, так, чтобы она подсчитывала количество полученных символов и прекращала печатать текст после того, как напечатано 3000 символов. Однако читать документ программа должна до конца – для подсчета числа символов во всём документе. По окончании чтения число символов должно быть напечатано.

Упражнение 23.3.

Используйте urllib для того, чтобы иным способом решить предыдущее упражнение:

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

    Упражнение 23.4.

    Измените программу urllinks.py так, чтобы извлечь теги <p> ("paragraph" – абзац) из полученного HTML-документа, подсчитать их число и на выходе напечатать количество абзацев в документе. Не нужно печатать текст абзацев – только подсчитать их. Протестируйте вашу программу на нескольких небольших веб-страницах и затем попробуйте на каких-нибудь объемных страницах.

    Упражнение 23.5 (более сложное).

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

    Вернуться к учебному плану