Python, Sqlite и GooBot за ubuntu-bg

март 2nd, 2010 | Tags: , , , , ,

От доста време не бях писал. Бях си обещал да пиша повече, но имам чувството, че точно след като дадох обещанието спрях и да пиша. Всъщност напоследък и задачките станаха повечко, но за това в друг пост. Сега ми се щеше просто да споделя няколко трика, които се оказаха доста полезни в писането на goobot – или, може би, по-точно дописването му.

GooBot е идеята да бъде създаден един сравнително прост, но същевременно и функционален irc-бот за канала на българската Убунту общност. Готови решение, разбира се, също имаше, но или бяха прекалено сложни, с модули и незнам още какви безполезни функции, или пък не работеха. Основното желание, което имах беше да бъде написан на Python, просто защото това е езика, на който ми харесва да пиша скриптове и приложения и в който се чувствам свободен. По-добре да не се отклонявам от темата (кратка пауза). Идеята да напиша бота от нищото все още не е изчезнала, но може би за сега не настъпил точният момент. Та ровейки се из google (къде ли другаде?!) се натъкнах на ub0tu.py – разработен от Abhinay Omkar. Бота е събран в един файл и първоначално предлага доста интересни функции, като търсене в google и delicious, проверка на описанието на Debian пакети, отговор на команди от канала. Последната промяна обаче на самия бот беше от 2007-ма година, с което част от функциите просто не работеха. Е преглътнах липсата им – за какво ти е бот, който ти показва резултати от google след като можеш да си отвориш браузъра и да провериш? И така започна едно бавно редактиране, заменяне на функции, добавяне на такива. Бяха му добавени следните функции:

  • Записване на дефиниции в sqlite3 база данни
  • Записване на лог във файлове по дата и в базата данни на бота
  • Забавна команда @women, която изважда смешни афоризми за жените (самия текст за това взех от една бележка на phreaky във facebook).

За да не задълбавам, какво беше толкова променено или как точно бота върши своята работа. Ще се спра на няколко ключови момента и проблеми, с които се сблъсках и които благодарение на безрезервната помощ на dejuren успях да реша.

От прост текст към база данни

Това се отнася за функцията, която при подадена команда @women от потребител в канала, бота изважда произволен запис от базата данни. Дойде като идея, след като във facebook, phreaky беше публикувал една бележка с „Афоризми за жените“ – общо 150 такива на адрес: http://is.gd/9w6cj можете да видите съдържанието в суров вариант. Така както може да се види във суровия вариант това са 150*2 реда започващи като всеки нечетен ред започва с номер и всеки четен е празен. Идеята да да отворя файла и да го променя на ръка не ме блазнеше, за това трябваше да си създам някакъв скрипт, който да свърши тази работа вместо мен. Първият скрипт просто премахна празния ред:

def main():
	file = open('newtexts', 'w')
	fp = codecs.open('texts', 'r', 'utf-8')
	for line in fp:
		if line == "\n" :
			pass
		else:
			file.write(line.encode('utf-8'))
	file.close()
	fp.close()
 
if __name__ == '__main__': main()

Всичко добре, но сами виждате, че не е особено умен. За какво ми е да маха само празните редове? И така записването беше разширено до:

