Skip to content

Instantly share code, notes, and snippets.

@kepoorz
Created April 11, 2025 04:37
Show Gist options
  • Save kepoorz/170158c5ce5512c492cfb11f14c607db to your computer and use it in GitHub Desktop.
Save kepoorz/170158c5ce5512c492cfb11f14c607db to your computer and use it in GitHub Desktop.
ObsidianのVault内のタグをテキストファイルに抽出するスクリプト
r"""
Obsidian Tag Exporter
主な機能:
- 指定されたObsidianボールト内の全てのマークダウンファイル (.md) を再帰的に検索します。
- 各ファイルからフロントマター (YAML形式) と本文中のハッシュタグ (#tag) を抽出します。
- 抽出したタグの使用回数を集計します。
- 集計結果を指定されたファイルにCSV形式で出力します。
- 出力するタグは、使用回数またはタグ名でソートできます。
- 指定した最小使用回数以上のタグのみを出力できます。
コマンドラインサンプル:
Windows (コマンドプロンプト or PowerShell):
python tag_exporter.py "C:\\Users\\YourUser\\Documents\\MyObsidianVault" -o my_tags.txt -s count -m 5
Mac/Linux (ターミナル):
python3 tag_exporter.py "/Users/youruser/Documents/MyObsidianVault" -o my_tags.txt -s name -m 3
使用方法:
1. スクリプトを実行する環境に Python がインストールされていることを確認してください。
2. ターミナルまたはコマンドプロンプトを開き、スクリプトが存在するディレクトリに移動します。
3. 以下の形式でコマンドを実行します:
python tag_exporter.py <vault_path> [オプション]
<vault_path>: (必須) タグを抽出したいObsidianボールトのルートディレクトリへのパス。
パスにスペースが含まれる場合は、引用符 (") で囲んでください。
オプション:
-o, --output <ファイル名>: 出力ファイル名を指定します (デフォルト: obsidian_tags.txt)。
-s, --sort <ソート方法>: タグのソート方法を指定します。
- 'count': 使用回数が多い順 (デフォルト)
- 'name': タグ名のアルファベット順
-m, --min-count <最小回数>: 出力するタグの最小使用回数を指定します (デフォルト: 1)。
この回数未満のタグは出力されません。
注意点:
- ボールトのパスは正確に指定してください。
- ファイルの読み込み権限が必要です。
- フロントマターのタグは、`tags: [tag1, "tag2"]` や YAMLリスト形式 (`tags:\n - tag1\n - tag2`) に対応しています。
- 本文中のタグは `#` で始まる英数字、ハイフン、アンダースコア、スラッシュで構成されるものを抽出します (例: #project/a-1_b)。
- 出力ファイルはスクリプトを実行したディレクトリに作成されます。既存の同名ファイルは上書きされます。
"""
import os
import re
import argparse
from collections import Counter
def extract_tags(content):
"""マークダウンコンテンツからタグを抽出する(改良版)"""
# フロントマターを処理
frontmatter_tags = []
frontmatter_match = re.search(r'---\s*\n(.*?)\n---', content, re.DOTALL)
if frontmatter_match:
frontmatter = frontmatter_match.group(1)
# タグリスト形式: tags: [tag1, tag2]
tags_list_match = re.search(r'tags:\s*\[(.*?)\]', frontmatter, re.DOTALL)
if tags_list_match:
tags_str = tags_list_match.group(1)
# 引用符で囲まれたタグを抽出
quoted_tags = re.findall(r'["\'](.*?)["\'"]', tags_str)
# 引用符なしのタグも抽出 (カンマで区切られている)
unquoted_tags = [t.strip() for t in re.split(r',\s*', tags_str) if t.strip() and not (t.strip().startswith('"') or t.strip().startswith("'"))]
frontmatter_tags.extend(quoted_tags + unquoted_tags)
# リスト形式: tags:\n - tag1\n - tag2
tags_yaml_list = re.findall(r'tags:\s*(?:\n\s*-\s*(.*?))+', frontmatter, re.DOTALL)
if tags_yaml_list:
# タグ行を抽出
tag_lines = re.findall(r'\n\s*-\s*(.*?)(?=\n\s*-|\n\s*[a-zA-Z]|\Z)', frontmatter + '\n')
frontmatter_tags.extend([t.strip() for t in tag_lines if t.strip()])
# タグを正規化
frontmatter_tags = [tag.strip() for tag in frontmatter_tags if tag and tag.strip()]
# 本文中のハッシュタグを抽出 (#tag)
# 有効なタグは英数字、ハイフン、アンダースコア、スラッシュを含み、特殊文字や空白を含まない
body_tags = re.findall(r'(?<!\S)#([a-zA-Z0-9_/-]+)(?!\S)', content)
# フロントマターの範囲内のタグは除外(すでに処理済み)
if frontmatter_match:
fm_start = content.find('---')
fm_end = content.find('---', fm_start + 3) + 3
if fm_start >= 0 and fm_end > fm_start:
body_content = content[:fm_start] + content[fm_end:]
body_tags = re.findall(r'(?<!\S)#([a-zA-Z0-9_/-]+)(?!\S)', body_content)
# 空のタグや無効なタグをフィルタリング
body_tags = [tag for tag in body_tags if tag and not tag.isspace()]
return frontmatter_tags + body_tags
def find_tags_in_vault(vault_path):
"""ボールト内の全マークダウンファイルからタグを収集"""
all_tags = Counter()
for root, _, files in os.walk(vault_path):
for file in files:
if file.endswith('.md'):
file_path = os.path.join(root, file)
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
tags = extract_tags(content)
all_tags.update(tags)
except Exception as e:
print(f"ファイル {file_path} の処理中にエラーが発生しました: {e}")
return all_tags
def main():
parser = argparse.ArgumentParser(description='Obsidianボールト内のタグを抽出してテキストファイルに出力します')
parser.add_argument('vault_path', help='Obsidianボールトのパス')
parser.add_argument('--output', '-o', default='obsidian_tags.txt', help='出力ファイル名 (デフォルト: obsidian_tags.txt)')
parser.add_argument('--sort', '-s', choices=['name', 'count'], default='count',
help='ソート方法: name (名前順) または count (使用回数順、デフォルト)')
parser.add_argument('--min-count', '-m', type=int, default=1,
help='表示する最小使用回数 (デフォルト: 1)')
args = parser.parse_args()
print(f"ボールト {args.vault_path} からタグを抽出しています...")
tags = find_tags_in_vault(args.vault_path)
# フィルタリング
valid_tags = {tag: count for tag, count in tags.items()
if tag and count >= args.min_count and not tag.isspace()}
# タグをソート
if args.sort == 'name':
sorted_tags = sorted(valid_tags.items())
else: # count
sorted_tags = sorted(valid_tags.items(), key=lambda x: x[1], reverse=True)
# 結果を出力
with open(args.output, 'w', encoding='utf-8') as f:
f.write(f"# Obsidian タグ一覧 ({len(valid_tags)} タグ)\n\n")
f.write("タグ,使用回数\n")
for tag, count in sorted_tags:
f.write(f"{tag},{count}\n")
print(f"{len(valid_tags)} 個のタグを {args.output} に出力しました")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment