├── requirements.txt ├── src └── images │ ├── 00.jpg │ ├── 01.jpg │ └── 02.jpg ├── Anixart_Bookmarks.txt ├── LICENSE ├── README.md └── main.py /requirements.txt: -------------------------------------------------------------------------------- 1 | requests==2.28.2 2 | tqdm==4.66.2 3 | urllib3~=1.26.20 -------------------------------------------------------------------------------- /src/images/00.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/the-dise/anixart-to-shikimori/HEAD/src/images/00.jpg -------------------------------------------------------------------------------- /src/images/01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/the-dise/anixart-to-shikimori/HEAD/src/images/01.jpg -------------------------------------------------------------------------------- /src/images/02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/the-dise/anixart-to-shikimori/HEAD/src/images/02.jpg -------------------------------------------------------------------------------- /Anixart_Bookmarks.txt: -------------------------------------------------------------------------------- 1 | 1 / "Одинокий рокер!" / "Bocchi the Rock!" / "Рок-тихоня!" / В избранном / Просмотрено / 5 из 5 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 the_dise 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # anixart-to-shikimori 2 | 3 | Python script that allows you to extract anime data from Anixart and save it in JSON format for Shikimori 4 | 5 | ## Install 6 | 7 | 1. Make sure you have Python installed (version 3.6 or higher is recommended). 8 | 2. Clone this repository using the following command: 9 | 10 | ```shell 11 | git clone https://github.com/the-dise/anixart-to-shikimori.git 12 | cd anixart-to-shikimori 13 | ``` 14 | 15 | 3. Install the dependencies using the following command: 16 | 17 | ```shell 18 | pip install -r requirements.txt 19 | ``` 20 | 21 | ## Usage 22 | 23 | 1. Place the **Anixart_Bookmarks.txt** file in the project directory. 24 | 2. Convert **Anixart_Bookmarks.txt** to UFT-8. By default it is saved in UTF-8 with BOM. 25 | 3. Run the script using the following command: 26 | 27 | ```shell 28 | python ./main.py 29 | ``` 30 | 31 | 4. When the script completes, the results will be saved to the anime_data.json file. 32 | 5. Go to Shikimori profile settings and import anime_data.json. 33 | 34 | ## How to export Anixart data 35 | 36 | Go to Profile tab -> Settings -> Data Management - Export bookmarks 37 | 38 | | 1 | 2 | 3 | 39 | | :-------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------: | 40 | | ![](https://github.com/the-dise/anixart-to-shikimori/blob/main/src/images/00.jpg) | ![](https://github.com/the-dise/anixart-to-shikimori/blob/main/src/images/01.jpg) | ![](https://github.com/the-dise/anixart-to-shikimori/blob/main/src/images/02.jpg) | 41 | 42 | ## License 43 | 44 | This project is licensed under the [MIT](LICENSE) license. 45 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | 4 | import requests 5 | import json 6 | from tqdm import tqdm 7 | from requests.adapters import HTTPAdapter 8 | from urllib3.util.retry import Retry 9 | 10 | # Session settings 11 | session = requests.Session() 12 | retry = Retry(connect=3, backoff_factor=0.5) 13 | adapter = HTTPAdapter(max_retries=retry) 14 | session.mount('http://', adapter) 15 | session.mount('https://', adapter) 16 | 17 | # Function to split the input file into smaller files with a maximum of 100 lines each 18 | def split_file(input_file, lines_per_file=100): 19 | with open(input_file, 'r', encoding='utf-8') as file: 20 | lines = file.readlines() 21 | 22 | file_count = 0 23 | for i in range(0, len(lines), lines_per_file): 24 | split_file_name = f"{input_file.split('.')[0]}_part_{file_count + 1}.txt" 25 | with open(split_file_name, 'w', encoding='utf-8') as split_file: 26 | split_file.writelines(lines[i:i + lines_per_file]) 27 | file_count += 1 28 | return file_count 29 | 30 | # Function to map status strings to status codes 31 | def get_status(status_str): 32 | status_mapping = { 33 | "Просмотрено": "completed", 34 | "В планах": "planned", 35 | "Смотрю": "watching", 36 | "Брошено": "dropped", 37 | "Пересматриваю": "rewatching" 38 | } 39 | return status_mapping.get(status_str.strip(), "planned") # Default to "planned" if status not found 40 | 41 | # Function to map score strings to score values 42 | def get_score(score_str): 43 | score_mapping = { 44 | "Не оценено": 0, 45 | "0 из 5": 1, 46 | "1 из 5": 2, 47 | "2 из 5": 4, 48 | "3 из 5": 5, 49 | "4 из 5": 7, 50 | "5 из 5": 10 51 | } 52 | return score_mapping.get(score_str.strip(), 0) # Default to 0 if score not found 53 | 54 | # Function to process each anime entry 55 | def process_anime_entry(anime_entry): 56 | parts = anime_entry.split("/") 57 | if len(parts) < 7: 58 | return None # Invalid entry 59 | 60 | anime_name = parts[1].strip().strip("\"") 61 | status_str = parts[5].strip() 62 | score_str = parts[6].strip() 63 | 64 | url = f"https://shikimori.one/api/animes?search={anime_name}" 65 | response = session.get(url, headers=headers) 66 | 67 | 68 | try: 69 | anime_info = response.json() 70 | except json.decoder.JSONDecodeError: 71 | print("Response is not in JSON format") 72 | return None 73 | 74 | if not anime_info: 75 | print(f"No results found for '{anime_name}'") 76 | return None 77 | 78 | target_title = anime_info[0]['name'] 79 | target_id = anime_info[0]['id'] 80 | status = get_status(status_str) 81 | score = get_score(score_str) 82 | episodes = anime_info[0]['episodes'] if status == "completed" else 0 83 | 84 | return { 85 | "target_title": target_title, 86 | "target_id": target_id, 87 | "target_type": "Anime", 88 | "score": score, 89 | "status": status, 90 | "episodes": episodes, 91 | "text": None 92 | } 93 | 94 | headers = { 95 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36' 96 | } 97 | 98 | anime_data_list = [] 99 | 100 | # Split the input file into smaller files 101 | input_file = "Anixart_Bookmarks.txt" 102 | file_count = split_file(input_file) 103 | 104 | # Process each smaller file 105 | for file_index in range(1, file_count + 1): 106 | split_file_name = f"{input_file.split('.')[0]}_part_{file_index}.txt" 107 | with open(split_file_name, "r", encoding="utf-8") as file: 108 | anime_list = file.readlines() 109 | 110 | # Use tqdm to display progress 111 | with tqdm(total=len(anime_list), desc=f"Processing {split_file_name}") as pbar: 112 | for anime_entry in anime_list: 113 | anime_data = process_anime_entry(anime_entry) 114 | if anime_data: 115 | anime_data_list.append(anime_data) 116 | pbar.update(1) 117 | # Delay to prevent "Too many requests"(code 429) 118 | time.sleep(0.5) 119 | 120 | # Optionally remove the processed split file 121 | os.remove(split_file_name) 122 | 123 | # Write anime data to JSON file 124 | with open("anime_info.json", "w", encoding="utf-8") as json_file: 125 | json.dump(anime_data_list, json_file, ensure_ascii=False, indent=4) 126 | --------------------------------------------------------------------------------