「後で読む」と思ってPocketに保存した記事が、気がつけば数百件と溜まっていませんか?私もその一人です😫興味深い記事を見つけるたびにPocketに保存するものの、実際に読み返すことは少なく、積読状態😑
しかし、Pocketに大きな変化が起きました。Mozilla(Firefox の開発元)によるPocketの終了が2025年7月と発表されました。これにより、多くのユーザーが代替サービスへの移行を余儀なくされます。
Pocketは記事を保存するには便利なツールですが、記事を整理・活用するとなると少し物足りない部分もありました。
- タイトルやタグでの検索はできるが、内容からの検索が難しい
- メモやプロジェクトとの関連付けができず、さらに別の場所に格納する必要がある
- カテゴリ分けが思うようにいかない
移行後にはこれらの問題を解決できるものにしたいと思い、個人的にはNotionを採用することを決めました。
Notionについて
Notionは情報管理がかなり優秀だと感じています。その理由は以下でしょうか。
データベース機能による構造化
Notionのデータベース機能を使えば、URLやタイトルだけでなく、評価、メモなど、いろいろな情報を加えて管理ができます。
検索・フィルタリング機能のアップ
データベース化することで、複数の条件を組み合わせたフィルタリングが検索ができるようになります。
他の情報との連携
データベースの情報を互いに関連付けることができます。個人的にはこれが一番やりたいことになります。
移行は、登録している件数が非常に多いため、Pythonでツールを作ることにします。すでに移行ツールはありそうではありますが、車輪の再開発大好き🤩なので自作にチャレンジとなります。
- Notionについて
- Pocketエクスポート形式の変更
- 移行ツールの概要
- 処理の全体像
- 実際に使ってみる
- おわりに
- 完全なソースコード
Pocketエクスポート形式の変更
ツール作成を始めて最初に気づいたのが、Pocketのエクスポート形式が2024年あたりに大きく変わったことでした。 以前はHTMLファイル形式だったのが、現在はCSV形式(ZIP圧縮)に変更されています。
- ファイル形式がCSV(カンマ区切り値)
- エクスポートは管理ページからリクエスト
- エクスポートデータはメールで送られたリンクからダウンロード(Pocket.zipという名前になります)
- 10,000件ずつ複数のCSVファイルに分割
以前からなぜHTMLなのかな🤔と思っていた節もあったので、今回の変更はどちらかといえば嬉しく思えます。
【参考】以前のエクスポート情報 古いので使用しないように!
移行ツールの概要
PocketのCSVエクスポートデータからNotionデータベースに記事情報を一括移行するPythonツールとなります。
以降では、PocketのCSVデータ構造、Notion APIの設定について準備していきます。
Pocketデータの構造
Pocketのエクスポートデータがどのような構造になっているかを理解していきます。
最新のエクスポート方法
【参考】
Pocketからのデータエクスポート手順は以下のようになります。
1)Pocketアカウントでログインした状態で、getpocket.com/export にアクセスし、【CSVファイルをエクスポート】をクリック

2)エクスポート要求の確認メッセージが表示、同時に要求確認のメールが届きます。


3)最大24時間以内(通常は数分)でメールが届くので、メール内のリンクからpocket.zipファイルをダウンロード

注意点
- 大量のデータがある場合は処理に時間がかかる
- ダウンロードリンクは3日で有効期限切れ
- 10,000件を超える場合、複数のCSVファイルに分割される
ZIPファイルの構造
ダウンロードしたpocket.zipは以下の構造になっています。
pocket.zip ├── pocket_data_1.csv # 1〜10,000件目 ├── pocket_data_2.csv # 10,001〜20,000件目(データが多い場合) └── ... # 必要に応じて追加のCSVファイル
大量のデータがある場合、自動的に10,000件ずつ複数のCSVファイルに分割されます。
CSVファイルの構造詳細
各CSVファイルの構造は以下のようになっています:
CSVデータの例
title,url,time_added,tags,status とほほのWWW入門,https://www.tohoho-web.com/www.htm,1652050889,,unread 国土数値情報ダウンロードサービス,http://nlftp.mlit.go.jp/ksj/index.html,1523686303,map,unread https://streamyard.com/,https://streamyard.com/,1618294655,,unread …以下略…
カラム(列)の詳細説明
| カラム名 | データ型 | 説明 | 例 |
|---|---|---|---|
title |
文字列 | 記事のタイトル(空の場合はURLと同じ値) | とほほのWWW入門 |
url |
文字列 | 記事のURL(必須) | https://www.tohoho-web.com/www.htm |
time_added |
数値 | 追加日時のUnix timestamp | 1652050889 |
tags |
文字列 | タグ(単一、空の場合が多い) | map |
status |
文字列 | 記事のステータス | unread / archive |
データの注意点
- タイトル … 一部の記事では
titleがURLと同じ値になっている(Pocketが記事のタイトルを取得できなかった場合) - タイムスタンプ … Unix timestamp形式(1970年1月1日からの秒数)
- タグ … 多くの記事でタグが空(空文字列)の事が多い
Notionの準備
移行先となるNotion側の準備を行います。
基本的には以前書いた以下の記事とほぼ同じ操作を行うことになるので、概要だけの説明にします。
参考
Integrationの作成(Notion APIを使用時に必要)
1. Notionにログインした状態で、Notionクリエータープロフィール/インテグレーション にアクセスして、【新しいインテグレーション】ボタンをクリック

