class KinoPoisk: """ API: scraper - скрапер movie - профайл фильма search - поиск фильма best - поиск лучших фильмов person - поиск персон work - информация о работах персоны """ def __init__(self): self.cache = Cache('kinopoisk.db') self.html = Clear() self.http = HTTP() self.headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; rv:10.0.2) Gecko/20100101 Firefox/10.0.2', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'ru-ru,ru;q=0.8,en-us;q=0.5,en;q=0.3', 'Cache-Control': 'no-cache', 'Referer': 'http://www.kinopoisk.ru/level/7/' } # API def scraper(self, name, year=None, trailer_quality=None): try: tag = 'scraper:' + urllib.quote_plus(name.encode('windows-1251')) except: return None else: if year: tag += ':' + str(year) id = self.cache.get(tag, self._scraper, name, year) if not id: return None return self.movie(id, trailer_quality) def movie(self, id, trailer_quality=None): id = str(id) if trailer_quality is None: trailer_quality = 6 movie = self.cache.get('movie:' + id, self._movie, id) if not movie: return None if movie['trailers']: # компилируем список с нужным нам качеством video = [] for m in movie['trailers']: url = [x for x in m['video'] if x[0] <= trailer_quality] if url: m['video'] = url[-1] video.append(m) movie['trailers'] = video if movie['trailers']: # готовим главный трейлер r = [x for x in movie['trailers'] if x['trailer']] if r: movie['info']['trailer'] = r[0]['video'][1] else: # если трейлер не найден, то отдаем что попало... movie['info']['trailer'] = movie['trailers'][0]['video'][1] return movie def search(self, name, trailer_quality=None): return self._search_movie(name) def best(self, **kwarg): page = kwarg.get('page', 1) limit = kwarg.get('limit', 50) url = 'http://www.kinopoisk.ru/top/navigator/m_act%5Bis_film%5D/on/m_act%5Bnum_vote%5D/' + str( kwarg.get('votes', 100)) + '/' if kwarg.get('dvd'): url += 'm_act%5Bis_dvd%5D/on/' if kwarg.get('decade'): url += 'm_act%5Bdecade%5D/' + str(kwarg['decade']) + '/' if kwarg.get('genre'): url += 'm_act%5Bgenre%5D/' + str(GENRE[kwarg['genre']]) + '/' if kwarg.get('country'): url += 'm_act%5Bcountry%5D/' + str(kwarg['country']) + '/' if kwarg.get('rate'): url += 'm_act%5Brating%5D/' + str(kwarg['rate']) + ':/' if kwarg.get('mpaa'): url += 'm_act%5Bmpaa%5D/' + str(kwarg['mpaa']) + '/' url += 'perpage/' + str(limit) + '/order/ex_rating/' if page > 1: url += 'page/' + str(page) + '/' response = self.http.fetch(url, headers=self.headers) if response.error: return None res = {'pages': (1, 0, 1, 0), 'data': []} r = re.compile('<div class="pagesFromTo(.+?)<div class="pagesFromTo', re.U | re.S).search( response.body.decode('windows-1251')) if r: body = r.group(1) # compile pagelist p = re.compile('>([0-9]+)—[0-9]+[^0-9]+?([0-9]+)', re.U).search(body) if p: page = (int(p.group(1)) - 1) / limit + 1 total = int(p.group(2)) pages = total / limit if limit * pages != total: pages += 1 res['pages'] = (pages, 0 if page == 1 else page - 1, page, 0 if page == pages else page + 1) # end compile for id in re.compile('<div id="tr_([0-9]+)"', re.U | re.S).findall(body): res['data'].append(int(id)) return res def person(self, name): #response = self.http.fetch('https://www.kinopoisk.ru/index.php?level=7&from=forma&result=adv&m_act%5Bfrom%5D=forma&m_act%5Bwhat%5D=actor&m_act%5Bfind%5D=' + urllib.quote_plus(name.encode('windows-1251')), headers=self.headers) response = self.http.fetch( 'http://www.kinopoisk.ru/s/type/people/list/1/find/' + urllib.quote_plus(name.encode('windows-1251')) + '/order/relevant/', headers=self.headers) if response.error: return None res = [] body = re.compile( '<div class="navigator">(.+?)<div class="navigator">', re.U | re.S).search(response.body.decode('windows-1251')) if body: for block in re.compile('<p class="pic">(.+?)<div class="clear">', re.U | re.S).findall(body.group(1)): id, name, original, year, poster = None, None, None, None, None r = re.compile( '<p class="name"><a href="/name/([0-9]+)[^>]+>([^<]+)</a>', re.U | re.S).search(block) if r: id = r.group(1) name = r.group(2).strip() if id and name: r = re.compile('<span class="gray">([^<]+)</span>', re.U | re.S).search(block) if r: original = r.group(1).strip() if not original: original = None r = re.compile('<span class="year">([0-9]{4})</span>', re.U | re.S).search(block) if r: year = int(r.group(1)) if block.find('no-poster.gif') == -1: poster = 'http://st.kinopoisk.ru/images/actor/' + id + '.jpg' res.append({ 'id': int(id), 'name': name, 'originalname': original, 'year': year, 'poster': poster }) return {'pages': (1, 0, 1, 0), 'data': res} def work(self, id): response = self.http.fetch('http://www.kinopoisk.ru/name/' + str(id) + '/', headers=self.headers) if response.error: return None res = {} r = re.compile('id="sort_block">(.+?)<div id="block_right"', re.U | re.S).search(response.body.decode('windows-1251')) if r: for block in r.group(1).split( u'<tr><td colspan="3" class="specializationBox')[1:]: work = None for w in ('actor', 'director', 'writer', 'producer', 'producer_ussr', 'composer', 'operator', 'editor', 'design', 'voice', 'voice_director'): if block.find(u'id="' + w + u'"') != -1: work = 'producer' if w == 'producer_ussr' else w break if work: movies = [] for id, name in re.compile( '<span class="name"><a href="/film/([0-9]+)/[^>]+>([^<]+?)</a>', re.U).findall(block): for tag in (u'(мини-сериал)', u'(сериал)'): if name.find(tag) != -1: break else: movies.append(int(id)) if movies: res.setdefault(work, []).extend(movies) return res def review(self, id, query): query_s = 'all' if query == 'stat' else query data = self.cache.get('review:' + str(id) + ':' + query_s, self._review, id, query_s) if not data: return data return data[query] def countries(self): return COUNTRIES def country(self, id, default=None): country = [x[1] for x in COUNTRIES if x[0] == id] return country[0] if country else default # PRIVATE def _search_movie(self, name, year=None): url = 'http://www.kinopoisk.ru/s/type/film/list/1/find/' + urllib.quote_plus( name.encode('windows-1251')) + '/order/relevant' if year: url += '/m_act%5Byear%5D/' + str(year) url += '/m_act%5Btype%5D/film/' response = self.http.fetch(url, headers=self.headers) if response.error: return None res = [] r = re.compile('<div class="navigator">(.+?)<div class="navigator">', re.U | re.S).search( response.body.decode('windows-1251')) if r: for id in re.compile( '<p class="name"><a href="/level/1/film/([0-9]+)', re.U | re.S).findall(r.group(1)): res.append(int(id)) return {'pages': (1, 0, 1, 0), 'data': res} def _scraper(self, name, year): timeout = True # если фильм свежий, то кладем в кэш НЕ на долго (могут быть обновления на сайте) if year and year >= time.gmtime(time.time()).tm_year: timeout = 7 * 24 * 60 * 60 #week ids = self._search_movie(name, year) if ids is None: return False, None elif not ids['data']: # сохраняем пустой результат на 3-е суток return 259200, None else: return timeout, ids['data'][0] def _review(self, id, query): url = 'http://www.kinopoisk.ru/film/' + str(id) + '/ord/rating/' if query in ('good', 'bad', 'neutral'): url += 'status/' + query + '/' url += 'perpage/200/' response = self.http.fetch(url, headers=self.headers) if response.error: return False, None html = response.body.decode('windows-1251') res = { 'stat': { 'all': 0, 'good': 0, 'bad': 0, 'neutral': 0 }, query: [] } r = re.compile('<ul class="resp_type">(.+?)</ul>', re.U | re.S).search(html) if r: ul = r.group(1) for q, t in (('pos', 'good'), ('neg', 'bad'), ('neut', 'neutral')): r = re.compile( '<li class="' + q + '"><a href="[^>]+>[^<]+</a><b>([0-9]+)</b></li>', re.U).search(ul) if r: res['stat'][t] = int(r.group(1)) res['stat']['all'] = res['stat']['good'] + res['stat'][ 'bad'] + res['stat']['neutral'] r = re.compile('<div class="navigator">(.+?)<div class="navigator">', re.U | re.S).search(html) if r: for block in r.group(1).split('itemprop="reviews"'): review = { 'nick': None, 'count': None, 'title': None, 'review': None, 'time': None } r = re.compile('itemprop="reviewBody">(.+?)</div>', re.U | re.S).search(block) if r: text = r.group(1) for tag1, tag2 in ((u'<=end=>', u'\n'), (u'<b>', u'[B]'), (u'</b>', u'[/B]'), (u'<i>', u'[I]'), (u'</i>', u'[/I]'), (u'<u>', u'[U]'), (u'</u>', u'[/U]')): text = text.replace(tag1, tag2) r = self.html.text(text) if r: review['review'] = r user = None r = re.compile( '<p class="profile_name"><s></s><a href="[^>]+>([^<]+)</a></p>' ).search(block) if r: user = self.html.string(r.group(1)) else: r = re.compile('<p class="profile_name"><s></s>([^<]+)</p>' ).search(block) if r: user = self.html.string(r.group(1)) if user: review['nick'] = user r = re.compile('<p class="sub_title"[^>]+>([^<]+)</p>').search( block) if r: title = self.html.string(r.group(1)) if title: review['title'] = title r = re.compile('<span class="date">([^<]+)</span>', re.U | re.S).search(block) if r: review['time'] = r.group(1).replace(u' |', u',') r = re.compile(u'<a href="[^>]+>рецензии \(([0-9]+)\)</a>', re.U | re.S).search(block) if r: review['count'] = int(r.group(1)) if review['nick'] and review['review']: res[query].append(review) return 3600, res # one hour def _movie(self, id): response = self.http.fetch('http://www.kinopoisk.ru/film/' + id + '/', headers=self.headers) if response.error: return False, None html = response.body.decode('windows-1251') res = { 'id': int(id), 'thumb': None, 'fanart': None, 'trailers': [], 'info': {} } # имя, оригинальное имя, девиз, цензура, год, top250 # runtime - длительность фильма (в отдельную переменную, иначе не видно размер файла) for tag, reg, cb in ( ('title', '<h1 class="moviename-big" itemprop="name">(.+?)</h1>', self.html.string), ('originaltitle', 'itemprop="alternativeHeadline">([^<]*)</span>', self.html.string), ('tagline', '<td style="color\: #555">«(.+?)»</td></tr>', self.html.string), ('mpaa', 'images/mpaa/([^\.]+).gif', self.html.string), ('runtime', '<td class="time" id="runtime">[^<]+<span style="color\: #999">/</span>([^<]+)</td>', self.html.string), ('year', '<a href="/lists/m_act%5Byear%5D/([0-9]+)/"', int), ('top250', '<a href="/level/20/#([0-9]+)', int)): r = re.compile(reg, re.U).search(html) if r: value = r.group(1).strip() if value: res['info'][tag] = cb(value) # режисеры, сценаристы, жанры for tag, reg in (('director', u'<td itemprop="director">(.+?)</td>'), ( 'writer', u'<td class="type">сценарий</td><td[^>]*>(.+?)</td>'), ('genre', u'<span itemprop="genre">(.+?)</span>')): r = re.compile(reg, re.U | re.S).search(html) if r: r2 = [] for r in re.compile('<a href="[^"]+">([^<]+)</a>', re.U).findall(r.group(1)): r = self.html.string(r) if r and r != '...': r2.append(r) if r2: res['info'][tag] = u', '.join(r2) # описание фильма r = re.compile( '<span class="_reachbanner_"><div class="brand_words film-synopsys" itemprop="description">(.+?)</div></span>', re.U).search(html) if r: plot = self.html.text(r.group(1).replace('<=end=>', '\n')) if plot: res['info']['plot'] = plot # IMDB r = re.compile('IMDb: ([0-9.]+) \(([0-9\s]+)\)</div>', re.U).search(html) if r: res['info']['rating'] = float(r.group(1).strip()) res['info']['votes'] = r.group(2).strip() # премьера r = re.compile(u'премьера \(мир\)</td>(.+?)</tr>', re.U | re.S).search(html) if r: r = re.compile(u'data\-ical\-date="([^"]+)"', re.U | re.S).search(r.group(1)) if r: data = r.group(1).split(' ') if len(data) == 3: i = 0 for mon in (u'января', u'февраля', u'марта', u'апреля', u'мая', u'июня', u'июля', u'августа', u'сентября', u'октября', u'ноября', u'декабря'): i += 1 if mon == data[1]: mon = str(i) if len(mon) == 1: mon = '0' + mon day = data[0] if len(day) == 1: day = '0' + day res['info']['premiered'] = '-'.join( [data[2], mon, day]) break # постер r = re.compile(u'onclick="openImgPopup\(([^\)]+)\)', re.U | re.S).search(html) if r: poster = r.group(1).replace("'", '').strip() if poster: res['thumb'] = 'http://kinopoisk.ru' + poster # актеры r = re.compile(u'<h4>В главных ролях:</h4>(.+?)</ul>', re.U | re.S).search(html) if r: actors = [] for r in re.compile( '<li itemprop="actors"><a [^>]+>([^<]+)</a></li>', re.U).findall(r.group(1)): r = self.html.string(r) if r and r != '...': actors.append(r) if actors: res['info']['cast'] = actors[:] menu = re.compile( '<ul id="newMenuSub" class="clearfix(.+?)<!\-\- /menu \-\->', re.U | re.S).search(html) if menu: menu = menu.group(1) # фанарт if menu.find('/film/' + id + '/wall/') != -1: response = self.http.fetch('http://www.kinopoisk.ru/film/' + id + '/wall/', headers=self.headers) if not response.error: html = response.body.decode('windows-1251') fanart = re.compile( '<a href="/picture/([0-9]+)/w_size/([0-9]+)/">', re.U).findall(html) if fanart: fanart.sort(cmp=lambda (id1, size1), (id2, size2): cmp(int(size1), int(size2))) # пробуем взять максимально подходящее fanart_best = [x for x in fanart if int(x[1]) <= 1280] if fanart_best: fanart = fanart_best response = self.http.fetch( 'http://www.kinopoisk.ru/picture/' + fanart[-1][0] + '/w_size/' + fanart[-1][1] + '/', headers=self.headers) if not response.error: html = response.body.decode('windows-1251') r = re.compile('id="image" src="([^"]+)"', re.U | re.S).search(html) if r: res['fanart'] = r.group(1).strip() if res['fanart'].startswith('//'): res['fanart'] = 'http:' + res['fanart'] # если нет фанарта (обоев), то пробуем получить кадры if not res['fanart'] and menu.find('/film/' + id + '/stills/') != -1: response = self.http.fetch('http://www.kinopoisk.ru/film/' + id + '/stills/', headers=self.headers) if not response.error: html = response.body.decode('windows-1251') fanart = re.compile( '<a href="/picture/([0-9]+)/"><img src="[^<]+</a>[^<]+<b><i>([0-9]+)×([0-9]+)</i>', re.U).findall(html) if fanart: fanart.sort(cmp=lambda (id1, size1, t1), ( id2, size2, t2): cmp(int(size1), int(size2))) # пробуем взять максимально подходящее fanart_best = [ x for x in fanart if int(x[1]) <= 1280 and int(x[1]) > int(x[2]) ] if fanart_best: fanart = fanart_best response = self.http.fetch( 'http://www.kinopoisk.ru/picture/' + fanart[-1][0] + '/', headers=self.headers) if not response.error: html = response.body.decode('windows-1251') r = re.compile('id="image" src="([^"]+)"', re.U | re.S).search(html) if r: res['fanart'] = r.group(1).strip() if res['fanart'].startswith('//'): res['fanart'] = 'http:' + res['fanart'] # студии if menu.find('/film/' + id + '/studio/') != -1: response = self.http.fetch('http://www.kinopoisk.ru/film/' + id + '/studio/', headers=self.headers) if not response.error: html = response.body.decode('windows-1251') r = re.compile(u'<b>Производство:</b>(.+?)</table>', re.U | re.S).search(html) if r: studio = [] for r in re.compile( '<a href="/lists/m_act%5Bstudio%5D/[0-9]+/" class="all">(.+?)</a>', re.U).findall(r.group(1)): r = self.html.string(r) if r: studio.append(r) if studio: res['info']['studio'] = u', '.join(studio) # трэйлеры trailers1 = [] # русские трейлеры trailers2 = [] # другие русские видео trailers3 = [] # трейлеры trailers4 = [] # другие видео if menu.find('/film/' + id + '/video/') != -1: response = self.http.fetch('http://www.kinopoisk.ru/film/' + id + '/video/', headers=self.headers) if not response.error: html = response.body.decode('windows-1251') for row in re.compile( u'<!-- ролик -->(.+?)<!-- /ролик -->', re.U | re.S).findall(html): # отсекаем лишние блоки if row.find(u'>СМОТРЕТЬ</a>') != -1: # русский ролик? if row.find('class="flag flag2"') == -1: is_ru = False else: is_ru = True # получаем имя трейлера r = re.compile( '<a href="/film/' + id + '/video/[0-9]+/[^>]+ class="all">(.+?)</a>', re.U).search(row) if r: name = self.html.string(r.group(1)) if name: trailer = { 'name': name, 'time': None, 'trailer': False, 'ru': is_ru, 'video': [] } # трейлер или тизер? for token in (u'Трейлер', u'трейлер', u'Тизер', u'тизер'): if name.find(token) != -1: trailer['trailer'] = True break # получаем время трейлера r = re.compile( u'clock.gif"[^>]+></td>\s*<td style="color\: #777">[^0-9]*([0-9\:]+)</td>', re.U | re.S).search(row) if r: trailer['time'] = r.group(1).strip() # делим ролики по качеству for r in re.compile( 'trailer/([1-3])a.gif"(.+?)link=([^"]+)" class="continue">.+?<td style="color\:#777">([^<]+)</td>\s*</tr>', re.U | re.S).findall(row): quality = int(r[0]) if r[1].find('icon-hd') != -1: quality += 3 trailer['video'].append( (quality, r[2].strip(), r[3])) if id == '462754': #raise pass if trailer['video']: if trailer['ru']: if trailer['trailer']: trailers1.append(trailer) else: trailers2.append(trailer) else: if trailer['trailer']: trailers3.append(trailer) else: trailers4.append(trailer) # склеиваем трейлеры res['trailers'].extend(trailers1) res['trailers'].extend(trailers2) res['trailers'].extend(trailers3) res['trailers'].extend(trailers4) timeout = True # если фильм свежий, то кладем в кэш НЕ на долго (могут быть обновления на сайте) if 'year' not in res['info'] or int( res['info']['year']) >= time.gmtime(time.time()).tm_year: timeout = 7 * 24 * 60 * 60 #week return timeout, res
class KinoPoisk: """ API: scraper - скрапер movie - профайл фильма search - поиск фильма best - поиск лучших фильмов person - поиск персон work - информация о работах персоны """ def __init__(self): self.cache = Cache('kinopoisk.db') self.html = Clear() self.http = HTTP() self.headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; rv:10.0.2) Gecko/20100101 Firefox/10.0.2', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'ru-ru,ru;q=0.8,en-us;q=0.5,en;q=0.3', 'Cache-Control': 'no-cache', 'Referer': 'http://www.kinopoisk.ru/level/7/' } # API def scraper(self, name, year=None, trailer_quality=None): try: tag = 'scraper:' + urllib.quote_plus(name.encode('windows-1251')) except: return None else: if year: tag += ':' + str(year) id = self.cache.get(tag, self._scraper, name, year) if not id: return None return self.movie(id, trailer_quality) def movie(self, id, trailer_quality=None): id = str(id) if trailer_quality is None: trailer_quality = 6 movie = self.cache.get('movie:' + id, self._movie, id) if not movie: return None if movie['trailers']: # компилируем список с нужным нам качеством video = [] for m in movie['trailers']: url = [x for x in m['video'] if x[0] <= trailer_quality] if url: m['video'] = url[-1] video.append(m) movie['trailers'] = video if movie['trailers']: # готовим главный трейлер r = [x for x in movie['trailers'] if x['trailer']] if r: movie['info']['trailer'] = r[0]['video'][1] else: # если трейлер не найден, то отдаем что попало... movie['info']['trailer'] = movie['trailers'][0]['video'][1] return movie def search(self, name, trailer_quality=None): return self._search_movie(name) def best(self, **kwarg): page = kwarg.get('page', 1) limit = kwarg.get('limit', 50) url = 'http://www.kinopoisk.ru/top/navigator/m_act%5Bis_film%5D/on/m_act%5Bnum_vote%5D/' + str(kwarg.get('votes', 100)) + '/' if kwarg.get('dvd'): url += 'm_act%5Bis_dvd%5D/on/' if kwarg.get('decade'): url += 'm_act%5Bdecade%5D/' + str(kwarg['decade']) + '/' if kwarg.get('genre'): url += 'm_act%5Bgenre%5D/' + str(GENRE[kwarg['genre']]) + '/' if kwarg.get('country'): url += 'm_act%5Bcountry%5D/' + str(kwarg['country']) + '/' if kwarg.get('rate'): url += 'm_act%5Brating%5D/' + str(kwarg['rate']) + ':/' if kwarg.get('mpaa'): url += 'm_act%5Bmpaa%5D/' + str(kwarg['mpaa']) + '/' url += 'perpage/' + str(limit) + '/order/ex_rating/' if page > 1: url += 'page/' + str(page) + '/' response = self.http.fetch(url, headers=self.headers) if response.error: return None res = {'pages': (1, 0, 1, 0), 'data': []} r = re.compile('<div class="pagesFromTo(.+?)<div class="pagesFromTo', re.U|re.S).search(response.body.decode('windows-1251')) if r: body = r.group(1) # compile pagelist p = re.compile('>([0-9]+)—[0-9]+[^0-9]+?([0-9]+)', re.U).search(body) if p: page = (int(p.group(1))-1)/limit + 1 total = int(p.group(2)) pages = total/limit if limit*pages != total: pages += 1 res['pages'] = (pages, 0 if page == 1 else page-1, page, 0 if page==pages else page+1) # end compile for id in re.compile('<div id="tr_([0-9]+)"', re.U|re.S).findall(body): res['data'].append(int(id)) return res def person(self, name): response = self.http.fetch('http://www.kinopoisk.ru/s/type/people/list/1/find/' + urllib.quote_plus(name.encode('windows-1251')) + '/order/relevant/', headers=self.headers) if response.error: return None res = [] body = re.compile('<div class="navigator">(.+?)<div class="navigator">', re.U|re.S).search(response.body.decode('windows-1251')) if body: for block in re.compile('<p class="pic">(.+?)<div class="clear">', re.U|re.S).findall(body.group(1)): id, name, original, year, poster = None, None, None, None, None r = re.compile('<p class="name"><a href="http://www\.kinopoisk\.ru/level/4/people/([0-9]+)[^>]+>([^<]+)</a>', re.U|re.S).search(block) if r: id = r.group(1) name = r.group(2).strip() if id and name: r = re.compile('<span class="gray">([^<]+)</span>', re.U|re.S).search(block) if r: original = r.group(1).strip() if not original: original = None r = re.compile('<span class="year">([0-9]{4})</span>', re.U|re.S).search(block) if r: year = int(r.group(1)) if block.find('no-poster.gif') == -1: poster = 'http://st.kinopoisk.ru/images/actor/' + id + '.jpg' res.append({'id': int(id), 'name': name, 'originalname': original, 'year': year, 'poster': poster}) return {'pages': (1, 0, 1, 0), 'data': res} def work(self, id): response = self.http.fetch('http://www.kinopoisk.ru/name/' + str(id) + '/', headers=self.headers) if response.error: return None res = {} r = re.compile('id="sort_block">(.+?)<style>', re.U|re.S).search(response.body.decode('windows-1251')) if r: for block in r.group(1).split(u'<table cellspacing="0" cellpadding="0" border="0" width="100%">'): work = None for w in ('actor', 'director', 'writer', 'producer', 'producer_ussr', 'composer', 'operator', 'editor', 'design', 'voice', 'voice_director'): if block.find(u'id="' + w + u'"') != -1: work = 'producer' if w == 'producer_ussr' else w break if work: movies = [] for id, name in re.compile('<span class="name"><a href="/film/([0-9]+)/" >([^<]+?)</a>', re.U).findall(block): for tag in (u'(мини-сериал)', u'(сериал)'): if name.find(tag) != -1: break else: movies.append(int(id)) if movies: res.setdefault(work, []).extend(movies) return res def review(self, id, query): query_s = 'all' if query == 'stat' else query data = self.cache.get('review:' + str(id) + ':' + query_s, self._review, id, query_s) if not data: return data return data[query] def countries(self): return COUNTRIES def country(self, id, default=None): country = [x[1] for x in COUNTRIES if x[0] == id] return country[0] if country else default # PRIVATE def _search_movie(self, name, year=None): url = 'http://www.kinopoisk.ru/s/type/film/list/1/find/' + urllib.quote_plus(name.encode('windows-1251')) + '/order/relevant' if year: url += '/m_act%5Byear%5D/' + str(year) url += '/m_act%5Btype%5D/film/' response = self.http.fetch(url, headers=self.headers) if response.error: return None res = [] r = re.compile('<div class="navigator">(.+?)<div class="navigator">', re.U|re.S).search(response.body.decode('windows-1251')) if r: for id in re.compile('<p class="name"><a href="/level/1/film/([0-9]+)', re.U|re.S).findall(r.group(1)): res.append(int(id)) return {'pages': (1, 0, 1, 0), 'data': res} def _scraper(self, name, year): timeout = True # если фильм свежий, то кладем в кэш НЕ на долго (могут быть обновления на сайте) if year and year >= time.gmtime(time.time()).tm_year: timeout = 7*24*60*60 #week ids = self._search_movie(name, year) if ids is None: return False, None elif not ids['data']: # сохраняем пустой результат на 3-е суток return 259200, None else: return timeout, ids['data'][0] def _review(self, id, query): url = 'http://www.kinopoisk.ru/film/' + str(id) + '/ord/rating/' if query in ('good', 'bad', 'neutral'): url += 'status/' + query + '/' url += 'perpage/200/' response = self.http.fetch(url, headers=self.headers) if response.error: return False, None html = response.body.decode('windows-1251') res = { 'stat': {'all': 0, 'good': 0, 'bad': 0, 'neutral': 0}, query: [] } r = re.compile('<ul class="resp_type">(.+?)</ul>', re.U|re.S).search(html) if r: ul = r.group(1) for q, t in (('pos', 'good'), ('neg', 'bad'), ('neut', 'neutral')): r = re.compile('<li class="' + q + '"><a href="[^>]+>[^<]+</a><b>([0-9]+)</b></li>', re.U).search(ul) if r: res['stat'][t] = int(r.group(1)) res['stat']['all'] = res['stat']['good'] + res['stat']['bad'] + res['stat']['neutral'] r = re.compile('<div class="navigator">(.+?)<div class="navigator">', re.U|re.S).search(html) if r: for block in r.group(1).split('itemprop="reviews"'): review = { 'nick': None, 'count': None, 'title': None, 'review': None, 'time': None } r = re.compile('itemprop="reviewBody">(.+?)</div>', re.U|re.S).search(block) if r: text = r.group(1) for tag1, tag2 in ((u'<=end=>', u'\n'), (u'<b>', u'[B]'), (u'</b>', u'[/B]'), (u'<i>', u'[I]'), (u'</i>', u'[/I]'), (u'<u>', u'[U]'), (u'</u>', u'[/U]')): text = text.replace(tag1, tag2) r = self.html.text(text) if r: review['review'] = r user = None r = re.compile('<p class="profile_name"><s></s><a href="[^>]+>([^<]+)</a></p>').search(block) if r: user = self.html.string(r.group(1)) else: r = re.compile('<p class="profile_name"><s></s>([^<]+)</p>').search(block) if r: user = self.html.string(r.group(1)) if user: review['nick'] = user r = re.compile('<p class="sub_title"[^>]+>([^<]+)</p>').search(block) if r: title = self.html.string(r.group(1)) if title: review['title'] = title r = re.compile('<span class="date">([^<]+)</span>', re.U|re.S).search(block) if r: review['time'] = r.group(1).replace(u' |', u',') r = re.compile(u'<a href="[^>]+>рецензии \(([0-9]+)\)</a>', re.U|re.S).search(block) if r: review['count'] = int(r.group(1)) if review['nick'] and review['review']: res[query].append(review) return 3600, res # one hour def _movie(self, id): response = self.http.fetch('http://www.kinopoisk.ru/film/' + id + '/', headers=self.headers) if response.error: return False, None html = response.body.decode('windows-1251') res = { 'id': int(id), 'thumb': None, 'fanart': None, 'trailers': [], 'info': {} } # имя, оригинальное имя, девиз, цензура, год, top250 # runtime - длительность фильма (в отдельную переменную, иначе не видно размер файла) for tag, reg, t in ( ('title', '<title>(.+?)</title>', 'str'), ('originaltitle', 'itemprop="alternativeHeadline">([^<]*)</span>', 'str'), ('tagline', '<td style="color\: #555">«(.+?)»</td></tr>', 'str'), ('mpaa', 'images/mpaa/([^\.]+).gif', 'str'), ('runtime', '<td class="time" id="runtime">[^<]+<span style="color\: #999">/</span>([^<]+)</td>', 'str'), ('year', '<a href="/lists/m_act%5Byear%5D/([0-9]+)/"', 'int'), ('top250', 'Топ250\: <a\shref="/level/20/#([0-9]+)', 'int') ): r = re.compile(reg, re.U).search(html) if r: value = r.group(1).strip() if value: res['info'][tag] = value if t == 'int': res['info'][tag] = int(res['info'][tag]) else: res['info'][tag] = self.html.string(res['info'][tag]) # режисеры, сценаристы, жанры for tag, reg in ( ('director', u'<td itemprop="director">(.+?)</td>'), ('writer', u'<td class="type">сценарий</td><td[^>]*>(.+?)</td>'), ('genre', u'<span itemprop="genre">(.+?)</span>') ): r = re.compile(reg, re.U|re.S).search(html) if r: r2 = [] for r in re.compile('<a href="[^"]+">([^<]+)</a>', re.U).findall(r.group(1)): r = self.html.string(r) if r and r != '...': r2.append(r) if r2: res['info'][tag] = u', '.join(r2) # актеры r = re.compile(u'<h4>В главных ролях:</h4>(.+?)</ul>', re.U|re.S).search(html) if r: actors = [] for r in re.compile('<li itemprop="actors"><a [^>]+>([^<]+)</a></li>', re.U).findall(r.group(1)): r = self.html.string(r) if r and r != '...': actors.append(r) if actors: res['info']['cast'] = actors[:] #res['info']['castandrole'] = actors[:] # описание фильма r = re.compile('<span class="_reachbanner_"><div class="brand_words" itemprop="description">(.+?)</div></span>', re.U).search(html) if r: plot = self.html.text(r.group(1).replace('<=end=>', '\n')) if plot: res['info']['plot'] = plot # IMDB r = re.compile('IMDb: ([0-9.]+) \(([0-9\s]+)\)</div>', re.U).search(html) if r: res['info']['rating'] = float(r.group(1).strip()) res['info']['votes'] = r.group(2).strip() # премьера r = re.compile(u'премьера \(мир\)</td>(.+?)</tr>', re.U|re.S).search(html) if r: r = re.compile(u'data\-ical\-date="([^"]+)"', re.U|re.S).search(r.group(1)) if r: data = r.group(1).split(' ') if len(data) == 3: i = 0 for mon in (u'января', u'февраля', u'марта', u'апреля', u'мая', u'июня', u'июля', u'августа', u'сентября', u'октября', u'ноября', u'декабря'): i += 1 if mon == data[1]: mon = str(i) if len(mon) == 1: mon = '0' + mon day = data[0] if len(day) == 1: day = '0' + day res['info']['premiered'] = '-'.join([data[2], mon, day]) break # постер r = re.compile(u'onclick="openImgPopup\(([^\)]+)\)', re.U|re.S).search(html) if r: poster = r.group(1).replace("'", '').strip() if poster: res['thumb'] = 'http://kinopoisk.ru' + poster menu = re.compile('<ul id="newMenuSub" class="clearfix(.+?)<!\-\- /menu \-\->', re.U|re.S).search(html) if menu: menu = menu.group(1) # фанарт if menu.find('/film/' + id + '/wall/') != -1: response = self.http.fetch('http://www.kinopoisk.ru/film/' + id + '/wall/', headers=self.headers) if not response.error: html = response.body.decode('windows-1251') fanart = re.compile('<a href="/picture/([0-9]+)/w_size/([0-9]+)/">', re.U).findall(html) if fanart: fanart.sort(cmp=lambda (id1, size1), (id2, size2): cmp(int(size1), int(size2))) # пробуем взять максимально подходящее fanart_best = [x for x in fanart if int(x[1]) <= 1280] if fanart_best: fanart = fanart_best response = self.http.fetch('http://www.kinopoisk.ru/picture/' + fanart[-1][0] + '/w_size/' + fanart[-1][1] + '/', headers=self.headers) if not response.error: html = response.body.decode('windows-1251') r = re.compile('id="image" src="([^"]+)"', re.U|re.S).search(html) if r: res['fanart'] = r.group(1).strip() # если нет фанарта (обоев), то пробуем получить кадры if not res['fanart'] and menu.find('/film/' + id + '/stills/') != -1: response = self.http.fetch('http://www.kinopoisk.ru/film/' + id + '/stills/', headers=self.headers) if not response.error: html = response.body.decode('windows-1251') fanart = re.compile('<a href="/picture/([0-9]+)/"><img src="[^<]+</a>[^<]+<b><i>([0-9]+)×([0-9]+)</i>', re.U).findall(html) if fanart: fanart.sort(cmp=lambda (id1, size1, t1), (id2, size2, t2): cmp(int(size1), int(size2))) # пробуем взять максимально подходящее fanart_best = [x for x in fanart if int(x[1]) <= 1280 and int(x[1]) > int(x[2])] if fanart_best: fanart = fanart_best response = self.http.fetch('http://www.kinopoisk.ru/picture/' + fanart[-1][0] + '/', headers=self.headers) if not response.error: html = response.body.decode('windows-1251') r = re.compile('id="image" src="([^"]+)"', re.U|re.S).search(html) if r: res['fanart'] = r.group(1).strip() # студии if menu.find('/film/' + id + '/studio/') != -1: response = self.http.fetch('http://www.kinopoisk.ru/film/' + id + '/studio/', headers=self.headers) if not response.error: html = response.body.decode('windows-1251') r = re.compile(u'<b>Производство:</b>(.+?)</table>', re.U|re.S).search(html) if r: studio = [] for r in re.compile('<a href="/lists/m_act%5Bstudio%5D/[0-9]+/" class="all">(.+?)</a>', re.U).findall(r.group(1)): r = self.html.string(r) if r: studio.append(r) if studio: res['info']['studio'] = u', '.join(studio) # трэйлеры trailers1 = [] # русские трейлеры trailers2 = [] # другие русские видео trailers3 = [] # трейлеры trailers4 = [] # другие видео if menu.find('/film/' + id + '/video/') != -1: response = self.http.fetch('http://www.kinopoisk.ru/film/' + id + '/video/', headers=self.headers) if not response.error: html = response.body.decode('windows-1251') for row in re.compile(u'<!-- ролик -->(.+?)<!-- /ролик -->', re.U|re.S).findall(html): # отсекаем лишние блоки if row.find(u'>СМОТРЕТЬ</a>') != -1: # русский ролик? if row.find('class="flag flag2"') == -1: is_ru = False else: is_ru = True # получаем имя трейлера r = re.compile('<a href="/film/' + id + '/video/[0-9]+/[^>]+ class="all">(.+?)</a>', re.U).search(row) if r: name = self.html.string(r.group(1)) if name: trailer = { 'name': name, 'time': None, 'trailer': False, 'ru': is_ru, 'video': [] } # трейлер или тизер? for token in (u'Трейлер', u'трейлер', u'Тизер', u'тизер'): if name.find(token) != -1: trailer['trailer'] = True break # получаем время трейлера r = re.compile(u'clock.gif"[^>]+></td>\s*<td style="color\: #777">[^0-9]*([0-9\:]+)</td>', re.U|re.S).search(row) if r: trailer['time'] = r.group(1).strip() print 'F**K' # делим ролики по качеству for r in re.compile('trailer/([1-3])a.gif"(.+?)link=([^"]+)" class="continue">.+?<td style="color\:#777">([^<]+)</td>\s*</tr>', re.U|re.S).findall(row): print str(r) quality = int(r[0]) if r[1].find('icon-hd') != -1: quality += 3 trailer['video'].append((quality, r[2].strip(), r[3])) print str(trailer) if id == '462754': #raise pass if trailer['video']: if trailer['ru']: if trailer['trailer']: trailers1.append(trailer) else: trailers2.append(trailer) else: if trailer['trailer']: trailers3.append(trailer) else: trailers4.append(trailer) # склеиваем трейлеры res['trailers'].extend(trailers1) res['trailers'].extend(trailers2) res['trailers'].extend(trailers3) res['trailers'].extend(trailers4) timeout = True # если фильм свежий, то кладем в кэш НЕ на долго (могут быть обновления на сайте) if 'year' not in res['info'] or int(res['info']['year']) >= time.gmtime(time.time()).tm_year: timeout = 7*24*60*60 #week return timeout, res
class TvDb: """ API: scraper - скрапер search - поиск сериалов movie - профайл фильма """ def __init__(self): self.api_key = '1D62F2F90030C444' self.cache = Cache('tvdb.db') self.http = HTTP() self.headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; rv:10.0.2) Gecko/20100101 Firefox/10.0.2', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'ru-ru,ru;q=0.8,en-us;q=0.5,en;q=0.3', 'Cache-Control': 'no-cache', 'Referer': 'http://www.thetvdb.com/' } # API def scraper(self, name, year=None): try: tag = 'scraper:' + urllib.quote_plus(name.encode('utf8')) except: return None else: if year: tag += ':' + str(year) id = self.cache.get(tag, self._scraper, name, year) if not id: return None return self.movie(id) def search(self, name): return self._search(name) def movie(self, id): id = str(id) return self.cache.get('movie:' + id, self._movie, id) def _movie(self, id): dirname = tempfile.mkdtemp() response = self.http.fetch('http://www.thetvdb.com/api/' + self.api_key + '/series/' + id + '/all/ru.zip', headers=self.headers, download=os.path.join(dirname, 'movie.zip')) if response.error: self._movie_clear(dirname) return False, None try: filezip = zipfile.ZipFile(os.path.join(dirname, 'movie.zip'), 'r') filezip.extractall(dirname) filezip.close() movie = file(os.path.join(dirname, 'ru.xml'), 'rb').read().decode('utf8') except: self._movie_clear(dirname) return False, None self._movie_clear(dirname) body = re.compile(r'<Series>(.+?)</Series>', re.U | re.S).search(movie) if not body: return False, None body = body.group(1) res = { 'id': int(id), 'thumb': None, 'fanart': None, 'trailers': [], 'info': {} } # режисеры и сценаристы for tag in ('Director', 'Writer'): people = {} people_list = [] [ people_list.extend(x.split('|')) for x in re.compile(r'<' + tag + r'>([^<]+)</' + tag + r'>', re.U | re.S).findall(movie) ] [ people.update({x: 1}) for x in [x.strip() for x in people_list] if x ] if people: res['info'][tag.lower()] = u', '.join( [x for x in people.keys() if x]) for tag, retag, typeof in (('plot', 'Overview', None), ('mpaa', 'ContentRating', None), ('premiered', 'FirstAired', None), ('studio', 'Network', None), ('title', 'SeriesName', None), ('runtime', 'Runtime', None), ('votes', 'RatingCount', None), ('rating', 'Rating', float), ('genre', 'Genre', list), ('cast', 'Actors', list)): r = re.compile(r'<' + retag + r'>([^<]+)</' + retag + r'>', re.U | re.S).search(body) if r: r = r.group(1).strip() if typeof == float: res['info'][tag] = float(r) elif typeof == list: res['info'][tag] = [ x for x in [x.strip() for x in r.split(u'|')] if x ] if tag == 'genre': res['info'][tag] = u', '.join(res['info'][tag]) else: res['info'][tag] = r # год if 'premiered' in res['info']: res['info']['year'] = int(res['info']['premiered'].split('-')[0]) # постер r = re.compile(r'<poster>([^<]+)</poster>', re.U | re.S).search(body) if r: res['thumb'] = 'http://thetvdb.com/banners/' + r.group(1).strip() # фанарт r = re.compile(r'<fanart>([^<]+)</fanart>', re.U | re.S).search(body) if r: res['fanart'] = 'http://thetvdb.com/banners/' + r.group(1).strip() timeout = True # если фильм свежий, то кладем в кэш НЕ на долго (могут быть обновления на сайте) if 'year' not in res['info'] or int( res['info']['year']) >= time.gmtime(time.time()).tm_year: timeout = 7 * 24 * 60 * 60 #week return timeout, res def _movie_clear(self, dirname): for filename in os.listdir(dirname): try: os.unlink(os.path.join(dirname, filename)) except: raise try: os.rmdir(dirname) except: raise def _search(self, name): response = self.http.fetch( 'http://www.thetvdb.com/api/GetSeries.php?language=ru&seriesname=' + urllib.quote_plus(name.encode('utf8')), headers=self.headers) if response.error: return None res = [] rows = re.compile('<Series>(.+?)</Series>', re.U | re.S).findall(response.body.decode('utf8')) if rows: recmd = re.compile('<seriesid>([0-9]+)</seriesid>', re.U | re.S) for row in [ x for x in rows if x.find(u'<language>ru</language>') != -1 ]: r = recmd.search(row) if r: res.append(int(r.group(1))) return {'pages': (1, 0, 1, 0), 'data': res} def _scraper(self, name, year): timeout = True # если фильм свежий, то кладем в кэш НЕ на долго (могут быть обновления на сайте) if year and year >= time.gmtime(time.time()).tm_year: timeout = 7 * 24 * 60 * 60 #week ids = self._search(name) if ids is None: return False, None elif not ids['data']: # сохраняем пустой результат на 3-е суток return 259200, None else: return timeout, ids['data'][0]
class RuTracker: def __init__(self, expire=0, size=0): self.cache_catalog = Cache('rutracker_catalog.db') self.cache_profile = Cache('rutracker_profile.db', expire, size) self.http = RuTrackerHTTP() self.re = { 'is_int': re.compile(r'^([0-9]+)', re.U), 'is_float': re.compile(r'^([0-9]{1,10}\.[0-9]+)', re.U) } self.html = Clear() self.status = { '%': 'check', 'D': 'repeat', '!': 'nodesc', '©': 'copyright', '∏': 'moder', 'x': 'close', '∑': 'absorb', '#': 'doubtful', '*': 'nocheck', '?': 'neededit', 'T': 'temp', '√': 'ok' } # API def get(self, id=None, page=1): """ Получение списка директорий и раздач На вход функции надо подавать следующие параметры: id - [int] id директории page - [int] номер страницы Возвращает словарь, состоящий из следующих полей: pages - [list] кортеж [int] для навигации = (кол-во страниц, предыдущая, текущая, следующая) data - [list] данные выборки, состоящая из следующих полей: id - [int] id (для директорий и топиков ID могут совпадать) name - [str] имя записи type - [str] тип записи (torrent - торрент, folder - директория) size - [int] размер раздачи в байтах seeder - [int] кол-во сидов leecher - [int] кол-во личеров download - [int] кол-во скачиваний торрента comment - [int] кол-во комментариев status - [str] символ отметки модератора status_human - [str] отметка модератора в удобочитаемом виде (описание смотри ниже). Описание возвращаемых отметок status_human: Скачать торрент нельзя (красный): moder - премодерация, check - проверяется, repeat - повтор, nodesc - не оформлено, copyright - закрыто правообладателем, close - закрыто, absorb - поглощено. Скачать торрент можно (желтый): nocheck - не проверено, neededit - недооформлено, doubtful - сомнительно, temp - временная Скачать торрент можно (зеленый): ok - проверено """ # INDEX if id is None: html = self.http.get('http://rutracker.org/forum/index.php') if not html: return html res = [] r = re.compile(r'<div\sid="forums_wrap">(.+)<div\sid="board_stats">', re.U|re.S).search(html) if r: r = re.compile(r'<h4\sclass="forumlink"><a\shref="\./viewforum\.php\?f=([0-9]+)">(.+?)</a></h4>', re.U|re.S).findall(r.group(1)) if r: res = [{'id': int(i), 'name': self.html.string(x), 'type': 'folder'} for i, x in r] if not res: return None return {'pages': (1, 0, 1, 0), 'data': res} else: page_query = '' if page > 1: page_query = '&start=' + str(50*(page-1)) html = self.http.get('http://rutracker.org/forum/viewforum.php?f=' + str(id) + '&sort=2' + page_query) if not html: return html pages = self._compile_pages(html) folder = [] torrent = [] group_list = re.compile(r'<table class="forumline forum">(.+?)</table>', re.U|re.S).findall(html) if group_list: for group in group_list: # вытаскиваем папки (если есть) r = re.compile(r'<h4\sclass="forumlink"><a\shref="viewforum\.php\?f=([0-9]+)">(.+?)</a></h4>', re.U|re.S).findall(group) if r: folder.extend([{'id': int(i), 'name': self.html.string(x), 'type': 'folder'} for i, x in r]) # нарубаем на строчки топиков topic_list = group.split(u'topicSep">') if len(topic_list) > 1: topic_list = topic_list[1:] for html in topic_list: # вытаскиваем id for text in re.compile(r'<tr\sid="tr\-[0-9]+"(.+?)</tr>', re.U|re.S).findall(html): item = self._compile_topic(text) if item: torrent.append(item) folder.extend(torrent) return {'pages': pages, 'data': folder} def search(self, search, folder=None, index=None, ignore=None): """ Поиск по РуТрекеру На вход функции надо подавать следующие параметры: search - [str] поисковая строка запроса (Unicode) folder - [list] список ID директорий, в которых необходимо искать (None - искать везде) Возвращает словарь, аналогичный выводу метода GET """ if isinstance(search, unicode): search = search.encode('windows-1251') # проверяем авторизацию html = self.http.get('http://rutracker.org/forum/index.php') if not html: return html # готовим запрос для получения дерева разделов if folder: if not isinstance(folder, list) and not isinstance(folder, tuple): folder = [folder] else: if index is not None: if not isinstance(index, list) and not isinstance(index, tuple): index = [index] if not isinstance(ignore, list) and not isinstance(ignore, tuple): ignore = [ignore] if not index and not ignore: folder = [] else: folder = self._load_catalog(index, ignore) if not folder: return folder # готовим запрос params = [('nm', search), ('o', 10), ('s', 2), ('prev_my', 0), ('prev_new', 0), ('prev_oop', 0), ('submit', r'Поиск')] params.extend([('f[]', x) for x in folder]) # делаем поиск html = self.http.post('http://rutracker.org/forum/tracker.php', params) if not html: return html res = [] table = re.compile('id="tor\-tbl">(.+?)</table>', re.U|re.S).search(html) if table: for tr in re.compile('<tr\sclass="tCenter\shl\-tr">(.+?)</tr>', re.U|re.S).findall(table.group(1)): item = self._compile_topic(tr, True) if item: res.append(item) return {'pages': (1, 0, 1, 0), 'data': res} def profile(self, id): """ Получение дополнительной информации о раздачи На вход функции надо подавать следующие параметры: id - [int] id топика с раздачей Возвращает словарь, состоящий из: descript - [str] описание на RuTracker cover - [str] url обложки screenshot - [list] Список url скриншотов """ return self.cache_profile.get('profile:' + str(id), self._profile, id) def comment(self, id, page=1): """ Получение комментариев раздачи На вход функции надо подавать следующие параметры: id - [int] id топика page - [int] номер страницы Возвращает словарь, состоящий из следующих полей: pages - [list] кортеж [int] для навигации = (кол-во страниц, предыдущая, текущая, следующая) data - [list] данные выборки - список словарей, состоящих из следующих полей: nick - [str] ник автора комментария usertime - [str] стаж юзера count - [str] кол-во сообщений у юзера location - [str] откуда юзер time - [str] время добавления комментария message - [str] комментарий """ page_query = '' if page > 1: page_query = '&start=' + str(30*(page-1)) html = self.http.get('http://rutracker.org/forum/viewtopic.php?t=' + str(id) + page_query) if not html: return html res = { 'pages': self._compile_pages(html), 'comments': [] } # нарубаем страницу по постам rows = re.compile('<tbody id="post_[0-9]+" class="row1|2">(.+?)<!\-\-/post_body\-\->', re.U|re.S).findall(html) if rows: if page == 1: rows.pop(0) if rows: # функция для очистки комментариев def _def_subn1(m): return u'<div class="q-wrap"><div class="q">' + self.html.string(m.group(1)) + u':\n' def _def_subn2(m): r = u'[BR][I]' + m.group(1).replace(u'[I]', u'').replace(u'[/I]', u'') + u'[/I][BR]' n = 1 while n: r, n = re.compile(u'\[BR\]\[BR\]', re.U|re.S).subn(u'[BR]', r) return r for html in rows: comment = { 'nick': None, 'usertime': None, 'count': None, 'location': u'', 'time': None, 'message': None } # вытаскиваем ник r = re.compile('<p class="nick[^>]+>([^<]+)</p>', re.U).search(html) if r: comment['nick'] = self.html.string(r.group(1).strip()) # смотрим стаж r = re.compile(u'<p class="joined"><em>Стаж:</em>([^<]+)</p>', re.U).search(html) if r: comment['usertime'] = r.group(1).strip() # смотрим кол-во коментов у юзера r = re.compile(u'<p class="posts"><em>Сообщений:</em>([^<]+)</p>', re.U).search(html) if r: comment['count'] = r.group(1).strip() # смотрим город юзера r = re.compile(u'<p class="from"><em>Откуда:</em>([^<]+)</p>', re.U).search(html) if r: comment['location'] = r.group(1).strip() # смотрим страну юзера r = re.compile('<p class="flag"><img [^>]*title="([^"]+)"[^>]*></p>', re.U).search(html) if r: if comment['location']: comment['location'] += u', ' comment['location'] += r.group(1).strip() # смотрим время коммента r = re.compile('<a class="small" href="\./viewtopic.php\?p=[^>]+>([0-9]{1,2}\-[^\-]+\-[0-9]{2} [0-9]{1,2}\:[0-9]{1,2})</a>', re.U).search(html) if r: comment['time'] = r.group(1).strip() # вытаскиваем тело коммента r = re.compile('<div class="post_body"[^>]+>(.+)$', re.U|re.S).search(html) if r: html = r.group(1).strip() if html: # заменяем что можем... for reg, rep in ( ('<span class="post\-b">([^(?:</span>)]+)</span>', u'[B]\g<1>[/B]'), ): html = re.compile(reg, re.U|re.S).sub(rep, html) # конвертируем цитаты html, n = re.compile('<div class="q-wrap">\s*<div class="q" head="([^"]+)">', re.U|re.S).subn(_def_subn1, html) n = 1 while n: html, n = re.compile('<div class="q-wrap">\s*<div class="q">(.+?)</div>\s*</div>', re.U|re.S).subn(_def_subn2, html) # прогоняем через полную очистку comment['message'] = self.html.text(html) if comment['nick'] and comment['message']: res['comments'].append(comment) return res def download(self, id): """ Скачивание торрента раздачи На вход функции надо подавать следующие параметры: id - [str] топика с раздачей Возвращает торрент или None (в случае неудачи) """ return self.http.download(id) # PRIVATE def _compile_pages(self, text): r = re.compile(u'<p style="float\: left">Страница <b>([0-9]+)</b> из <b>([0-9]+)</b></p>', re.U|re.S).search(text) if r: current = int(r.group(1)) total = int(r.group(2)) next = current + 1 if next > total: next = 0 return total, current-1, current, next return 1, 0, 1, 0 def _compile_topic(self, text, is_search=False): r = re.compile(r'<a\s[^>]*href="\./viewtopic\.php\?t=([0-9]+)"[^>]*>(.+?)</a>', re.U|re.S).search(text) if r: id = r.group(1) name = self.html.string(r.group(2)) r = re.compile(r'<a[^>]+href="http://dl\.rutracker\.org/forum/dl\.php\?t=' + id + '"[^>]*>(.+?)</a>', re.U|re.S).search(text) if r: size = self._compile_size(r.group(1)) if size and name: item = self._create_torrent(int(id), name) item['size'] = size r = re.compile(r'"tor-icon[^>]+>([^<]+)<', re.U|re.S).search(text) if r: stat = r.group(1) try: status = self.status[stat] except KeyError: pass else: item['status'] = self.html.char(stat) item['status_human'] = status if is_search: item['comment'] = -1 query = (('download', '<td\sclass="row4\ssmall">([0-9]+)</td>'), ('seeder', '<td\sclass="row4\sseedmed"><b>([0-9]+)</b></td>'), ('leecher', '<td\sclass="row4\sleechmed"[^>]+><b>([0-9]+)</b></td>')) else: query = (('comment', u'<span title="Ответов">([0-9]+)</span>'), ('download', u'title="Торрент скачан">[^<]*<b>([0-9]+)</b>[^<]*</p>'), ('seeder', 'title="Seeders"><b>([0-9]+)</b></span>'), ('leecher', 'title="Leechers"><b>([0-9]+)</b></span>')) for tag, reg in query: r = re.compile(reg, re.U|re.S).search(text) if r: item[tag] = int(r.group(1)) return item return None def _create_torrent(self, id, name): return { 'id': id, 'name': name, 'type': 'torrent', 'size': 0, 'seeder': 0, 'leecher': 0, 'download': 0, 'comment': 0, 'status': None, 'status_human': None } def _compile_size(self, text): text = self.html.string(text.replace(u'↓', u'')) if text: text = text.lower() prefix = 1 for p, v in ((u'kb', 1024), (u'mb', 1024*1024), (u'gb', 1024*1024*1024), (u'tb', 1024*1024*1024*1024)): if text.find(p) != -1: prefix = v text = text.replace(p, u'').strip() break num = self.re['is_float'].search(text) if num: return int(float(prefix)*float(num.group(1))) num = self.re['is_int'].search(text) if num: return prefix*int(num.group(1)) return None def _profile(self, id): html = self.http.guest('http://rutracker.org/forum/viewtopic.php?t=' + str(id)) if not html: return False, html res = { 'descript': None, 'cover': None, 'screenshot': None } r = re.compile('<table class="topic" id="topic_main" cellpadding="0" cellspacing="0">(.+?)<legend>Download</legend>', re.U|re.S).search(html) if r: html = r.group(1) # ищем коверы (перебирая все возможные варианты хостингов картинок) for api in (self.pic_hosting_fastpic, ): cover = api('cover', html) if cover: res['cover'] = cover break # вытаскиваем блок со скриншотами r = re.compile(u'<h3 class="sp-title">Скриншоты(.+?)</div>', re.U|re.S).search(html) if r: body = r.group(1) # ищем скрины (перебирая все возможные варианты хостингов картинок) for api in (self.pic_hosting_fastpic, ): screenshot = api('screenshot', body) if screenshot: res['screenshot'] = screenshot break # пытаемся получить текст описания r = re.compile('<div class="post_body"[^>]+>(.+)', re.U|re.S).search(html) if r: html = r.group(1) # режем и заменяем все что можем... for reg, rep in ( (u'<div class="sp-wrap">.+?</div>', u''), # удаляем все спойлеры (u'<var[^>]+>[^<]+</var>', u''), # удаляем все изображения (u'<span class="post\-hr">\-</span>', u'\n'), # удаляем HR (u'<span class="post\-b">([^<]+)</span>', u'[COLOR FF0DA09E]\g<1>[/COLOR]') # заменяем болды ): html = re.compile(reg, re.U|re.S).sub(rep, html) # прогоняем через полную очистку html = self.html.text(html) if html: res['descript'] = html return True, res # CATALOG def _load_catalog(self, index, ignore): catalog = self.cache_catalog.get('catalog', self._load_catalog_http) if not catalog: return [] res = [] for key, folders in catalog.iteritems(): if index is None or key in index: res.extend([x for x in folders if x not in ignore]) return res def _load_catalog_http(self): html = self.http.get('http://rutracker.org/forum/tracker.php') if not html: return html r = re.compile('<select id="fs-main"(.+?)</select>', re.U|re.S).search(html) if not r: return None res = {} root = None for cat, is_root_forum in re.compile('<option id="fs\-([0-9]+)"([^>]+)>', re.U).findall(r.group(1)): cat = int(cat) if is_root_forum.find('root_forum') != -1: root = cat res[root] = [cat] elif root: res[root].append(cat) return (86400 if res else False), res # day # SCREENSHOT def pic_hosting_fastpic(self, img, html): if img == 'cover': r = re.compile('<var[^>]+class="postImg postImgAligned img\-right"[^>]+title="(http\://[0-9a-z]+\.fastpic\.ru/big/[0-9a-f/]+\.[a-z]{3,4})"[^>]*>', re.U|re.S).search(html) if r: return r.group(1) return None else: res = [] for r in re.compile('<a[^>]*href="http\://fastpic\.ru/view/[^\.]+\.([a-z]{3,4})\.html"[^>]*>[.]*?<var[^>]+title="(http\://[0-9a-z]+\.fastpic\.ru/thumb/[0-9a-f/]+)\.[a-z]{3,4}"[^>]*>', re.U|re.S).findall(html): res.append('.'.join([r[1].replace('thumb', 'big'), r[0]])) return res if res else None
class TvDb: """ API: scraper - скрапер search - поиск сериалов movie - профайл фильма """ def __init__(self): self.api_key = '1D62F2F90030C444' self.cache = Cache('tvdb.db') self.http = HTTP() self.headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; rv:10.0.2) Gecko/20100101 Firefox/10.0.2', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'ru-ru,ru;q=0.8,en-us;q=0.5,en;q=0.3', 'Cache-Control': 'no-cache', 'Referer': 'http://www.thetvdb.com/' } # API def scraper(self, name, year=None): try: tag = 'scraper:' + urllib.quote_plus(name.encode('utf8')) except: return None else: if year: tag += ':' + str(year) id = self.cache.get(tag, self._scraper, name, year) if not id: return None return self.movie(id) def search(self, name): return self._search(name) def movie(self, id): id = str(id) return self.cache.get('movie:' + id, self._movie, id) def _movie(self, id): dirname = tempfile.mkdtemp() response = self.http.fetch('http://www.thetvdb.com/api/' + self.api_key + '/series/' + id + '/all/ru.zip', headers=self.headers, download=os.path.join(dirname, 'movie.zip')) if response.error: self._movie_clear(dirname) return False, None try: filezip = zipfile.ZipFile(os.path.join(dirname, 'movie.zip'), 'r') filezip.extractall(dirname) filezip.close() movie = file(os.path.join(dirname, 'ru.xml'), 'rb').read().decode('utf8') except: self._movie_clear(dirname) return False, None self._movie_clear(dirname) body = re.compile(r'<Series>(.+?)</Series>', re.U|re.S).search(movie) if not body: return False, None body = body.group(1) res = { 'id': int(id), 'thumb': None, 'fanart': None, 'trailers': [], 'info': {} } # режисеры и сценаристы for tag in ('Director', 'Writer'): people = {} people_list = [] [people_list.extend(x.split('|')) for x in re.compile(r'<' + tag + r'>([^<]+)</' + tag + r'>', re.U|re.S).findall(movie)] [people.update({x: 1}) for x in [x.strip() for x in people_list] if x] if people: res['info'][tag.lower()] = u', '.join([x for x in people.keys() if x]) for tag, retag, typeof in ( ('plot', 'Overview', None), ('mpaa', 'ContentRating', None), ('premiered', 'FirstAired', None), ('studio', 'Network', None), ('title', 'SeriesName', None), ('runtime', 'Runtime', None), ('votes', 'RatingCount', None), ('rating', 'Rating', float), ('genre', 'Genre', list), ('cast', 'Actors', list) ): r = re.compile(r'<' + retag + r'>([^<]+)</' + retag + r'>', re.U|re.S).search(body) if r: r = r.group(1).strip() if typeof == float: res['info'][tag] = float(r) elif typeof == list: res['info'][tag] = [x for x in [x.strip() for x in r.split(u'|')] if x] if tag == 'genre': res['info'][tag] = u', '.join(res['info'][tag]) else: res['info'][tag] = r # год if 'premiered' in res['info']: res['info']['year'] = int(res['info']['premiered'].split('-')[0]) # постер r = re.compile(r'<poster>([^<]+)</poster>', re.U|re.S).search(body) if r: res['thumb'] = 'http://thetvdb.com/banners/' + r.group(1).strip() # фанарт r = re.compile(r'<fanart>([^<]+)</fanart>', re.U|re.S).search(body) if r: res['fanart'] = 'http://thetvdb.com/banners/' + r.group(1).strip() timeout = True # если фильм свежий, то кладем в кэш НЕ на долго (могут быть обновления на сайте) if 'year' not in res['info'] or int(res['info']['year']) >= time.gmtime(time.time()).tm_year: timeout = 7*24*60*60 #week return timeout, res def _movie_clear(self, dirname): for filename in os.listdir(dirname): try: os.unlink(os.path.join(dirname, filename)) except: raise try: os.rmdir(dirname) except: raise def _search(self, name): response = self.http.fetch('http://www.thetvdb.com/api/GetSeries.php?language=ru&seriesname=' + urllib.quote_plus(name.encode('utf8')), headers=self.headers) if response.error: return None res = [] rows = re.compile('<Series>(.+?)</Series>', re.U|re.S).findall(response.body.decode('utf8')) if rows: recmd = re.compile('<seriesid>([0-9]+)</seriesid>', re.U|re.S) for row in [x for x in rows if x.find(u'<language>ru</language>') != -1]: r = recmd.search(row) if r: res.append(int(r.group(1))) return {'pages': (1, 0, 1, 0), 'data': res} def _scraper(self, name, year): timeout = True # если фильм свежий, то кладем в кэш НЕ на долго (могут быть обновления на сайте) if year and year >= time.gmtime(time.time()).tm_year: timeout = 7*24*60*60 #week ids = self._search(name) if ids is None: return False, None elif not ids['data']: # сохраняем пустой результат на 3-е суток return 259200, None else: return timeout, ids['data'][0]
class RuTracker: def __init__(self, expire=0, size=0): self.cache_catalog = Cache('rutracker_catalog.db') self.cache_profile = Cache('rutracker_profile.db', expire, size) self.http = RuTrackerHTTP() self.re = { 'is_int': re.compile(r'^([0-9]+)', re.U), 'is_float': re.compile(r'^([0-9]{1,10}\.[0-9]+)', re.U), 'hash': re.compile(r'<span id="tor-hash">([0-9A-F]{40})</span>', re.U) } self.html = Clear() self.status = { '%': 'check', 'D': 'repeat', '!': 'nodesc', '©': 'copyright', '∏': 'moder', 'x': 'close', '∑': 'absorb', '#': 'doubtful', '*': 'nocheck', '?': 'neededit', 'T': 'temp', '√': 'ok' } # API def get(self, id=None, page=1): """ Получение списка директорий и раздач На вход функции надо подавать следующие параметры: id - [int] id директории page - [int] номер страницы Возвращает словарь, состоящий из следующих полей: pages - [list] кортеж [int] для навигации = (кол-во страниц, предыдущая, текущая, следующая) data - [list] данные выборки, состоящая из следующих полей: id - [int] id (для директорий и топиков ID могут совпадать) name - [str] имя записи type - [str] тип записи (torrent - торрент, folder - директория) size - [int] размер раздачи в байтах seeder - [int] кол-во сидов leecher - [int] кол-во личеров download - [int] кол-во скачиваний торрента comment - [int] кол-во комментариев status - [str] символ отметки модератора status_human - [str] отметка модератора в удобочитаемом виде (описание смотри ниже). Описание возвращаемых отметок status_human: Скачать торрент нельзя (красный): moder - премодерация, check - проверяется, repeat - повтор, nodesc - не оформлено, copyright - закрыто правообладателем, close - закрыто, absorb - поглощено. Скачать торрент можно (желтый): nocheck - не проверено, neededit - недооформлено, doubtful - сомнительно, temp - временная Скачать торрент можно (зеленый): ok - проверено """ # INDEX if id is None: html = self.http.get('http://rutracker.lib/forum/index.php') if not html: return html res = [] r = re.compile( r'<div\sid="forums_wrap">(.+)<div\sclass="bottom_info">', re.U | re.S).search(html) if r: r = re.compile( r'<h4\sclass="forumlink"><a\shref="viewforum\.php\?f=([0-9]+)">(.+?)</a></h4>', re.U | re.S).findall(r.group(1)) if r: res = [{ 'id': int(i), 'name': self.html.string(x), 'type': 'folder' } for i, x in r] if not res: return None return {'pages': (1, 0, 1, 0), 'data': res} else: page_query = '' if page > 1: page_query = '&start=' + str(50 * (page - 1)) html = self.http.get( 'http://rutracker.lib/forum/viewforum.php?f=' + str(id) + '&sort=2' + page_query) if not html: return html pages = self._compile_pages(html) folder = [] torrent = [] group_list = re.compile( r'<table class="[^"]*forumline forum">(.+?)</table>', re.U | re.S).findall(html) if group_list: for group in group_list: # вытаскиваем папки (если есть) r = re.compile( r'<h4\sclass="forumlink"><a\shref="viewforum\.php\?f=([0-9]+)">(.+?)</a></h4>', re.U | re.S).findall(group) if r: folder.extend([{ 'id': int(i), 'name': self.html.string(x), 'type': 'folder' } for i, x in r]) # нарубаем на строчки топиков topic_list = group.split(u'topicSep">') if len(topic_list) > 1: topic_list = topic_list[1:] for html in topic_list: # вытаскиваем id for text in re.compile( r'<tr\sid="tr\-[0-9]+"(.+?)</tr>', re.U | re.S).findall(html): item = self._compile_topic(text) if item: torrent.append(item) folder.extend(torrent) return {'pages': pages, 'data': folder} def search(self, search, folder=None, index=None, ignore=None): """ Поиск по РуТрекеру На вход функции надо подавать следующие параметры: search - [str] поисковая строка запроса (Unicode) folder - [list] список ID директорий, в которых необходимо искать (None - искать везде) Возвращает словарь, аналогичный выводу метода GET """ if isinstance(search, unicode): search = search.encode('windows-1251') # проверяем авторизацию html = self.http.get('http://rutracker.lib/forum/index.php') if not html: return html # готовим запрос для получения дерева разделов if folder: if not isinstance(folder, list) and not isinstance(folder, tuple): folder = [folder] else: if index is not None: if not isinstance(index, list) and not isinstance( index, tuple): index = [index] if ignore is not None: if not isinstance(ignore, list) and not isinstance( ignore, tuple): ignore = [ignore] if not index and not ignore: folder = [] else: folder = self._load_catalog(index, ignore) if not folder: return folder # готовим запрос params = [('nm', search), ('o', 10), ('s', 2), ('prev_my', 0), ('prev_new', 0), ('prev_oop', 0), ('submit', r'Поиск')] params.extend([('f[]', x) for x in folder]) # делаем поиск html = self.http.post('http://rutracker.lib/forum/tracker.php', params) if not html: return html res = [] table = re.compile('id="tor\-tbl">(.+?)</table>', re.U | re.S).search(html) if table: for tr in re.compile('<tr\sclass="tCenter\shl\-tr">(.+?)</tr>', re.U | re.S).findall(table.group(1)): item = self._compile_topic(tr, True) if item: res.append(item) return {'pages': (1, 0, 1, 0), 'data': res} def profile(self, id): """ Получение дополнительной информации о раздачи На вход функции надо подавать следующие параметры: id - [int] id топика с раздачей Возвращает словарь, состоящий из: descript - [str] описание на RuTracker cover - [str] url обложки screenshot - [list] Список url скриншотов """ return self.cache_profile.get('profile:' + str(id), self._profile, id) def comment(self, id, page=1): """ Получение комментариев раздачи На вход функции надо подавать следующие параметры: id - [int] id топика page - [int] номер страницы Возвращает словарь, состоящий из следующих полей: pages - [list] кортеж [int] для навигации = (кол-во страниц, предыдущая, текущая, следующая) data - [list] данные выборки - список словарей, состоящих из следующих полей: nick - [str] ник автора комментария usertime - [str] стаж юзера count - [str] кол-во сообщений у юзера location - [str] откуда юзер time - [str] время добавления комментария message - [str] комментарий """ page_query = '' if page > 1: page_query = '&start=' + str(30 * (page - 1)) html = self.http.get('http://rutracker.lib/forum/viewtopic.php?t=' + str(id) + page_query) if not html: return html res = {'pages': self._compile_pages(html), 'comments': []} # нарубаем страницу по постам rows = re.compile( '<tbody id="post_[0-9]+" class="row1|2">(.+?)<!\-\-/post_body\-\->', re.U | re.S).findall(html) if rows: if page == 1: rows.pop(0) if rows: # функция для очистки комментариев def _def_subn1(m): return u'<div class="q-wrap"><div class="q">' + self.html.string( m.group(1)) + u':\n' def _def_subn2(m): r = u'[BR][I]' + m.group(1).replace(u'[I]', u'').replace( u'[/I]', u'') + u'[/I][BR]' n = 1 while n: r, n = re.compile(u'\[BR\]\[BR\]', re.U | re.S).subn(u'[BR]', r) return r for html in rows: comment = { 'nick': None, 'usertime': None, 'count': None, 'location': u'', 'time': None, 'message': None } # вытаскиваем ник r = re.compile('<p class="nick[^>]+>([^<]+)</p>', re.U).search(html) if r: comment['nick'] = self.html.string(r.group(1).strip()) # смотрим стаж r = re.compile( u'<p class="joined"><em>Стаж:</em>([^<]+)</p>', re.U).search(html) if r: comment['usertime'] = r.group(1).strip() # смотрим кол-во коментов у юзера r = re.compile( u'<p class="posts"><em>Сообщений:</em>([^<]+)</p>', re.U).search(html) if r: comment['count'] = r.group(1).strip() # смотрим город юзера r = re.compile( u'<p class="from"><em>Откуда:</em>([^<]+)</p>', re.U).search(html) if r: comment['location'] = r.group(1).strip() # смотрим страну юзера r = re.compile( '<p class="flag"><img [^>]*title="([^"]+)"[^>]*></p>', re.U).search(html) if r: if comment['location']: comment['location'] += u', ' comment['location'] += r.group(1).strip() # смотрим время коммента r = re.compile( '<a class="small" href="\./viewtopic.php\?p=[^>]+>([0-9]{1,2}\-[^\-]+\-[0-9]{2} [0-9]{1,2}\:[0-9]{1,2})</a>', re.U).search(html) if r: comment['time'] = r.group(1).strip() # вытаскиваем тело коммента r = re.compile('<div class="post_body"[^>]+>(.+)$', re.U | re.S).search(html) if r: html = r.group(1).strip() if html: # заменяем что можем... for reg, rep in ( ('<span class="post\-b">([^(?:</span>)]+)</span>', u'[B]\g<1>[/B]'), ): html = re.compile(reg, re.U | re.S).sub(rep, html) # конвертируем цитаты html, n = re.compile( '<div class="q-wrap">\s*<div class="q" head="([^"]+)">', re.U | re.S).subn(_def_subn1, html) n = 1 while n: html, n = re.compile( '<div class="q-wrap">\s*<div class="q">(.+?)</div>\s*</div>', re.U | re.S).subn(_def_subn2, html) # прогоняем через полную очистку comment['message'] = self.html.text(html) if comment['nick'] and comment['message']: res['comments'].append(comment) return res def download(self, id): """ Скачивание торрента раздачи На вход функции надо подавать следующие параметры: id - [str] топика с раздачей Возвращает торрент или None (в случае неудачи) """ return self.http.download(id) def hash(self, id): """ Получение инфо-хеша раздачи На вход функции надо подавать следующие параметры: id - [str] топика с раздачей Возвращает шеснадцатеричное число хэша (в виде строки) или None (в случае неудачи) """ return self.cache_profile.get('hash:' + str(id), self._hash, id) def magnet(self, id): """ Получение инфо-хеша раздачи На вход функции надо подавать следующие параметры: id - [str] топика с раздачей Возвращает шеснадцатеричное число хэша (в виде строки) или None (в случае неудачи) """ hash = self.hash(id) if hash: return 'magnet:?xt=urn:btih:' + hash return hash # PRIVATE def _compile_pages(self, text): r = re.compile( u'<p style="float\: left">Страница <b>([0-9]+)</b> из <b>([0-9]+)</b></p>', re.U | re.S).search(text) if r: current = int(r.group(1)) total = int(r.group(2)) next = current + 1 if next > total: next = 0 return total, current - 1, current, next return 1, 0, 1, 0 def _compile_topic(self, text, is_search=False): r = re.compile( r'<a\s[^>]*href="viewtopic\.php\?t=([0-9]+)"[^>]*>(.+?)</a>', re.U | re.S).search(text) if r: id = r.group(1) name = self.html.string(r.group(2)) r = re.compile( r'<a[^>]+href="dl\.php\?t=' + id + '"[^>]*>(.+?)</a>', re.U | re.S).search(text) if r: size = self._compile_size(r.group(1)) if size and name: item = self._create_torrent(int(id), name) item['size'] = size r = re.compile(r'"tor-icon[^>]+>([^<]+)<', re.U | re.S).search(text) if r: stat = r.group(1) try: status = self.status[stat] except KeyError: pass else: item['status'] = self.html.char(stat) item['status_human'] = status if is_search: item['comment'] = -1 query = (( 'download', '<td\sclass="row4\ssmall">([0-9]+)</td>' ), ( 'seeder', '<b class="seedmed">([0-9]+)</b></td>' ), ('leecher', '<td\sclass="row4 leechmed"[^>]+><b>([0-9]+)</b></td>' )) else: query = (( 'comment', u'<span title="Ответов">([0-9]+)</span>' ), ('download', u'title="Торрент скачан">[^<]*<b>([0-9]+)</b>[^<]*</p>' ), ('seeder', 'title="Seeders"><b>([0-9]+)</b></span>'), ('leecher', 'title="Leechers"><b>([0-9]+)</b></span>')) for tag, reg in query: r = re.compile(reg, re.U | re.S).search(text) if r: item[tag] = int(r.group(1)) return item return None def _create_torrent(self, id, name): return { 'id': id, 'name': name, 'type': 'torrent', 'size': 0, 'seeder': 0, 'leecher': 0, 'download': 0, 'comment': 0, 'status': None, 'status_human': None } def _compile_size(self, text): text = self.html.string(text.replace(u'↓', u'')) if text: text = text.lower() prefix = 1 for p, v in ((u'kb', 1024), (u'mb', 1024 * 1024), (u'gb', 1024 * 1024 * 1024), (u'tb', 1024 * 1024 * 1024 * 1024)): if text.find(p) != -1: prefix = v text = text.replace(p, u'').strip() break num = self.re['is_float'].search(text) if num: return int(float(prefix) * float(num.group(1))) num = self.re['is_int'].search(text) if num: return prefix * int(num.group(1)) return None def _hash(self, id): html = self.http.get('http://rutracker.lib/forum/viewtopic.php?t=' + str(id)) if not html: return False, html r = self.re['hash'].search(html) if not r: return False, None return True, str(r.group(1)) def _profile(self, id): html = self.http.guest('http://rutracker.lib/forum/viewtopic.php?t=' + str(id)) if not html: return False, html res = {'descript': None, 'cover': None, 'screenshot': None} r = re.compile( '<div class="post_body"[^>]+>(.+?)<legend>Download</legend>', re.U | re.S).search(html) if r: html = r.group(1) # ищем коверы (перебирая все возможные варианты хостингов картинок) for api in (self.pic_hosting_fastpic, ): cover = api('cover', html) if cover: res['cover'] = cover break # вытаскиваем блок со скриншотами r = re.compile(u'<span>Скриншоты</span></div>(.+?)</div>', re.U | re.S).search(html) if r: body = r.group(1) # ищем скрины (перебирая все возможные варианты хостингов картинок) for api in (self.pic_hosting_fastpic, ): screenshot = api('screenshot', body) if screenshot: res['screenshot'] = screenshot break # пытаемся получить текст описания # режем и заменяем все что можем... for reg, rep in ( (u'<div class="sp\-wrap">.+?<div class="sp-body">.+?</div>', u''), # удаляем все спойлеры (u'<var[^>]+>[^<]+</var>', u''), # удаляем все изображения (u'<span class="post\-hr">\-</span>', u'\n'), # удаляем HR (u'<span class="post\-b">([^<]+)</span>', u'[COLOR FF0DA09E]\g<1>[/COLOR]') # заменяем болды ): html = re.compile(reg, re.U | re.S).sub(rep, html) # прогоняем через полную очистку html = self.html.text(html) if html: res['descript'] = html return True, res # CATALOG def _load_catalog(self, index, ignore): catalog = self.cache_catalog.get('catalog', self._load_catalog_http) if not catalog: return [] res = [] for key, folders in catalog.iteritems(): if index is None or key in index: res.extend([x for x in folders if x not in ignore]) return res def _load_catalog_http(self): html = self.http.get('http://rutracker.lib/forum/tracker.php') if not html: return html r = re.compile('<select id="fs-main"(.+?)</select>', re.U | re.S).search(html) if not r: return None res = {} root = None for cat, is_root_forum in re.compile( '<option id="fs\-([0-9]+)"([^>]+)>', re.U).findall(r.group(1)): cat = int(cat) if is_root_forum.find('root_forum') != -1: root = cat res[root] = [cat] elif root: res[root].append(cat) return (86400 if res else False), res # day # SCREENSHOT def pic_hosting_fastpic(self, img, html): if img == 'cover': r = re.compile( '<var[^>]+class="postImg postImgAligned img\-right"[^>]+title="(http\://[0-9a-z]+\.fastpic\.ru/big/[0-9a-f/]+\.[a-z]{3,4})"[^>]*>', re.U | re.S).search(html) if r: return r.group(1) return None else: res = [] for r in re.compile( '<a[^>]*href="http\://fastpic\.ru/view/[^\.]+\.([a-z]{3,4})\.html"[^>]*>[.]*?<var[^>]+title="(http\://[0-9a-z]+\.fastpic\.ru/thumb/[0-9a-f/]+)\.[a-z]{3,4}"[^>]*>', re.U | re.S).findall(html): res.append('.'.join([r[1].replace('thumb', 'big'), r[0]])) return res if res else None