# coding=utf-8 #!/usr/bin/python #This script will take your anime-planet.com username and add your list to MAL using its API #Errors are output so you can enter those manually #set debug = True to get more information on all of your entries #Additional info and packages: # Python 3.5.2 - http://python.org/download/ # BeautifulSoup4 - http://www.crummy.com/software/BeautifulSoup/#Download #Tips: # * You can leave your MAL username empty if it's the same as on AnimePlanet. # * To install BeautifulSoup unpack it anywhere and type "setup.py install" in the console from that folder. # * In order to successfully import the exported Anime-Planet animelist to MAL, first export MAL animelist, # and copy the block just after tag. from bs4 import BeautifulSoup,NavigableString import urllib.request,urllib.parse,base64,sys,re,codecs import xml.etree.ElementTree as et debug = False delimiter = "\t" userAgent = "Mozilla/5.0 (Windows NT 6.2; Win64; x64;) Gecko/20100101 Firefox/20.0" print("This script will export your anime-planet.com anime list to myanimelist.net") output = open("ap2mal-output.log", 'w') username = input("Enter your AP username: ") malusername = input("Enter your MAL username: ") if (malusername == ""): malusername = username malpassword = input("Enter your MAL password: ") baseURL = "http://www.anime-planet.com/users/%s/anime" % username apiURL = "https://myanimelist.net/api/anime/search.xml" apiURLadd = "https://myanimelist.net/api/animelist/add/%s.xml" apiURLupdate = "https://myanimelist.net/api/animelist/update/%s.xml" passStr = str("%s:%s" % (malusername, malpassword)).replace("\n", "") authString = str(base64.b64encode(bytes(passStr, "utf-8")), "utf-8") if debug: print("MAL authorization hash: " + authString) #Try to get HTML of first page. try: req = urllib.request.Request(baseURL) req.add_header("User-Agent", userAgent) html = BeautifulSoup(urllib.request.urlopen(req).read(), "html.parser") pageNumber = int (html.find("li","next").findPrevious("li").next.contents[0]) #if your list is only one page, uncomment the line below and comment the line above #pageNumber = int (html.find('li','next').findPrevious('li').contents[0]) except BaseException as e: print("Request to " + baseURL + " failed. " +str(e)) raise SystemExit print("Processing AP list and requesting data from MAL...") #loop through all of your pages for i in range(1,pageNumber+1): try: req = urllib.request.Request(baseURL + "?" + urllib.parse.urlencode({"page": str(i)})) if debug: print("Calling URL:" + baseURL + "?" + urllib.parse.urlencode({"page": str(i)})) req.add_header("User-Agent", userAgent) html = BeautifulSoup(urllib.request.urlopen(req).read(), "html.parser") except BaseException as e: print("Request to " + baseURL + "?" + urllib.parse.urlencode({"page": str(i)}) + " failed. " +str(e)) raise SystemExit #loop through all of the anime posters on page i for animeItem in html.findAll("li",class_="card"): animeItem = BeautifulSoup(animeItem.renderContents(), "html.parser") animeName = "" + animeItem.a.div.img["alt"] #pretty apostophe was breaking things animeName = animeName.replace("’","'") output.write(animeName+" ~~> ") queryTitle = "" try: titlereq = urllib.request.Request(apiURL + "?" + urllib.parse.urlencode({ "q" : animeName })) titlereq.add_header("Authorization", "Basic %s" % authString) titlereq.add_header("User-Agent", userAgent) queryTitle = urllib.request.urlopen(titlereq).read().decode("utf-8") #I think this removes the synopsis for some reason, whatever queryTitle = re.sub(r"(?is).+", "", queryTitle) except BaseException as e: print("Anime: " + animeName) print("Request to " + apiURL + "?" + urllib.parse.urlencode({ "q" : animeName }) + " failed. " +str(e)) output.write("failed\n") raise SystemExit #get the status, which is now a class name status = animeItem.find("div","statusArea").span["class"][0] formattedStatus = "" if status=="status6": formattedStatus = "won't watch" status="4" elif status=="status3": formattedStatus = "dropped" status="4" elif status=="status4": formattedStatus = "want to watch" status="6" elif status=="status5": formattedStatus = "stalled" status="3" elif status=="status1": formattedStatus = "watched" status="2" elif status=="status2": formattedStatus = "watching" status="1" search = "" try: if queryTitle != '': search = et.fromstring(queryTitle) if debug: print("================") if debug: print("Anime: " + animeName) if debug: print(apiURL + "?" + urllib.parse.urlencode({ "q" : animeName })) else: # This item failed to get a title match if ":" not in animeName: print("================") print("Anime: " + animeName) print(apiURL + "?" + urllib.parse.urlencode({ "q" : animeName })) print("Search failed; no match found.") print("Status: " + formattedStatus) output.write("failed\n") continue else: #try truncated name for initial search formattedName = animeName.split(":")[0] try: titlereq = urllib.request.Request(apiURL + "?" + urllib.parse.urlencode({ "q" : formattedName })) titlereq.add_header("Authorization", "Basic %s" % authString) titlereq.add_header("User-Agent", userAgent) queryTitle = urllib.request.urlopen(titlereq).read().decode("utf-8") #I think this removes the synopsis for some reason, whatever queryTitle = re.sub(r"(?is).+", "", queryTitle) except BaseException as e: print("Anime: " + animeName) print("Request to " + apiURL + "?" + urllib.parse.urlencode({ "q" : formattedName }) + " failed. " +str(e)) output.write("failed\n") raise SystemExit if queryTitle != '': search = et.fromstring(queryTitle) if debug: print("================") if debug: print("Anime: " + animeName) if debug: print(apiURL + "?" + urllib.parse.urlencode({ "q" : formattedName })) else: # This item failed to get a title match print("================") print("Anime: " + animeName) print(apiURL + "?" + urllib.parse.urlencode({ "q" : formattedName })) print("Search failed; no match found.") print("Status: " + formattedStatus) output.write("failed\n") continue except BaseException as e: print("Decoding of anime data failed. Error: " +str(e)) output.write("failed\n") # for adding anime manually continue localName = animeName.lower().replace(":","").replace("(","").replace(")","").replace("- ","") animeID = "" episodeCount = "" #check all results for an id for entry in search.findall("./entry"): try: if entry.find("id") is not None and entry.find("id").text.strip()!="": if entry.find("title") is not None and localName in entry.find("title").text.lower().replace(":","").replace("(","").replace(")","").replace("- ",""): animeID=entry.find("id").text episodeCount = entry.find("episodes").text break elif entry.find("english") is not None and localName in entry.find("english").text.lower().replace(":","").replace("(","").replace(")","").replace("- ",""): animeID=entry.find("id").text episodeCount = entry.find("episodes").text break elif entry.find("synonyms") is not None and localName in entry.find("synonyms").text.lower().replace(":","").replace("(","").replace(")","").replace("- ",""): animeID=entry.find("id").text episodeCount = entry.find("episodes").text break except: continue print("anime "+animeID) if animeID=="": output.write("failed\n") print("No MAL ID found in returned results for "+animeName) continue else: output.write(animeName+"\n") if debug: print("MAL ID = " + animeID) xmlData = "\n" xmlData += "\n" if status == "4" or status == "6": xmlData += "\t\n" #had to include watched here because there's no way to get the # of eps anymore from AP elif status == "2": xmlData += "\t" + episodeCount + "\n" else: xmlData += "\t"+ animeItem.find("div","statusArea").text.split("ep")[0].replace("\t", "").replace("\n", "").replace("\r", "").replace(" ", "") +"\n" xmlData += "\t" + status +"\n" try: rating = animeItem.find("div", attrs={"class": "ttRating"}).text; xmlData += "\t" + str(int(float(rating)*2)) + "\n" except: xmlData += "\t\n" xmlData += "\t\n" xmlData += "\t\n" xmlData += "\t\n" xmlData += "\t\n" xmlData += "\t\n" xmlData += "\t\n" xmlData += "\t\n" xmlData += "\t\n" xmlData += "\t\n" xmlData += "\t\n" xmlData += "\t\n" xmlData += "\t\n" xmlData += "\t\n" xmlData += "\n" params = {'id' : animeID, 'data' : xmlData} isAdded = False try: if debug: print("Trying to add anime... ") url = urllib.request.Request(apiURLadd % animeID, urllib.parse.urlencode(params).encode("utf-8")) url.add_header("Authorization", "Basic %s" % authString) url.add_header("User-Agent", userAgent) urllib.request.urlopen(url) isAdded = True except: isAdded = False if not isAdded: try: if debug: print("\rTrying to update anime... ") url = urllib.request.Request(apiURLupdate % animeID, urllib.parse.urlencode(params).encode("utf-8")) url.add_header("Authorization", "Basic %s" % authString) url.add_header("User-Agent", userAgent) urllib.request.urlopen(url) isAdded = True except: isAdded = False if isAdded == False: output.write("failed\n") if debug: if isAdded: sys.stdout.write("OK\n") else: sys.stdout.write("FAILED\n") sys.stdout.flush() print("\nDone")