ナツぬいと一緒にpixivのブクマを見返していました。
削除済み 非公開となってそこに何があったのかすら思い出せない、泣きゲーの結末のような儚さがありますよね。ですが、往生際が悪いのがオタクの性というものです。なんとかしてサルベージする方法を検討してみました。
まず、ブックマークした作品であるとF12で開発者ツールを開いて該当箇所にカーソルを合わせれば作品IDが分かります。(スマホの場合は長押しすると分かったりします。)作品IDを取得できたら pixiv artworks 1145141919(作品ID) みたいな感じでgoogle検索をしてみてください。運が良ければNozomi.la sankaku complex danbooru等に無断転載されたものが見つかります。見つからなかった場合はyandexとbaiduでも一応検索してみてください。もしかしたらweiboにあるかもしれません。あるいはXで検索するのも一つの手です。作者がそっちで公開している可能性もミリ単位であります。
もしそれでも見つからなかった場合は、魚拓が取られている可能性に賭けて、archive.isとmegalodonで検索してみてください。ナツぬい編集部ではpixivの投稿をarchive.todayでアーカイブしよう運動を奨励しております。
まあここまでのサルベージ方法なら知ってる方が大半だと思います。 なのでもう少し踏み込んだ内容を。
海外のギーク・ナードなら何かしら新規性のある方法を見つけているのではないと思いredditを読んでいたところ気になる書き込みを見つけました。
pixivの画像は削除されたとしてもサーバーには保存されていることがたまにあるようです。基準は不明。もしかしたら非公開とかマイピク限定公開なら見れて削除された場合は見れないのかも。
ttps://i.pximg.net/img-original/img/year/month/day/hours/mins/secs/<ID>.{extension}
これがpximg側のCDNのアドレスらしいですね。 ブクマの削除された作品もIDは取得できるので、その前後IDの投稿時間から絞り込みを行って総当たりを行えばワンチャン復元できる可能性があります。
ナツぬいと一緒にこの手法を試してみました。
https://www.pixiv.net/artworks/128906393 が削除済みで、これの投稿時間を前後128906392-128906394の投稿時間で絞ってスクリプトで総当たりしたところ、
https://i.pximg.net/img-original/img/2025/04/03/13/50/19/128906393_p0.jpg
を得ました。
有能な読者の方が自動発掘スクリプトを書いてくださったので共有します!
https://gitlab.com/-/snippets/4893870
それではよきpixivライフを。
以下改定前の駄文です
(2025-08-06追記:リファラーをpximg.netにしないと弾かれます )
chromeならこのリンクからTampermonkeyっていうuserscriptを実行できる拡張を入れた後、
これをコピペして適用してください。たぶんいけます。上のアロナのえっちな絵が出てきたら勝ちです。
10%くらいの確率で復元できると思います。 それでは、良きpixivライフを
以下、チャッピー製の自動発掘スクリプトです。
import tkinter as tk
from tkinter import messagebox
from datetime import datetime, timedelta
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from time import sleep
driver = None # ← グローバルで保持!
def generate_candidate_times(before_str, after_str):
before = datetime.strptime(before_str, “%Y/%m/%d/%H/%M/%S”)
after = datetime.strptime(after_str, “%Y/%m/%d/%H/%M/%S”)
times = []
t = before # ← 開始を「beforeちょうど」に変更
while t <= after: # ← 「after秒」も含める
times.append(t.strftime(“%Y/%m/%d/%H/%M/%S”))
t += timedelta(seconds=1)
return times
def open_urls():
global driver # ← ここで global 宣言
try:
pixiv_id = int(entry_id.get())
before_time = entry_before.get()
after_time = entry_after.get()
extensions = [“.jpg”, “.png”]
before_id = pixiv_id – 1
after_id = pixiv_id + 1
candidate_times = generate_candidate_times(before_time, after_time)
options = Options()
options.add_argument(“–start-maximized”)
driver = webdriver.Chrome(options=options)
driver.get(“https://www.pixiv.net/”)
sleep(2)
for t in candidate_times:
for ext in extensions:
url = f”https://i.pximg.net/img-original/img/{t}/{pixiv_id}_p0{ext}”
driver.execute_script(f”window.open(‘{url}’, ‘_blank’);”)
sleep(1.2)
messagebox.showinfo(“完了”, f”Pixiv ID {pixiv_id} の候補URLをすべて開きました!\nChromeウィンドウは閉じません。”)
except Exception as e:
messagebox.showerror(“エラー”, str(e))
# GUI構築
root = tk.Tk()
root.title(“Pixiv CDN探索ツール”)
tk.Label(root, text=”Pixiv ID:”).grid(row=0, column=0, padx=10, pady=5, sticky=”e”)
entry_id = tk.Entry(root, width=30)
entry_id.grid(row=0, column=1, padx=10, pady=5)
tk.Label(root, text=”前の投稿時刻 (YYYY/MM/DD/HH/MM/SS):”).grid(row=1, column=0, padx=10, pady=5, sticky=”e”)
entry_before = tk.Entry(root, width=30)
entry_before.grid(row=1, column=1, padx=10, pady=5)
tk.Label(root, text=”後の投稿時刻 (YYYY/MM/DD/HH/MM/SS):”).grid(row=2, column=0, padx=10, pady=5, sticky=”e”)
entry_after = tk.Entry(root, width=30)
entry_after.grid(row=2, column=1, padx=10, pady=5)
tk.Button(root, text=”探索開始”, command=open_urls, width=20).grid(row=3, column=0, columnspan=2, pady=15)
root.mainloop()
コメント
スクリプトを改善(というより作り直し)してみました。
IDだけで絞り込めます。GUIは実装してません。
Google Colabに貼り付けるだけで動きます。
import itertools
from datetime import datetime, timedelta
import time
from typing import List, Optional, Tuple
import requests
PIXIV_ARTWORKS_URL = “https://www.pixiv.net/artworks/”
ARTWORKS_DETAIL_ENDPOINT = “https://www.pixiv.net/ajax/illust/”
ARTWORKS_EXT_LIST = (“jpg”, “png”, “gif”) #複数の拡張子を混ぜるとjpgに変換されるのでjpg優先
ARTWORKS_TEMPLATE = (
“https://i.pximg.net/img-original/img/{timestamp}/{id}_p{number}.{ext}”
)
session = requests.Session()
session.headers.update(
{
“Referer”: “https://pixiv.net/”,
“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″,
}
)
def get_artwork_timestamp(artwork_id: int) -> Optional[str]:
try:
response = session.get(f”{ARTWORKS_DETAIL_ENDPOINT}{artwork_id}”, timeout=10)
response.raise_for_status()
except requests.RequestException as e:
print(f”IDの取得に失敗しました {artwork_id}: {e}”)
return None
try:
artwork_data = response.json().get(“body”, {}).get(“userIllusts”, {})
timestamp = artwork_data.get(str(artwork_id), {}).get(“updateDate”)
if not timestamp:
print(f”タイムスタンプが見つかりませんでした {artwork_id}”)
return timestamp
except ValueError as e:
print(f”Jsonのパースに失敗しました {artwork_id}: {e}”)
return None
def find_valid_id(
start_id: int, step: int, max_attempts: int = 30, interval: float = 0.5
) -> Optional[Tuple[int, str]]:
current_id = start_id
for attempt in range(max_attempts):
timestamp = get_artwork_timestamp(current_id)
if timestamp is not None:
print(f”有効なIDを見つけました: {current_id} タイムスタンプ: {timestamp}”)
return current_id, timestamp
else:
print(f”タイムスタンプの取得に失敗しました: {current_id}, 次のIDを取得します…”)
current_id += step
time.sleep(interval)
print(
f”ID {start_id} から {max_attempts} 回試行しましたが、有効なIDを見つけられませんでした”
)
return None
def generate_timestamps(start: datetime, end: datetime):
current = start
while current Optional[Tuple[str, str, str]]:
for ext in ARTWORKS_EXT_LIST:
for ts in generate_timestamps(start, end):
url = ARTWORKS_TEMPLATE.format(
timestamp=ts, id=artwork_id, number=0, ext=ext
)
response = session.head(url, timeout=10)
if response.status_code == 200:
print(“ベースリンクの取得に成功しました:” + url)
return url, ts, ext
else:
print(“URLが見つかりませんでした:” + url)
time.sleep(interval)
return None
def get_all_artworks_images(artwork_id: int, timestamp: str, ext: str, interval: float = 0.5) -> List[str]:
urls = []
for i in itertools.count():
url = ARTWORKS_TEMPLATE.format(
timestamp=timestamp, id=artwork_id, number=i, ext=ext
)
response = session.head(url, timeout=10)
if response.status_code == 200:
urls.append(url)
print(“取得に成功しました:” + url)
time.sleep(interval)
else:
break
return urls
def salvage_pixiv(artwork_id: int, interval: float = 0.5) -> Optional[List[str]]:
start_result = find_valid_id(start_id=artwork_id – 1, step=-1, interval=interval)
if start_result is None:
return None
start_id, start_timestamp = start_result
end_result = find_valid_id(start_id=artwork_id + 1, step=1)
if end_result is None:
return None
end_id, end_timestamp = end_result
start_datetime = datetime.fromisoformat(start_timestamp)
end_datetime = datetime.fromisoformat(end_timestamp)
result = check_all_pattern_artworks(start_datetime, end_datetime, artwork_id, interval)
if result is None:
return None
url, ts, ext = result
return get_all_artworks_images(artwork_id, ts, ext, interval)
if “__main__” == __name__:
artwork_id = int(input(“IDを入力してください:”).strip())
interval = 0.75 #HEADがメインなので0.5秒くらいでも多分大丈夫。ただ長ければ長いほど安全
result = salvage_pixiv(artwork_id, interval=interval)
if result is not None:
BAR_LENGTH = 80
print(“=” * BAR_LENGTH)
print(f”{len(result)}件の画像をサルベージしました”)
print(“-” * BAR_LENGTH)
for i, url in enumerate(result, start=1):
print(f”[{i:02d}] {url}”)
print(“=” * BAR_LENGTH)
else:
print(“サルベージに失敗しました”)
インデント崩れたのでpastebinに記載します
https://pastebin.com/SX7KZgj1
おお まさかこのコードを改善してくださる方が現れるとは…
ありがとうございます。前後のidの絞り込みまで自動で行えるようになると効率爆上がりなので大変嬉しいコードです。
webアプリ的な感じで実行できるようにしてみたいですね チャッピーと格闘してみます!
せっかくなのでGUIも作ってみました。
tkinterは初めてなので、UIはClaudeに作ってもらいました。ついでにZip保存も積んでます。
ファイルを1つにまとめようとしたのですが長過ぎたので分割してます。
あと、前のコメントで書き忘れていたんですが、リクエスト間隔を設定できるようにしたのと、IDから全ての画像を取得するようにしています。
https://gitlab.com/-/snippets/4893870
ありがとうございます!
プログラミングは全くの門外漢なので本当に助かります!