PDFリンク抽出
PDFの7つの注釈から、実際のWebリンク4件をページ番号付きで取り出します。コメントやハイライト、文書内のページ移動は除かれ、3ページに同じリンクがあれば3件として残ります。ブラウザで無料で使えます。
- 無料
- ブラウザで動作
- 登録不要
- データは端末の外に出ません
無料で実行
処理はブラウザ内で完結します。ファイルは送信されません。無料でご利用いただけます。
PDFリンク抽出機能は、PDFのページごとの注釈リスト — pdf.jsなどのリーダーライブラリがgetAnnotationsから返すものそのもの — を読み取り、見つかったすべてのハイパーリンクをページ番号とリンク先URLとともに返します。「注釈は手に入れた」と「このドキュメントがどこを指しているか分かった」の間に欠けていたステップです。決定論的な1回の処理で、レンダリングもOCRも推測もありません。内部専用リンクやリンク以外の注釈は除外されるため、返ってくるのはドキュメントが行う外部参照のクリーンで順序付けられたリストであり、監査、クローラーのシードリスト、コンプライアンスチェックにそのまま使えます。
生テキストではなく注釈を使う理由
抽出したテキストを走査してリンクを探すのは信頼できません。テキストに見えるURLはクリック可能とは限らず、クリック可能なリンクのアンカーテキストはリンク先と似ても似つかないことがあります。PDFからリンクを抽出するうえで権威ある情報源は注釈レイヤーです。
PDFリーダーはインタラクティブな要素をページに紐づく注釈として保存し、ハイパーリンクはURIを持つサブタイプLinkのエントリとしてそこに存在します。この機能はそのリスト — ページ番号、サブタイプ、任意のURIを持つ注釈ごとのレコード — を入力として受け取り、実際の外部向けハイパーリンクだけに絞り込みます。
ページ番号が各注釈と一緒に保持されるため、各結果はリンクがドキュメントのどこにあるかを正確に示し、単なるURLの羅列ではなくレビューや監査に使える出力になります。また、契約は各レコードを処理しながら検証します。
抽出機能が残すものと捨てるもの
フィルターは意図的に厳格です。Link注釈の中でも、空でないURIを持つものだけが残ります。URIのないリンク注釈は通常、別ページへの内部ジャンプであり、ドキュメント内のナビゲーションであって外部ターゲットへのハイパーリンクではないため、除外されます。
URIは前後の空白を取り除くだけで、それ以外はそのまま返されます。書き換えも解決も重複排除もありません。同じURLが3ページに現れれば3件のエントリになります。
3回現れるという事実そのものが情報だからです。出力は注釈が到着した順のまま並び、実際にはドキュメント順になります。
同じURLが長いドキュメントで複数回現れるのは正当なことです — 繰り返しのフッター、参考文献、連絡先ページなど — それらの出現をまとめてしまうと、単発の引用と体系的な参照の区別が消えてしまうため、抽出機能はすべてを保持し、下流で何を集約するかは呼び出し側に委ねます。
パイプラインのどこに組み込むか
典型的な流れはこうです。リーダーライブラリでPDFを開き、ページごとに注釈を集め、この機能が期待するレコードリストに平坦化して送信します。返ってくるのは件数と{page, url}のペアの配列です。
チームはこれを、公開前の契約書やレポートの外部参照の棚卸し、リンクチェック用のシードリストの作成、匿名化したドキュメントが指すべきでない場所をもう指していないことの確認に使います。空の注釈リストは空の結果ではなくエラーとして扱われます。
空のリストは、上流の抽出ステップが失敗したか、ファイルに注釈レイヤーが最初からなかったことをほぼ必ず意味し、ゼロ件を黙って返すとそれを隠してしまうからです。
活用例
公開前に外部参照を監査する
レポートや契約書が行うすべてのハイパーリンクをページ番号付きで棚卸しし、レビュアーが各リンク先が意図的で正しいことを確認できるようにします。
リンク切れチェックの種を作る
抽出したURLをリンクチェッカーに渡し、クライアントがまもなくダウンロードするドキュメント内の死んだリンク先を検出します。
匿名化ドキュメントを検証する
公開版のPDFが、削除されるべきだった社内ステージングやパートナーのURLをもう指していないことを確認します。
よくある質問
どのような入力を受け付けますか?
平坦化された注釈リストです。1始まりのページ番号、サブタイプ、リンクの場合はURIを持つ注釈ごとのレコードです。pdf.jsなどのリーダーライブラリは、小さなマッピング処理を挟んでgetAnnotationsからこれを生成できます。
空のリストではなくエラーが返るのはなぜですか?
空の注釈配列は、上流の抽出が失敗したか、PDFに注釈レイヤーがないことを意味します。
内部ページジャンプは含まれますか?
いいえ。 URIのないリンク注釈はドキュメント内の内部移動先であり、外部向けハイパーリンクではないためスキップされます。
重複するURLは重複排除されますか?
いいえ。 同じURLが3ページでリンクされていれば3件のエントリになります。各出現が独自のページ番号を持ち、その繰り返し自体が有用な情報だからです。
取り出したリンクはどの順番で並びますか?
注釈が渡された順、つまり実際には文書の順に並びます。並べ替えはしないため、1ページ目のリンクから順に、どのページで何を参照しているかをそのまま確認できます。
関連ツール
開発者向け — APIアクセス
このページの機能はすべてAPIからも利用できます。自社システムに組み込みたいチーム向けのセクションです。それ以外の方は上のツールをそのままお使いください。
エンドポイント
Bearerトークンで認証し、POST1回でタスクをキューに登録します。結果はWebhookまたは署名付きリンクで受け取れます。
お使いのスタックから呼び出す
curl -X POST https://api.kit.forhosting.com/pdf/extract-links \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"annotations":[{"page":1,"subtype":"Link","uri":"https://example.com/docs"},{"page":1,"subtype":"Text","uri":null},{"page":3,"subtype":"Link","uri":"https://example.com/contact"}]}'const res = await fetch("https://api.kit.forhosting.com/pdf/extract-links", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"annotations": [
{
"page": 1,
"subtype": "Link",
"uri": "https://example.com/docs"
},
{
"page": 1,
"subtype": "Text",
"uri": null
},
{
"page": 3,
"subtype": "Link",
"uri": "https://example.com/contact"
}
]
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/pdf/extract-links",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"annotations": [
{
"page": 1,
"subtype": "Link",
"uri": "https://example.com/docs"
},
{
"page": 1,
"subtype": "Text",
"uri": null
},
{
"page": 3,
"subtype": "Link",
"uri": "https://example.com/contact"
}
]
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/pdf/extract-links", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"annotations":[{"page":1,"subtype":"Link","uri":"https://example.com/docs"},{"page":1,"subtype":"Text","uri":null},{"page":3,"subtype":"Link","uri":"https://example.com/contact"}]}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"annotations":[{"page":1,"subtype":"Link","uri":"https://example.com/docs"},{"page":1,"subtype":"Text","uri":null},{"page":3,"subtype":"Link","uri":"https://example.com/contact"}]}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/pdf/extract-links", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)リクエスト例
{
"annotations": [
{
"page": 1,
"subtype": "Link",
"uri": "https://example.com/docs"
},
{
"page": 1,
"subtype": "Text",
"uri": null
},
{
"page": 3,
"subtype": "Link",
"uri": "https://example.com/contact"
}
]
}レスポンス例
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "pdf.extract_links",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}非同期APIです。task_idは即時に返ります。ポーリングは1秒あたり1リクエストまでです。
料金
単価はすべて公開しています。トークン換算や独自クレジットはありません。失敗したタスクは課金されません。
制限
max_mb | 1 |
max_annotations | 50000 |
エラー
| HTTP | コード | 意味 |
|---|---|---|
401 | unauthorized | APIキーが無効か、指定されていません。Authorizationヘッダーを確認してください。 |
402 | insufficient_balance | 残高が不足しています。チャージ後に再度お試しください。 |
404 | unknown_type | 指定されたタスクタイプは存在しません。タイプ名を確認してください。 |
429 | rate_limited | リクエストが多すぎます。しばらく待ってから再度お試しください。 |
技術的な注記
生テキストではなく注釈を使う理由
ページ番号の欠落、整数でないページ、サブタイプの欠如があれば、部分的な回答を黙って返すのではなく、明確なinvalid_inputエラーで呼び出しを停止します。
パイプラインのどこに組み込むか
同じコードがこのページのブラウザ上で無料で動くため、ペイロードを貼り付けて検証し、呼び出しを自動化するときだけ料金を支払うことができます。
技術的な質問
料金はいくらですか?
1リクエスト$0.002です。このページのブラウザ上でも無料で実行できます。
URLの取得や検証は行いますか?
いいえ。抽出は純粋にローカルで決定論的であり、リンク先へのネットワークリクエストは一切行いません。