Программирование и научные вычисления на языке Python/§14

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

Веб-страницы просматриваются с помощью браузеров таких как Internet Explorer, Safari, Firefox, Opera, Google Chrome и так далее. Выбор браузера зависит от предпочтений и осведомленности пользователя. Любая посещаемая вами веб-страница имеет свой адрес, что-то вроде этого:


http://www.some.where.net/some/file.htm


Такой адрес называется URL. Вся графическая начинка, что мы видим на странице, организуется на ней с помощью языка разметки HTML. Грубо говоря, действия HTML это то же, что команды языка программирования. Все эти действия записаны в файле и выполняются при обращении по адресу, подобно запуску программы. Язык HTML легко распознать по характерным особенностям записи команд, которые записываются в виде тегов, ограничивающих какой-то текст с двух сторон:


<tag>текст внутри</tag>


Первый тег — открывающий, второй, точно такой же, но со слэшем перед ним — закрывающий. Теги производят какие-то действия над текстом внутри: увеличивают его размер, задают цвет, наклон, вставляют его в таблицу, делают его гиперссылкой и так далее. После имени открывающего тега также, для той же ссылки в двойных кавычках может идти URL ссылки или, например, одиночный тег для изображения содержит ссылку на место где он расположен:


<a href="http://www.w3.org/MarkUp/Guide/">HTML</a> # выводит ссылку HTML,
# нажимая на которую, мы переходим по адресу http://www.w3.org/MarkUp/Guide/
<img src="http://www.simula.no/simula_logo.gif">   # выводит gif-картинку


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

Как использовать web-страницы в программах

править

Зачем нам знать о HTML и том как составляются страницы? Затем, что интернет полон информации, постоянно обновляемой, которую мы путем несложных действий можем использовать в своих программах. Как мы выяснили, то, что мы видим на экране это лишь продукт HTML и информация лежит в html-файле, представляющим собой текст. А с текстом работать мы уже умеем, особенно получать из него нужные данные.

Имея URL в виде строки, у нас есть два пути заполучить текст html-файла:

  • Скачать html-файл и сохранить его, дав имя, скажем, webpage.html:


import urllib
url = 'http://www.simula.no/research/scientific/cbc'
urllib.urlretrieve(url, filename='webpage.html')


  • Открыть html-файл как обычный файловый объект:


infile = urllib.urlopen(url)


Чтение простого текстового файла

править

Код html-файлов по своему виду часто похож на текст большой программы, в которой та информация, которую мы хотим извлечь, похоронена под множеством тэгов и довольно скверного синтаксиса. По этой причине извлечение информации именно из html-файла может оказаться довольно непростой задачей. Как эта проблема решается, мы поговорим в следующем пункте. Однако, мир виртуальных численных измерений может иногда оказаться более доступным: данные, что мы ищем, зачастую располагаются в простых txt-файлах, которые можно также, зная URL, загрузить.

Например, на сайте [1] можно найти txt-файлы о температурах в различных городах начиная с 1 января 1995 года по сегодняшний день. Из российских городов мы там обнаруживаем Москву — [2]. Вы, конечно можете выбрать для себя и любой другой город. В соответствии с URL загружаем нужный нам файл:


import urllib
url = 'http://www.engr.udayton.edu/faculty/jkissock/gsod/RSMOSCOW.txt'
urllib.urlretrieve(url, filename='Moscow.txt')


Если просмотреть этот файл в текстовом редакторе или в том же браузере, мы увидим, что он содержит четыре столбца: первый обозначает номер месяца, второй — число, третий — год, четвертый — температуру в градусах Фаренгейта.

Как мы можем использовать эти данные в программе? Во-первых, мы должны определиться со структурой хранения данных. Для этих целей неплохо подойдет вложенный словарь в виде temp[year][month][date]. Узнаем месяц, потом номер месяца, потом число и получаем соответствующее значение температуры. Процесс конвертирования файла в нашу базу данных заключается в последовательном чтении строк, их разбиении на слова, использовании первых трех слов в качестве ключей и последнего в качестве значения:


import urllib
url = 'http://www.engr.udayton.edu/faculty/jkissock/gsod/RSMOSCOW.txt'
urllib.urlretrieve(url, filename='Moscow.txt')

infile = open('Moscow.txt', 'r')
temps = {}
for line in infile:
    month, date, year, temperature = line.split()
    month = int(month)
    date = int(date)
    year = int(year)
    temperature = float(temperature)
    if not year in temps:
        temps[year] = {}
    if not month in temps[year]:
        temps[year][month] = {}
    temps[year][month][date] = temperature
