WebページのURLからタイトルを取得するなら、URLを第三者の変換サイトへ送るのではなく、自分のWindows PC上でPowerShell 7を使う方法が安全です。Invoke-WebRequestで許可済みのHTTPSページを取得し、HTMLの<title>要素を読み、文字参照をデコードしてCSVへ保存できます。社内URL、認証付きURL、共有前の文書URLを外部サービスへ渡す必要はありません。
ただし、URLへのアクセスは読み取り目的のGETでも相手サーバーのログに残り、ページによってはアクセス自体が処理を発生させる可能性があります。対象は自分が閲覧を許可されている公開ページに限定し、認証情報やトークンをURLへ含めないでください。大量取得では利用規約、サイトの方針、レート制限、管理者への負荷も考慮します。
ページタイトルとURL文字列は別の情報
一般的なHTMLページでは、ブラウザーのタブやブックマークに使われるタイトルが<head>内の<title>要素で示されます。URL末尾の文字列やページ内の最初の見出しをタイトルとみなすのではなく、まずtitle要素を読みます。WHATWG HTML仕様でもDocument.titleは文書のtitle要素と結び付くプロパティとして定義されています。
すべてのURLにタイトルがあるわけではありません。PDF、画像、JSON、ダウンロードファイルはHTMLではなく、HTMLでもtitle要素が空の場合があります。また、ページ読み込み後にJavaScriptがタイトルを書き換えるサイトでは、HTTPで取得した元HTMLとブラウザー表示が異なることがあります。取得結果が空なら、適当な名称を推測せず「タイトルなし」またはエラーとして記録します。
準備:PowerShell 7と許可済みURLを用意する
- PowerShell 7を使用し、バージョンを確認する
- 対象をHTTPSの公開ページへ限定する
- アクセスを許可するホスト名を先に列挙する
- ユーザー名、パスワード、APIキー、署名付きクエリをURLへ含めない
- 少数URLで動作と文字化けを確認してから件数を増やす
PowerShellのバージョンは次の読み取りコマンドで確認できます。
$PSVersionTable.PSVersion
Windows PowerShell 5.1とPowerShell 7ではInvoke-WebRequestの内部動作や利用できるパラメーターが異なります。本記事はPowerShell 7を前提とします。会社のPCへ未承認ソフトを追加せず、PowerShell 7が配布されていない場合は管理者へ確認してください。
1件のURLからタイトルを取得する基本形
まず、自分が閲覧を許可されているHTTPSページを一件だけ指定します。次の例はHTMLを取得し、Content-Typeを確認し、最初のtitle要素を抽出します。URLは実際の許可済みページへ置き換えてください。
$uri = [uri]'https://learn.microsoft.com/'
if ($uri.Scheme -ne 'https' -or $uri.UserInfo) {
throw '認証情報を含まないHTTPS URLだけを指定してください。'
}
$response = Invoke-WebRequest -Uri $uri -Method Get -MaximumRedirection 5 -TimeoutSec 30 -ErrorAction Stop
$contentType = [string]$response.Headers.'Content-Type'
if ($contentType -notmatch '(?i)(text/html|application/xhtml\+xml)') {
throw "HTMLではありません: $contentType"
}
$match = [regex]::Match(
$response.Content,
'(?is)<title(?:\s[^>]*)?>(.*?)</title>'
)
if (-not $match.Success) {
throw 'title要素が見つかりません。'
}
$title = [Net.WebUtility]::HtmlDecode($match.Groups[1].Value)
$title = [regex]::Replace($title, '\s+', ' ').Trim()
$title
WordPress上でコードを安全に表示するため、上の正規表現内の山括弧はHTML文字参照で記述しています。コードブロックからコピーした環境によって<や>が文字参照のまま残る場合は、正規表現のtitleタグ部分を実際の山括弧へ直します。通常の画面表示からコピーすれば、ブラウザーが山括弧として表示します。
Invoke-WebRequestはレスポンスを取得し、Contentプロパティに本文を保持します。TimeoutSecは応答しないURLで無期限に待たないための上限です。MaximumRedirectionはリダイレクト回数の上限であり、無制限追跡を避けます。ErrorAction Stopにより、DNS、TLS、HTTPなどの失敗を例外として扱いやすくします。
HTML内では「&」のような文字参照が使われるため、WebUtility.HtmlDecodeで人が読む文字へ戻します。その後、改行や連続空白を一つの空白へ正規化します。タイトルにCSVの区切り文字や引用符が含まれても、後述のExport-Csvへ文字列のまま渡せば適切に引用されます。
許可ホストを確認する関数にまとめる
複数URLへ使うときは、入力を何でも受け入れる関数にせず、許可ホストの一覧を必須にします。次の関数はHTTPSとユーザー情報の有無を検査し、自動リダイレクトを無効化して3xx応答を拒否します。転送を許可する必要がある場合は、Locationを絶対URIへ解決し、各ホップのScheme、UserInfo、DnsSafeHostを検証してから次の要求を送る別実装を用意してください。
function Get-ApprovedPageTitle {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[uri]$Uri,
[Parameter(Mandatory)]
[string[]]$AllowedHost
)
if ($Uri.Scheme -ne 'https') {
throw 'HTTPS URLだけを指定してください。'
}
if ($Uri.UserInfo) {
throw 'URLにユーザー名やパスワードを含めないでください。'
}
if ($AllowedHost -notcontains $Uri.DnsSafeHost) {
throw "許可されていないホストです: $($Uri.DnsSafeHost)"
}
$response = Invoke-WebRequest -Uri $Uri -Method Get -MaximumRedirection 0 -TimeoutSec 30 -ErrorAction Stop
if ([int]$response.StatusCode -ge 300 -and [int]$response.StatusCode -lt 400) {
throw "リダイレクトは許可していません: $($response.StatusCode)"
}
$finalUri = $Uri
$contentType = [string]$response.Headers.'Content-Type'
if ($contentType -notmatch '(?i)(text/html|application/xhtml\+xml)') {
throw "HTMLではありません: $contentType"
}
$m = [regex]::Match(
$response.Content,
'(?is)<title(?:\s[^>]*)?>(.*?)</title>'
)
if (-not $m.Success) {
throw 'title要素が見つかりません。'
}
$title = [Net.WebUtility]::HtmlDecode($m.Groups[1].Value)
$title = [regex]::Replace($title, '\s+', ' ').Trim()
[pscustomobject]@{
RequestedUrl = $Uri.AbsoluteUri
FinalUrl = $finalUri.AbsoluteUri
Title = $title
Error = ''
}
}
許可判定にはDnsSafeHostの完全一致を使います。「example.comを含む文字列」のような部分一致は、example.com.evil.testのような別ホストを誤って許可するため避けます。サブドメインを許可するなら、必要なホストを個別に列挙します。localhost、ルーター管理画面、クラウドのメタデータ用アドレス、社内システムなどを、出所不明のURLリストから自動取得しないでください。
この関数は自動リダイレクトを追跡しません。3xxが返った場合は失敗として記録し、短縮URLや転送URLをそのまま再要求しないでください。転送先を扱う必要がある場合も、Locationを解決した後にHTTPS、ユーザー情報なし、AllowedHostの完全一致を確認してから一ホップずつ要求します。
複数URLをCSVへ保存する
一行に一つのURLを書いたurls.txtを用意します。空行は無視し、URLごとの失敗はError列へ残すと、途中の一件で全体が止まった理由を追跡できます。次の例ではMicrosoft Learnのホストだけを許可しています。実際の対象に合わせ、信頼できるホストを明示的に追加してください。
$allowedHosts = @(
'learn.microsoft.com'
)
$results = foreach ($line in Get-Content -LiteralPath '.\urls.txt') {
$value = $line.Trim()
if ([string]::IsNullOrWhiteSpace($value)) {
continue
}
try {
Get-ApprovedPageTitle -Uri ([uri]$value) -AllowedHost $allowedHosts
}
catch {
[pscustomobject]@{
RequestedUrl = $value
FinalUrl = ''
Title = ''
Error = $_.Exception.Message
}
}
}
$results | Export-Csv -LiteralPath '.\titles.csv' -NoTypeInformation -Encoding utf8BOM
Export-Csvを使うと、タイトルにカンマや引用符が含まれてもCSVとしてエスケープされます。画面出力をコピーしてカンマ区切りへ手作業で直すより、欠落や列ずれを防げます。utf8BOMはWindows版Excelで日本語を開く用途を想定した例です。後続システムがBOMなしUTF-8を要求する場合は、その仕様に合わせてEncodingを変更します。
取得結果を検証する
- 最初は三件程度の公式ページだけで実行します。
- RequestedUrlとFinalUrlのホストが許可範囲にあることを確認します。
- Titleを実際のブラウザータブと比較します。
- Errorがある行は、URL、Content-Type、アクセス許可を個別に確認します。
- 問題がなければ、待ち時間を置きながら少数ずつ件数を増やします。
同じホストへ短時間に大量リクエストを送ると、レート制限やアクセス遮断の原因になります。並列化を急がず、サイトの方針に従います。定期収集が必要なら、提供元のAPI、サイトマップ、RSS、エクスポート機能がないか先に確認してください。HTMLスクレイピングはページ構造変更の影響を受けます。
タイトルが取れない主な理由
HTMLではない
Content-Typeがapplication/pdf、image/*、application/jsonなどなら、HTMLのtitle要素はありません。PDFの文書タイトルを取得したい場合はPDFメタデータを扱う別手順が必要です。ファイル名からタイトルを推測して同じ列へ混ぜず、種類ごとに処理を分けます。
JavaScriptで後から設定される
Invoke-WebRequestは通常のブラウザーのようにページのJavaScriptを実行しません。元HTMLにtitleがない、または読み込み後に書き換えられるサイトでは、ブラウザータブと違う結果になります。まずページ提供元のAPIやメタデータを確認し、それでも必要なら承認済みのブラウザー自動化を別設計にします。認証済みブラウザーのCookieを安易にPowerShellへコピーしないでください。
文字化け・不正なHTML
レスポンスの文字コード宣言が誤っているページや、titleの閉じタグがないHTMLでは、単純な抽出が失敗します。正規表現は一般的な静的HTMLの第一候補であり、完全なHTMLパーサーではありません。結果を黙って補正せずErrorへ残し、少数の例外を人が確認します。外部の「文字化け修復サイト」へURLや本文を送るのは避けます。
第三者サイトを使わない方がよい理由
タイトル取得サービスへURLを入力すると、その事業者へURLが送信されます。公開前ページの共有URL、長い署名付きURL、社内ホスト名、顧客IDを含むパスは、それ自体が機密情報になり得ます。サービスのアクセスログ、保存期間、再利用範囲が分からない場合、単にタイトルを知る目的に対して情報開示が大きすぎます。
ローカルのPowerShellでもアクセス先サーバーには通信しますが、不要な第三者を経由しません。許可ホスト、HTTPS、タイムアウト、エラー記録、少量実行を組み合わせれば、どこへ何を送ったかを管理しやすくなります。認証が必要なページは本記事の対象外とし、組織が承認したAPIやエクスポート手段を使ってください。
要点
URLタイトル取得の基本は、許可済みのHTTPS URLへInvoke-WebRequestでGETし、HTMLであることを確かめ、title要素を抽出し、WebUtility.HtmlDecodeで文字参照を戻すことです。複数件はExport-Csvへ渡し、失敗も行として残します。動的ページや非HTMLを無理に同じ処理へ通さず、第三者サイトへ内部URLを送らないことが最も重要です。

コメント