秀丸エディタで「URL一括抽出」:文中のURLだけ抜き出して一覧化(重複排除も)

文章・議事録・ログ・メモの中に散らばったURLだけを、まとめて抜き出して一覧にしたい。
そんな時に効くのが「URL抽出 → 一覧化」。さらに重複を消せると、リンク集づくりや確認作業が一気に楽になる。

この記事では、秀丸マクロから PowerShell を呼び出して、

  • 選択範囲(なければ全文)からURLを抽出
  • 抽出結果を 新規タブ に一覧出力
  • 重複排除(保持順 or ソート)も選べる

…を一発でやる。


できること

  • http:// / https:// / www. のURLを抽出
  • 末尾につきがちな ) ] , などの“邪魔記号”をある程度取り除く(実用寄り)
  • 出力モード
  • 重複排除(出現順を保持)
  • 重複排除(ソートして集合化)
  • 重複そのまま
  • 抽出結果は 新規タブに出る(元の文章は壊さない)

導入手順(3分)

1) ファイル配置(この形にする)

マクロフォルダ(秀丸のマクロ用フォルダ)に、次の構成で置く。

(マクロフォルダ)
├─ url_extract_list.mac
└─ filters
   └─ extract_urls.ps1

2) マクロ登録

秀丸で url_extract_list.mac を開いて、必要なら「マクロ登録」。
(キー割り当てしておくと快適)


PowerShell:URL抽出フィルタ(filters\extract_urls.ps1)

param(
  # 出力モード
  # - unique_keep: 重複排除(出現順を保持)
  # - unique_sort: 重複排除(ソートして集合化)
  # - all_keep   : 重複排除なし(全部出す)
  [ValidateSet("unique_keep", "unique_sort", "all_keep")]
  [string]$Mode = "unique_keep",

  # www.example.com みたいなURLに https:// を付けて正規化する
  [switch]$AddScheme,

  # 重複判定を大小無視でやる(用途次第。URLは大小区別があり得るので注意)
  [switch]$CaseInsensitive
)

# UTF-8で入出力(秀丸側 runex の encode=6 と合わせる)
try {
  $utf8 = New-Object System.Text.UTF8Encoding($false)
  [Console]::InputEncoding  = $utf8
  [Console]::OutputEncoding = $utf8
} catch {}

$text = [Console]::In.ReadToEnd()
if ([string]::IsNullOrEmpty($text)) { exit 0 }

# URLっぽいものを拾う(実用寄り)
# - http/https
# - www.
$pattern = '(?i)\bhttps?://[^\s<>"''`]+|\bwww\.[^\s<>"''`]+'

# 末尾に付きがちな“邪魔記号”を落とす(完全パーサではない。現場向け)
function Trim-TrailingPunct([string]$u) {
  if ([string]::IsNullOrEmpty($u)) { return $u }

  $trimChars = @(
    '.', ',', ';', ':', '!', '?',
    '"', "'", '”', '’', '»',
    '。', '、', '!', '?',
    '>', '〉', '》', '」', '』', '】',
    '…',
    ' '  # 全角スペース
  )

  while ($u.Length -gt 0) {
    $last = $u.Substring($u.Length - 1, 1)

    # 単純に落とす系
    if ($trimChars -contains $last) {
      $u = $u.Substring(0, $u.Length - 1)
      continue
    }

    # ) ] } は「囲みURL」にくっつきやすいので、括弧バランスを見て落とす
    if ($last -eq ")") {
      $open  = ([regex]::Matches($u, '\(')).Count
      $close = ([regex]::Matches($u, '\)')).Count
      if ($close -gt $open) {
        $u = $u.Substring(0, $u.Length - 1)
        continue
      }
    }
    if ($last -eq "]") {
      $open  = ([regex]::Matches($u, '\[')).Count
      $close = ([regex]::Matches($u, '\]')).Count
      if ($close -gt $open) {
        $u = $u.Substring(0, $u.Length - 1)
        continue
      }
    }
    if ($last -eq "}") {
      $open  = ([regex]::Matches($u, '\{')).Count
      $close = ([regex]::Matches($u, '\}')).Count
      if ($close -gt $open) {
        $u = $u.Substring(0, $u.Length - 1)
        continue
      }
    }

    break
  }

  return $u
}

$matches = [regex]::Matches($text, $pattern)
if ($matches.Count -eq 0) { exit 0 }

$urls = New-Object System.Collections.Generic.List[string]
foreach ($m in $matches) {
  $u = $m.Value

  # <https://...> のような囲みを軽く剥がす
  if ($u.StartsWith("<")) { $u = $u.TrimStart("<") }
  if ($u.EndsWith(">"))   { $u = $u.TrimEnd(">") }

  $u = Trim-TrailingPunct $u

  if ($AddScheme -and $u -match '^(?i)www\.') {
    $u = "https://$u"
  }

  if (-not [string]::IsNullOrWhiteSpace($u)) {
    $urls.Add($u)
  }
}

