Guest User

PixivSalvage.py

a guest
Oct 3rd, 2025
395
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 5.14 KB | None | 0 0
  1. import itertools
  2. from datetime import datetime, timedelta
  3. import time
  4. from typing import List, Optional, Tuple
  5.  
  6. import requests
  7.  
  8. PIXIV_ARTWORKS_URL = "https://www.pixiv.net/artworks/"
  9. ARTWORKS_DETAIL_ENDPOINT = "https://www.pixiv.net/ajax/illust/"
  10. ARTWORKS_EXT_LIST = ("jpg", "png", "gif") #複数の拡張子を混ぜるとjpgに変換されるのでjpg優先
  11. ARTWORKS_TEMPLATE = (
  12.     "https://i.pximg.net/img-original/img/{timestamp}/{id}_p{number}.{ext}"
  13. )
  14.  
  15. session = requests.Session()
  16. session.headers.update(
  17.     {
  18.         "Referer": "https://pixiv.net/",
  19.         "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 18_3_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/135.0.7049.53 Mobile/15E148 Safari/604.1",
  20.     }
  21. )
  22.  
  23.  
  24. def get_artwork_timestamp(artwork_id: int) -> Optional[str]:
  25.     try:
  26.         response = session.get(f"{ARTWORKS_DETAIL_ENDPOINT}{artwork_id}", timeout=10)
  27.         response.raise_for_status()
  28.     except requests.RequestException as e:
  29.         print(f"IDの取得に失敗しました {artwork_id}: {e}")
  30.         return None
  31.  
  32.     try:
  33.         artwork_data = response.json().get("body", {}).get("userIllusts", {})
  34.         timestamp = artwork_data.get(str(artwork_id), {}).get("updateDate")
  35.         if not timestamp:
  36.             print(f"タイムスタンプが見つかりませんでした {artwork_id}")
  37.         return timestamp
  38.     except ValueError as e:
  39.         print(f"Jsonのパースに失敗しました {artwork_id}: {e}")
  40.         return None
  41.  
  42.  
  43. def find_valid_id(
  44.     start_id: int, step: int, max_attempts: int = 30, interval: float = 0.5
  45. ) -> Optional[Tuple[int, str]]:
  46.     current_id = start_id
  47.     for attempt in range(max_attempts):
  48.         timestamp = get_artwork_timestamp(current_id)
  49.         if timestamp is not None:
  50.             print(f"有効なIDを見つけました: {current_id} タイムスタンプ: {timestamp}")
  51.             return current_id, timestamp
  52.         else:
  53.             print(f"タイムスタンプの取得に失敗しました: {current_id}, 次のIDを取得します...")
  54.             current_id += step
  55.             time.sleep(interval)
  56.  
  57.     print(
  58.         f"ID {start_id} から {max_attempts} 回試行しましたが、有効なIDを見つけられませんでした"
  59.     )
  60.     return None
  61.  
  62.  
  63. def generate_timestamps(start: datetime, end: datetime):
  64.     current = start
  65.     while current <= end:
  66.         yield current.strftime("%Y/%m/%d/%H/%M/%S")
  67.         current += timedelta(seconds=1)
  68.  
  69.  
  70. def check_all_pattern_artworks(
  71.     start: datetime, end: datetime, artwork_id: int, interval: float = 0.5
  72. ) -> Optional[Tuple[str, str, str]]:
  73.     for ext in ARTWORKS_EXT_LIST:
  74.         for ts in generate_timestamps(start, end):
  75.             url = ARTWORKS_TEMPLATE.format(
  76.                 timestamp=ts, id=artwork_id, number=0, ext=ext
  77.             )
  78.             response = session.head(url, timeout=10)
  79.  
  80.             if response.status_code == 200:
  81.                 print("ベースリンクの取得に成功しました:" + url)
  82.                 return url, ts, ext
  83.             else:
  84.                 print("URLが見つかりませんでした:" + url)
  85.  
  86.             time.sleep(interval)
  87.  
  88.     return None
  89.  
  90.  
  91. def get_all_artworks_images(artwork_id: int, timestamp: str, ext: str, interval: float = 0.5) -> List[str]:
  92.     urls = []
  93.     for i in itertools.count():
  94.         url = ARTWORKS_TEMPLATE.format(
  95.             timestamp=timestamp, id=artwork_id, number=i, ext=ext
  96.         )
  97.         response = session.head(url, timeout=10)
  98.         if response.status_code == 200:
  99.             urls.append(url)
  100.             print("取得に成功しました:" + url)
  101.             time.sleep(interval)
  102.         else:
  103.             break
  104.  
  105.     return urls
  106.  
  107.  
  108. def salvage_pixiv(artwork_id: int, interval: float = 0.5) -> Optional[List[str]]:
  109.     start_result = find_valid_id(start_id=artwork_id - 1, step=-1, interval=interval)
  110.     if start_result is None:
  111.         return None
  112.     start_id, start_timestamp = start_result
  113.  
  114.     end_result = find_valid_id(start_id=artwork_id + 1, step=1)
  115.     if end_result is None:
  116.         return None
  117.     end_id, end_timestamp = end_result
  118.  
  119.     start_datetime = datetime.fromisoformat(start_timestamp)
  120.     end_datetime = datetime.fromisoformat(end_timestamp)
  121.  
  122.     result = check_all_pattern_artworks(start_datetime, end_datetime, artwork_id, interval)
  123.     if result is None:
  124.         return None
  125.     url, ts, ext = result
  126.     return get_all_artworks_images(artwork_id, ts, ext, interval)
  127.  
  128.  
  129. if "__main__" == __name__:
  130.     artwork_id = int(input("IDを入力してください:").strip())
  131.     interval = 0.75 #HEADがメインなので0.5秒くらいでも多分大丈夫。ただ長ければ長いほど安全
  132.     result = salvage_pixiv(artwork_id, interval=interval)
  133.  
  134.     if result is not None:
  135.         BAR_LENGTH = 80
  136.         print("=" * BAR_LENGTH)
  137.         print(f"{len(result)}件の画像をサルベージしました")
  138.         print("-" * BAR_LENGTH)
  139.  
  140.         for i, url in enumerate(result, start=1):
  141.             print(f"[{i:02d}] {url}")
  142.  
  143.         print("=" * BAR_LENGTH)
  144.     else:
  145.         print("サルベージに失敗しました")
  146.  
Advertisement
Add Comment
Please, Sign In to add comment