566 lines
20 KiB
Python
566 lines
20 KiB
Python
# -*- coding: utf-8 -*-
|
||
|
||
import re
|
||
import time
|
||
import urllib
|
||
|
||
from net import HTTP
|
||
from cache import Cache
|
||
from html import Clear
|
||
import kinopoisk.LOGGER
|
||
import kinopoisk.pageparser
|
||
import kinopoisk.common
|
||
|
||
GENRE = {
|
||
'anime': 1750,
|
||
'biography': 22,
|
||
'action': 3,
|
||
'western': 13,
|
||
'military': 19,
|
||
'detective': 17,
|
||
'children': 456,
|
||
'for adults': 20,
|
||
'documentary': 12,
|
||
'drama': 8,
|
||
'game': 27,
|
||
'history': 23,
|
||
'comedy': 6,
|
||
'concert': 1747,
|
||
'short': 15,
|
||
'criminal': 16,
|
||
'romance': 7,
|
||
'music': 21,
|
||
'cartoon': 14,
|
||
'musical': 9,
|
||
'news': 28,
|
||
'adventures': 10,
|
||
'realitytv': 25,
|
||
'family': 11,
|
||
'sports': 24,
|
||
'talk shows': 26,
|
||
'thriller': 4,
|
||
'horror': 1,
|
||
'fiction': 2,
|
||
'filmnoir': 18,
|
||
'fantasy': 5
|
||
}
|
||
|
||
COUNTRIES = (
|
||
(0, u'Все'),
|
||
(2, u'Россия'),
|
||
(1, u'США'),
|
||
(13, u'СССР'),
|
||
(25, u'Австралия'),
|
||
(57, u'Австрия'),
|
||
(136, u'Азербайджан'),
|
||
(120, u'Албания'),
|
||
(20, u'Алжир'),
|
||
(1026, u'Американские Виргинские острова'),
|
||
(139, u'Ангола'),
|
||
(159, u'Андорра'),
|
||
(1044, u'Антарктида'),
|
||
(1030, u'Антигуа и Барбуда'),
|
||
(1009, u'Антильские Острова'),
|
||
(24, u'Аргентина'),
|
||
(89, u'Армения'),
|
||
(175, u'Аруба'),
|
||
(113, u'Афганистан'),
|
||
(124, u'Багамы'),
|
||
(75, u'Бангладеш'),
|
||
(105, u'Барбадос'),
|
||
(164, u'Бахрейн'),
|
||
(69, u'Беларусь'),
|
||
(173, u'Белиз'),
|
||
(41, u'Бельгия'),
|
||
(140, u'Бенин'),
|
||
(109, u'Берег Слоновой кости'),
|
||
(1004, u'Бермуды'),
|
||
(148, u'Бирма'),
|
||
(63, u'Болгария'),
|
||
(118, u'Боливия'),
|
||
(178, u'Босния'),
|
||
(39, u'Босния-Герцеговина'),
|
||
(145, u'Ботсвана'),
|
||
(10, u'Бразилия'),
|
||
(92, u'Буркина-Фасо'),
|
||
(162, u'Бурунди'),
|
||
(114, u'Бутан'),
|
||
(1059, u'Вануату'),
|
||
(11, u'Великобритания'),
|
||
(49, u'Венгрия'),
|
||
(72, u'Венесуэла'),
|
||
(1043, u'Восточная Сахара'),
|
||
(52, u'Вьетнам'),
|
||
(170, u'Вьетнам Северный'),
|
||
(127, u'Габон'),
|
||
(99, u'Гаити'),
|
||
(165, u'Гайана'),
|
||
(1040, u'Гамбия'),
|
||
(144, u'Гана'),
|
||
(135, u'Гватемала'),
|
||
(129, u'Гвинея'),
|
||
(116, u'Гвинея-Бисау'),
|
||
(3, u'Германия'),
|
||
(60, u'Германия (ГДР)'),
|
||
(18, u'Германия (ФРГ)'),
|
||
(1022, u'Гибралтар'),
|
||
(112, u'Гондурас'),
|
||
(28, u'Гонконг'),
|
||
(117, u'Гренландия'),
|
||
(55, u'Греция'),
|
||
(61, u'Грузия'),
|
||
(142, u'Гуаделупа'),
|
||
(1045, u'Гуам'),
|
||
(4, u'Дания'),
|
||
(1037, u'Демократическая Республика Конго'),
|
||
(1028, u'Джибути'),
|
||
(1031, u'Доминика'),
|
||
(128, u'Доминикана'),
|
||
(101, u'Египет'),
|
||
(155, u'Заир'),
|
||
(133, u'Замбия'),
|
||
(104, u'Зимбабве'),
|
||
(42, u'Израиль'),
|
||
(29, u'Индия'),
|
||
(73, u'Индонезия'),
|
||
(154, u'Иордания'),
|
||
(90, u'Ирак'),
|
||
(48, u'Иран'),
|
||
(38, u'Ирландия'),
|
||
(37, u'Исландия'),
|
||
(15, u'Испания'),
|
||
(14, u'Италия'),
|
||
(169, u'Йемен'),
|
||
(146, u'Кабо-Верде'),
|
||
(122, u'Казахстан'),
|
||
(1051, u'Каймановы острова'),
|
||
(84, u'Камбоджа'),
|
||
(95, u'Камерун'),
|
||
(6, u'Канада'),
|
||
(1002, u'Катар'),
|
||
(100, u'Кения'),
|
||
(64, u'Кипр'),
|
||
(1024, u'Кирибати'),
|
||
(31, u'Китай'),
|
||
(56, u'Колумбия'),
|
||
(1058, u'Коморы'),
|
||
(134, u'Конго'),
|
||
(1014, u'Конго (ДРК)'),
|
||
(156, u'Корея'),
|
||
(137, u'Корея Северная'),
|
||
(26, u'Корея Южная'),
|
||
(1013, u'Косово'),
|
||
(131, u'Коста-Рика'),
|
||
(76, u'Куба'),
|
||
(147, u'Кувейт'),
|
||
(86, u'Кыргызстан'),
|
||
(149, u'Лаос'),
|
||
(54, u'Латвия'),
|
||
(1015, u'Лесото'),
|
||
(176, u'Либерия'),
|
||
(97, u'Ливан'),
|
||
(126, u'Ливия'),
|
||
(123, u'Литва'),
|
||
(125, u'Лихтенштейн'),
|
||
(59, u'Люксембург'),
|
||
(115, u'Маврикий'),
|
||
(67, u'Мавритания'),
|
||
(150, u'Мадагаскар'),
|
||
(153, u'Макао'),
|
||
(80, u'Македония'),
|
||
(1025, u'Малави'),
|
||
(83, u'Малайзия'),
|
||
(151, u'Мали'),
|
||
(1050, u'Мальдивы'),
|
||
(111, u'Мальта'),
|
||
(43, u'Марокко'),
|
||
(102, u'Мартиника'),
|
||
(1042, u'Масаи'),
|
||
(17, u'Мексика'),
|
||
(1041, u'Мелкие отдаленные острова США'),
|
||
(81, u'Мозамбик'),
|
||
(58, u'Молдова'),
|
||
(22, u'Монако'),
|
||
(132, u'Монголия'),
|
||
(1034, u'Мьянма'),
|
||
(91, u'Намибия'),
|
||
(106, u'Непал'),
|
||
(157, u'Нигер'),
|
||
(110, u'Нигерия'),
|
||
(12, u'Нидерланды'),
|
||
(138, u'Никарагуа'),
|
||
(35, u'Новая Зеландия'),
|
||
(1006, u'Новая Каледония'),
|
||
(33, u'Норвегия'),
|
||
(119, u'ОАЭ'),
|
||
(1019, u'Оккупированная Палестинская территория'),
|
||
(1003, u'Оман'),
|
||
(1052, u'Остров Мэн'),
|
||
(1047, u'Остров Святой Елены'),
|
||
(1007, u'острова Теркс и Кайкос'),
|
||
(74, u'Пакистан'),
|
||
(1057, u'Палау'),
|
||
(78, u'Палестина'),
|
||
(107, u'Панама'),
|
||
(163, u'Папуа - Новая Гвинея'),
|
||
(143, u'Парагвай'),
|
||
(23, u'Перу'),
|
||
(32, u'Польша'),
|
||
(36, u'Португалия'),
|
||
(82, u'Пуэрто Рико'),
|
||
(1036, u'Реюньон'),
|
||
(1033, u'Российская империя'),
|
||
(2, u'Россия'),
|
||
(103, u'Руанда'),
|
||
(46, u'Румыния'),
|
||
(121, u'Сальвадор'),
|
||
(1039, u'Самоа'),
|
||
(1011, u'Сан-Марино'),
|
||
(158, u'Саудовская Аравия'),
|
||
(1029, u'Свазиленд'),
|
||
(1010, u'Сейшельские острова'),
|
||
(65, u'Сенегал'),
|
||
(1055, u'Сент-Винсент и Гренадины'),
|
||
(1049, u'Сент-Люсия'),
|
||
(177, u'Сербия'),
|
||
(174, u'Сербия и Черногория'),
|
||
(1021, u'Сиам'),
|
||
(45, u'Сингапур'),
|
||
(98, u'Сирия'),
|
||
(94, u'Словакия'),
|
||
(40, u'Словения'),
|
||
(160, u'Сомали'),
|
||
(13, u'СССР'),
|
||
(167, u'Судан'),
|
||
(171, u'Суринам'),
|
||
(1, u'США'),
|
||
(1023, u'Сьерра-Леоне'),
|
||
(70, u'Таджикистан'),
|
||
(44, u'Таиланд'),
|
||
(27, u'Тайвань'),
|
||
(130, u'Танзания'),
|
||
(161, u'Того'),
|
||
(1012, u'Тонго'),
|
||
(88, u'Тринидад и Тобаго'),
|
||
(1053, u'Тувалу'),
|
||
(50, u'Тунис'),
|
||
(152, u'Туркменистан'),
|
||
(68, u'Турция'),
|
||
(172, u'Уганда'),
|
||
(71, u'Узбекистан'),
|
||
(62, u'Украина'),
|
||
(79, u'Уругвай'),
|
||
(1008, u'Фарерские острова'),
|
||
(1038, u'Федеративные Штаты Микронезии'),
|
||
(166, u'Фиджи'),
|
||
(47, u'Филиппины'),
|
||
(7, u'Финляндия'),
|
||
(8, u'Франция'),
|
||
(1032, u'Французская Гвиана'),
|
||
(1046, u'Французская Полинезия'),
|
||
(85, u'Хорватия'),
|
||
(141, u'ЦАР'),
|
||
(77, u'Чад'),
|
||
(1020, u'Черногория'),
|
||
(34, u'Чехия'),
|
||
(16, u'Чехословакия'),
|
||
(51, u'Чили'),
|
||
(21, u'Швейцария'),
|
||
(5, u'Швеция'),
|
||
(108, u'Шри-Ланка'),
|
||
(96, u'Эквадор'),
|
||
(87, u'Эритрея'),
|
||
(53, u'Эстония'),
|
||
(168, u'Эфиопия'),
|
||
(30, u'ЮАР'),
|
||
(19, u'Югославия'),
|
||
(66, u'Югославия (ФР)'),
|
||
(93, u'Ямайка'),
|
||
(9, u'Япония')
|
||
)
|
||
|
||
|
||
class KinoPoisk:
|
||
"""
|
||
|
||
API:
|
||
scraper - скрапер
|
||
movie - профайл фильма
|
||
search - поиск фильма
|
||
best - поиск лучших фильмов
|
||
person - поиск персон
|
||
work - информация о работах персоны
|
||
|
||
"""
|
||
|
||
def __init__(self, language='ru'):
|
||
dbname = 'kinopoisk.%s.db' % language
|
||
self.cache = Cache(dbname, 1.0)
|
||
self.html = Clear()
|
||
|
||
self.timeout = 60.0
|
||
|
||
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, search, year=None):
|
||
|
||
try:
|
||
if not isinstance(search, list):
|
||
search = [search]
|
||
tag = 'scraper:' + urllib.quote_plus(":".join(search).encode('utf8'))
|
||
except:
|
||
return None
|
||
else:
|
||
|
||
if year:
|
||
tag += ':' + str(year)
|
||
|
||
id = self.cache.get(tag, self._scraper, search, year)
|
||
if not id:
|
||
return None
|
||
|
||
return self.movie(id)
|
||
|
||
def movie(self, id):
|
||
id = str(id)
|
||
return self.cache.get('movie:' + id, self._movie, id)
|
||
|
||
def search(self, search, year):
|
||
return self._search_movie(search, year)
|
||
|
||
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
|
||
|
||
def _search_movie(self, search, year=None):
|
||
parser = kinopoisk.pageparser.PageParser(kinopoisk.LOGGER, isDebug=True)
|
||
orginalname = search[0]
|
||
if len(search) > 1:
|
||
name = search[1]
|
||
else:
|
||
name = None
|
||
results = parser.fetchAndParseSearchResults(orginalname, year, name)
|
||
if results and results[0][3] > 70:
|
||
return results[0][0]
|
||
|
||
def _scraper(self, search, year):
|
||
timeout = True
|
||
|
||
# если фильм свежий, то кладем в кэш НЕ на долго (могут быть обновления на сайте)
|
||
if year and year > time.gmtime(time.time()).tm_year:
|
||
timeout = 7 * 24 * 60 * 60 * 4 # 4 week
|
||
|
||
movie_id = self._search_movie(search, year)
|
||
|
||
if movie_id is None:
|
||
# сохраняем пустой результат на 4 week
|
||
return 7 * 24 * 60 * 60 * 4, None
|
||
|
||
else:
|
||
return timeout, movie_id
|
||
|
||
def _movie(self, id):
|
||
response = self.http.fetch('http://www.kinopoisk.ru/film/' + id + '/', headers=self.headers,
|
||
timeout=self.timeout)
|
||
if response.error:
|
||
return False, None
|
||
|
||
html = response.body.decode('windows-1251')
|
||
|
||
res = {
|
||
'icon': None,
|
||
'thumbnail': None,
|
||
'properties': {
|
||
'fanart_image': None,
|
||
},
|
||
'info': {
|
||
'count': int(id)
|
||
}
|
||
}
|
||
|
||
# имя, оригинальное имя, девиз, цензура, год, top250
|
||
# runtime - длительность фильма (в отдельную переменную, иначе не видно размер файла)
|
||
for tag, reg, cb in (
|
||
('title', '<title>(.+?)</title>', self.html.string),
|
||
('originaltitle', 'itemprop="alternativeHeadline">([^<]*)</span>', self.html.string),
|
||
('tagline', '<td style="color\: #555">«(.+?)»</td></tr>', self.html.string),
|
||
('mpaa', 'images/mpaa/([^\.]+).gif', self.html.string),
|
||
('runtime', '<td class="time" id="runtime">[^<]+<span style="color\: #999">/</span>([^<]+)</td>',
|
||
self.html.string),
|
||
('year', '<a href="/lists/m_act%5Byear%5D/([0-9]+)/"', int),
|
||
('top250', 'Топ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] = 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(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['thumbnail'] = res['icon'] = '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,
|
||
timeout=self.timeout)
|
||
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, timeout=self.timeout)
|
||
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['properties']['fanart_image'] = r.group(1).strip()
|
||
|
||
|
||
# если нет фанарта (обоев), то пробуем получить кадры
|
||
if not res['properties']['fanart_image'] and menu.find('/film/' + id + '/stills/') != -1:
|
||
response = self.http.fetch('http://www.kinopoisk.ru/film/' + id + '/stills/', headers=self.headers,
|
||
timeout=self.timeout)
|
||
if not response.error:
|
||
html = response.body.decode('windows-1251')
|
||
fanart = re.compile(
|
||
'<a href="/picture/([0-9]+)/"><img src="[^<]+</a>[^<]+<b><i>([0-9]+)×([0-9]+)</i>',
|
||
re.U).findall(html)
|
||
if fanart:
|
||
fanart.sort(cmp=lambda (id1, size1, t1), (id2, size2, t2): cmp(int(size1), int(size2)))
|
||
|
||
# пробуем взять максимально подходящее
|
||
fanart_best = [x for x in fanart if int(x[1]) <= 1280 and int(x[1]) > int(x[2])]
|
||
if fanart_best:
|
||
fanart = fanart_best
|
||
|
||
response = self.http.fetch('http://www.kinopoisk.ru/picture/' + fanart[-1][0] + '/',
|
||
headers=self.headers, timeout=self.timeout)
|
||
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['properties']['fanart_image'] = 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,
|
||
timeout=self.timeout)
|
||
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)
|
||
|
||
timeout = True
|
||
# если фильм свежий, то кладем в кэш НЕ на долго (могут быть обновления на сайте)
|
||
if 'year' not in res['info'] or not res['properties']['fanart_image'] \
|
||
or int(res['info']['year']) > time.gmtime(time.time()).tm_year:
|
||
timeout = 7 * 24 * 60 * 60 * 4 # 4 week
|
||
|
||
return timeout, res
|