diff --git a/app/modules/bangumi/__init__.py b/app/modules/bangumi/__init__.py index 3246d03a..4721c35f 100644 --- a/app/modules/bangumi/__init__.py +++ b/app/modules/bangumi/__init__.py @@ -82,6 +82,29 @@ class BangumiModule(_ModuleBase): return None + async def async_recognize_media(self, bangumiid: int = None, + **kwargs) -> Optional[MediaInfo]: + """ + 识别媒体信息(异步版本) + :param bangumiid: 识别的Bangumi ID + :return: 识别的媒体信息,包括剧集信息 + """ + if not bangumiid: + return None + + # 直接查询详情 + info = await self.async_bangumi_info(bangumiid=bangumiid) + if info: + # 赋值TMDB信息并返回 + mediainfo = MediaInfo(bangumi_info=info) + logger.info(f"{bangumiid} Bangumi识别结果:{mediainfo.type.value} " + f"{mediainfo.title_year}") + return mediainfo + else: + logger.info(f"{bangumiid} 未匹配到Bangumi媒体信息") + + return None + def search_medias(self, meta: MetaBase) -> Optional[List[MediaInfo]]: """ 搜索媒体信息 @@ -99,6 +122,23 @@ class BangumiModule(_ModuleBase): or meta.name.lower() in str(info.get("name_cn")).lower()] return [] + async def async_search_medias(self, meta: MetaBase) -> Optional[List[MediaInfo]]: + """ + 搜索媒体信息(异步版本) + :param meta: 识别的元数据 + :reutrn: 媒体信息 + """ + if settings.SEARCH_SOURCE and "bangumi" not in settings.SEARCH_SOURCE: + return None + if not meta.name: + return [] + infos = await self.bangumiapi.async_search(meta.name) + if infos: + return [MediaInfo(bangumi_info=info) for info in infos + if meta.name.lower() in str(info.get("name")).lower() + or meta.name.lower() in str(info.get("name_cn")).lower()] + return [] + def bangumi_info(self, bangumiid: int) -> Optional[dict]: """ 获取Bangumi信息 @@ -110,6 +150,17 @@ class BangumiModule(_ModuleBase): logger.info(f"开始获取Bangumi信息:{bangumiid} ...") return self.bangumiapi.detail(bangumiid) + async def async_bangumi_info(self, bangumiid: int) -> Optional[dict]: + """ + 获取Bangumi信息(异步版本) + :param bangumiid: BangumiID + :return: Bangumi信息 + """ + if not bangumiid: + return None + logger.info(f"开始获取Bangumi信息:{bangumiid} ...") + return await self.bangumiapi.async_detail(bangumiid) + def bangumi_calendar(self) -> Optional[List[MediaInfo]]: """ 获取Bangumi每日放送 @@ -119,6 +170,15 @@ class BangumiModule(_ModuleBase): return [MediaInfo(bangumi_info=info) for info in infos] return [] + async def async_bangumi_calendar(self) -> Optional[List[MediaInfo]]: + """ + 获取Bangumi每日放送(异步版本) + """ + infos = await self.bangumiapi.async_calendar() + if infos: + return [MediaInfo(bangumi_info=info) for info in infos] + return [] + def bangumi_credits(self, bangumiid: int) -> List[schemas.MediaPerson]: """ 根据TMDBID查询电影演职员表 @@ -129,6 +189,16 @@ class BangumiModule(_ModuleBase): return [schemas.MediaPerson(source='bangumi', **person) for person in persons] return [] + async def async_bangumi_credits(self, bangumiid: int) -> List[schemas.MediaPerson]: + """ + 根据TMDBID查询电影演职员表(异步版本) + :param bangumiid: BangumiID + """ + persons = await self.bangumiapi.async_credits(bangumiid) + if persons: + return [schemas.MediaPerson(source='bangumi', **person) for person in persons] + return [] + def bangumi_recommend(self, bangumiid: int) -> List[MediaInfo]: """ 根据BangumiID查询推荐电影 @@ -139,6 +209,16 @@ class BangumiModule(_ModuleBase): return [MediaInfo(bangumi_info=subject) for subject in subjects] return [] + async def async_bangumi_recommend(self, bangumiid: int) -> List[MediaInfo]: + """ + 根据BangumiID查询推荐电影(异步版本) + :param bangumiid: BangumiID + """ + subjects = await self.bangumiapi.async_subjects(bangumiid) + if subjects: + return [MediaInfo(bangumi_info=subject) for subject in subjects] + return [] + def bangumi_person_detail(self, person_id: int) -> Optional[schemas.MediaPerson]: """ 获取人物详细信息 @@ -156,6 +236,23 @@ class BangumiModule(_ModuleBase): }) return None + async def async_bangumi_person_detail(self, person_id: int) -> Optional[schemas.MediaPerson]: + """ + 获取人物详细信息(异步版本) + :param person_id: 豆瓣人物ID + """ + personinfo = await self.bangumiapi.async_person_detail(person_id) + if personinfo: + return schemas.MediaPerson(source='bangumi', **{ + "id": personinfo.get("id"), + "name": personinfo.get("name"), + "images": personinfo.get("images"), + "biography": personinfo.get("summary"), + "birthday": personinfo.get("birth_day"), + "gender": personinfo.get("gender") + }) + return None + def bangumi_person_credits(self, person_id: int) -> List[MediaInfo]: """ 根据TMDBID查询人物参演作品 @@ -166,6 +263,16 @@ class BangumiModule(_ModuleBase): return [MediaInfo(bangumi_info=credit) for credit in credits_info] return [] + async def async_bangumi_person_credits(self, person_id: int) -> List[MediaInfo]: + """ + 根据TMDBID查询人物参演作品(异步版本) + :param person_id: 人物ID + """ + credits_info = await self.bangumiapi.async_person_credits(person_id=person_id) + if credits_info: + return [MediaInfo(bangumi_info=credit) for credit in credits_info] + return [] + def bangumi_discover(self, **kwargs) -> Optional[List[MediaInfo]]: """ 发现Bangumi番剧 @@ -174,3 +281,12 @@ class BangumiModule(_ModuleBase): if infos: return [MediaInfo(bangumi_info=info) for info in infos] return [] + + async def async_bangumi_discover(self, **kwargs) -> Optional[List[MediaInfo]]: + """ + 发现Bangumi番剧(异步版本) + """ + infos = await self.bangumiapi.async_discover(**kwargs) + if infos: + return [MediaInfo(bangumi_info=info) for info in infos] + return [] diff --git a/app/modules/bangumi/bangumi.py b/app/modules/bangumi/bangumi.py index b5378c6b..93663906 100644 --- a/app/modules/bangumi/bangumi.py +++ b/app/modules/bangumi/bangumi.py @@ -5,7 +5,7 @@ import requests from app.core.cache import cached from app.core.config import settings -from app.utils.http import RequestUtils +from app.utils.http import RequestUtils, AsyncRequestUtils class BangumiApi(object): @@ -29,6 +29,7 @@ class BangumiApi(object): def __init__(self): self._session = requests.Session() self._req = RequestUtils(session=self._session) + self._async_req = AsyncRequestUtils() @cached(maxsize=settings.CONF.bangumi, ttl=settings.CONF.meta) def __invoke(self, url, key: Optional[str] = None, **kwargs): @@ -46,6 +47,22 @@ class BangumiApi(object): print(e) return None + @cached(maxsize=settings.CONF.bangumi, ttl=settings.CONF.meta) + async def __async_invoke(self, url, key: Optional[str] = None, **kwargs): + req_url = self._base_url + url + params = {} + if kwargs: + params.update(kwargs) + resp = await self._async_req.get_res(url=req_url, params=params) + try: + if not resp: + return None + result = resp.json() + return result.get(key) if key else result + except Exception as e: + print(e) + return None + def search(self, name): """ 搜索媒体信息 @@ -55,6 +72,15 @@ class BangumiApi(object): return result.get("list") return [] + async def async_search(self, name): + """ + 搜索媒体信息(异步版本) + """ + result = await self.__async_invoke("search/subject/%s" % name) + if result: + return result.get("list") + return [] + def calendar(self): """ 获取每日放送,返回items @@ -153,12 +179,29 @@ class BangumiApi(object): ret_list.extend(item.get("items") or []) return ret_list + async def async_calendar(self): + """ + 获取每日放送,返回items(异步版本) + """ + ret_list = [] + result = await self.__async_invoke(self._urls["calendar"], _ts=datetime.strftime(datetime.now(), '%Y%m%d')) + if result: + for item in result: + ret_list.extend(item.get("items") or []) + return ret_list + def detail(self, bid: int): """ 获取番剧详情 """ return self.__invoke(self._urls["detail"] % bid, _ts=datetime.strftime(datetime.now(), '%Y%m%d')) + async def async_detail(self, bid: int): + """ + 获取番剧详情(异步版本) + """ + return await self.__async_invoke(self._urls["detail"] % bid, _ts=datetime.strftime(datetime.now(), '%Y%m%d')) + def credits(self, bid: int): """ 获取番剧人物 @@ -175,18 +218,48 @@ class BangumiApi(object): ret_list.append(actor_info) return ret_list + async def async_credits(self, bid: int): + """ + 获取番剧人物(异步版本) + """ + ret_list = [] + result = await self.__async_invoke(self._urls["characters"] % bid, + _ts=datetime.strftime(datetime.now(), '%Y%m%d')) + if result: + for item in result: + character_id = item.get("id") + actors = item.get("actors") + if character_id and actors and actors[0]: + actor_info = actors[0] + actor_info.update({'career': [item.get('name')]}) + ret_list.append(actor_info) + return ret_list + def subjects(self, bid: int): """ 获取关联条目信息 """ return self.__invoke(self._urls["subjects"] % bid, _ts=datetime.strftime(datetime.now(), '%Y%m%d')) + async def async_subjects(self, bid: int): + """ + 获取关联条目信息(异步版本) + """ + return await self.__async_invoke(self._urls["subjects"] % bid, _ts=datetime.strftime(datetime.now(), '%Y%m%d')) + def person_detail(self, person_id: int): """ 获取人物详细信息 """ return self.__invoke(self._urls["person_detail"] % person_id, _ts=datetime.strftime(datetime.now(), '%Y%m%d')) + async def async_person_detail(self, person_id: int): + """ + 获取人物详细信息(异步版本) + """ + return await self.__async_invoke(self._urls["person_detail"] % person_id, + _ts=datetime.strftime(datetime.now(), '%Y%m%d')) + def person_credits(self, person_id: int): """ 获取人物参演作品 @@ -199,6 +272,18 @@ class BangumiApi(object): ret_list.append(item) return ret_list + async def async_person_credits(self, person_id: int): + """ + 获取人物参演作品(异步版本) + """ + ret_list = [] + result = await self.__async_invoke(self._urls["person_credits"] % person_id, + _ts=datetime.strftime(datetime.now(), '%Y%m%d')) + if result: + for item in result: + ret_list.append(item) + return ret_list + def discover(self, **kwargs): """ 发现 @@ -207,6 +292,14 @@ class BangumiApi(object): key="data", _ts=datetime.strftime(datetime.now(), '%Y%m%d'), **kwargs) + async def async_discover(self, **kwargs): + """ + 发现(异步版本) + """ + return await self.__async_invoke(self._urls["discover"], + key="data", + _ts=datetime.strftime(datetime.now(), '%Y%m%d'), **kwargs) + def close(self): if self._session: self._session.close() diff --git a/app/modules/douban/__init__.py b/app/modules/douban/__init__.py index e436201c..c5029e49 100644 --- a/app/modules/douban/__init__.py +++ b/app/modules/douban/__init__.py @@ -485,6 +485,62 @@ class DoubanModule(_ModuleBase): else: return __douban_movie() or __douban_tv() + @rate_limit_exponential(source="douban_info") + async def async_douban_info(self, doubanid: str, mtype: MediaType = None, raise_exception: bool = True) -> Optional[ + dict]: + """ + 获取豆瓣信息(异步版本) + :param doubanid: 豆瓣ID + :param mtype: 媒体类型 + :param raise_exception: 触发速率限制时是否抛出异常 + :return: 豆瓣信息 + """ + + async def __async_douban_tv(): + """ + 获取豆瓣剧集信息(异步版本) + """ + info = await self.doubanapi.async_tv_detail(doubanid) + if info: + if "subject_ip_rate_limit" in info.get("msg", ""): + msg = f"触发豆瓣IP速率限制,错误信息:{info} ..." + logger.warn(msg) + raise APIRateLimitException(msg) + celebrities = await self.doubanapi.async_tv_celebrities(doubanid) + if celebrities: + info["directors"] = celebrities.get("directors") + info["actors"] = celebrities.get("actors") + return info + + async def __async_douban_movie(): + """ + 获取豆瓣电影信息(异步版本) + """ + info = await self.doubanapi.async_movie_detail(doubanid) + if info: + if "subject_ip_rate_limit" in info.get("msg", ""): + msg = f"触发豆瓣IP速率限制,错误信息:{info} ..." + logger.warn(msg) + raise APIRateLimitException(msg) + celebrities = await self.doubanapi.async_movie_celebrities(doubanid) + if celebrities: + info["directors"] = celebrities.get("directors") + info["actors"] = celebrities.get("actors") + return info + + if not doubanid: + return None + logger.info(f"开始获取豆瓣信息:{doubanid} ...") + if mtype == MediaType.TV: + return await __async_douban_tv() + elif mtype == MediaType.MOVIE: + return await __async_douban_movie() + else: + movie_result = await __async_douban_movie() + if movie_result: + return movie_result + return await __async_douban_tv() + def douban_discover(self, mtype: MediaType, sort: str, tags: str, page: int = 1, count: int = 30) -> Optional[List[MediaInfo]]: """ @@ -513,6 +569,34 @@ class DoubanModule(_ModuleBase): and "tv_large.jpg" not in media.poster_path] return [] + async def async_douban_discover(self, mtype: MediaType, sort: str, tags: str, + page: int = 1, count: int = 30) -> Optional[List[MediaInfo]]: + """ + 发现豆瓣电影、剧集(异步版本) + :param mtype: 媒体类型 + :param sort: 排序方式 + :param tags: 标签 + :param page: 页码 + :param count: 数量 + :return: 媒体信息列表 + """ + logger.info(f"开始发现豆瓣 {mtype.value} ...") + if mtype == MediaType.MOVIE: + infos = await self.doubanapi.async_movie_recommend(start=(page - 1) * count, count=count, + sort=sort, tags=tags) + else: + infos = await self.doubanapi.async_tv_recommend(start=(page - 1) * count, count=count, + sort=sort, tags=tags) + if infos and infos.get("items"): + medias = [MediaInfo(douban_info=info) for info in infos.get("items")] + return [media for media in medias if media.poster_path + and "movie_large.jpg" not in media.poster_path + and "tv_normal.png" not in media.poster_path + and "movie_large.jpg" not in media.poster_path + and "tv_normal.jpg" not in media.poster_path + and "tv_large.jpg" not in media.poster_path] + return [] + def movie_showing(self, page: int = 1, count: int = 30) -> List[MediaInfo]: """ 获取正在上映的电影 @@ -523,6 +607,16 @@ class DoubanModule(_ModuleBase): return [MediaInfo(douban_info=info) for info in infos.get("subject_collection_items")] return [] + async def async_movie_showing(self, page: int = 1, count: int = 30) -> List[MediaInfo]: + """ + 获取正在上映的电影(异步版本) + """ + infos = await self.doubanapi.async_movie_showing(start=(page - 1) * count, + count=count) + if infos and infos.get("subject_collection_items"): + return [MediaInfo(douban_info=info) for info in infos.get("subject_collection_items")] + return [] + def tv_weekly_chinese(self, page: int = 1, count: int = 30) -> List[MediaInfo]: """ 获取豆瓣本周口碑国产剧 @@ -533,6 +627,16 @@ class DoubanModule(_ModuleBase): return [MediaInfo(douban_info=info) for info in infos.get("subject_collection_items")] return [] + async def async_tv_weekly_chinese(self, page: int = 1, count: int = 30) -> List[MediaInfo]: + """ + 获取豆瓣本周口碑国产剧(异步版本) + """ + infos = await self.doubanapi.async_tv_chinese_best_weekly(start=(page - 1) * count, + count=count) + if infos: + return [MediaInfo(douban_info=info) for info in infos.get("subject_collection_items")] + return [] + def tv_weekly_global(self, page: int = 1, count: int = 30) -> List[MediaInfo]: """ 获取豆瓣本周口碑外国剧 @@ -543,6 +647,16 @@ class DoubanModule(_ModuleBase): return [MediaInfo(douban_info=info) for info in infos.get("subject_collection_items")] return [] + async def async_tv_weekly_global(self, page: int = 1, count: int = 30) -> List[MediaInfo]: + """ + 获取豆瓣本周口碑外国剧(异步版本) + """ + infos = await self.doubanapi.async_tv_global_best_weekly(start=(page - 1) * count, + count=count) + if infos and infos.get("subject_collection_items"): + return [MediaInfo(douban_info=info) for info in infos.get("subject_collection_items")] + return [] + def tv_animation(self, page: int = 1, count: int = 30) -> List[MediaInfo]: """ 获取豆瓣动画剧 @@ -553,6 +667,16 @@ class DoubanModule(_ModuleBase): return [MediaInfo(douban_info=info) for info in infos.get("subject_collection_items")] return [] + async def async_tv_animation(self, page: int = 1, count: int = 30) -> List[MediaInfo]: + """ + 获取豆瓣动画剧(异步版本) + """ + infos = await self.doubanapi.async_tv_animation(start=(page - 1) * count, + count=count) + if infos and infos.get("subject_collection_items"): + return [MediaInfo(douban_info=info) for info in infos.get("subject_collection_items")] + return [] + def movie_hot(self, page: int = 1, count: int = 30) -> List[MediaInfo]: """ 获取豆瓣热门电影 @@ -563,6 +687,16 @@ class DoubanModule(_ModuleBase): return [MediaInfo(douban_info=info) for info in infos.get("subject_collection_items")] return [] + async def async_movie_hot(self, page: int = 1, count: int = 30) -> List[MediaInfo]: + """ + 获取豆瓣热门电影(异步版本) + """ + infos = await self.doubanapi.async_movie_hot_gaia(start=(page - 1) * count, + count=count) + if infos and infos.get("subject_collection_items"): + return [MediaInfo(douban_info=info) for info in infos.get("subject_collection_items")] + return [] + def tv_hot(self, page: int = 1, count: int = 30) -> List[MediaInfo]: """ 获取豆瓣热门剧集 @@ -573,6 +707,16 @@ class DoubanModule(_ModuleBase): return [MediaInfo(douban_info=info) for info in infos.get("subject_collection_items")] return [] + async def async_tv_hot(self, page: int = 1, count: int = 30) -> List[MediaInfo]: + """ + 获取豆瓣热门剧集(异步版本) + """ + infos = await self.doubanapi.async_tv_hot(start=(page - 1) * count, + count=count) + if infos and infos.get("subject_collection_items"): + return [MediaInfo(douban_info=info) for info in infos.get("subject_collection_items")] + return [] + def search_medias(self, meta: MetaBase) -> Optional[List[MediaInfo]]: """ 搜索媒体信息 @@ -606,6 +750,39 @@ class DoubanModule(_ModuleBase): media.season = meta.begin_season return ret_medias + async def async_search_medias(self, meta: MetaBase) -> Optional[List[MediaInfo]]: + """ + 搜索媒体信息(异步版本) + :param meta: 识别的元数据 + :reutrn: 媒体信息 + """ + if settings.SEARCH_SOURCE and "douban" not in settings.SEARCH_SOURCE: + return None + if not meta.name: + return [] + result = await self.doubanapi.async_search(meta.name) + if not result or not result.get("items"): + return [] + # 返回数据 + ret_medias = [] + for item_obj in result.get("items"): + if meta.type and meta.type != MediaType.UNKNOWN and meta.type.value != item_obj.get("type_name"): + continue + if item_obj.get("type_name") not in (MediaType.TV.value, MediaType.MOVIE.value): + continue + if meta.name not in item_obj.get("target", {}).get("title"): + continue + ret_medias.append(MediaInfo(douban_info=item_obj.get("target"))) + # 将搜索词中的季写入标题中 + if ret_medias and meta.begin_season: + # 小写数据转大写 + season_str = cn2an.an2cn(meta.begin_season, "low") + for media in ret_medias: + if media.type == MediaType.TV: + media.title = f"{media.title} 第{season_str}季" + media.season = meta.begin_season + return ret_medias + def search_persons(self, name: str) -> Optional[List[MediaPerson]]: """ 搜索人物信息 @@ -624,6 +801,24 @@ class DoubanModule(_ModuleBase): }) for item in result.get('items') if name in item.get('target', {}).get('title')] return [] + async def async_search_persons(self, name: str) -> Optional[List[MediaPerson]]: + """ + 搜索人物信息(异步版本) + """ + if not name: + return [] + result = await self.doubanapi.async_person_search(keyword=name) + if result and result.get('items'): + return [MediaPerson(source='douban', **{ + 'id': item.get('target_id'), + 'name': item.get('target', {}).get('title'), + 'url': item.get('target', {}).get('url'), + 'images': item.get('target', {}).get('cover', {}), + 'avatar': (item.get('target', {}).get('cover_img', {}).get('url') + or '').replace("/l/public/", "/s/public/"), + }) for item in result.get('items') if name in item.get('target', {}).get('title')] + return [] + @retry(Exception, 5, 3, 3, logger=logger) @rate_limit_exponential(source="match_doubaninfo") def match_doubaninfo(self, name: str, imdbid: str = None, @@ -696,6 +891,16 @@ class DoubanModule(_ModuleBase): return [MediaInfo(douban_info=info) for info in infos.get("subject_collection_items")] return [] + async def async_movie_top250(self, page: int = 1, count: int = 30) -> List[MediaInfo]: + """ + 获取豆瓣电影TOP250(异步版本) + """ + infos = await self.doubanapi.async_movie_top250(start=(page - 1) * count, + count=count) + if infos and infos.get("subject_collection_items"): + return [MediaInfo(douban_info=info) for info in infos.get("subject_collection_items")] + return [] + def metadata_nfo(self, mediainfo: MediaInfo, season: int = None, **kwargs) -> Optional[str]: """ 获取NFO文件内容文本 diff --git a/app/modules/douban/apiv2.py b/app/modules/douban/apiv2.py index 07b29d1c..ffec6dea 100644 --- a/app/modules/douban/apiv2.py +++ b/app/modules/douban/apiv2.py @@ -11,7 +11,7 @@ import requests from app.core.cache import cached from app.core.config import settings -from app.utils.http import RequestUtils +from app.utils.http import RequestUtils, AsyncRequestUtils from app.utils.singleton import WeakSingleton @@ -154,6 +154,7 @@ class DoubanApi(metaclass=WeakSingleton): def __init__(self): self._session = requests.Session() + self._async_req = AsyncRequestUtils() @classmethod def __sign(cls, url: str, ts: str, method='GET') -> str: @@ -177,6 +178,13 @@ class DoubanApi(metaclass=WeakSingleton): """ return self.__invoke(url, **kwargs) + @cached(maxsize=settings.CONF.douban, ttl=settings.CONF.meta) + async def __async_invoke_recommend(self, url: str, **kwargs) -> dict: + """ + 推荐/发现类API(异步版本) + """ + return await self.__async_invoke(url, **kwargs) + @cached(maxsize=settings.CONF.douban, ttl=settings.CONF.meta) def __invoke_search(self, url: str, **kwargs) -> dict: """ @@ -184,6 +192,13 @@ class DoubanApi(metaclass=WeakSingleton): """ return self.__invoke(url, **kwargs) + @cached(maxsize=settings.CONF.douban, ttl=settings.CONF.meta) + async def __async_invoke_search(self, url: str, **kwargs) -> dict: + """ + 搜索类API(异步版本) + """ + return await self.__async_invoke(url, **kwargs) + def __invoke(self, url: str, **kwargs) -> dict: """ GET请求 @@ -212,6 +227,32 @@ class DoubanApi(metaclass=WeakSingleton): return resp.json() return resp.json() if resp else {} + @cached(maxsize=settings.CONF.douban, ttl=settings.CONF.meta) + async def __async_invoke(self, url: str, **kwargs) -> dict: + """ + GET请求(异步版本) + """ + req_url = self._base_url + url + + params: dict = {'apiKey': self._api_key} + if kwargs: + params.update(kwargs) + + ts = params.pop( + '_ts', + datetime.strftime(datetime.now(), '%Y%m%d') + ) + params.update({ + 'os_rom': 'android', + 'apiKey': self._api_key, + '_ts': ts, + '_sig': self.__sign(url=req_url, ts=ts) + }) + resp = await self._async_req.get_res(url=req_url, params=params) + if resp is not None and resp.status_code == 400 and "rate_limit" in resp.text: + return resp.json() + return resp.json() if resp else {} + @cached(maxsize=settings.CONF.douban, ttl=settings.CONF.meta) def __post(self, url: str, **kwargs) -> dict: """ @@ -241,6 +282,22 @@ class DoubanApi(metaclass=WeakSingleton): return resp.json() return resp.json() if resp else {} + @cached(maxsize=settings.CONF.douban, ttl=settings.CONF.meta) + async def __async_post(self, url: str, **kwargs) -> dict: + """ + POST请求(异步版本) + """ + req_url = self._api_url + url + params = {'apikey': self._api_key2} + if kwargs: + params.update(kwargs) + if '_ts' in params: + params.pop('_ts') + resp = await self._async_req.post_res(url=req_url, data=params) + if resp is not None and resp.status_code == 400 and "rate_limit" in resp.text: + return resp.json() + return resp.json() if resp else {} + def imdbid(self, imdbid: str, ts=datetime.strftime(datetime.now(), '%Y%m%d')): """ @@ -248,6 +305,13 @@ class DoubanApi(metaclass=WeakSingleton): """ return self.__post(self._urls["imdbid"] % imdbid, _ts=ts) + async def async_imdbid(self, imdbid: str, + ts=datetime.strftime(datetime.now(), '%Y%m%d')): + """ + IMDBID搜索(异步版本) + """ + return await self.__async_post(self._urls["imdbid"] % imdbid, _ts=ts) + def search(self, keyword: str, start: Optional[int] = 0, count: Optional[int] = 20, ts=datetime.strftime(datetime.now(), '%Y%m%d')) -> dict: """ @@ -256,6 +320,14 @@ class DoubanApi(metaclass=WeakSingleton): return self.__invoke_search(self._urls["search"], q=keyword, start=start, count=count, _ts=ts) + async def async_search(self, keyword: str, start: Optional[int] = 0, count: Optional[int] = 20, + ts=datetime.strftime(datetime.now(), '%Y%m%d')) -> dict: + """ + 关键字搜索(异步版本) + """ + return await self.__async_invoke_search(self._urls["search"], q=keyword, + start=start, count=count, _ts=ts) + def movie_search(self, keyword: str, start: Optional[int] = 0, count: Optional[int] = 20, ts=datetime.strftime(datetime.now(), '%Y%m%d')): """ @@ -264,6 +336,14 @@ class DoubanApi(metaclass=WeakSingleton): return self.__invoke_search(self._urls["movie_search"], q=keyword, start=start, count=count, _ts=ts) + async def async_movie_search(self, keyword: str, start: Optional[int] = 0, count: Optional[int] = 20, + ts=datetime.strftime(datetime.now(), '%Y%m%d')): + """ + 电影搜索(异步版本) + """ + return await self.__async_invoke_search(self._urls["movie_search"], q=keyword, + start=start, count=count, _ts=ts) + def tv_search(self, keyword: str, start: Optional[int] = 0, count: Optional[int] = 20, ts=datetime.strftime(datetime.now(), '%Y%m%d')): """ @@ -272,6 +352,14 @@ class DoubanApi(metaclass=WeakSingleton): return self.__invoke_search(self._urls["tv_search"], q=keyword, start=start, count=count, _ts=ts) + async def async_tv_search(self, keyword: str, start: Optional[int] = 0, count: Optional[int] = 20, + ts=datetime.strftime(datetime.now(), '%Y%m%d')): + """ + 电视搜索(异步版本) + """ + return await self.__async_invoke_search(self._urls["tv_search"], q=keyword, + start=start, count=count, _ts=ts) + def book_search(self, keyword: str, start: Optional[int] = 0, count: Optional[int] = 20, ts=datetime.strftime(datetime.now(), '%Y%m%d')): """ @@ -280,6 +368,14 @@ class DoubanApi(metaclass=WeakSingleton): return self.__invoke_search(self._urls["book_search"], q=keyword, start=start, count=count, _ts=ts) + async def async_book_search(self, keyword: str, start: Optional[int] = 0, count: Optional[int] = 20, + ts=datetime.strftime(datetime.now(), '%Y%m%d')): + """ + 书籍搜索(异步版本) + """ + return await self.__async_invoke_search(self._urls["book_search"], q=keyword, + start=start, count=count, _ts=ts) + def group_search(self, keyword: str, start: Optional[int] = 0, count: Optional[int] = 20, ts=datetime.strftime(datetime.now(), '%Y%m%d')): """ @@ -288,6 +384,14 @@ class DoubanApi(metaclass=WeakSingleton): return self.__invoke_search(self._urls["group_search"], q=keyword, start=start, count=count, _ts=ts) + async def async_group_search(self, keyword: str, start: Optional[int] = 0, count: Optional[int] = 20, + ts=datetime.strftime(datetime.now(), '%Y%m%d')): + """ + 小组搜索(异步版本) + """ + return await self.__async_invoke_search(self._urls["group_search"], q=keyword, + start=start, count=count, _ts=ts) + def person_search(self, keyword: str, start: Optional[int] = 0, count: Optional[int] = 20, ts=datetime.strftime(datetime.now(), '%Y%m%d')): """ @@ -296,6 +400,14 @@ class DoubanApi(metaclass=WeakSingleton): return self.__invoke_search(self._urls["search_subject"], type="person", q=keyword, start=start, count=count, _ts=ts) + async def async_person_search(self, keyword: str, start: Optional[int] = 0, count: Optional[int] = 20, + ts=datetime.strftime(datetime.now(), '%Y%m%d')): + """ + 人物搜索(异步版本) + """ + return await self.__async_invoke_search(self._urls["search_subject"], type="person", q=keyword, + start=start, count=count, _ts=ts) + def movie_showing(self, start: Optional[int] = 0, count: Optional[int] = 20, ts=datetime.strftime(datetime.now(), '%Y%m%d')): """ @@ -304,6 +416,14 @@ class DoubanApi(metaclass=WeakSingleton): return self.__invoke_recommend(self._urls["movie_showing"], start=start, count=count, _ts=ts) + async def async_movie_showing(self, start: Optional[int] = 0, count: Optional[int] = 20, + ts=datetime.strftime(datetime.now(), '%Y%m%d')): + """ + 正在热映(异步版本) + """ + return await self.__async_invoke_recommend(self._urls["movie_showing"], + start=start, count=count, _ts=ts) + def movie_soon(self, start: Optional[int] = 0, count: Optional[int] = 20, ts=datetime.strftime(datetime.now(), '%Y%m%d')): """ @@ -312,6 +432,14 @@ class DoubanApi(metaclass=WeakSingleton): return self.__invoke_recommend(self._urls["movie_soon"], start=start, count=count, _ts=ts) + async def async_movie_soon(self, start: Optional[int] = 0, count: Optional[int] = 20, + ts=datetime.strftime(datetime.now(), '%Y%m%d')): + """ + 即将上映(异步版本) + """ + return await self.__async_invoke_recommend(self._urls["movie_soon"], + start=start, count=count, _ts=ts) + def movie_hot_gaia(self, start: Optional[int] = 0, count: Optional[int] = 20, ts=datetime.strftime(datetime.now(), '%Y%m%d')): """ @@ -320,6 +448,14 @@ class DoubanApi(metaclass=WeakSingleton): return self.__invoke_recommend(self._urls["movie_hot_gaia"], start=start, count=count, _ts=ts) + async def async_movie_hot_gaia(self, start: Optional[int] = 0, count: Optional[int] = 20, + ts=datetime.strftime(datetime.now(), '%Y%m%d')): + """ + 热门电影(异步版本) + """ + return await self.__async_invoke_recommend(self._urls["movie_hot_gaia"], + start=start, count=count, _ts=ts) + def tv_hot(self, start: Optional[int] = 0, count: Optional[int] = 20, ts=datetime.strftime(datetime.now(), '%Y%m%d')): """ @@ -328,6 +464,14 @@ class DoubanApi(metaclass=WeakSingleton): return self.__invoke_recommend(self._urls["tv_hot"], start=start, count=count, _ts=ts) + async def async_tv_hot(self, start: Optional[int] = 0, count: Optional[int] = 20, + ts=datetime.strftime(datetime.now(), '%Y%m%d')): + """ + 热门剧集(异步版本) + """ + return await self.__async_invoke_recommend(self._urls["tv_hot"], + start=start, count=count, _ts=ts) + def tv_animation(self, start: Optional[int] = 0, count: Optional[int] = 20, ts=datetime.strftime(datetime.now(), '%Y%m%d')): """ @@ -336,6 +480,14 @@ class DoubanApi(metaclass=WeakSingleton): return self.__invoke_recommend(self._urls["tv_animation"], start=start, count=count, _ts=ts) + async def async_tv_animation(self, start: Optional[int] = 0, count: Optional[int] = 20, + ts=datetime.strftime(datetime.now(), '%Y%m%d')): + """ + 动画(异步版本) + """ + return await self.__async_invoke_recommend(self._urls["tv_animation"], + start=start, count=count, _ts=ts) + def tv_variety_show(self, start: Optional[int] = 0, count: Optional[int] = 20, ts=datetime.strftime(datetime.now(), '%Y%m%d')): """ @@ -344,6 +496,14 @@ class DoubanApi(metaclass=WeakSingleton): return self.__invoke_recommend(self._urls["tv_variety_show"], start=start, count=count, _ts=ts) + async def async_tv_variety_show(self, start: Optional[int] = 0, count: Optional[int] = 20, + ts=datetime.strftime(datetime.now(), '%Y%m%d')): + """ + 综艺(异步版本) + """ + return await self.__async_invoke_recommend(self._urls["tv_variety_show"], + start=start, count=count, _ts=ts) + def tv_rank_list(self, start: Optional[int] = 0, count: Optional[int] = 20, ts=datetime.strftime(datetime.now(), '%Y%m%d')): """ @@ -352,6 +512,14 @@ class DoubanApi(metaclass=WeakSingleton): return self.__invoke_recommend(self._urls["tv_rank_list"], start=start, count=count, _ts=ts) + async def async_tv_rank_list(self, start: Optional[int] = 0, count: Optional[int] = 20, + ts=datetime.strftime(datetime.now(), '%Y%m%d')): + """ + 电视剧排行榜(异步版本) + """ + return await self.__async_invoke_recommend(self._urls["tv_rank_list"], + start=start, count=count, _ts=ts) + def show_hot(self, start: Optional[int] = 0, count: Optional[int] = 20, ts=datetime.strftime(datetime.now(), '%Y%m%d')): """ @@ -360,36 +528,74 @@ class DoubanApi(metaclass=WeakSingleton): return self.__invoke_recommend(self._urls["show_hot"], start=start, count=count, _ts=ts) + async def async_show_hot(self, start: Optional[int] = 0, count: Optional[int] = 20, + ts=datetime.strftime(datetime.now(), '%Y%m%d')): + """ + 综艺热门(异步版本) + """ + return await self.__async_invoke_recommend(self._urls["show_hot"], + start=start, count=count, _ts=ts) + def movie_detail(self, subject_id: str): """ 电影详情 """ return self.__invoke_search(self._urls["movie_detail"] + subject_id) + async def async_movie_detail(self, subject_id: str): + """ + 电影详情(异步版本) + """ + return await self.__async_invoke_search(self._urls["movie_detail"] + subject_id) + def movie_celebrities(self, subject_id: str): """ 电影演职员 """ return self.__invoke_search(self._urls["movie_celebrities"] % subject_id) + async def async_movie_celebrities(self, subject_id: str): + """ + 电影演职员(异步版本) + """ + return await self.__async_invoke_search(self._urls["movie_celebrities"] % subject_id) + def tv_detail(self, subject_id: str): """ 电视剧详情 """ return self.__invoke_search(self._urls["tv_detail"] + subject_id) + async def async_tv_detail(self, subject_id: str): + """ + 电视剧详情(异步版本) + """ + return await self.__async_invoke_search(self._urls["tv_detail"] + subject_id) + def tv_celebrities(self, subject_id: str): """ 电视剧演职员 """ return self.__invoke_search(self._urls["tv_celebrities"] % subject_id) + async def async_tv_celebrities(self, subject_id: str): + """ + 电视剧演职员(异步版本) + """ + return await self.__async_invoke_search(self._urls["tv_celebrities"] % subject_id) + def book_detail(self, subject_id: str): """ 书籍详情 """ return self.__invoke_search(self._urls["book_detail"] + subject_id) + async def async_book_detail(self, subject_id: str): + """ + 书籍详情(异步版本) + """ + return await self.__async_invoke_search(self._urls["book_detail"] + subject_id) + def movie_top250(self, start: Optional[int] = 0, count: Optional[int] = 20, ts=datetime.strftime(datetime.now(), '%Y%m%d')): """ @@ -398,6 +604,14 @@ class DoubanApi(metaclass=WeakSingleton): return self.__invoke_recommend(self._urls["movie_top250"], start=start, count=count, _ts=ts) + async def async_movie_top250(self, start: Optional[int] = 0, count: Optional[int] = 20, + ts=datetime.strftime(datetime.now(), '%Y%m%d')): + """ + 电影TOP250(异步版本) + """ + return await self.__async_invoke_recommend(self._urls["movie_top250"], + start=start, count=count, _ts=ts) + def movie_recommend(self, tags='', sort='R', start: Optional[int] = 0, count: Optional[int] = 20, ts=datetime.strftime(datetime.now(), '%Y%m%d')): """ @@ -406,6 +620,14 @@ class DoubanApi(metaclass=WeakSingleton): return self.__invoke_recommend(self._urls["movie_recommend"], tags=tags, sort=sort, start=start, count=count, _ts=ts) + async def async_movie_recommend(self, tags='', sort='R', start: Optional[int] = 0, count: Optional[int] = 20, + ts=datetime.strftime(datetime.now(), '%Y%m%d')): + """ + 电影探索(异步版本) + """ + return await self.__async_invoke_recommend(self._urls["movie_recommend"], tags=tags, sort=sort, + start=start, count=count, _ts=ts) + def tv_recommend(self, tags='', sort='R', start: Optional[int] = 0, count: Optional[int] = 20, ts=datetime.strftime(datetime.now(), '%Y%m%d')): """ @@ -414,6 +636,14 @@ class DoubanApi(metaclass=WeakSingleton): return self.__invoke_recommend(self._urls["tv_recommend"], tags=tags, sort=sort, start=start, count=count, _ts=ts) + async def async_tv_recommend(self, tags='', sort='R', start: Optional[int] = 0, count: Optional[int] = 20, + ts=datetime.strftime(datetime.now(), '%Y%m%d')): + """ + 电视剧探索(异步版本) + """ + return await self.__async_invoke_recommend(self._urls["tv_recommend"], tags=tags, sort=sort, + start=start, count=count, _ts=ts) + def tv_chinese_best_weekly(self, start: Optional[int] = 0, count: Optional[int] = 20, ts=datetime.strftime(datetime.now(), '%Y%m%d')): """ @@ -422,6 +652,14 @@ class DoubanApi(metaclass=WeakSingleton): return self.__invoke_recommend(self._urls["tv_chinese_best_weekly"], start=start, count=count, _ts=ts) + async def async_tv_chinese_best_weekly(self, start: Optional[int] = 0, count: Optional[int] = 20, + ts=datetime.strftime(datetime.now(), '%Y%m%d')): + """ + 华语口碑周榜(异步版本) + """ + return await self.__async_invoke_recommend(self._urls["tv_chinese_best_weekly"], + start=start, count=count, _ts=ts) + def tv_global_best_weekly(self, start: Optional[int] = 0, count: Optional[int] = 20, ts=datetime.strftime(datetime.now(), '%Y%m%d')): """ @@ -430,6 +668,14 @@ class DoubanApi(metaclass=WeakSingleton): return self.__invoke_recommend(self._urls["tv_global_best_weekly"], start=start, count=count, _ts=ts) + async def async_tv_global_best_weekly(self, start: Optional[int] = 0, count: Optional[int] = 20, + ts=datetime.strftime(datetime.now(), '%Y%m%d')): + """ + 全球口碑周榜(异步版本) + """ + return await self.__async_invoke_recommend(self._urls["tv_global_best_weekly"], + start=start, count=count, _ts=ts) + def doulist_detail(self, subject_id: str): """ 豆列详情 @@ -437,6 +683,13 @@ class DoubanApi(metaclass=WeakSingleton): """ return self.__invoke_search(self._urls["doulist"] + subject_id) + async def async_doulist_detail(self, subject_id: str): + """ + 豆列详情(异步版本) + :param subject_id: 豆列id + """ + return await self.__async_invoke_search(self._urls["doulist"] + subject_id) + def doulist_items(self, subject_id: str, start: Optional[int] = 0, count: Optional[int] = 20, ts=datetime.strftime(datetime.now(), '%Y%m%d')): """ @@ -449,6 +702,18 @@ class DoubanApi(metaclass=WeakSingleton): return self.__invoke_search(self._urls["doulist_items"] % subject_id, start=start, count=count, _ts=ts) + async def async_doulist_items(self, subject_id: str, start: Optional[int] = 0, count: Optional[int] = 20, + ts=datetime.strftime(datetime.now(), '%Y%m%d')): + """ + 豆列列表(异步版本) + :param subject_id: 豆列id + :param start: 开始 + :param count: 数量 + :param ts: 时间戳 + """ + return await self.__async_invoke_search(self._urls["doulist_items"] % subject_id, + start=start, count=count, _ts=ts) + def movie_recommendations(self, subject_id: str, start: Optional[int] = 0, count: Optional[int] = 20, ts=datetime.strftime(datetime.now(), '%Y%m%d')): """ @@ -461,6 +726,18 @@ class DoubanApi(metaclass=WeakSingleton): return self.__invoke_recommend(self._urls["movie_recommendations"] % subject_id, start=start, count=count, _ts=ts) + async def async_movie_recommendations(self, subject_id: str, start: Optional[int] = 0, count: Optional[int] = 20, + ts=datetime.strftime(datetime.now(), '%Y%m%d')): + """ + 电影推荐(异步版本) + :param subject_id: 电影id + :param start: 开始 + :param count: 数量 + :param ts: 时间戳 + """ + return await self.__async_invoke_recommend(self._urls["movie_recommendations"] % subject_id, + start=start, count=count, _ts=ts) + def tv_recommendations(self, subject_id: str, start: Optional[int] = 0, count: Optional[int] = 20, ts=datetime.strftime(datetime.now(), '%Y%m%d')): """ @@ -473,6 +750,18 @@ class DoubanApi(metaclass=WeakSingleton): return self.__invoke_recommend(self._urls["tv_recommendations"] % subject_id, start=start, count=count, _ts=ts) + async def async_tv_recommendations(self, subject_id: str, start: Optional[int] = 0, count: Optional[int] = 20, + ts=datetime.strftime(datetime.now(), '%Y%m%d')): + """ + 电视剧推荐(异步版本) + :param subject_id: 电视剧id + :param start: 开始 + :param count: 数量 + :param ts: 时间戳 + """ + return await self.__async_invoke_recommend(self._urls["tv_recommendations"] % subject_id, + start=start, count=count, _ts=ts) + def movie_photos(self, subject_id: str, start: Optional[int] = 0, count: Optional[int] = 20, ts=datetime.strftime(datetime.now(), '%Y%m%d')): """ @@ -485,6 +774,18 @@ class DoubanApi(metaclass=WeakSingleton): return self.__invoke_search(self._urls["movie_photos"] % subject_id, start=start, count=count, _ts=ts) + async def async_movie_photos(self, subject_id: str, start: Optional[int] = 0, count: Optional[int] = 20, + ts=datetime.strftime(datetime.now(), '%Y%m%d')): + """ + 电影剧照(异步版本) + :param subject_id: 电影id + :param start: 开始 + :param count: 数量 + :param ts: 时间戳 + """ + return await self.__async_invoke_search(self._urls["movie_photos"] % subject_id, + start=start, count=count, _ts=ts) + def tv_photos(self, subject_id: str, start: Optional[int] = 0, count: Optional[int] = 20, ts=datetime.strftime(datetime.now(), '%Y%m%d')): """ @@ -497,6 +798,18 @@ class DoubanApi(metaclass=WeakSingleton): return self.__invoke_search(self._urls["tv_photos"] % subject_id, start=start, count=count, _ts=ts) + async def async_tv_photos(self, subject_id: str, start: Optional[int] = 0, count: Optional[int] = 20, + ts=datetime.strftime(datetime.now(), '%Y%m%d')): + """ + 电视剧剧照(异步版本) + :param subject_id: 电视剧id + :param start: 开始 + :param count: 数量 + :param ts: 时间戳 + """ + return await self.__async_invoke_search(self._urls["tv_photos"] % subject_id, + start=start, count=count, _ts=ts) + def person_detail(self, subject_id: int): """ 用户详情 @@ -505,6 +818,14 @@ class DoubanApi(metaclass=WeakSingleton): """ return self.__invoke_search(self._urls["person_detail"] + str(subject_id)) + async def async_person_detail(self, subject_id: int): + """ + 用户详情(异步版本) + :param subject_id: 人物 id + :return: + """ + return await self.__async_invoke_search(self._urls["person_detail"] + str(subject_id)) + def person_work(self, subject_id: int, start: Optional[int] = 0, count: Optional[int] = 20, sort_by: Optional[str] = "time", collection_title: Optional[str] = "影视", @@ -523,6 +844,24 @@ class DoubanApi(metaclass=WeakSingleton): collection_title=collection_title, start=start, count=count, _ts=ts) + async def async_person_work(self, subject_id: int, start: Optional[int] = 0, count: Optional[int] = 20, + sort_by: Optional[str] = "time", + collection_title: Optional[str] = "影视", + ts=datetime.strftime(datetime.now(), '%Y%m%d')): + """ + 用户作品集(异步版本) + :param subject_id: work_collection id + :param start: 开始页 + :param count: 数量 + :param sort_by: collection or time or vote + :param collection_title: 影视 or 图书 or 音乐 + :param ts: 时间戳 + :return: + """ + return await self.__async_invoke_search(self._urls["person_work"] % subject_id, sortby=sort_by, + collection_title=collection_title, + start=start, count=count, _ts=ts) + def clear_cache(self): """ 清空LRU缓存