2. インテグレーションの名を入力(今回はPocket CSV Importerとする)、関連ワークスペースを選択、【保存】ボタンをクリック


3. 表示された内部インテグレーションシークレット(以降トークン)をコピーして保存しておく。

このトークンは後ほどプログラムで使用します。絶対に外部に漏らさないよう注意してください。
格納するデータベースを事前に作成する
Pocketデータに対応したNotionデータベースを作成します。

今回は以下のような構造にしていますが、必要な情報があれば適宜追加してください。データベースの名前はPocketにしました。

| プロパティ名 | タイプ | 必須 | 説明 |
|---|---|---|---|
| Title | Title(デフォルト値) | ◯ | 記事のタイトル |
| URL | URL | ◯ | 記事のURL |
| Domain | テキスト | ◯ | ウェブサイトのドメイン名(自動抽出) |
| Source | 選択 | ◯ | データソース(「Pocket」を選択肢として作成) |
| Status | 選択 | △ | 元のPocketでのステータス(Unread/Archive) |
| AddedDate | Date | △ | Pocketに追加された日時 |
| Tags | マルチセレクト | △ | 記事のタグ |
| ReadingStatus | 選択 | △ | 新しい読了状況(未読、読了、保留など) |
| Rating | 選択 | △ | 記事の評価(⭐-⭐⭐⭐⭐⭐) |
選択のプロパティの設定
以下の選択・マルチセレクトのタイプはオプション(プロパティ)は、事前に選択項目を設定します。
プロパティの【オプション+】ボタンをクリックすると追加できます。
Sourceオプション(プロパティ)
- Pocket(必須)

Statusオプション(プロパティ)
- Unread
- Archive

ReadingStatusオプション(プロパティ)
未読読了保留要再読

Ratingオプション(プロパティ)
- なし
- ⭐⭐⭐⭐⭐
- ⭐⭐⭐⭐
- ⭐⭐⭐
- ⭐⭐
- ⭐

データベースの共有設定
作成したデータベースをIntegrationに接続します。
1. データベースページの右上【…】をクリック
2. 【接続】をクリック
3. 作成したインテグレーションPocket CSV Importerを検索して選択

4. ダイアログで接続する【はい】ボタンをクリック