infile.close()

# выберем день, чтобы проверить, что работа выполнена правильно:
year = 2003; month = 3; date = 31
T = temps[year][month][date]
print '%d-%d-%d: %.1f' % (date, month, year, T)


Как видите, по сути наши действия ничем не отличаются от действий с простыми файлами.

Далее, проверив, что значение температуры для заданных дней совпадают, несложными действиями мы можем заставить программу пересчитывать значения температур для шкалы Цельсия и, например, строить график для выбранного временного интервала. К примеру, нам интересно каким морозным в Москве был январь 2010 года и как изменялась температура в течение месяца:


import urllib
url = 'http://www.engr.udayton.edu/faculty/jkissock/gsod/RSMOSCOW.txt'
urllib.urlretrieve(url, filename='Moscow.txt')

infile = open('Moscow.txt', 'r')
temps = {}
for line in infile:
    month, date, year, temperature = line.split()
    month = int(month)
    date = int(date)
    year = int(year)
    ftemp = float(temperature)
    ctemp = (ftemp - 32)/1.8
    if not year in temps:
        temps[year] = {}
    if not month in temps[year]:
        temps[year][month] = {}
    temps[year][month][date] = ctemp
infile.close()

date = xrange(1, len(temps[2010][1])+1)
temp = [temps[2010][1][i] for i in date]

import matplotlib.pyplot as plt
plt.plot(date, temp)
plt.show()

Извлечение данных из html

править

Прогноз погоды на Yahoo! вместе с данными о погоде содержит множество графиков и объявлений. Представим, вам нужно быстро узнать погоду и температуру в каком-то городе. Можем ли мы сделать программу, которая находит эту информацию на странице? Да, можем, умеем.

Прогноз погоды, например, для Санкт-Петербурга, на момент написания этих строк расположен по адресу: http://weather.yahoo.com/russia/st.-peterburg/st.-petersburg-2123260/. Естественно, вы можете выбрать и любой другой доступный город.

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


import  urllib
w = 'http://weather.yahoo.com/russia/st.-peterburg/st.-petersburg-2123260/'
urllib.urlretrieve(url=w, filename='weather.html')


Если мы станем просматривать исходный код страницы, то увидим множество ненужной нам информации, в гораздо большем количестве, чем та, что нам требуется. Все это нужно для построения страницы, установления шрифтов, вывода текста, обращения к скриптам и так далее. Мы же ищем текст «Current conditions» (текущее состояние), который обнаружили еще просматривая страницу в привычном графическом представлении.

И выбираем следующий кусок текста html (естественно, он скорее всего отличается по значениям величин):


<div id="yw-forecast" class="night">
<em>Current conditions as of 9:00 PM MSK</em>
<div id="yw-cond">Light Snow</div>
<dl>

<dt>Feels Like:</dt><dd>21 &deg;F</dd>
<dt>Barometer:</dt><dd style="position:relative;">29.91 in and rising rapidly</dd>
<dt>Humidity:</dt><dd>83 %</dd>
<dt>Visibility:</dt><dd>3.73 mi</dd>

<dt>Dewpoint:</dt><dd>18 &#176;F</dd>
<dt>Wind:</dt><dd>NW 11 mph</dd>
<dt>Sunrise:</dt><dd>7:47 AM</dd>
<dt>Sunset:</dt><dd>6:35 PM</dd>

</dl>
<div class="forecast-temp">
<div id="yw-temp">21&#176;</div>


Нас интересуют два куска этого текста:

  • После строки, содержащей «Current conditions», мы видим строку, рассказывающую о сегодняшней погоде:
<em>Current conditions as of 9:00 PM MSK</em>
<div id="yw-cond">Light Snow</div>


  • После строки с "forecast-temp" имеется строка, указывающая сегодняшнюю температуру:
<div class="forecast-temp">
<div id="yw-temp">21&#176;</div>


Чтобы заполучить нужную информацию, нам необходимо выполнить следующую последовательность действий: прочитать построчно файл; найти по ключевым словам Current conditions и forecast-temp строки, в которых они содержатся; требуемые данные расположены в следующих за ними строках, с помощью строковой функции strip() отрезаем ненужные пробелы по краям строк; воспользовавшись срезами вырезаем нужную нам подстроку, убирая из нее div-теги и все лишнее. Получаем следующий цикл:


lines = infile.readlines()
for i in range(len(lines)):
    line = lines[i]  # короткая форма
    if 'Current conditions' in line:
        without_space = lines[i+1].strip()
        weather = without_space[18:-6]
    if 'forecast-temp' in line:
        without_space = lines[i+1].strip()
        ftemp = float(without_space[18:-12])
        temperature = (ftemp - 32)/1.8
        break # все что нужно найдено, уходим из цикла


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

Теперь полученный цикл мы можем зашить в тело функции и использовать ее далее как нам заблагорассудится:


def get_data(url):
    urllib.urlretrieve(url=url, filename='tmp_weather.html')

    infile = open('tmp_weather.html')
    lines = infile.readlines()
    
    # [...] здесь наш цикл

    infile.close()
    return weather, temperature


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

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


import urllib

cities = {
    'St-Petersburg':
    'http://weather.yahoo.com/russia/st.-peterburg/st.-petersburg-2123260/',
    'Priozersk':
    'http://weather.yahoo.com/russia/leningradskaya-oblast/priozersk-2123030/',
    'Vaskelovo':
    'http://weather.yahoo.com/russia/leningradskaya-oblast/vaskelovo-2124169/',
    }

def get_data(url):
    urllib.urlretrieve(url=url, filename='tmp_weather.html')

    infile = open('tmp_weather.html')
    lines = infile.readlines()
    for i in range(len(lines)):
        line = lines[i]  # короткая форма
        if 'Current conditions' in line:
            without_space = lines[i+1].strip()
            weather = without_space[18:-6]
        if 'forecast-temp' in line:
            without_space = lines[i+1].strip()
            ftemp = float(without_space[18:-12])
            temperature = round((ftemp - 32)/1.8)
            break  # все что нужно найдено
        infile.close()
    print "In %s it's %s and %d degrees." % (city, weather.lower(), temperature)

for city in cities: get_data(cities[city])


Новый исходный код html

править

Интернет быстро меняется, и за те пол-года, которые прошли с момента написания этого урока Yahoo существенно изменили своего робота по написанию страниц погоды, причем беспорядка в html стало еще больше. Поэтому, когда я стал работать над презентацией данного курса в своем университете, мой старый код выдавал мне только ошибки. Оценив новую ситуацию, я снова обрадовался тому, как легко и приятно поддерживать код на Python и переписал старый код на новый лад, теперь уже для зимнего человека:


import urllib
 
cities = {
    'St-Petersburg':
    'http://weather.yahoo.com/russia/st.-peterburg/st.-petersburg-2123260/',
    'Priozersk':
    'http://weather.yahoo.com/russia/leningradskaya-oblast/priozersk-2123030/',
    'Vaskelovo':
    'http://weather.yahoo.com/russia/leningradskaya-oblast/vaskelovo-2124169/',
    }
 
def get_data(url):
    urllib.urlretrieve(url=url, filename='tmp_weather.html')
 
    infile = open('tmp_weather.html')
    lines = infile.readlines()
    for i in range(len(lines)):
        line = lines[i]  # короткая форма
        if 'yw-cond' in line:
            start = line.find('yw-cond')
            without_space = line.strip()
            startweather = start+9  # 9 -> yw-cond">
            endweather = without_space.find('<', startweather)
            weather = without_space[startweather:endweather]
            
        if 'yw-temp' in line:
            start = line.find('yw-temp')
            without_space = line.strip()
            starttemp = start+9  # 9 -> yw-temp">
            endtemp = without_space.find('&', starttemp)
            ftemp = float(without_space[starttemp:endtemp])
            temperature = round((ftemp - 32)/1.8)
            break  # все, что нужно найдено
        infile.close()
    print u"In %s it's %s and %s degrees." % (city, weather.lower(), temperature)
 
for city in cities: get_data(cities[city])


Результат работы программы на 16 января таков:

In St-Petersburg it's cloudy and -18.0 degrees.
In Vaskelovo it's cloudy and -17.0 degrees.
In Priozersk it's mostly cloudy and -21.0 degrees.

Замечание

править

Рассмотрение html-файла в качестве строк является не самым мощным, но позволяет использовать уже известные нам действия над строками. Если вам интересна эта тема и вы планируете постоянно использовать много данных из интернета, то для этого вам надо узнать о регулярных выражениях (regular expressions) и тому как с помощью Python осуществляется парсинг (parsing).