Довольно часто встречается задача получения курсов валют и сохранение результатов в базу. Задача достаточно проста, чтобы привлекать для этого "тяжелую артиллерию" типа java, поэтому предлагается воспользоваться Python.
Почему Python?
Во-первых, это современный и популярный язык, во-вторых, python повсеместно используется для автоматизации задач в Linux, так что практически на любом хосте с Linux будет Python, ну и в-третьих интересно было попробовать Python на реальной задаче.
Итак, имеем базу данных на MSSQL 2000, в которую нужно сохранить список курсов валют на каждый день. В базу нужно сохранять не все доступные курсы, а только те, которые актуальны для приложения. Таблица со списком актуальных валют также находится в базе данных.
Еще имеется внешний ресурс, который выдает список курсов, он расположен по адресу http://www.cbr.ru/scripts/XML_daily.asp. Курсы выдаются в формате XML.
Алгоритм работы скрипта выглядит следующим образом:
- Получить из базы список актуальных валют,
- Получить и распарсить XML с курсами валют от cbr.ru,
- Найти среди списка курсов курсы нужных валют и сохранить в базу,
- Отправить сообщение на электронную почту с результатами работы скрипта.
Для работы скрипта понадобится следующее:
- Linux - ну это понятно, я использовал Ubuntu 12, но скорее всего подойдет любой современный дистрибутив,
- Python 2.6 и выше
- База данных, у меня используется MSSQL 2000
- Библиотека pymssql для создания запросов к БД
Про установку pymssql нужно рассказать чуть подробнее. Хотя для Ubuntu существует пакет deb, но он у меня не заработал, т.е. правильный запрос к базе не возвращал никаких результатов, при этом не выдавая какую-либо ошибку. Поэтому, по многочисленным советам я установил последнюю версию pymssql через pip.
Сначала установим pip:
sudo apt-get install python-pip
Затем установим библиотеку Cython, которая нужна для сборки pymssql и не только его:
sudo pip install cython
И еще pyrex, если она не установлена
sudo pip install pyrex
После чего наконец можно установить pymssql:
sudo pip install pymssql
Примеры использования pymssql можно найти на странице
проекта, а у меня получение списка валют выглядит следующим образом:
def load_currencies():
currencies = []
conn = pymssql.connect(host = 'dbhost', user='user',password='secret',database='my_database')
try:
cur = conn.cursor()
cur.execute('SELECT ShortName, ID FROM [Currency] where not exists(select * from Currency_Exchange_Rate \
where ListID = %d and FromCurrency = Currency.ID and [Date] = dbo.GetDay(GETDATE()))', 1)
row = cur.fetchone()
while row:
currencies.append({'Name':row[0], 'ID':row[1]})
row = cur.fetchone()
finally:
conn.close()
return currencies
Функция load_currencies возвращает список актуальных валют, для которых не установлен курс на текущую дату.
Затем, получение и парсинг списка курсов валют:
u = urllib2.urlopen("http://www.cbr.ru/scripts/XML_daily.asp", timeout=10)
parser = xml.sax.make_parser()
handler = CurrencyRateHandler()
parser.setContentHandler(handler)
parser.parse(u)
print handler.mapping
Для загрузки файла используется метод urlopen из библиотеки urllib2. Он возвращает поток, который можно сохранить в файл, а можно напрямую передать парсеру, как в данном случае. Для разбора полученного XML используется стандартный SAX-парсер. Чтобы парсер мог что-то сделать с полученным файлом ему необходим обработчик. Код обработчика представлен в классе CurrencyRateHandler, который наследуется от библиотечного класса xml.sax.handler.ContentHandler. Код обработчика:
class CurrencyRateHandler(xml.sax.handler.ContentHandler):
def __init__(self):
self.inTag = ""
self.charCode = ""
self.mapping = {}
def startElement(self, name, attributes):
if name in set(["CharCode","Nominal", "Value"]):
self.buffer = ""
self.inTag = name
else:
self.inTag = ""
def characters(self, data):
if self.inTag:
self.buffer += data
def endElement(self,name):
if name == "CharCode":
self.charCode = self.buffer
self.mapping[self.charCode] = {}
elif name == "Nominal":
self.mapping[self.charCode][self.inTag] = int(self.buffer)
elif name == "Value":
self.mapping[self.charCode][self.inTag] = float(self.buffer.replace(',','.'))
self.inTag = ""
SAX-обработчик получает события startElement и endElement при обработке открывающего и закрывающего тегов XML соответственно. Содержимое тега поступает в метод characters. Обработчик устроен достаточно просто он проверяет на соответствие имени тега с определенными шаблонами и аггрегирует данные в словарь mapping. В результате разбора получается словарь ключами, которого являются коды валют, а значениями словари, каждый из которых содержит информацию о курсе валюты и номинале обмена (1, 10 или 100 единиц).
Затем нам нужно из полученного от парсера словаря отфильтровать только те валюты, которые нас интересуют и записать значения их курсов в базу. Фильтрация словаря выполняется с помощью функции filter:
update_rates(filter(lambda c: rates.has_key(c['Name']), currencies), rates)
Функция update_rates выполняет запись в базу следующим образом:
def update_rates(currencies, rates):
conn = get_connection()
try:
cur = conn.cursor()
cur.executemany('INSERT INTO Currency_Exchange_Rate(Date,ListID, FromCurrency, Factor,Amount)\
VALUES(dbo.GetDay(GETDATE()),1,%d,%d,%s)', map(lambda c: (c['ID'], rates[c['Name']]['Nominal'], rates[c['Name']]['Value']), currencies))
conn.commit()
finally:
conn.close()
самое интересное в этой функции находится в строке 6, там для формирования списка значений для команды INSERT используется функция map и лямбда-функция, возвращающая кортеж значений для вставки в базу из словаря курсов валют.
Вот в общем-то и все, остаются всякие полезные мелочи вроде логгирования и отправки сообщения по электронной почте.
Полученный скрипт можно поместить в /etc/cron.daily, чтобы он выполнялся ежедневно по расписанию. Для этого в заголовке нужно указать оболочке какой интерпретатор следует использовать (python конечно же):
#!/usr/bin/env python
import sys
import pymssql
import urllib2
import xml.sax.handler
и дать файлу разрешение на выполнение:
sudo chmod 744 updater.py