データベースIDの取得
プログラムで使用するデータベースIDを取得します。データベースIDはデータベースのURLに含まれています。 URLは以下のような形式になっています。
https://www.notion.so/[ドメイン名(省略可)]/[DATABASE ID(32桁の文字列)]?v=[VIEW ID]
具体的には、以下のようなURLとなっています。
例
https://www.notion.so/1f602b8bc47b8097a475e1a362a1da8d?v=1f602b8bc47b809e87c2000cc1081d49
このうちドメインの後にある”/” と ”?” で囲まれた部分の32桁の部分がデータベースIDとなります。この値をコピーして控えておきます。この例であれば1f602b8bc47b8097a475e1a362a1da8dとなります。
(注意点)APIの制限事項と対策
Notion APIには以下のレート制限があります。
- 同時リクエスト制限: 1つのIntegrationあたり平均で毎秒3リクエストまで(短期的なバーストは許容)。HTTP接続の同時数に明確な制限はないが、レート制限に達すると
429 Too Many Requestsが返る。
大量のCSVデータを移行する際は、上記の制限を考慮して処理間隔を調整できるようにしています。 今回は0.3秒の待機時間を設けています。
処理の全体像
ここまでの準備ができたので、以下のような流れでデータ移行を行います。 ソースコードの全体はこのページの末尾にあります。
- Pocketからエクスポート: CSV形式のZIPファイルをメールで受信
- ZIPファイル展開: 複数のCSVファイルを抽出
- CSVファイル解析: pandasでデータ読み込み・クリーニング
- データ変換: PocketのCSVデータをNotion形式に変換
- API呼び出し: Notion APIでデータベースに順次追加
- 結果確認: 移行成功・失敗の集計とレポート
ツールの実装
ライブラリ選定の変更
- pandas … CSVデータの処理
- notion-client … Notion API操作
- zipfile … ZIPファイルの展開(標準ライブラリのためインストールは不要)
- python-dotenv … APIキーなどの設定管理
ZIPファイル展開処理
Pocketのエクスポートデータは、ZIPファイル内に複数のCSVが含むため、ツールにも展開処理を含めています。
def extract_csv_from_zip(self, zip_file_path: str) -> List[str]: """ ZIPファイルからCSVファイルを抽出する Args: zip_file_path (str): PocketエクスポートZIPファイルのパス Returns: List[str]: 抽出されたCSVファイルのパスリスト Raises: FileNotFoundError: ZIPファイルが存在しない場合 zipfile.BadZipFile: 無効なZIPファイルの場合 """ # ... 省略 ... except FileNotFoundError: logger.error(f"ZIPファイルが見つかりません: {zip_file_path}") raise except zipfile.BadZipFile: logger.error(f"無効なZIPファイルです: {zip_file_path}") raise except Exception as e: logger.error(f"ZIPファイルの展開中にエラーが発生しました: {str(e)}") raise
pandas活用したCSV解析処理
PocketのCSVファイルから記事情報を抽出しています。
def parse_pocket_csv(self, csv_file_path: str) -> List[Dict[str, Any]]: """PocketのCSVファイルを解析して記事情報を抽出""" try: # 複数エンコーディングに対応したCSV読み込み try: df = pd.read_csv(csv_file_path, encoding='utf-8') except UnicodeDecodeError: try: df = pd.read_csv(csv_file_path, encoding='cp1252') except UnicodeDecodeError: df = pd.read_csv(csv_file_path, encoding='shift_jis') articles: List[Dict[str, Any]] = [] # 各行を処理 for _, row in df.iterrows(): # タイトルの処理(空の場合はURLを使用) title = row.get('title', '') if pd.isna(title) or title == '': title = row.get('url', '') # URLチェック(必須項目) url = row.get('url', '') if pd.isna(url): continue # URLが無い行はスキップ article: Dict[str, Any] = { 'title': str(title), 'url': str(url), 'tags': [], 'added_date': None, 'time_added': None, 'status': str(row.get('status', 'unread')) } # タイムスタンプ処理 time_added = row.get('time_added') if not pd.isna(time_added): try: timestamp = int(float(time_added)) article['added_date'] = datetime.fromtimestamp(timestamp) article['time_added'] = str(timestamp) except (ValueError, TypeError, OSError) as e: logger.warning(f"タイムスタンプの変換に失敗: {time_added}") # タグを処理 tags = row.get('tags') if not pd.isna(tags) and tags != '': # タグは単一の文字列として格納されている場合が多い tag_str = str(tags).strip() if tag_str: # カンマ区切りの場合とスペース区切りの場合に対応 if ',' in tag_str: article['tags'] = [tag.strip() for tag in tag_str.split(',') if tag.strip()] else: article['tags'] = [tag_str] articles.append(article) return articles except Exception as e: logger.error(f"CSVファイルの解析中にエラー: {str(e)}") raise
Notion API呼び出し処理
抽出したCSVデータをNotionデータベースに送信する処理になります。
以前のエントリの拡張版となります。基本は以下を参照してください。
def create_notion_page(self, article: Dict[str, Any]) -> bool: """Notionデータベースに記事ページを作成""" try: # URLからドメインを自動抽出 domain = '' try: from urllib.parse import urlparse parsed_url = urlparse(article['url']) domain = parsed_url.netloc except Exception: domain = '' # 必須プロパティを構築 properties: Dict[str, Any] = { "Title": { "title": [{"text": {"content": article['title'][:100]}}] }, "URL": { "url": article['url'] }, "Domain": { "rich_text": [{"text": {"content": domain}}] }, "Source": { "select": {"name": "Pocket"} } } # オプションプロパティを存在確認してから追加 # Status プロパティ(Pocketでの元ステータス) if "Status" in self.available_properties: properties["Status"] = { "select": {"name": article.get('status', 'unread').capitalize()} } # ReadingStatus プロパティ(Notionでの読了管理、デフォルトは「未読」) if "ReadingStatus" in self.available_properties: properties["ReadingStatus"] = { "select": {"name": "未読"} } # 追加日時があり、プロパティが存在する場合のみ設定 if article.get('added_date') and "AddedDate" in self.available_properties: properties["AddedDate"] = { "date": {"start": article['added_date'].isoformat()} } # タグがあり、プロパティが存在する場合のみ設定 if article.get('tags') and "Tags" in self.available_properties: properties["Tags"] = { "multi_select": [ {"name": tag[:100]} for tag in article['tags'][:10] ] } # ページ作成 response = self.notion.pages.create( parent={"database_id": self.database_id}, properties=properties ) self.imported_count += 1 return True except APIResponseError as e: logger.error(f"Notion API エラー: {str(e)}") self.error_count += 1 return False
APIキーなどの設定と管理
APIキーやデータベースIDといった情報に関しては.envに格納して使用しています。
def main() -> None: """ メイン実行関数 環境変数から設定を読み込み、PocketからNotionへのインポートを実行する 必要な環境変数が設定されていない場合は、エラーメッセージを表示して終了する """ # 設定を.envファイルから読み込み notion_token: Optional[str] = os.getenv('NOTION_TOKEN') database_id: Optional[str] = os.getenv('NOTION_DATABASE_ID') file_path: str = os.getenv('POCKET_FILE', 'pocket.zip') # 設定の確認 if not notion_token: print("エラー: NOTION_TOKENが設定されていません") print(".envファイルにNotion Integration Tokenを設定してください") print("例: NOTION_TOKEN=secret_your_token_here") return if not database_id: print("エラー: NOTION_DATABASE_IDが設定されていません") print(".envファイルにNotionデータベースIDを設定してください") print("例: NOTION_DATABASE_ID=your_database_id_here") return if not os.path.exists(file_path): print(f"エラー: ファイルが見つかりません: {file_path}") print("Pocketからエクスポートしたファイル(pocket.zipまたは.csv)を同じディレクトリに配置してください") print("または.envファイルでファイルパスを設定してください: POCKET_FILE=path/to/your/file") return
.envファイルの例
各値は以下と入れ替えてください。
- secret_your_integration_token … Integrationのトークン
- your_database_id … データベースID
# Notion設定 NOTION_TOKEN=secret_your_integration_token_here NOTION_DATABASE_ID=your_database_id_here # ファイル設定(CSVまたはZIP) POCKET_FILE=pocket.zip # API制御設定 API_DELAY=0.3
実際に使ってみる
セットアップ手順
1. 必要なファイルの準備
まず、プロジェクト用のディレクトリを作成し、必要なファイルを配置します:
pocket-to-notion/ ├── pocket_to_notion.py # メインプログラム(CSV対応版) ├── requirements.txt # 依存ライブラリ(pandas追加) ├── .env.example # 設定テンプレート ├── .env # 実際の設定(後で作成) └── pocket.zip # Pocketエクスポートファイル(ZIPまたはCSV)
2. Python環境の準備**
仮想環境を作成して、ライブラリのインストールします。
# 仮想環境の作成 $ python -m venv venv # 仮想環境の有効化 $ source venv/bin/activate # 依存ライブラリのインストール $ pip install -r requirements.txt
requirements.txtの内容
notion-client>=2.0.0 pandas>=1.5.0 requests>=2.28.0 python-dotenv>=1.0.0
3. 環境変数などの設定
設定ファイルの作成
env.exampleを.envにコピーして、実際の設定値を入力します。
リポジトリのファイル名の先頭にはドットがありませんが、実際に使用するファイルは先頭にドットがありますので注意してください。
$ cp env.example .env
4. データの準備確認
- Pocketからエクスポートした
pocket.zipがプロジェクトディレクトリにあることを確認 - NotionのIntegrationトークンとデータベースIDが正しく設定されていることを確認
- Notionデータベースが適切なプロパティ(Status含む)を持っていることを確認
実行
ツールは以下のように実行します。
$ python pocket_to_notion.py
実行結果


おわりに
Pocketのサービス終了が2025年7月に迫っているということもあり、自分も無事に移行できて良かったです。 ただ、長年愛用してきたサービスがなくなる寂しさを感じています😢