Безопасный eval в python

Об этом уже достаточно много сказано, но может и мой вариант кому-то приглянется. Обычно запрещают список опасных функций для eval’а. Можно пойти чуть по другому пути и наоборот, разрешить, что-то. Я набросал пример, цель которого была — вычисление математических выражений. Чтобы было не так скучно, сделал это в django через аякс 🙂

Итак, views.py
[code lang=»python»]
import re, json
from django.shortcuts import render_to_response
from django.http import HttpResponse

#…

def calc(request):
return render_to_response(‘calc.html’, {})

def calc_json_expression(request):
p = re.compile(r'[-+*\/\(\)0-9(sin)(cos)(tan)]*’, re.IGNORECASE)
calcmath = request.POST["calc-math"]
res = ».join(p.findall(calcmath))
from math import sin, cos, tan
json_data = {}
try:
json_data[‘exp’] = res
json_data[‘res’] = eval(res)
except:
json_data[‘exp’] = »
json_data[‘res’] = ‘0’
return HttpResponse(json.dumps(json_data), mimetype="application/json")
[/code]

urls.py
[code lang=»python»]
urlpatterns = patterns(‘kernel.views’,
# …
url(r’^calc/$’, ‘calc’),
url(r’^calc-json-expression/$’, ‘calc_json_expression’),
)
[/code]

и файл шаблона calc.html
[code lang=»html»]
<!DOCTYPE html>
<html>
<head>
<title></title>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
</head>
<body>
<h1>Calc</h1>
<form method="post" id="calc-form">
<p><label for="calc-math">Print math expression</label><input type="text" name="calc-math" id="calc-math" value="" placeholder="Expression"></p>
<p>Expression: <span id="calc-math-expression"></span></p>
<p>Result: <span id="calc-math-answer">0</span></p>
</form>

<script type="text/javascript">
$(‘#calc-math’).keyup(function() {
$.post(‘calc-json-expression/’, $("#calc-form").serialize(),function(data){
$(‘#calc-math-answer’).text(data["res"]);
$(‘#calc-math-expression’).text(data["exp"]);
}, "json");
});
</script>
</body>
</html>
[/code]

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

[code lang=»python»]
p = re.compile(r'[-+*\/\(\)0-9(sin)(cos)(tan)]*’, re.IGNORECASE)
[/code]

Собственно, это и есть мой способ, подобных при поиске «безопасный eval» не увидел. Проблема, с которой столкнулся — это ограничение на время исполнения скрипта. Такой штуки нет ни в джанго, ни в питоне. Поэтому 10 в миллиардной степени, например, этот скрипт не обработает, а сайт просто упадет. Есть решения, интернет ими кишит, но они, в основном, только для *nix, либо практически нерабочие. Если кто-то найдет кроссплатформенный рабочий код — отпишитесь, пожалуйста 🙂

Парсер веб-страниц на Python

Для успешного парсинга страниц достаточно двух инструментов — urllib и BeautifulSoup. Первый обычно доступен сразу после установки python, второй легко можно найти в интернете — http://www.crummy.com/software/BeautifulSoup/bs3/download/

Пример

[code lang=»python»]
>>> from BeautifulSoup import BeautifulSoup
>>> import urllib
>>> f = urllib.urlopen(‘http://my.site’) # Открываем сайт, который будем парсить
>>> soup = BeautifulSoup(f.read()) # Считываем его и одновременно закидываем в BeautifulSoup
>>> my = soup.findAll(name=’div’, attrs={‘class’: ‘news’}) # Ищем все div`ы с классом ‘news’
>>> for m in my:
… print m # Выводим их
[/code]
Результат, например, может быть следующий (если найден только один div):

[u’Содержание статьи 1.’]

По шагам

1. Качаем по ссылке BeautifulSoup.tar.gz, который лежит в корне.

2. Распаковываем gz, затем tar.

3. Копируем в папку (желательно без русских символов и пробелов).

4. Устанавливаем:
 Нажимаем Ctrl+R
 cmd
 cd C:\BeautifulSoup-3.2.1
 python setup.py install

5. Должно успешно установиться. Теперь можем испытать скрипт. Заходим в python:
[code lang="python"]
>>> from BeautifulSoup import BeautifulSoup
>>> import urllib
>>> f = urllib.urlopen('http://my.site') # Открываем сайт, который будем парсить
>>> soup = BeautifulSoup(f.read()) # Считываем его и одновременно закидываем в BeautifulSoup
>>> my = soup.findAll(name='div', attrs={'class': 'news'}) # Ищем все div`ы с классом 'news'
>>> for m in my:
...    print m.findAll(text=True) # Выводим их
[/code]

6. В результате должны были получить содержимое всех div`ов c классом `news`.

Примечание. Если вы линуксоид, то сами разберетесь, что и куда кидать :)