import os import time from datetime import datetime import requests from bs4 import BeautifulSoup BASE_URL = "http://api.anidb.net:9001/httpapi" SEARCH_URL = "http://anisearch.outrance.pl/" PROTOCOL_VERSION = 1 try: str = unicode except: pass # str = str class AniDB: def __init__(self, client_id, client_ver=0): self.client_id = client_id self.client_ver = client_ver self._cache = {} def _request(self, datapage, params={}, cache=True): cache_filename = f'cache_{params["aid"]}.xml' if os.path.exists(cache_filename): print('D: cache_filename exists, return its content:', cache_filename) with open(cache_filename, 'r', encoding='utf8') as fpi: return fpi.read() time.sleep(3) # avoid being banned with subsequent requests params.update({ 'client': self.client_id, 'clientver': self.client_ver, 'protover': PROTOCOL_VERSION, 'request': datapage }) r = requests.get(BASE_URL, params=params) r.raise_for_status() #print('D: r.text:', r.text) print('D: creating cache file:', cache_filename) with open(cache_filename, 'w', encoding='utf8') as fpo: fpo.write(r.text) return r.text # Anime http://wiki.anidb.net/w/HTTP_API_Definition#Access def search(self, q): """ Search for `aid`s by anime title using service provided by eloyard. http://anisearch.outrance.pl/doc.html """ r = requests.get(SEARCH_URL, params={ 'task': "search", 'query': q, }) r.raise_for_status() results = [] animetitles = BeautifulSoup(r.text, 'xml').animetitles for anime in animetitles.find_all('anime'): print('D: got anime: ', anime) aid = int(anime['aid']) print('D: got aid: ', aid) #results.append(Anime({ # 'aid': aid, # 'title': str(anime.find('title', attrs={'type': "main"}).string) #}, partial=True, updater=lambda: self.get(aid))) results.append(self.get(aid)) print('D: result appended') return results def get(self, aid): """ Allows retrieval of non-file or episode related information for a specific anime by AID (AniDB anime id). http://wiki.anidb.net/w/HTTP_API_Definition#Anime """ r = self._request("anime", {'aid': aid}) #print('D: got in AniDB.get(), r:', r) soup = BeautifulSoup(r, 'xml') if soup.error is not None: raise Exception(soup.error.string) anime = soup.anime titles = anime.titles a = Anime({ 'aid': aid, 'type': str(anime.type.string), 'episodecount': int(anime.episodecount.string), #'startdate': datetime(*list(map(int, anime.startdate.string.split("-")))), #'enddate': datetime(*list(map(int, anime.enddate.string.split("-")))), 'titles': [( str(title.string), title['type'] if 'type' in title else "unknown" ) for title in anime.find_all('title')], 'title': str(titles.find('title', attrs={'type': "main"}).string), 'relatedanime': [], 'url': str(anime.url.string if anime.url else None), 'creators': [], 'description': str(anime.description.string if anime.description else ''), #'ratings': SmartDict({ # 'permanent': float(anime.ratings.permanent.string), # 'temporary': float(anime.ratings.temporary.string), # 'review': float(anime.ratings.review.string if anime.ratings.review # else 0) #}), 'picture': "http://img7.anidb.net/pics/anime/" + str(anime.picture.string), 'categories': [], 'tags': [], 'characters': [], 'episodes': [], }) self._cache[aid] = a return a class SmartDict(dict): def __init__(self, *a, **kw): super(SmartDict, self).__init__(**kw) for x in a: try: self.update(x) except TypeError: self.update(x.__dict__) self.__dict__ = self class Anime: def __init__(self, data={}, partial=False, **kw): self._data = data self._partial = partial if partial: self._updater = kw['updater'] def _update(self): if not self._partial: raise Exception("Attempted to update a ready object") else: self._data = self._updater()._data self._partial = False def __getattr__(self, name): if name in self._data: return self._data[name] else: if self._partial: self._update() if name in self._data: return self._data[name] else: raise AttributeError("no attribute called '%s'" % name) else: raise AttributeError("no attribute called '%s'" % name) def __repr__(self): return u'' % (self.aid, self.title)