Что это такое и как работает:
Tornado
- это неблокирующий, высоко производительный веб-сервер с открытыми
исходным кодом. Сервер написан на языке Python и без проблем выдерживает
несколько тысяч одновременных подключений.
Nginx - маленький но очень быстрый веб-сервер, думаю с ним вы уже знакомы.
Django - фреймворк для веб-приложений на языке Python.
Это все будет работать по нашей любимой схеме frontend - backend:
frontend - nginx, передаёт все соединения, кроме статических файлов, на backend. Просто отдаёт статику.
backend - tornado + django
По запуску Tornado+Nginx под linux написано очень много и трогать єту тему не будем. Мы же займёмся
установкой этой чудесной связки под винду. Но крайне не рекомендую ставить
рабочие проекты под Windows (тем более под XP).
Для большей реалистичности эксперименты будут проводится над django приложением OSQA(качать по ссылке ниже). OSQA - это Open Source система построения Q&A(Вопросы и ответы) сообществ.
Понадобиться:
1. Windows
2. Python 2.6 или 2.7
3. Dajngo
4. Tornado
5. NIMPix 1.2.0 RC2
6. OSQA
7. Консоль
В статье подразумевается ,что у вас уже установлены :
1. Windows
2. Python
2.0 djаngo
2.1 markdown
2.2 html5lib
2.3 south
2.4 python-openid
2.5 setuptools
3. NIMPix
процесс их установки не описан.
Сервер NIMPix находится на диске С:\
Глава 2. Установка и настройка OSQA
Внимание если у вас версия django выше 1.3, то OSQA работать не будет. Рекомендовано django 1.2 на 1.3(с плясками).
1. Создать папку apps в С:\nimpix в ней папку osqa. Здесь будет находиться наш сайт вопросов и ответов.
2. Распаковать все файлы из архива OSQA в эту папку.
3. Запускам Mysql и Nginx на NIMPix
4. Созадем Mysql базу с названием osqa
5. Проверяем все ли нужные модули Python установлены. Для этого в консоли пишем
python
открывается интерпретатор в нем вводим
>>> help(’modules’)
Найдите в появившемся списке такие модули django, html5lib, south, markdown, openid. Если одного из них нет то необходимо его установить т.к. все они нужный для работы OSQA.
Для этого :
1. Ставим setuptools
http://pypi.python.org/pypi/setuptools или distribute
http://pypi.python.org/pypi/distribute. Рекомендую distribute, но я
пользуюсь setuptools(по привычке).
2. Ставим Если у вас не прописан в PATCH путь до каталога site-packages, то пишите как написано ниже.
“C:\Program Files\Python27\Lib\site-packages\easy_install.py” django
“C:\Program Files\Python27\Lib\site-packages\easy_install.py” markdown
“C:\Program Files\Python27\Lib\site-packages\easy_install.py” html5lib
“C:\Program Files\Python27\Lib\site-packages\easy_install.py” south
“C:\Program Files\Python27\Lib\site-packages\easy_install.py” python-openid
Если же у вас этот путь есть то будет достаточно просто easy_install ….. , без указания полного пути.
Переходим к настройке OSQA.
1. !!!ВНИМАНЕ!!!Для тех у кого django 1.3
в С:\nimpix\apps\osqa\forum\utils\html.py строку
from django.template import mark_safe
меняем на
from django.utils.safestring import mark_safe
в C:\nimpix\apps\osqa\forum\urls.py
вместо
url(r’^%s(.*)’ % _(’nimda/’), admin.site.root),
должно быть так
url(r’^%s’ % _(’nimda/’), include(admin.site.urls)),
2. Открываем С:\nimpix\apps\osqa\settings_local.py и настраиваем подключение к БД
DATABASES = {
‘default’: {
‘ENGINE’: ‘django.db.backends.mysql’,
‘NAME’: ‘osqa’,
‘USER’: ‘root’,
‘PASSWORD’: ‘J8uv01Bq431j’,
‘HOST’: ”,
‘PORT’: ”,
}
}
3. Создаём базу данных и все такое.
С:\nimpix\apps\osqa\manage.py syncdb –all
!!!При запросе супер пользователя НЕ СОЗДАЁМ!!!
С:\nimpix\apps\osqa\manage.py migrate forum –fake
Глава 3. Установка и настройка связки Tornado+Nginx
Начнем:
Установка торнадо:
Способ(Рекомендованный) 1.
Устанавливаем с помощью setuptools или distribute.
“C:\Program Files\Python27\Lib\site-packages\easy_install.py” tornado
Способ 2.
С шаманскими плясками и бубнами.
1. Скачать Tornado
http://github.com/downloads/facebook/tornado/tornado-1.2.1.tar.gz
2. распаковать в C:\tornado
3. Заходим в папку и устанавливаем.
cd C:\tornado
setup.py build
setup.py install
4. торнадо установлен.
5. Создаём файл tornading.py и помешаем его в корень нашего проекта.
# -*- coding: utf-8 -*-
import os
import sys
# настройки
DJANGO_ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Между ” укажите путь до ваших приложений, пустой указывает на корень сайта.
DJANGO_APPS_DIR = os.path.join(DJANGO_ROOT_DIR, ”)
def daemon(iport):
import tornado.wsgi
import tornado.ioloop
import tornado.httpserver
# настраиваем django
sys.path.insert(0, DJANGO_APPS_DIR)
os.environ['DJANGO_SETTINGS_MODULE'] = ’settings’
import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()
# подключаем tornado
container = tornado.wsgi.WSGIContainer(application)
http_server = tornado.httpserver.HTTPServer(container)
http_server.listen(iport)
tornado.ioloop.IOLoop.instance().start()
if __name__ == "__main__":
# на вход должен быть передан порт,
# на котором будет запущен tornado-сервер
daemon(int(sys.argv[1]))
6. Запускаем 2 экземпляра на портах 8001 и 8002(порты могут быть и
другие). Естественно путь к файлу tornading.py должен быть ваш.
С:\nimpix\apps\osqa\tornading.py 8001&
С:\nimpix\apps\osqa\tornading.py 8002&
7. Запускаем NIMPix, создаём виртуальный хост osqa.lan, открываем его для редактирования и приводим его к такому виду.
У меня путь к проекту django С:/nimpix/apps/osqa/, вы его должны поменять на свой.
upstream backends {
server 127.0.0.1:8001;
server 127.0.0.1:8002;
}
server {
listen 80;
server_name www.osqa.lan osqa.lan;
access_log /nimpix/component/logs/site/osqa.buh.lan.nginx.access.log;
error_log /nimpix/component/logs/site/osqa.buh.lan.nginx.error.log;
location /media/ {
alias С:/nimpix/apps/osqa/media/;
expires 30d;
}
location /admin/ {
alias С:/nimpix/apps/osqa/media_admin/;
expires 30d;
}
location / {
proxy_pass http://backends;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
8. Включаем виртуальный хост и запускаем сервер.
9. Заходим на сайт, создаём пользователя. Первый созданный пользователь будет администратором .
10. Вот и все
среда, 26 декабря 2012 г.
Установка сервера Apache, mod_python, Django, MySQL и Sqlite3 на Windows
Сегодня я расскажу вам, как собрать полноценный сервер для Django
приложений. Для написания и тестирования вполне хватает встроенного
сервера, но часто бывает, что работа приложения на деволоперском сервере
запускаемом командой (django-admin.py runserver) отличается от работы на боевом хостинге, проявляются разнообразные баги глюки и остальная нечисть.
И сегодняшней нашей целью является создание полноценного сервера, что бы проверить работу нашего сайта в условиях максимально приближенным к боевым.
Конечно, можно воспользоваться уже готовыми решениями типа Bitnami Djangostack это замечательная сборка из Apache HTTP Server, MySQL, Python, SQLite после установки вы получите работающий сервер, но мы, же не ищем лёгких путей и будем устанавливать, и настраивать это всё сами.
Связка будет выглядеть, таким образом: Apache+Mysql+mod_python+Django+Sqlite и сие чудо мы будем ставить на Windows.
Что нам надо:
0. Windows
1. Apach v.2.2.11
2. Mysql v.5.1.31
3. Sqlite v.3.6.11
4. Python v.3.0.1
5. Django v.1.0.2
6. mod_python v.3.3.1
ШАГ 1
Надеюсь, Windows у вас уже установлен и я опущу этот этап и начну с установки Apache.
Установить http сервер не сложно: просто запускаем скачанный инсталлер apache_2.2.11-win32-x86-no_ssl.msi

Клацаем по кнопке “Next” до тех пор, пока установщик не попросит нас ввести Network Domain и Server name, так как мы делаем сервер исключительно для себя, то в обоих случаях вводим localhost или 127.0.0.1 кому как нравится.

Это значит, что наш сервер будет видно только для данного компьютера. Если же вы хотите, что бы он был доступен для всех, введите свой IP адрес, или домен. После того как все заполнили выбираем “For All Users” и жмём Next и смотрим как устанавливается. По окончанию установки он должен запустится автоматически как сервис. Для проверки набираем в браузере http://localhost, если вы видите надпись “It works!” всё готово и можете читать дальше.

Если же нет то
1.Попробуйте перезагрузить компьютер.
2.Проверьте, а не запущен ли у вас другой http сервер, если да то остановите его.
3.Может фаерволом (брендмуаром) у вас заблокирован порт 80, необходимо этот порт разблокировать.
4.Попробуйте просмотреть через нормальный браузер НЕ Internet Explorer
5.Если ни одно из вышеуказанных действий не помогло, то стучите в бубен, наверняка это проделки демонов мешают запуску нашего сервера.
Теперь нам понадобится конфигурационный файл, который находится
C:\Program Files\Apache Software Foundation\Apache2.2\conf\httpd.conf
открываем его в блокноте:
строка 170 расскоментируйте (уберите знак #)
ServerName localhost:80
строка 177 и 204 замените параметр DocumentRoot на свой, например C:/www
DocumentRoot “C:/www”
Сохраняем файл httpd.conf и перезапускаем сервер вуаля все должно работать.
ШАГ 2 - Установка MySql
MySql установить не сложнее, чем Apache. Для начала запускаем mysql-5.1.31-win32.msi и снова кнопка next->

Выбираем способ установки (я советую Typical)

После того все файлы установились, будим конфигурировать mysql

Выбираем “Detailed Configuratin”(детальная настройка)

Клацаем на пункте “Developer Machine”, мы же не просто пользователи, а Разработчики

Потом выбираем “Multifunctional Database”, тоесть мы сможем работать как с таблицами типа InnoDB, так и со скоростными MyISAM в большинстве случаев используются последние.

Выбираем диск для хранения таблиц типа InnoDB, оставляем всё как есть и жмем “Дальше”

Далее мы должны выбрать, сколько будет одновременных подключений к нашему серверу. Думаю 20 для нас будет предостаточно, поэтому выбираем “Decision Support (DSS)/OLAP”,
Следующий этап оставляем по умолчанию, должно быть отмечено “Enable TCP/IP Networking” и “Enable Strict Mode”

Выбираем кодировку, которую MySql сервер будет использовать по умолчанию
“Manual Selected Default Character Set / Collation” и выделяем utf-8

Будет ли наш сервер запускаться как сервис или нет, мы выбираем на этом этапе, а так же рекомендую включить Include Bin Directory in Windows PATH” - это позволит нам работать с сервером из командной строки.

Завершающий этап - это ввод пароля (хотя можно и без него, но крайне нежелательно, поэтому введите что нибудь).

Ура всё!!

Далее идём в C:\Program Files\MySQL\MySQL Server 5.1\my.ini
Здесь нам необходимо заменить строку 84, что бы использовалась по “default” MYISAM
меняем
default-storage-engine=INNODB
на
default-storage-engine=MYISAM
Пуск -> программы -> MySQL -> MySQL Server 5.1 -> MySQL Comand Line Client
Появляется консоль с запросом ввести пароль. Вводим и получаем

Проверяем, что у нас за базы у нас есть! Вводим: SHOW DATABASES;

Все, с установкой MySql закончили!
С установкой python думаю проблем у вас не возникнет, так как вся установка заключается из двух этапов, скачать дистрибутив и запустить его, а вот на остальных моментах остановлюсь подробнее. Сразу скажу, что установка связки Apache + mod_python омрачила меня одним моментами, бинарник mod_python под Windows подходит только для Python 2.5. Если хотите установить его на Python 2.6 и выше, то придется устраивать ритуальные пляски с бубнами.
Приступим к установке mod_python (я надеюсь, python 2.5 вы уже установили)!!
Идем на офф сайт и закачиваем последнюю версию на сегодня это 3.3.1. Запускаем приложение.
Если вылетает окно с ошибкой типа “не найден MSVCR71.dll повторная установка приложения может решить проблему” то надо закачать отсюда MSVCR71.dll и положить его в C:\WINDOWS\system32 это должно решить проблему. Повторно запускаем mod_python-3.3.1.win32-py2.5-Apache2.2.exe.
В процессе указываем корневой каталог Apach и ждем окончания установки.
Теперь установим Django framework, есть несколько способов установки Django. Мы пойдем простейшим - установим из svn последнею версию дистрибутива. Для этого нам потребуется svn клиент (я взял Slik) Устанавливаем клиент!! После этого открываем консоль(cmd) заходим в каталог site-packages
cd C:\Python25\Lib\site-packages
И скачиваем django командой
svn co http://code.djangoproject.com/svn/django/trunk/django django

Теперь наш свежеиспечённый django находится в C:\Python25\Lib\site-packages\django. Если у вас уже есть установленный, то надо его обновить командой.
svn update
при этом находясь в директории django. Вот и все!

Далее нам понадобится пустой джанго проект для тестирования. Создадим:
C:\Documents and Settings\User>cd C:\
C:\>mkdir example
C:\>cd example
С:\example>django-admin.py startproject testproject
Если у вас появилась ошибка типа “django-admin.py не является внутренней или внешней командой…” То скопируйте django-admin.py из папки C:\Python25\Lib\site-packages\django\bin в ту папку, где вы хотите создать проект, в нашем случае это С:\example, есть и другой способ решения данной проблемы, но об этом в другой раз. Повторите запрос
django-admin.py startproject testproject
Вот у нас есть пустой проект. Переходим к конфигурации Apache, для этого открываем файл
C:\Program Files\Apache Software Foundation\Apache2.2\conf\httpd.conf
И подключаем в самом конце файла модулю питона.
LoadModule python_module modules/mod_python.so
затем пишем
<Location “/testproject/”>
SetHandler python-program
PythonHandler django.core.handlers.modpython
SetEnv DJANGO_SETTINGS_MODULE testproject.settings
PythonOption django.root c:/example/testproject
PythonDebug On
PythonPath “['/example'] + sys.path”
</Location>

Перезапускаем Apache заходим http://localhost/testproject/ радуемся.

И сегодняшней нашей целью является создание полноценного сервера, что бы проверить работу нашего сайта в условиях максимально приближенным к боевым.
Конечно, можно воспользоваться уже готовыми решениями типа Bitnami Djangostack это замечательная сборка из Apache HTTP Server, MySQL, Python, SQLite после установки вы получите работающий сервер, но мы, же не ищем лёгких путей и будем устанавливать, и настраивать это всё сами.
Связка будет выглядеть, таким образом: Apache+Mysql+mod_python+Django+Sqlite и сие чудо мы будем ставить на Windows.
Что нам надо:
0. Windows
1. Apach v.2.2.11
2. Mysql v.5.1.31
3. Sqlite v.3.6.11
4. Python v.3.0.1
5. Django v.1.0.2
6. mod_python v.3.3.1
ШАГ 1
Надеюсь, Windows у вас уже установлен и я опущу этот этап и начну с установки Apache.
Установить http сервер не сложно: просто запускаем скачанный инсталлер apache_2.2.11-win32-x86-no_ssl.msi
Клацаем по кнопке “Next” до тех пор, пока установщик не попросит нас ввести Network Domain и Server name, так как мы делаем сервер исключительно для себя, то в обоих случаях вводим localhost или 127.0.0.1 кому как нравится.
Это значит, что наш сервер будет видно только для данного компьютера. Если же вы хотите, что бы он был доступен для всех, введите свой IP адрес, или домен. После того как все заполнили выбираем “For All Users” и жмём Next и смотрим как устанавливается. По окончанию установки он должен запустится автоматически как сервис. Для проверки набираем в браузере http://localhost, если вы видите надпись “It works!” всё готово и можете читать дальше.
Если же нет то
1.Попробуйте перезагрузить компьютер.
2.Проверьте, а не запущен ли у вас другой http сервер, если да то остановите его.
3.Может фаерволом (брендмуаром) у вас заблокирован порт 80, необходимо этот порт разблокировать.
4.Попробуйте просмотреть через нормальный браузер НЕ Internet Explorer
5.Если ни одно из вышеуказанных действий не помогло, то стучите в бубен, наверняка это проделки демонов мешают запуску нашего сервера.
Теперь нам понадобится конфигурационный файл, который находится
C:\Program Files\Apache Software Foundation\Apache2.2\conf\httpd.conf
открываем его в блокноте:
строка 170 расскоментируйте (уберите знак #)
ServerName localhost:80
строка 177 и 204 замените параметр DocumentRoot на свой, например C:/www
DocumentRoot “C:/www”
Сохраняем файл httpd.conf и перезапускаем сервер вуаля все должно работать.
ШАГ 2 - Установка MySql
MySql установить не сложнее, чем Apache. Для начала запускаем mysql-5.1.31-win32.msi и снова кнопка next->
Выбираем способ установки (я советую Typical)
После того все файлы установились, будим конфигурировать mysql
Выбираем “Detailed Configuratin”(детальная настройка)
Клацаем на пункте “Developer Machine”, мы же не просто пользователи, а Разработчики
Потом выбираем “Multifunctional Database”, тоесть мы сможем работать как с таблицами типа InnoDB, так и со скоростными MyISAM в большинстве случаев используются последние.
Выбираем диск для хранения таблиц типа InnoDB, оставляем всё как есть и жмем “Дальше”
Далее мы должны выбрать, сколько будет одновременных подключений к нашему серверу. Думаю 20 для нас будет предостаточно, поэтому выбираем “Decision Support (DSS)/OLAP”,
Выбираем кодировку, которую MySql сервер будет использовать по умолчанию
“Manual Selected Default Character Set / Collation” и выделяем utf-8
Будет ли наш сервер запускаться как сервис или нет, мы выбираем на этом этапе, а так же рекомендую включить Include Bin Directory in Windows PATH” - это позволит нам работать с сервером из командной строки.
Завершающий этап - это ввод пароля (хотя можно и без него, но крайне нежелательно, поэтому введите что нибудь).
Ура всё!!
Далее идём в C:\Program Files\MySQL\MySQL Server 5.1\my.ini
Здесь нам необходимо заменить строку 84, что бы использовалась по “default” MYISAM
меняем
default-storage-engine=INNODB
на
default-storage-engine=MYISAM
Пуск -> программы -> MySQL -> MySQL Server 5.1 -> MySQL Comand Line Client
Появляется консоль с запросом ввести пароль. Вводим и получаем
Проверяем, что у нас за базы у нас есть! Вводим: SHOW DATABASES;
Все, с установкой MySql закончили!
С установкой python думаю проблем у вас не возникнет, так как вся установка заключается из двух этапов, скачать дистрибутив и запустить его, а вот на остальных моментах остановлюсь подробнее. Сразу скажу, что установка связки Apache + mod_python омрачила меня одним моментами, бинарник mod_python под Windows подходит только для Python 2.5. Если хотите установить его на Python 2.6 и выше, то придется устраивать ритуальные пляски с бубнами.
Приступим к установке mod_python (я надеюсь, python 2.5 вы уже установили)!!
Идем на офф сайт и закачиваем последнюю версию на сегодня это 3.3.1. Запускаем приложение.
В процессе указываем корневой каталог Apach и ждем окончания установки.
cd C:\Python25\Lib\site-packages
И скачиваем django командой
svn co http://code.djangoproject.com/svn/django/trunk/django django
Теперь наш свежеиспечённый django находится в C:\Python25\Lib\site-packages\django. Если у вас уже есть установленный, то надо его обновить командой.
svn update
при этом находясь в директории django. Вот и все!
Далее нам понадобится пустой джанго проект для тестирования. Создадим:
C:\Documents and Settings\User>cd C:\
C:\>mkdir example
C:\>cd example
С:\example>django-admin.py startproject testproject
Если у вас появилась ошибка типа “django-admin.py не является внутренней или внешней командой…” То скопируйте django-admin.py из папки C:\Python25\Lib\site-packages\django\bin в ту папку, где вы хотите создать проект, в нашем случае это С:\example, есть и другой способ решения данной проблемы, но об этом в другой раз. Повторите запрос
django-admin.py startproject testproject
Вот у нас есть пустой проект. Переходим к конфигурации Apache, для этого открываем файл
C:\Program Files\Apache Software Foundation\Apache2.2\conf\httpd.conf
И подключаем в самом конце файла модулю питона.
LoadModule python_module modules/mod_python.so
затем пишем
<Location “/testproject/”>
SetHandler python-program
PythonHandler django.core.handlers.modpython
SetEnv DJANGO_SETTINGS_MODULE testproject.settings
PythonOption django.root c:/example/testproject
PythonDebug On
PythonPath “['/example'] + sys.path”
</Location>
Перезапускаем Apache заходим http://localhost/testproject/ радуемся.
вторник, 25 декабря 2012 г.
Python Получение переменных GET и POST из адресной строки
В PHP вы можете использовать $_POST для POST и $_GET для GET для получения переменных из адресной строки (Query string variables). Каков же будет эквивалент этих команд в Python?
Python - это просто язык программирования, поэтому для того, чтобы получить данные GET и POST вам необходимо использовать специальный фреймворк, написанный на Python, такой, например, как: Django, Turbogears, Pylons, CherryPy, web.py, mod_python, fastcgi и многие другие.
Представим, что вы отправили значения на сервер из данной формы:
<input type="text" name="username" />
Тогда при использовании чистого CGI вам поможет данный код:
import cgi
form = cgi.FieldStorage()
print form["username"]
Если вы используете Django или Pylons:
print request.GET['username'] # для GET
print request.POST['username'] # для POST
Если вы используете Turbogears или Cherrypy:
from cherrypy import request
print request.params['username']
Используя Cherrypy или Turbogears вы можете также определить управляющую функцию, принимающую параметры напрямую:
def index(self, username):
print username
Если вы используете Web.py:
form = web.input()
print form.username
Если вы используете Werkzeug:
print request.form['username']
Для совсем сырого способа получения данных воспользуйтесь этим кодом:
import os, sys
# строка запроса, содержащая необработанные данные GET
# (Для примера для http://example.com/myscript.py?a=b&c=d&e
# это будет "a=b&c=d&e")
os.getenv("QUERY_STRING")
# чистые данные POST
sys.stdin.read()
Python - это просто язык программирования, поэтому для того, чтобы получить данные GET и POST вам необходимо использовать специальный фреймворк, написанный на Python, такой, например, как: Django, Turbogears, Pylons, CherryPy, web.py, mod_python, fastcgi и многие другие.
Представим, что вы отправили значения на сервер из данной формы:
<input type="text" name="username" />
Тогда при использовании чистого CGI вам поможет данный код:
import cgi
form = cgi.FieldStorage()
print form["username"]
Если вы используете Django или Pylons:
print request.GET['username'] # для GET
print request.POST['username'] # для POST
Если вы используете Turbogears или Cherrypy:
from cherrypy import request
print request.params['username']
Используя Cherrypy или Turbogears вы можете также определить управляющую функцию, принимающую параметры напрямую:
def index(self, username):
print username
Если вы используете Web.py:
form = web.input()
print form.username
Если вы используете Werkzeug:
print request.form['username']
Для совсем сырого способа получения данных воспользуйтесь этим кодом:
import os, sys
# строка запроса, содержащая необработанные данные GET
# (Для примера для http://example.com/myscript.py?a=b&c=d&e
# это будет "a=b&c=d&e")
os.getenv("QUERY_STRING")
# чистые данные POST
sys.stdin.read()
MySQL CheatSheet
Query
SELECT * FROM table SELECT * FROM table1, table2, ... SELECT field1, field2, ... FROM table1, table2, ... SELECT ... FROM ... WHERE condition SELECT ... FROM ... WHERE condition GROUP BY field SELECT ... FROM ... WHERE condition GROUP BY field HAVING condition2 SELECT ... FROM ... WHERE condition ORDER BY field1, field2 SELECT ... FROM ... WHERE condition ORDER BY field1, field2 DESC SELECT ... FROM ... WHERE condition LIMIT 10 SELECT DISTINCT field1 FROM ... SELECT DISTINCT field1, field2 FROM ...
SELECT ... FROM t1 JOIN t2 ON t1.id1 = t2.id2 WHERE condition SELECT ... FROM t1 LEFT JOIN t2 ON t1.id1 = t2.id2 WHERE condition SELECT ... FROM t1 JOIN (t2 JOIN t3 ON ...) ON ... SELECT ... FROM t1 JOIN t2 USING(id) WHERE condition
Conditionals
field1 = value1 field1 <> value1 field1 LIKE 'value _ %' field1 IS NULL field1 IS NOT NULL field1 IN (value1, value2) field1 NOT IN (value1, value2) condition1 AND condition2 condition1 OR condition2
Data Manipulation
INSERT INTO table1 (field1, field2, ...) VALUES (value1, value2, ...) INSERT table1 SET field1=value_1, field2=value_2 ...
DELETE FROM table1 / TRUNCATE table1 DELETE FROM table1 WHERE condition -- join: DELETE FROM table1, table2 WHERE table1.id1 = table2.id2 AND condition
UPDATE table1 SET field1=new_value1 WHERE condition -- join: UPDATE table1, table2 SET field1=new_value1, field2=new_value2, ... WHERE table1.id1 = table2.id2 AND condition
Browsing
SHOW DATABASES SHOW TABLES SHOW FIELDS FROM table / SHOW COLUMNS FROM table / DESCRIBE table / DESC table / EXPLAIN table SHOW CREATE TABLE table SHOW CREATE TRIGGER trigger SHOW TRIGGERS LIKE '%update%' SHOW PROCESSLIST KILL process_number
$ mysqlshow $ mysqlshow database
Create / delete / select / alter database
CREATE DATABASE [IF NOT EXIST] mabase [CHARACTER SET charset] [COLLATE collation] CREATE DATABASE mabase CHARACTER SET utf8 DROP DATABASE mabase USE mabase
ALTER DATABASE mabase CHARACTER SET utf8
Create/delete/modify table
CREATE TABLE table (field1 type1, field2 type2, ...)
CREATE TABLE table (field1 type1, field2 type2, ..., INDEX (field))
CREATE TABLE table (field1 type1, field2 type2, ..., PRIMARY KEY (field1))
CREATE TABLE table (field1 type1, field2 type2, ..., PRIMARY KEY (field1, field2))
CREATE TABLE table1 (fk_field1 type1, field2 type2, ...,
FOREIGN KEY (fk_field1) REFERENCES table2 (t2_fieldA))
[ON UPDATE|ON DELETE] [CASCADE|SET NULL]
CREATE TABLE table1 (fk_field1 type1, fk_field2 type2, ...,
FOREIGN KEY (fk_field1, fk_field2) REFERENCES table2 (t2_fieldA, t2_fieldB))
CREATE TABLE table IF NOT EXISTS (...)
CREATE TABLE new_tbl_name LIKE tbl_name [SELECT ... FROM tbl_name ...]
CREATE TEMPORARY TABLE table (...)
DROP TABLE table DROP TABLE IF EXISTS table DROP TABLE table1, table2, ... DROP TEMPORARY TABLE table
ALTER TABLE table MODIFY field1 type1 ALTER TABLE table MODIFY field1 type1 NOT NULL ... ALTER TABLE table CHANGE old_name_field1 new_name_field1 type1 ALTER TABLE table CHANGE old_name_field1 new_name_field1 type1 NOT NULL ... ALTER TABLE table ALTER field1 SET DEFAULT ... ALTER TABLE table ALTER field1 DROP DEFAULT ALTER TABLE table ADD new_name_field1 type1 ALTER TABLE table ADD new_name_field1 type1 FIRST ALTER TABLE table ADD new_name_field1 type1 AFTER another_field ALTER TABLE table DROP field1 ALTER TABLE table ADD INDEX (field); ALTER TABLE table ADD PRIMARY KEY (field);
-- Change field order: ALTER TABLE table MODIFY field1 type1 FIRST ALTER TABLE table MODIFY field1 type1 AFTER another_field ALTER TABLE table CHANGE old_name_field1 new_name_field1 type1 FIRST ALTER TABLE table CHANGE old_name_field1 new_name_field1 type1 AFTER another_field
ALTER TABLE old_name RENAME new_name;
Keys
CREATE TABLE table (..., PRIMARY KEY (field1, field2)) CREATE TABLE table (..., FOREIGN KEY (field1, field2) REFERENCES table2 (t2_field1, t2_field2)) ALTER TABLE table ADD PRIMARY KEY (field);
Privileges
GRANT ALL PRIVILEGES ON base.* TO 'user'@'localhost' IDENTIFIED BY 'password'; GRANT SELECT, INSERT, DELETE ON base.* TO 'user'@'localhost' IDENTIFIED BY 'password'; REVOKE ALL PRIVILEGES ON base.* FROM 'user'@'host'; -- one permission only REVOKE ALL PRIVILEGES, GRANT OPTION FROM 'user'@'host'; -- all permissions
SET PASSWORD = PASSWORD('new_pass')
SET PASSWORD FOR 'user'@'host' = PASSWORD('new_pass')
SET PASSWORD = OLD_PASSWORD('new_pass')
DROP USER 'user'@'host'
Main data types
TINYINT (1o: -127+128) SMALLINT (2o: +-65 000) MEDIUMINT (3o: +-16 000 000) INT (4o: +- 2 000 000 000) BIGINT (8o: +-9.10^18) Precise interval: -(2^(8*N-1)) -> (2^8*N)-1 /!\ INT(2) = "2 digits displayed" -- NOT "number with 2 digits max"
INT NOT NULL auto_increment PRIMARY KEY -- auto-counter for PK
FLOAT(M,D) DOUBLE(M,D) FLOAT(D=0->53) /!\ 8,3 -> 12345,678 -- NOT 12345678,123!
TIME (HH:MM) YEAR (AAAA) DATE (AAAA-MM-JJ) DATETIME (AAAA-MM-JJ HH:MM; années 1000->9999) TIMESTAMP (like DATETIME, but 1970->2038, compatible with Unix)
VARCHAR (single-line; explicit size) TEXT (multi-lines; max size=65535) BLOB (binary; max size=65535) Variants for TEXT&BLOB: TINY (max=255) MEDIUM (max=~16000) LONG (max=4Go) Ex: VARCHAR(32), TINYTEXT, LONGBLOB, MEDIUMTEXT
ENUM ('value1', 'value2', ...) -- (default NULL, or '' if NOT NULL)
Forgot root password?
$ /etc/init.d/mysql stop
$ mysqld_safe --skip-grant-tables
$ mysql # on another terminal
mysql> UPDATE mysql.user SET password=PASSWORD('nouveau') WHERE user='root';
## Kill mysqld_safe from the terminal, using Control + \
$ /etc/init.d/mysql start
Repair tables after unclean shutdown
mysqlcheck --all-databases mysqlcheck --all-databases --fast
понедельник, 24 декабря 2012 г.
Отличия TCP от UDP
В чем отличия TCP от UDP?
Прежде всего, TCP и UDP - это протоколы передачи данных по сети. Основное их отличие в том, что TCP - это протокол с гарантированной доставкой пакетов, а UDP - нет.
TCP - предоставляет "гарантированный" транспортный механизм с предварительным установлением соединения, предоставляющий приложению надёжный поток данных, дающий уверенность в безошибочности получаемых данных, перезапрашивающий данные в случае потери и устраняющий дублирование данных. TCP позволяет регулировать нагрузку на сеть, а также уменьшать время ожидания данных при передаче на большие расстояния. Более того, TCP гарантирует, что полученные данные были отправлены точно в такой же последовательности. В этом его главное отличие от UDP.
UDP - это протокол передачи датаграмм без установления соединения. Также его называют протоколом "ненадёжной" передачи, в смысле невозможности удостовериться в доставке сообщения адресату, а также возможного перемешивания пакетов. В приложениях, требующих гарантированной передачи данных, используется протокол TCP.
UDP обычно используется в таких приложениях, как потоковое видео и компьютерные игры, где допускается потеря пакетов, а повторный запрос затруднён или не оправдан, либо в приложениях вида запрос-ответ (например, запросы к DNS), где создание соединения занимает больше ресурсов, чем повторная отправка.
И TCP, и UDP используют для определения протокола верхнего уровня число, называемое портом.
Прежде всего, TCP и UDP - это протоколы передачи данных по сети. Основное их отличие в том, что TCP - это протокол с гарантированной доставкой пакетов, а UDP - нет.
TCP - предоставляет "гарантированный" транспортный механизм с предварительным установлением соединения, предоставляющий приложению надёжный поток данных, дающий уверенность в безошибочности получаемых данных, перезапрашивающий данные в случае потери и устраняющий дублирование данных. TCP позволяет регулировать нагрузку на сеть, а также уменьшать время ожидания данных при передаче на большие расстояния. Более того, TCP гарантирует, что полученные данные были отправлены точно в такой же последовательности. В этом его главное отличие от UDP.
UDP - это протокол передачи датаграмм без установления соединения. Также его называют протоколом "ненадёжной" передачи, в смысле невозможности удостовериться в доставке сообщения адресату, а также возможного перемешивания пакетов. В приложениях, требующих гарантированной передачи данных, используется протокол TCP.
UDP обычно используется в таких приложениях, как потоковое видео и компьютерные игры, где допускается потеря пакетов, а повторный запрос затруднён или не оправдан, либо в приложениях вида запрос-ответ (например, запросы к DNS), где создание соединения занимает больше ресурсов, чем повторная отправка.
И TCP, и UDP используют для определения протокола верхнего уровня число, называемое портом.
пятница, 14 декабря 2012 г.
Python Keyboardinterrupt
Python Threading и KeyboardInterrupt.
Как организовать пул потоков?
Допустим у вас есть 100 данных (пусть они находятся в каком-либо списке) и все их нужно переработать в 10 потоков.
За всё время работы скрипта стартует только 10 потоков. Каждый из них, отработав со своим 1 данным, не дохнет, а берет из списка следующее 1 данное и делает с ним эту же работу:
# -*- coding: utf-8 -*-
import threading
inporty Queue
import time
import traceback
class Worker(threading.Thread):
def __init__(self,queue):
threading.Thread.__init__(self)
self.__queue = queue
def run(self):
while True:
try: item = self.__queue.get_nowait() # ждём данные
except Queue.Empty: break # данные закончились, прекращаем работу
try: self.work(item) # работа с данными
except Exception: traceback.print_exc()
time.sleep(0.5)
self.__queue.task_done() # задача завершена
return
def work(self,item):
print item
#pass
def main():
# выводим в 5 потоков цифры от 1 до 100.
queue = Queue.Queue()
num_threads = 5 # 5 потоков
for x in xrange(100):
queue.put(x) # заносим данные в очередь
for i in xrange(num_threads):
t = Worker(queue) # создаем поток
t.start() # стартуем
time.sleep(0.1) # чтобы в консоли друг на друга не накладывались
queue.join() #блокируем выполнение программы, пока не будут израсходованы данные
print "Done!"
if __name__ == '__main__':
main()
Хотелось бы еще, чтобы было можно все это в любой момент остановить. Но поскольку выполнение программы блокируем с помощью queue.join(), то скрипт не обратит внимание на нажатия клавиш Ctrl+C для перерывания программы. Воспользуемся данным решением.
# -*- coding: utf-8 -*-
import threading
import Queue
import time
import traceback
class Worker(threading.Thread):
def __init__(self,queue):
threading.Thread.__init__(self)
self.__queue = queue
self.kill_received = False # флаг прекращения работы
def run(self):
while not self.kill_received:
try: item = self.__queue.get_nowait() # ждём данные
except Queue.Empty: break
try: self.work(item)
except Exception: traceback.print_exc()
time.sleep(0.5)
self.__queue.task_done() # задача завершена
self.__queue.put(item) # зациклим
return
def work(self,item):
print item
def main():
queue = Queue.Queue()
num_threads = 5 # 5 потоков
threads = []
for x in xrange(100):
queue.put(x) # заносим данные в очередь
for i in xrange(num_threads):
t = Worker(queue) # создаем нить
threads.append(t)
t.start() # стартуем
time.sleep(0.1)
# ждем пока в живых не останется только главный поток
while threading.activeCount()>1:
try:
time.sleep(1)
except KeyboardInterrupt:
print 'Ctrl+C received! Sending kill to threads...'
for t in threads:
t.kill_received = True # даем сигнал о завершении всем потокам
print "Done!"
if __name__ == '__main__':
main()
Как организовать пул потоков?
Допустим у вас есть 100 данных (пусть они находятся в каком-либо списке) и все их нужно переработать в 10 потоков.
За всё время работы скрипта стартует только 10 потоков. Каждый из них, отработав со своим 1 данным, не дохнет, а берет из списка следующее 1 данное и делает с ним эту же работу:
# -*- coding: utf-8 -*-
import threading
inporty Queue
import time
import traceback
class Worker(threading.Thread):
def __init__(self,queue):
threading.Thread.__init__(self)
self.__queue = queue
def run(self):
while True:
try: item = self.__queue.get_nowait() # ждём данные
except Queue.Empty: break # данные закончились, прекращаем работу
try: self.work(item) # работа с данными
except Exception: traceback.print_exc()
time.sleep(0.5)
self.__queue.task_done() # задача завершена
return
def work(self,item):
print item
#pass
def main():
# выводим в 5 потоков цифры от 1 до 100.
queue = Queue.Queue()
num_threads = 5 # 5 потоков
for x in xrange(100):
queue.put(x) # заносим данные в очередь
for i in xrange(num_threads):
t = Worker(queue) # создаем поток
t.start() # стартуем
time.sleep(0.1) # чтобы в консоли друг на друга не накладывались
queue.join() #блокируем выполнение программы, пока не будут израсходованы данные
print "Done!"
if __name__ == '__main__':
main()
Хотелось бы еще, чтобы было можно все это в любой момент остановить. Но поскольку выполнение программы блокируем с помощью queue.join(), то скрипт не обратит внимание на нажатия клавиш Ctrl+C для перерывания программы. Воспользуемся данным решением.
# -*- coding: utf-8 -*-
import threading
import Queue
import time
import traceback
class Worker(threading.Thread):
def __init__(self,queue):
threading.Thread.__init__(self)
self.__queue = queue
self.kill_received = False # флаг прекращения работы
def run(self):
while not self.kill_received:
try: item = self.__queue.get_nowait() # ждём данные
except Queue.Empty: break
try: self.work(item)
except Exception: traceback.print_exc()
time.sleep(0.5)
self.__queue.task_done() # задача завершена
self.__queue.put(item) # зациклим
return
def work(self,item):
print item
def main():
queue = Queue.Queue()
num_threads = 5 # 5 потоков
threads = []
for x in xrange(100):
queue.put(x) # заносим данные в очередь
for i in xrange(num_threads):
t = Worker(queue) # создаем нить
threads.append(t)
t.start() # стартуем
time.sleep(0.1)
# ждем пока в живых не останется только главный поток
while threading.activeCount()>1:
try:
time.sleep(1)
except KeyboardInterrupt:
print 'Ctrl+C received! Sending kill to threads...'
for t in threads:
t.kill_received = True # даем сигнал о завершении всем потокам
print "Done!"
if __name__ == '__main__':
main()
вторник, 11 декабря 2012 г.
Python Многопоточный клиент
Касательно активности главного потока. Когда, как вам кажется, вы запускаете ОДИН поток, фактически работает уже ДВА потока. Нужно понимать, что количество потоков, активных в данный момент равняется количеству потоков, запущенных в данный момент вами +1 поток, в котором работает основное тело приложения. Лично я стараюсь писать таким образом, чтобы четко отделять основной поток от запущенных мной. Если этого не делать, то возможно преждевременное (как вам кажется) завершение работы приложения, хотя на самом деле приложение отработает именно так, как вы его написали.
Вроде на словах понятно, теперь приступаем к практике. На практике в CPython есть такое понятние как GIL (Global Interpreter Lock). Под сим подразумевается глобальная блокировка интерпритатора в тот момент когда потоки вашего приложения обращаются к процессору. Фактически, в каждый отдельно взятый момент с процессором работает только один поток. В связи с этим максимальное количество потоков, которое вообще можно запустить в стандартном CPython колеблется в районе 350 штук.
В качестве примера будет сделана попытка реализовать многопоточный парсер www.google.com. Как я уже написал выше, для работы будут использованы исключительно стандартные модули, для выполнения задачи понадобятся модули urllib2, urllib, queue, threading, re.
По порядку:
#==================<Имортирование необходимых модулей>==================
import urllib2
#Модуль для работы с протоколом HTTP, высокоуровневый
import urllib
#Модуль для работы с протоколом HTTP, более низкоуровневый чем urllib2,
#фактически из него необходима одна функция - urllib.urlquote
from Queue import Queue
#Модуль, который представляет собой "Pool", фактически это список, в
#котором на нужных местах вставлены замки таким образом, чтобы к нему
#одновременно мог обращаться только один поток
import threading
#Модуль для работы с потоками, из него понадобится только
#threading.active_count, threading.Thread, threading.Thread.start,
#threading.Rlock
import re
#Модуль для работы с регулярными выражениями, его использование выходит
#за пределы статьи
import time
#Модуль для работы со временем, из него нужна только функция sleep
queue = Queue()
#Обязательное присваивание, нужно делать именно так (т.е. импортировать
#класс Queue из модуля Queue и инициализировать его)
#==================</Имортирование необходимых модулей>=================
#==============================<Настройки>==============================
PROXY = "10.10.31.103:3128"
#Во время написания статьи сижу за прокси-сервером, поэтому в статье
#затрагивается и этот вопрос, этой строкой обьявляется глобальная
#переменная PROXY, в которой находится адрес прокси-сервера. Для работы
#напрямую необходимо указать значение None
HEADERS = {"User-Agent" : "Opera/9.64 (Windows NT 5.1; U; en) Presto/2.1.1",
"Accept" : "text/html, application/xml;q=0.9, application/xhtml+xml, image/ png, image/jpeg, image/gif, image/x-xbitmap, */*;q=0.1",
"Accept-Language" : "ru,uk-UA;q=0.9,uk;q=0.8,en;q=0.7",
"Accept-Charset" : "iso-8859-1, utf-8, utf-16, *;q=0.1",
"Accept-Encoding" : "identity, *;q=0",
"Connection" : "Keep-Alive"}
#Для того чтобы получить страницу с www.google.com НЕОБХОДИМО использовать
#заголовки браузера, они представлены выше в ассоциативном массиве HEADERS,
#соответствуют реальным заголовкам браузера Opera с маленько модификацией, эти
#заголовки означают что клиент не может принимать zlib compressed data, т.е.
#сжатые данные - не хотел я заморачиваться еще и с разархивироанием страниц, тем
#более что не все сайты их сжимают...
THREADS_COUNT = 10
#В принципе это все настройки приложения, это-количество потоков
DEEP = 30
#Это - значение, которое отвечает за глубину страниц поиска, которые
#нужно просматривать, фактически же определяет собой количество ссылок,
#которые будут собраны сборщиком.
ENCODING = "UTF-8"
#Кодировка ваших файлов (для загрузки данных из файла с запросами и
#последующего их перевода в юникод)
#==============================</Настройки>===================================
LOCK = threading.RLock()
# Вот тут то впервые и затрагивается модуль threading
#создается обьект LOCK, который представляет собой класс threading.RLock из
#модуля threading, это -простейший замок, который запрещает исполнение
#несколькими потоками участка кода который идет после вызова его метода
#acquire() Основным отличием threading.RLock от threading.Lock (тоже класс из
#модуля threading) является то, что каждый поток может обращаться к обьекту
#threading.RLock неограниченное количество раз, обьект threading.Lock может
#вызываться каждым потоком только единожды.
Основным принципом для беспроблемной реализации многопоточности я считаю модульность кода. Не обязательно выносить используемые Вами функции в отдельные файлы или классы, достаточно чтобы хотя бы это были отдельные функции.
Сейчас я выделю работу потока в отдельную функцию, пускай она будет представлять собой некое абстрактное понятие работы. На данном этапе пока что некоторые моменты будут непонятны, но позже все это выстроится в понятный алгоритм.
def worker():
# Обьявление функции worker, входных аргументов нет
global queue
#Здесь и далее я буду обьявлять функции из глобального пространства
#имен в локальном для лучшей читабельности кода, хотя в написании
#софта такое делать строго не рекомендую (!)
while True:
#Запуск бесконечного цикла, в котором будет происходить работа
try:
#Обработка ошибок, блок try/except, когда обработается
#ошибка QueueEmpty это значит, что список задач пуст, и поток
#должен завершить свою работу
target_link = queue.get_nowait()
#Эта строчка олицетворяет собой получение задачи потоком из
#списка задач queue
except Exception, error:
#сам перехват ошибки
return
#Завершение работы функции
parsed_data = get_and_parse_page(target_link)
#Позже будет реализована функция, которая будет получать
#страницу и доставать из нее необходимые значения
if parsed_data != "ERROR":
#Проверка на то, была ли получена страница
write_to_file(parsed_data)
#Также будет реализована функция для записи собранных данных в файл
else:
queue.put(target_link)
#Если страница не была получена, то забрасываем ее обратно в queue
Главное, что нужно четко усвоить — это алгоритм работы самого потока, и что именно потоки должны обрабатывать независимо друг от друга. Итого, задачи потока очень просты — получить ссылку на страницу поиска, передать ее в функцию-обработчик, из которой вернутся ссылки на найденные сайты а также title этих сайтов, после записать ссылки и title в файл (все это будет находиться в parsed_data).
По мере работы реализовываются задачи от простейшей к сложной, поэтому на этих этапах практически не затронуты вопросы многопоточности. Далее настала очередь реализации функции, которая будет отвечать за запись в файл.
def write_to_file(parsed_data):
#Обявление функции write_to_file, аргумент –массив данных для записи
global LOCK
global ENCODING
LOCK.acquire()
#"Накидывание замка", следующий далее участок кода может выполнятся
#только одним потоком в один и тот же момент времени
with open("parsed_data.txt", "a") as out:
#Используется with statement, открывается файл parsed_data.txt с
#правами "a", что означает дозапись в конец файла, и присваиваевается
#хэндлеру на файл имя out (я так привык)
for site in parsed_data:
#Проход циклом по всем элементам parsed data, имя активного в
#данный момент элемента будет site
link, title = site[0], site[1]
#Присваивание переменным link и title значений из кортежа site
title = title.replace("<em>", "").replace("</em>", "").replace("<b>", "").replace("</b>", "")
#.replace -это замена HTML-тэгов, которые проскакивают в title и совершено не нужны
out.write(u"{link}|{title}\n".format(link=link, title=title).encode("cp1251"))
#Производится сама запись в файл, используется оператор форматирования
#строк .format, в отличие от % он поддерживает именованные аргументы, чем я и не
#преминул воспользоваться, таким образом в файл пишется строка вида:
#ссылка на сайт | title страницы\n -символ переноса строки(все это переводится
#из юникода в cp1251)
LOCK.release()
#"Отпирание" замка, в противном случае ни один из следующих
#потоков не сможет работать с этим участком кода. По-хорошему, тут тоже нужно
#сделать обработку ошибок, но это учебный пример, да и ошибка там может
#возникнуть (после добавки замка в этот участок кода) только если во время
#работы приложения выставить атрибут “только чтение” для данного пользователя
#относительно файла parsed_data.txt
Далее идет реализация функции get_and_parse_page:
def get_and_parse_page(target_link):
#Обьявление функции, аргумент – ссылка на страницу
global PROXY
#Указывает на то, что в данной функции используется переменная PROXY
#из глобального пространства имен
global HEADERS
#То же и для переменной Headers
if PROXY is not None:
#Если значение PROXY не равно None
proxy_handler = urllib2.ProxyHandler( { "http": ""+PROXY+"/" } )
#Создается Прокси-Хэндлер с указанным прокси
opener = urllib2.build_opener(proxy_handler)
#Далее создается opener c созданным ранее Прокси-Хэндлером
urllib2.install_opener(opener)
#И наконец-то он устанавливается, теперь нет необходимости в
#шаманствах, все запросы в которых будет использоваться urllib2
#(в пределах этой функции будут направляться через указанный ранее
#PROXY)
page_request = urllib2.Request(url=target_link, headers=HEADERS)
#Создается обьект Request, который олицетворяет собой Request instance,
#фактически это GET запрос к серверу с указанными параметрами, мне
#же необходимо использовать заголовки...
try:
#Обработка всех возможных ошибок, возникающих во время получения
#страницы, это нехорошо, но лучше чем полное отсутствие обработки
page = urllib2.urlopen(url=page_request).read().decode("UTF-8", "replace")
#Переменной page присваиваем прочитанное значение страницы запроса, переведенное
#в unicode из кодировки UTF-8 (кодировка, используемая на www.google.com) (в
#Python 2.6 unicode -это отдельный тип данных(!))
except Exception ,error:
#Сам перехват ошибки и сохранение ее значения в переменную error
print str(error)
#Вывод ошибки в консоль, прведварительно переведя ее в строку
#(просто на всякий случай)
return "ERROR"
#Возврат из функции в том случае, если во время работы возникла ошибка
harvested_data = re.findall(r'''\<li\ class\=g\>\<h3\ class\=r\>\<a\ href\=\"(.*?)".*?>(.*?)\<\/a\>\<\/h3\>''', page)
#Сбор со страницы поиска ссылок и title найденных страниц
#Очистка данных от результатов поиска по блогам, картинкам и др. сервисам гугла
for data in harvested_data:
#Для каждого элемента массива harvested_data присвоить ему имя data
if data[0].startswith("/"):
#Если нулевой элемент массива data(ссылка) начинается с символа /
harvested_data.remove(data)
#Удаляем его из массива harvested_data
if ".google.com" in data[0]:
#Если нулевой элемент массива data(ссылка) имеет в себе .google.com
harvested_data.remove(data)
#Также удаляем его из массива harvested_data
return harvested_data
#Возвращаем собранные значения из функции
Наконец-то дошла очередь до реализации основного тела приложения:
def main():
#Обявление функции, входных аргментов нет
print "STARTED"
#Вывод в консоль о начале процесса
global THREADS_COUNT
global DEEP
global ENCODING
#Обьявляние о том что эти переменные будут использоваться
#из глобального пространства имен
with open("requests.txt") as requests:
#Открываем файл requests в котором находятся запросы к поисковику
for request in requests:
#На данном файлхэндлере доступен итератор, поэтому можно
#пройтись по файлу циклом, без загрузки файл в оперативку, но это
#тоже не важно, я все равно его туда загружу:)
request = request.translate(None, "\r\n").decode(ENCODING, "replace")
#Очистка запроса от символов конца строки а также их
#перевод в юникод (с заменой конфликтных символов)
empty_link = "www.google.com/search?hl=ru&client=opera&rls=ru&hs=67v&q={request}&start={N}&sa=N"
#Это пустой адрес страницы поиска, отформатирован
for i in xrange(0, DEEP, 10):
#Проход итератором по диапазону #чисел от 0 до DEEP,
#который представляет собой максимальную глубину поиска с
#шагом в 10, т.е. получаем из этого диапазона только
#числа десятков, т.е. 10, 20, 30 (как идет поиск у гугла)
queue.put(empty_link.format(request=request.encode("UTF-8"), N=i))
#Добавление в очередь каждой сгенерированной ссылки
#и перевод её в кодировку UTF-8 (для гугла)
for _ in xrange(THREADS_COUNT):
#Проход циклом по диапазону чисел количества потоков
thread_ = threading.Thread(target=worker)
#Создается поток, target-имя функции, которая являет собой
#участок кода, выполняемый многопоточно
thread_.start()
#Вызывается метод start() , таким образом поток запускается
while threading.active_count() >1:
#До тех пор, пока количество активных потоков больше 1 (значит,
#запущенные потоки продолжают работу)
time.sleep(1)
#Основной поток засыпает на 1 секунду
print "FINISHED"
#Вывод в консоль о завершении работы приложения
В итоге получаем нормально работающий многопоточный парсер.
Код:
Эта статья + исходники: sendspace.com/file/mw0pac
Код с русскими коментариями: dumpz.org/15202/
Добавлю свои 5 копеек: никто не запрещает разделить задачу еще больше — сделать 3 очереди и 3 типа потоков (скачка, парсинг, сохранение). Это позволит начать обработку входных данных не дожидаясь окончательной их загрузки (если потоки запустить до начала загрузки данных в очередь) и сильнее разнести части кода (например для увеличения кол-ва разработчиков). Дальше можно нитки демонизировать, что позволит оставить их болтаться до тех пор пока не закончится основной поток. Забирать данные в этом случае можно и проще и надежнее:
в нитке остаётся только бесконечно крутить:
while True:
# забираем данные из очереди дожидаясь пока они там будут
data = queueN.get()
# делаем свое грязно дело
do_some_work(data)
# помечаем сделанную работу в очереди
queue.task_done()
в основной поток будет выглядеть примерно вот так:
queue1 = Queue()
queue2 = Queue()
queue3 = Queue()
# тип 1
for _ in range(NUMBER):
thread_ = threading.Thread(target=worker)
thread_.setDaemon(True) # для питона 2.5 (в 2.6+ есть обратносовместимый метод)
thread_.start()
# тип 2
for _ in range(NUMBER):
thread_ = threading.Thread(target=worker2)
thread_.setDaemon(True)
thread_.start()
# тип 3
for _ in range(NUMBER):
thread_ = threading.Thread(target=worker3)
thread_.setDaemon(True)
thread_.start()
# тут запихиваем в очереди данные, и по мере того как очередь будет наполнятся она начнет обрабатываться
# ждем когда закончатся все очереди
queue1.join()
queue2.join()
queue3.join()
Все, после того как третья очередь завершится основной поток выйдет и грохнет все висящие нитки.
Вроде на словах понятно, теперь приступаем к практике. На практике в CPython есть такое понятние как GIL (Global Interpreter Lock). Под сим подразумевается глобальная блокировка интерпритатора в тот момент когда потоки вашего приложения обращаются к процессору. Фактически, в каждый отдельно взятый момент с процессором работает только один поток. В связи с этим максимальное количество потоков, которое вообще можно запустить в стандартном CPython колеблется в районе 350 штук.
В качестве примера будет сделана попытка реализовать многопоточный парсер www.google.com. Как я уже написал выше, для работы будут использованы исключительно стандартные модули, для выполнения задачи понадобятся модули urllib2, urllib, queue, threading, re.
По порядку:
#==================<Имортирование необходимых модулей>==================
import urllib2
#Модуль для работы с протоколом HTTP, высокоуровневый
import urllib
#Модуль для работы с протоколом HTTP, более низкоуровневый чем urllib2,
#фактически из него необходима одна функция - urllib.urlquote
from Queue import Queue
#Модуль, который представляет собой "Pool", фактически это список, в
#котором на нужных местах вставлены замки таким образом, чтобы к нему
#одновременно мог обращаться только один поток
import threading
#Модуль для работы с потоками, из него понадобится только
#threading.active_count, threading.Thread, threading.Thread.start,
#threading.Rlock
import re
#Модуль для работы с регулярными выражениями, его использование выходит
#за пределы статьи
import time
#Модуль для работы со временем, из него нужна только функция sleep
queue = Queue()
#Обязательное присваивание, нужно делать именно так (т.е. импортировать
#класс Queue из модуля Queue и инициализировать его)
#==================</Имортирование необходимых модулей>=================
#==============================<Настройки>==============================
PROXY = "10.10.31.103:3128"
#Во время написания статьи сижу за прокси-сервером, поэтому в статье
#затрагивается и этот вопрос, этой строкой обьявляется глобальная
#переменная PROXY, в которой находится адрес прокси-сервера. Для работы
#напрямую необходимо указать значение None
HEADERS = {"User-Agent" : "Opera/9.64 (Windows NT 5.1; U; en) Presto/2.1.1",
"Accept" : "text/html, application/xml;q=0.9, application/xhtml+xml, image/ png, image/jpeg, image/gif, image/x-xbitmap, */*;q=0.1",
"Accept-Language" : "ru,uk-UA;q=0.9,uk;q=0.8,en;q=0.7",
"Accept-Charset" : "iso-8859-1, utf-8, utf-16, *;q=0.1",
"Accept-Encoding" : "identity, *;q=0",
"Connection" : "Keep-Alive"}
#Для того чтобы получить страницу с www.google.com НЕОБХОДИМО использовать
#заголовки браузера, они представлены выше в ассоциативном массиве HEADERS,
#соответствуют реальным заголовкам браузера Opera с маленько модификацией, эти
#заголовки означают что клиент не может принимать zlib compressed data, т.е.
#сжатые данные - не хотел я заморачиваться еще и с разархивироанием страниц, тем
#более что не все сайты их сжимают...
THREADS_COUNT = 10
#В принципе это все настройки приложения, это-количество потоков
DEEP = 30
#Это - значение, которое отвечает за глубину страниц поиска, которые
#нужно просматривать, фактически же определяет собой количество ссылок,
#которые будут собраны сборщиком.
ENCODING = "UTF-8"
#Кодировка ваших файлов (для загрузки данных из файла с запросами и
#последующего их перевода в юникод)
#==============================</Настройки>===================================
LOCK = threading.RLock()
# Вот тут то впервые и затрагивается модуль threading
#создается обьект LOCK, который представляет собой класс threading.RLock из
#модуля threading, это -простейший замок, который запрещает исполнение
#несколькими потоками участка кода который идет после вызова его метода
#acquire() Основным отличием threading.RLock от threading.Lock (тоже класс из
#модуля threading) является то, что каждый поток может обращаться к обьекту
#threading.RLock неограниченное количество раз, обьект threading.Lock может
#вызываться каждым потоком только единожды.
Основным принципом для беспроблемной реализации многопоточности я считаю модульность кода. Не обязательно выносить используемые Вами функции в отдельные файлы или классы, достаточно чтобы хотя бы это были отдельные функции.
Сейчас я выделю работу потока в отдельную функцию, пускай она будет представлять собой некое абстрактное понятие работы. На данном этапе пока что некоторые моменты будут непонятны, но позже все это выстроится в понятный алгоритм.
def worker():
# Обьявление функции worker, входных аргументов нет
global queue
#Здесь и далее я буду обьявлять функции из глобального пространства
#имен в локальном для лучшей читабельности кода, хотя в написании
#софта такое делать строго не рекомендую (!)
while True:
#Запуск бесконечного цикла, в котором будет происходить работа
try:
#Обработка ошибок, блок try/except, когда обработается
#ошибка QueueEmpty это значит, что список задач пуст, и поток
#должен завершить свою работу
target_link = queue.get_nowait()
#Эта строчка олицетворяет собой получение задачи потоком из
#списка задач queue
except Exception, error:
#сам перехват ошибки
return
#Завершение работы функции
parsed_data = get_and_parse_page(target_link)
#Позже будет реализована функция, которая будет получать
#страницу и доставать из нее необходимые значения
if parsed_data != "ERROR":
#Проверка на то, была ли получена страница
write_to_file(parsed_data)
#Также будет реализована функция для записи собранных данных в файл
else:
queue.put(target_link)
#Если страница не была получена, то забрасываем ее обратно в queue
Главное, что нужно четко усвоить — это алгоритм работы самого потока, и что именно потоки должны обрабатывать независимо друг от друга. Итого, задачи потока очень просты — получить ссылку на страницу поиска, передать ее в функцию-обработчик, из которой вернутся ссылки на найденные сайты а также title этих сайтов, после записать ссылки и title в файл (все это будет находиться в parsed_data).
По мере работы реализовываются задачи от простейшей к сложной, поэтому на этих этапах практически не затронуты вопросы многопоточности. Далее настала очередь реализации функции, которая будет отвечать за запись в файл.
def write_to_file(parsed_data):
#Обявление функции write_to_file, аргумент –массив данных для записи
global LOCK
global ENCODING
LOCK.acquire()
#"Накидывание замка", следующий далее участок кода может выполнятся
#только одним потоком в один и тот же момент времени
with open("parsed_data.txt", "a") as out:
#Используется with statement, открывается файл parsed_data.txt с
#правами "a", что означает дозапись в конец файла, и присваиваевается
#хэндлеру на файл имя out (я так привык)
for site in parsed_data:
#Проход циклом по всем элементам parsed data, имя активного в
#данный момент элемента будет site
link, title = site[0], site[1]
#Присваивание переменным link и title значений из кортежа site
title = title.replace("<em>", "").replace("</em>", "").replace("<b>", "").replace("</b>", "")
#.replace -это замена HTML-тэгов, которые проскакивают в title и совершено не нужны
out.write(u"{link}|{title}\n".format(link=link, title=title).encode("cp1251"))
#Производится сама запись в файл, используется оператор форматирования
#строк .format, в отличие от % он поддерживает именованные аргументы, чем я и не
#преминул воспользоваться, таким образом в файл пишется строка вида:
#ссылка на сайт | title страницы\n -символ переноса строки(все это переводится
#из юникода в cp1251)
LOCK.release()
#"Отпирание" замка, в противном случае ни один из следующих
#потоков не сможет работать с этим участком кода. По-хорошему, тут тоже нужно
#сделать обработку ошибок, но это учебный пример, да и ошибка там может
#возникнуть (после добавки замка в этот участок кода) только если во время
#работы приложения выставить атрибут “только чтение” для данного пользователя
#относительно файла parsed_data.txt
Далее идет реализация функции get_and_parse_page:
def get_and_parse_page(target_link):
#Обьявление функции, аргумент – ссылка на страницу
global PROXY
#Указывает на то, что в данной функции используется переменная PROXY
#из глобального пространства имен
global HEADERS
#То же и для переменной Headers
if PROXY is not None:
#Если значение PROXY не равно None
proxy_handler = urllib2.ProxyHandler( { "http": ""+PROXY+"/" } )
#Создается Прокси-Хэндлер с указанным прокси
opener = urllib2.build_opener(proxy_handler)
#Далее создается opener c созданным ранее Прокси-Хэндлером
urllib2.install_opener(opener)
#И наконец-то он устанавливается, теперь нет необходимости в
#шаманствах, все запросы в которых будет использоваться urllib2
#(в пределах этой функции будут направляться через указанный ранее
#PROXY)
page_request = urllib2.Request(url=target_link, headers=HEADERS)
#Создается обьект Request, который олицетворяет собой Request instance,
#фактически это GET запрос к серверу с указанными параметрами, мне
#же необходимо использовать заголовки...
try:
#Обработка всех возможных ошибок, возникающих во время получения
#страницы, это нехорошо, но лучше чем полное отсутствие обработки
page = urllib2.urlopen(url=page_request).read().decode("UTF-8", "replace")
#Переменной page присваиваем прочитанное значение страницы запроса, переведенное
#в unicode из кодировки UTF-8 (кодировка, используемая на www.google.com) (в
#Python 2.6 unicode -это отдельный тип данных(!))
except Exception ,error:
#Сам перехват ошибки и сохранение ее значения в переменную error
print str(error)
#Вывод ошибки в консоль, прведварительно переведя ее в строку
#(просто на всякий случай)
return "ERROR"
#Возврат из функции в том случае, если во время работы возникла ошибка
harvested_data = re.findall(r'''\<li\ class\=g\>\<h3\ class\=r\>\<a\ href\=\"(.*?)".*?>(.*?)\<\/a\>\<\/h3\>''', page)
#Сбор со страницы поиска ссылок и title найденных страниц
#Очистка данных от результатов поиска по блогам, картинкам и др. сервисам гугла
for data in harvested_data:
#Для каждого элемента массива harvested_data присвоить ему имя data
if data[0].startswith("/"):
#Если нулевой элемент массива data(ссылка) начинается с символа /
harvested_data.remove(data)
#Удаляем его из массива harvested_data
if ".google.com" in data[0]:
#Если нулевой элемент массива data(ссылка) имеет в себе .google.com
harvested_data.remove(data)
#Также удаляем его из массива harvested_data
return harvested_data
#Возвращаем собранные значения из функции
Наконец-то дошла очередь до реализации основного тела приложения:
def main():
#Обявление функции, входных аргментов нет
print "STARTED"
#Вывод в консоль о начале процесса
global THREADS_COUNT
global DEEP
global ENCODING
#Обьявляние о том что эти переменные будут использоваться
#из глобального пространства имен
with open("requests.txt") as requests:
#Открываем файл requests в котором находятся запросы к поисковику
for request in requests:
#На данном файлхэндлере доступен итератор, поэтому можно
#пройтись по файлу циклом, без загрузки файл в оперативку, но это
#тоже не важно, я все равно его туда загружу:)
request = request.translate(None, "\r\n").decode(ENCODING, "replace")
#Очистка запроса от символов конца строки а также их
#перевод в юникод (с заменой конфликтных символов)
empty_link = "www.google.com/search?hl=ru&client=opera&rls=ru&hs=67v&q={request}&start={N}&sa=N"
#Это пустой адрес страницы поиска, отформатирован
for i in xrange(0, DEEP, 10):
#Проход итератором по диапазону #чисел от 0 до DEEP,
#который представляет собой максимальную глубину поиска с
#шагом в 10, т.е. получаем из этого диапазона только
#числа десятков, т.е. 10, 20, 30 (как идет поиск у гугла)
queue.put(empty_link.format(request=request.encode("UTF-8"), N=i))
#Добавление в очередь каждой сгенерированной ссылки
#и перевод её в кодировку UTF-8 (для гугла)
for _ in xrange(THREADS_COUNT):
#Проход циклом по диапазону чисел количества потоков
thread_ = threading.Thread(target=worker)
#Создается поток, target-имя функции, которая являет собой
#участок кода, выполняемый многопоточно
thread_.start()
#Вызывается метод start() , таким образом поток запускается
while threading.active_count() >1:
#До тех пор, пока количество активных потоков больше 1 (значит,
#запущенные потоки продолжают работу)
time.sleep(1)
#Основной поток засыпает на 1 секунду
print "FINISHED"
#Вывод в консоль о завершении работы приложения
В итоге получаем нормально работающий многопоточный парсер.
Код:
Эта статья + исходники: sendspace.com/file/mw0pac
Код с русскими коментариями: dumpz.org/15202/
Добавлю свои 5 копеек: никто не запрещает разделить задачу еще больше — сделать 3 очереди и 3 типа потоков (скачка, парсинг, сохранение). Это позволит начать обработку входных данных не дожидаясь окончательной их загрузки (если потоки запустить до начала загрузки данных в очередь) и сильнее разнести части кода (например для увеличения кол-ва разработчиков). Дальше можно нитки демонизировать, что позволит оставить их болтаться до тех пор пока не закончится основной поток. Забирать данные в этом случае можно и проще и надежнее:
в нитке остаётся только бесконечно крутить:
while True:
# забираем данные из очереди дожидаясь пока они там будут
data = queueN.get()
# делаем свое грязно дело
do_some_work(data)
# помечаем сделанную работу в очереди
queue.task_done()
в основной поток будет выглядеть примерно вот так:
queue1 = Queue()
queue2 = Queue()
queue3 = Queue()
# тип 1
for _ in range(NUMBER):
thread_ = threading.Thread(target=worker)
thread_.setDaemon(True) # для питона 2.5 (в 2.6+ есть обратносовместимый метод)
thread_.start()
# тип 2
for _ in range(NUMBER):
thread_ = threading.Thread(target=worker2)
thread_.setDaemon(True)
thread_.start()
# тип 3
for _ in range(NUMBER):
thread_ = threading.Thread(target=worker3)
thread_.setDaemon(True)
thread_.start()
# тут запихиваем в очереди данные, и по мере того как очередь будет наполнятся она начнет обрабатываться
# ждем когда закончатся все очереди
queue1.join()
queue2.join()
queue3.join()
Все, после того как третья очередь завершится основной поток выйдет и грохнет все висящие нитки.
Подписаться на:
Сообщения (Atom)