file.write(line.split(": ")[1].encode('utf-8))

Единствената добавка split(": ") разделя текста на две: номер и текст във вид на масив и ние избираме само втория елемент – текста, който и записваме във файла.

До тук всичко изглежда добре, имаме нов файл, който няма номера пред всеки ред и няма празни редове. Тук ми дойде идеята да запиша всичко в MySQL база данни. Нямам идея защо точно в такава. В последствие се оказа глупаво решение, тъй като бота използва sqlite база данни и стигнах до проблема, че бота използва две бази данни и то за неща, които няма смисъл да са разделени. Хубавото е, че отново се зарадвах на python модулите, които са изключително лесни за ползване. В общи линии без значение каква база данни ще използвате, синтаксиса си остава приблизително същия от към методи. За съжаление самия скрипт, който превърна всичко от редове в текстов файл в записи в базата го загубих, но пък мисля, че ще схванете принципа:

  1. Отваряте текстовия файл за четене и се свързвате към базата данни
  2. Създавате for – цикъл, който да прочита ред и да го записва в базата данни
  3. След приключване на цикъла просто затваряте файла и връзката към базата данни

Ако се вгледате решението за конвертиране на базата данни от MySQL към Sqlite3 не е по-различно от записването на редовете от текстовия файл в редове в базата данни. Единственото, което не трябва да забравите е в цикъла да определите диапазон (в моя случай от 0 до 149 за 150 записа), иначе цикъла може да стане малко безкраен:

import codecs
import MySQLdb
from pysqlite2 import dbapi2 as sqlite
 
def main():
	try:
		conn = MySQLdb.connect(host = 'localhost', user = 'root', passwd = '*****', db = 'women')
	except MySQLdb.Error, e:
		print "Error %d: %s" % (e.args[0], e.args[1])
		sys.exit(1)
	try:
		c = conn.cursor()
		ct = sqlite.connect('goobot.db')
		cursor = ct.cursor()
		for i in range(1, 150):
			c.execute("""SELECT lafs FROM texts WHERE id = %i""" % i)
			row = c.fetchone()
			print row
			cursor.execute("INSERT INTO women(womenfact) VALUES('%s')" % (row[0].split('. ')[1].decode('utf-8')))
			ct.commit()
		cursor.close()
		c.close()
	except MySQLdb.Error, e:
		print "Error %d: %s" % (e.args[0], e.args[1])
		sys.exit(1)
 
if __name__ == '__main__': main()

Вероятно забелязвате, че всеки път добавям модула codecs. Той е особено полезен ако боравите с кирилица и искате всичко да ви бъде в подходящ енкодинг – utf-8. Това, което може би ще ви трябва за базата данни е създаването й:

CREATE TABLE women (id INTEGER PRIMARY KEY, womenfact TEXT)

Логовете и качването им на сървър чрез FTP

Логовете са важна част от един бот или поне от един бот, който трябва да служи на една общност с цел помощ на потребителите. За това бота е настроен когато получи съобщение, което е PRIVMSG и като подател е канала, в който е бота, и името на потребителя пратил съобщението е различно от това на бота, той записва това съобщение в текстов файл с име – днешната дата. От мързел изневерих на принципа за пригледност и простота заложени в Zen of Python и си позволих да направя всичко това в един ред:

codecs.open('%s.txt' % time.strftime("%d_%b_%Y"),'a', encoding='utf-8').write(time.strftime("[%H:%M:%S]")+'\t'+"<"+line.split(":")[1].split("!")[0]+"> "+line.split('#ubuntu-bg')[1][2:].decode('utf-8')+'\n')

С това имаме лог вече лог във формат DD_MM_YYYY.txt, и тъй като сме заложи всеки път името на файла да е съответстващо на датата, в момента в който на системата датата се промени, то и файла, в който пише бота също ще бъде сменен.

След като вече имаме лог е добре този лог да бъде достъпен за публиката. Най-добрия начин за това би било да качваме готовите файлове на сървър. Най-разпространения вариант е през FTP. Но защо да бъде ръчно. Разбира се така и така е един файл на ден и бихте могли да го правите, но пък защо, след като можете да автоматизирате процеса. Тук както вече сигурно сте предположили идва отново вградена библиотека в python – ftplib. С нейна помощ можем да се свържем с FTP сървър, да изпълним команди на него и да качим файл. Всъщност функциите са много, но ще се спрем на краткия вариант – като ви покажа скрипта, който качва новите логове на сървъра:

#!/usr/bin/env python
 
import ftplib
import time
 
today = "%s.txt" % time.strftime("%d_%b_%Y")
 
try:
	s = ftplib.FTP('ftp.primer.com', 'user1', '******')
	f = open("/pyt/do/direktoria/%s" % today, 'rb')
	s.cwd('/pyt/kydeto/da_se_zapishat/irc_logs')
	s.storbinary('STOR %s' % today, f)
	f.close()
	s.quit()
except:
	print "Error"

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

До тук изглежда всичко автоматично, но не е. До тук просто направихме скрипт, който да ни спести стартирането на FileZilla и качването на файла ръчно. И не бихме могли да говорим за автоматизация. Истинското автоматизиране постигаме с cron. За което изключително благодаря на dejuren от #ubuntu-bg, който ми помогна, за да разбера принципа.

Първото нещо, което правим е да стартираме crontab с параметър -e. В случай, че нямате настроен редактор по подразбиране (като мен), програмата ще поиска да изберете желан от вас конзолен редактор. След което добавям следния запис:

5 23 * * * cd /pyt/do/log/direktoriqta/; ./daily.py

Приемам, че скрипта daily.py е във същата директория, в която се намират и логовете ви. Записът означава следното: В 23:05 влез в директорията с логовете и изпълни daily.py. Защо обаче в 23:05, а не в 00:00? Ми причината за това идва от самия бот, който е настроен да работи с времевата зона в България, докато моята времева зона е с 1 час назад. Съответно и логовете се записват спрямо българското време, но cron трябва да се изпълни според времевата зона на сървъра.

Надявам се тази първа публикация за 2010 да е била полезна на читателите и също така се надявам да не е последната за годината.

  1. Емил Баикански
    септември 9th, 2010 at 12:52
    Reply | Quote | #1

    Здравейте,
    Аз съм „emilbal“, става въпрос за никотинова течност и
    Ел. шокове:
    Автоматичен сгъваема палка TW-09 един брой 1
    Ел. шок палка 652мм. jsj – 911 три броя 3
    Ел. шок TW – 10 два броя 2

    Адресът ми е :
    София,
    код: 1202
    бул.”Мария-Луиза” 125 вх.В ап.86
    Емил Балкански
    Тел:02 832-57-04 0888/967831
    Успешен бизнес: emilbal