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]+)&mdash;[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">&laquo;(.+?)&raquo;</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]+)&times;([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
Exemple #2
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',
            '&copy;': 'copyright',
            '&#8719;': 'moder',
            'x': 'close',
            '&sum;': 'absorb',
            
            '#': 'doubtful',
            '*': 'nocheck',
            '?': 'neededit',
            'T': 'temp',
            
            '&radic;': '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'&#8595;', 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
Exemple #3
0
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]+)&mdash;[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">&laquo;(.+?)&raquo;</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]+)&times;([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
Exemple #4
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',
            '&copy;': 'copyright',
            '&#8719;': 'moder',
            'x': 'close',
            '&sum;': 'absorb',
            '#': 'doubtful',
            '*': 'nocheck',
            '?': 'neededit',
            'T': 'temp',
            '&radic;': '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'&#8595;', 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