【2024年版】Notionで重複したページを削除するPythonスクリプト
重複したページを削除するPythonスクリプト
このページの目的
現在、EvernoteからNotionにデータを移し替えてる最中なのであるが、Notionのインポート処理はたまにノートの中でページを重複させてコピーすることがある。
その場合、手作業で一つづつ重複したページを削除するか、NotionのAPIを利用してPython等で整理するぐらいしか方法がないので、サクッと作成してみた。
下準備
Notion Integration Tokenの取得
Notion Integration Tokenが必要になります。下記のページより、Notionを操作するのに必要になるトークンを取得してください。
ここで設定した名前が接続先でしようするアプリ名になります。
DBにプログラムから操作する権限を与える
次に、重複しているページが存在してるDBのIDが必要になります。こちらはそのDBのURLの下記の部分がDBのIDになります。
https://www.notion.so/1111112333333aaabbbbb?v=ccccddddeeeeeffff8888833333
上記の「1111112333333aaabbbbb」の部分がDBのIDです。
その次に、そのDBの三点リーダーで、そのDBの設定を開いて、「コネクト」 > 「接続先」を開きます。
こちらに先ほどNotion Integration Tokenで作成したアプリ名があるはずなので、APIからこのDBページを操作するための権限を与えるために許可します。
Pythonスクリプト
スクリプトの実行にはrequestsが必要です。仮想環境を作ってpipでインストールしてください。
import requests
import time
# Notion APIの設定
NOTION_API_URL = "https://api.notion.com/v1"
DATABASE_ID = "{{重複を整理したいDBのID}}"
NOTION_TOKEN = "{{先ほど取得したNotion Integration Token}}"
headers = {
"Authorization": f"Bearer {NOTION_TOKEN}",
"Content-Type": "application/json",
"Notion-Version": "2022-06-28"
}
def get_all_database_pages(database_id):
url = f"{NOTION_API_URL}/databases/{database_id}/query"
pages = []
has_more = True
next_cursor = None
while has_more:
data = {}
if next_cursor:
data["start_cursor"] = next_cursor
response = requests.post(url, headers=headers, json=data)
response.raise_for_status()
result = response.json()
pages.extend(result.get("results", []))
has_more = result.get("has_more", False)
next_cursor = result.get("next_cursor", None)
if has_more:
time.sleep(0.5) # 少し待機して次のリクエストを送信
return pages
def delete_page(page_id):
url = f"{NOTION_API_URL}/blocks/{page_id}"
print(f"Deleting page with ID: {page_id}")
while True:
response = requests.delete(url, headers=headers)
if response.status_code == 429:
print(f"Rate limit reached. Retrying in 1 minute...")
time.sleep(60)
elif response.status_code != 200:
print(f"Failed to delete page: {response.status_code}, {response.text}")
response.raise_for_status()
else:
break
print(f"Deleted page: {page_id}")
def find_and_delete_duplicates(pages):
titles_and_dates = {}
batch_size = 5
batch_count = 0
for page in pages:
properties = page.get("properties", {})
name_property = properties.get("名前", {})
title_list = name_property.get("title", [])
created_time = properties.get("作成日時", {}).get("created_time", "")
if title_list:
title = title_list[0].get("text", {}).get("content", "")
print(f"Processing title: {title}, Created time: {created_time}")
if title in titles_and_dates and titles_and_dates[title] == created_time:
print(f"Duplicate found: {title}, Page ID: {page['id']}")
delete_page(page["id"])
batch_count += 1
if batch_count >= batch_size:
print("Batch limit reached, waiting for 1 minute...")
time.sleep(60)
batch_count = 0
else:
titles_and_dates[title] = created_time
else:
print("No title found for page:", page["id"])
def main():
pages = get_all_database_pages(DATABASE_ID)
find_and_delete_duplicates(pages)
if __name__ == "__main__":
main()
このスクリプトでやってること
ページの取得
def get_all_database_pages(database_id):
url = f"{NOTION_API_URL}/databases/{database_id}/query"
pages = []
has_more = True
next_cursor = None
while has_more:
data = {}
if next_cursor:
data["start_cursor"] = next_cursor
response = requests.post(url, headers=headers, json=data)
response.raise_for_status()
result = response.json()
pages.extend(result.get("results", []))
has_more = result.get("has_more", False)
next_cursor = result.get("next_cursor", None)
if has_more:
time.sleep(0.5) # 少し待機して次のリクエストを送信
return pages
ページネーションに対応しつつ、全てのページのデータをpagesに保存して返します。
ページの削除
def delete_page(page_id):
url = f"{NOTION_API_URL}/blocks/{page_id}"
print(f"Deleting page with ID: {page_id}")
while True:
response = requests.delete(url, headers=headers)
if response.status_code == 429:
print(f"Rate limit reached. Retrying in 1 minute...")
time.sleep(60)
elif response.status_code != 200:
print(f"Failed to delete page: {response.status_code}, {response.text}")
response.raise_for_status()
else:
break
print(f"Deleted page: {page_id}")
ページを削除する関数。レートリミットエラーが発生した場合には1分間待機して再試行します。
重複ページの検出と削除
def find_and_delete_duplicates(pages):
titles_and_dates = {}
batch_size = 5
batch_count = 0
for page in pages:
properties = page.get("properties", {})
name_property = properties.get("名前", {})
title_list = name_property.get("title", [])
created_time = properties.get("作成日時", {}).get("created_time", "")
if title_list:
title = title_list[0].get("text", {}).get("content", "")
print(f"Processing title: {title}, Created time: {created_time}")
if title in titles_and_dates and titles_and_dates[title] == created_time:
print(f"Duplicate found: {title}, Page ID: {page['id']}")
delete_page(page["id"])
batch_count += 1
if batch_count >= batch_size:
print("Batch limit reached, waiting for 1 minute...")
time.sleep(60)
batch_count = 0
else:
titles_and_dates[title] = created_time
else:
print("No title found for page:", page["id"])
重複するタイトルと作成日時のページを検出して削除します。バッチ処理を用いて、一度に多くのリクエストを送信しないようにしています。
備考
無料ユーザだとAPIの利用制限が割と厳しいのだけど、一応、5個削除するたびに1分の停止をしてるので引っかからないとは思う。
Discussion