Python класс для работы c sape.ru
Представляю вам python класс для работы с sape.ru
Как будет время, оформлю всё красивым модулём с тестами, а пока голый код. Его работоспособность доказана практикой т.е. ссылки показываются, деньги капают ) Скрипт был написан путём анализа официальных php и perl версий.
Собственно, код:
Для джанговодов полезным окажется следующий inclusion tag:
Большая просьба: если вы будете использовать сей класс в своих программах и найдёте ошибку, напишите, пожалуйста, на lizendir@gmail.com или в комментарии этой записи.
UPD: обновил код скрипта и тэгов, теперь links.db хранится в каталоге sape.links в корне проекта
Как будет время, оформлю всё красивым модулём с тестами, а пока голый код. Его работоспособность доказана практикой т.е. ссылки показываются, деньги капают ) Скрипт был написан путём анализа официальных php и perl версий.
Собственно, код:
from os import path
import os
import urllib
import pickle
import fcntl
import time
import re
class Sape(object):
"""
Class for working with sape.ru
"""
def __init__(self,**kwargs):
"""
Required arguments:
user - sape account ID
host - hostname of the website
dir - directory for storing sape folder
uri - requested uri
Optional arguments:
charset - character set of resulting html, if None return in unicode
sape_server - server for requests
cache_lifetime - links db older than this value should be
refreshed
cache_reloadtime - minimal time between requests to sape server
"""
self.user = kwargs['user']
self.host = kwargs['host']
if -1 != self.host.find('www.'):
self.host = self.host[4:]
self.dir = kwargs['dir']
self.uri = kwargs['uri']
prefix = re.sub(r'(?i)[^-a-z0-9]','_',self.host)
self.file = '%s/sape.links/%s.links.db' % (
self.dir,prefix)
self.links_delimiter = ''
self.error = None
self.charset = None
self.sape_server = 'dispenser-01.sape.ru'
self.cache_lifetime = 3600
self.cache_reloadtime = 600
for key in ['charset','sape_server','cache_lifetime',\
'cache_reloadtime']:
if kwargs.has_key(key):
setattr(self,key,kwargs[key])
self.links = self.loadLinks()
#print 'self.links.keys():'
#print '\n'.join(self.links.keys())
#print 'Uri: `%s`' % self.uri
if self.links.has_key(self.uri):
print 'yes'
self.page_links = self.links[self.uri]
elif self.links.has_key('__sape_new_url__') and \
len(self.links['__sape_new_url__']):
print 'no'
self.page_links = self.links['__sape_new_url__']
else:
self.page_links = []
def loadLinks(self):
"""Get links from database"""
if not path.exists(self.file):
try:
open(self.file,'w').write('')
except IOError:
self.error = 'Could not open file %s for writing' %\
self.file
return {}
if path.getmtime(self.file) < time.time() - self.cache_lifetime or\
0 == path.getsize(self.file):
os.utime(self.file,None)
data = self.fetchLinks()
if data is None:
return {}
links = self.parseLinks(data)
# __sape_new_url__ should be in the parsed links
if links['__sape_new_url__']:
self.writeToFile(self.file,links)
else:
self.error = 'Network error. Incorrect data.'
return {}
if path.getsize(self.file):
links = self.readFromFile(self.file)
else:
links = {}
if links.has_key('__sape_delimiter__'):
self.links_delimiter = links['__sape_delimiter__']
return links
def parseLinks(self,data):
"""Parse links from data retrieved from sape server"""
links = {}
data = data.decode('windows-1251')
data = data.split('\n',2)
links['__sape_delimiter__'] = data[1]
for block in data[2].split('\n'):
slices = block.split('||SAPE||')
links[slices[0]] = slices[1:]
return links
def returnLinks(self, number=None, join=True):
"""Return html code"""
if self.page_links:
if not number or number > len(self.page_links):
number = len(self.page_links)
data = self.page_links[0:number]
del self.page_links[0:number]
if join:
data = self.links_delimiter.join(data)
else:
if join:
data = u''
else:
data = []
if self.charset:
if isinstance(data, list):
for iter in data:
iter = iter.encode(self.charset)
else:
data = data.encode(self.charset)
return data
def writeToFile(self,filename,obj):
"""Lock file and write into it"""
data = pickle.dumps(obj)
f = open(filename,'w')
fcntl.lockf(f,fcntl.LOCK_EX)
f.write(data)
fcntl.lockf(f,fcntl.LOCK_UN)
f.close()
def readFromFile(self,filename):
"""Lock file and read it"""
f = open(filename,'r')
fcntl.lockf(f,fcntl.LOCK_SH)
data = f.read()
fcntl.lockf(f,fcntl.LOCK_UN)
f.close()
try:
data = pickle.loads(data)
except:
data = ''
return data
def fetchLinks(self):
"""Fetch links from sape server"""
url = 'http://%s/code.php?user=%s&host=%s&charset=windows-1251&as_txt=true' % (
self.sape_server,self.user,self.host)
print url
try:
data = urllib.urlopen(url).read()
except IOError, err:
self.error = 'Network error: %s' % err
return None
return data
Для джанговодов полезным окажется следующий inclusion tag:
from django import template
from django.conf import settings
from sape import Sape
register = template.Library()
def sape_init(request, guess_host):
uri = '%s%s%s' % (
request.path,
len(request.META['QUERY_STRING']) and '?' or '',
request.META['QUERY_STRING'])
if guess_host:
host = request.META['HTTP_HOST']
else:
host = settings.DOMAIN
sape = Sape(
user=settings.SAPE_USER,
host=host,
uri=uri,
dir=settings.SAPE_DIR)
return sape
@register.simple_tag
def sape_links(request, guess_host=False):
sape = sape_init(request, guess_host)
return sape.returnLinks()
@register.simple_tag
def sape_links_list(request, guess_host=False):
sape = sape_init(request, guess_host)
return '\n'.join([u'<li>%s</li>' % x for x in sape.returnLinks(join=False)])
Большая просьба: если вы будете использовать сей класс в своих программах и найдёте ошибку, напишите, пожалуйста, на lizendir@gmail.com или в комментарии этой записи.
UPD: обновил код скрипта и тэгов, теперь links.db хранится в каталоге sape.links в корне проекта



















Comments
спасибо сейчас буду пробовать
Добавьте, пожалуйста, проверку существования в settings.py нужных констант.
Ага, завтра-сегодня сделаю. Вынесу в отдельное application в проекте http://dja.pydev.ru
в sape появилась возможность вставлять контекстные ссылки.
апдейт вашего класса планируется?
Спасибо за код!
Очень даже планируется, ибо сам хочу эту фичу попробовать ) К сожалению, о сроках точно не могу сказат, как бог на душу положит. Думаю, где-то в феврале это произойдёт )
Я сейчас, кстати, делаю рефакторинг кода, уж больно он страшный )
Вот меня, честно говоря, смущает этот кусок: uri = ‘%s%s%s’ % (
не всегда у request.META[‘QUERY_STRING’] можно взять длину.
Почему? Потому что его вообще может не быть или как?
ну да, то есть получается что-то вроде len(None). TypeError: len() of unsized object
Ок, учту. Уже почти доделал рефакторинг кода )
а когда примерно можно ждать контекстные ссылки?
Как только так сразу.
кхм..ну как там? :)
Выложите лучше код отдельным файлом
Читайте более поздные сообщения — я давал ссылку на репозиторий кода.
Спасибо за код, пригодился.