switch ($Mode) {
  "all_keep" {
    $urls | ForEach-Object { $_ }
  }

  "unique_sort" {
    if ($CaseInsensitive) {
      $set = New-Object System.Collections.Generic.HashSet[string]([System.StringComparer]::OrdinalIgnoreCase)
    } else {
      $set = New-Object System.Collections.Generic.HashSet[string]
    }
    foreach ($u in $urls) { [void]$set.Add($u) }

    $arr = $set.ToArray()
    [Array]::Sort($arr, [System.StringComparer]::Ordinal)
    $arr | ForEach-Object { $_ }
  }

  default { # unique_keep
    if ($CaseInsensitive) {
      $seen = New-Object System.Collections.Generic.HashSet[string]([System.StringComparer]::OrdinalIgnoreCase)
    } else {
      $seen = New-Object System.Collections.Generic.HashSet[string]
    }

    foreach ($u in $urls) {
      if ($seen.Add($u)) { $u }
    }
  }
}

秀丸マクロ:抽出して新規タブに一覧出力(url_extract_list.mac)

/*
  url_extract_list.mac
  選択(なければ全文)→ PowerShellでURL抽出 → 新規タブに一覧出力
  元の文章は変更しない。
*/

#scope = val(input("対象: 1=選択(無ければ全文) / 2=全文", "1"));

#mode = val(input(
  "出力モード: 1=重複排除(順保持) / 2=重複排除(ソート) / 3=重複そのまま",
  "1"
));

#add = val(input("www. に https:// を付ける: 0=しない / 1=する", "0"));
#ci  = val(input("重複判定を大小無視: 0=しない / 1=する", "0"));

if (#scope == 2 || !selecting) {
  selectall;
}

$ps1 = macrodir + "\\filters\\extract_urls.ps1";
if (!existfile($ps1)) {
  message "見つからない: " + $ps1 + "\r\nfilters\\extract_urls.ps1 を配置しろ。";
  endmacro;
}

if (#mode == 2) {
  $modeArg = "unique_sort";
} else if (#mode == 3) {
  $modeArg = "all_keep";
} else {
  $modeArg = "unique_keep";
}

$addArg = "";
if (#add == 1) $addArg = " -AddScheme";

$ciArg = "";
if (#ci == 1) $ciArg = " -CaseInsensitive";

$cmd = "powershell.exe -NoProfile -ExecutionPolicy Bypass -File \"" + $ps1 + "\" -Mode " + $modeArg + $addArg + $ciArg;

# runex:選択をstdinに渡して、stdoutは「新規ファイル(新規タブ)」へ
#  - stdin  : 5=選択範囲
#  - stdout : 4=新規ファイル
#  - encode : 6=UTF-8
runex $cmd
, 1
, 5, ""
, 4, ""
, 7, ""
, 1, ""
, 0
, 1
, 6
, 1
;

if (!result) {
  message "失敗。PowerShell実行、パス、ExecutionPolicy を確認。";
  endmacro;
}

message "URL抽出 完了(新規タブに一覧を出した)";

使い方

1) URLが含まれる文章を開く
2) 必要なら範囲選択(選択しないなら全文対象)
3) マクロ実行
4) 抽出URLが 新規タブ にズラッと並ぶ


出力モードのおすすめ

  • 重複排除(順保持):リンクが出てきた順番で並ぶ。確認用途に強い
  • 重複排除(ソート):集合として整理される。差分比較や棚卸しに強い
  • 重複そのまま:ログの“頻出リンク”を数えたい前処理に向く(後で集計する用)

ハマりやすいポイント

URLの末尾に「。」や「)」が付く

実用上よくあるので、末尾の邪魔記号はある程度落とすようにしてある。
ただし“完全に正しいURLパーサ”ではない。データに合わせて Trim-TrailingPunct を増やせばOK。

hxxp:// みたいに崩されている

それは抽出しない(このフィルタの対象外)。
必要なら、別で hxxphttp を戻してから抽出する。

www. だけだと扱いづらい

マクロ実行時に「wwwに https:// を付ける」を 1 にすれば、https://www... に正規化して出す。


おまけ:もっと強くする改造ネタ

  • utm_ など追跡パラメータを削ってから重複排除(リンク集が一気に綺麗になる)
  • ドメイン別にグルーピングして見出しを付ける(整理が楽)
  • 抽出結果に連番やコメントを付けて、レビュー用テンプレにする

まとめ

文章の中からURLだけ抜き出して一覧化できると、確認・引用・整理が速くなる。
秀丸は外部コマンド連携が強いので、こういう“抽出系”を一度作るとずっと使える。

タイトルとURLをコピーしました