Skip to content
cccskills
FreePublished on GitHub

web-archive

ウェブページのアーカイブ(保存)と、キャッシュ・削除済みページの取得を支援する。閲覧できなくなったページへのアクセス、ウェブ内容の保全、証拠としてのアーカイブ作成、多重保存ワークフローの構築に使う。

Wayback Machine、Archive.today、ArchiveBox、国立国会図書館WARP、証拠保全(日時記録・ハッシュ)を扱う。「ページが消える前に保存したい」「削除されたページを取りたい」「証拠として残したい」などの依頼で使う。

The English summary is being prepared. Showing the original description.

sgbms1/ja-news-skills3 stars(GitHub 3 + cccskills 0)

Run score Not tested

See how to install

Files included (1)

  • SKILL.md38.6 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

ウェブアーカイブの方法論

調査報道・取材・記録保全のために、アクセスできないウェブページを取得し、ウェブ内容を保存するための手順集。

削除や改変の前にページを押さえることは、調査報道における証拠保全そのものである。取材源が消す前、企業が訂正する前、SNS投稿が削除される前に保存しておく。本スキルは一般的な手引きであり、法的助言ではない。証拠として用いる場合の取り扱いは、自社の法務・デスクと相談すること。

アーカイブサービスの優先順位

最大限カバーするため、次の順で試す。

┌─────────────────────────────────────────────────────────────────┐
│                    アーカイブ取得カスケード                       │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  1. Wayback Machine (archive.org)                               │
│     └─ 9000億ページ超、長期の履歴、API公開                      │
│                         ↓ 見つからない                          │
│  2. Archive.today (archive.is/archive.ph)                       │
│     └─ オンデマンド保存、ペイウォール回避                       │
│     └─ 注意(2026): 2025年10月にFBIが登録業者へ召喚状。          │
│        2026年2月にWikipediaが引用元として非推奨化。             │
│        法的・引用用途ではWayback等を優先する                    │
│                         ↓ 見つからない                          │
│  3. Memento Time Travel(横断検索アグリゲータ)                  │
│     └─ 複数のアーカイブを同時に検索                             │
│                                                                  │
│  日本の公的アーカイブ:                                          │
│  ・国立国会図書館 WARP(インターネット資料収集保存事業)        │
│    https://warp.da.ndl.go.jp/ ─ 国・自治体・公的機関サイト等を │
│    継続収集。官公庁ページの過去版を探すときの第一候補。         │
│                                                                  │
│  廃止済み(使用不可):Googleキャッシュ(`cache:`演算子)は     │
│  2024年9月に終了。Bingのキャッシュ表示も同年に削除。いずれも    │
│  かつてはこのカスケードに含まれていた。                         │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

WARP補足:国立国会図書館のWARPは主に公的機関・公益団体等のサイトを対象とした保存事業で、誰でも検索・閲覧できる。官公庁・自治体・国立大学・公的団体のページが消えた/差し替えられた場合に有力。一方で民間サイトや一般のニュース記事は対象範囲が限られるため、Wayback Machine等と併用する。

Wayback Machine API

URLがアーカイブ済みか確認する

import requests
from typing import Optional
from datetime import datetime
from urllib.parse import quote, unquote

def check_wayback_availability(url: str) -> Optional[dict]:
    """指定URLがWayback Machineに存在するか確認する。"""
    api_url = "https://archive.org/wayback/available"

    try:
        response = requests.get(api_url, params={'url': url}, timeout=10)
        data = response.json()

        if data.get('archived_snapshots', {}).get('closest'):
            snapshot = data['archived_snapshots']['closest']
            return {
                'available': snapshot.get('available', False),
                'url': snapshot.get('url'),
                'timestamp': snapshot.get('timestamp'),
                'status': snapshot.get('status')
            }
        return None
    except Exception as e:
        return None

def get_wayback_url(url: str, timestamp: str = None) -> str:
    """あるページのWayback Machine URLを生成する。

    Waybackの再生URL規約に従い、正規の生形式
    (`.../web/<timestamp>/<url>`)を返す。返したリンクを
    ブラウザで開く予定で、かつ対象URLに `#` フラグメントが
    含まれる場合は、ブラウザがリクエスト送出前にフラグメントを
    落とさないよう、呼び出し側で urllib.parse.quote によって
    エンコードすること。

    Args:
        url: 取得したい元のURL
        timestamp: 任意。YYYYMMDDHHMMSS形式。Noneなら最新版
    """
    if timestamp:
        return f"https://web.archive.org/web/{timestamp}/{url}"
    return f"https://web.archive.org/web/{url}"

ページをWayback Machineに保存する

def save_to_wayback(url: str, s3_keys: Optional[tuple[str, str]] = None) -> Optional[str]:
    """Save Page Now 経由でWayback MachineにURLの保存を依頼する。

    成功すればアーカイブ後のURLを返す。

    匿名リクエストは概ね毎分15件程度に制限される。Internet Archive
    アカウントの `s3_keys=(access_key, secret)` を渡すと上限が上がり
    (匿名→認証で毎分50件程度)、ペイウォールや重いJSレンダリングの
    ページで保存が黙って失敗するのを避けられる。
    """
    # quote(unquote(url), ...) は既存の %xx エスケープを先に正規化し、
    # %25xx への二重エンコードを防ぐ。
    save_url = f"https://web.archive.org/save/{quote(unquote(url), safe='')}"

    headers = {'User-Agent': 'Mozilla/5.0 (research-archiver)'}
    if s3_keys:
        headers['Authorization'] = f'LOW {s3_keys[0]}:{s3_keys[1]}'

    try:
        response = requests.get(save_url, headers=headers, timeout=60)

        if response.status_code == 200:
            # SPN はリダイレクト追従後の最終URL(非同期キャプチャの
            # 場合は `Link` ヘッダ)で正規のアーカイブURLを返す。
            # 一般的なケースでは `response.url` が信頼できる。
            return response.url
        return None
    except Exception:
        return None

履歴スナップショットのためのCDX API

def get_all_snapshots(url: str, limit: int = 100) -> list[dict]:
    """CDX APIを使って、あるURLの全アーカイブ済みスナップショットを取得する。

    タイムスタンプとステータスコードを含むスナップショットの一覧を返す。
    """
    cdx_url = "https://web.archive.org/cdx/search/cdx"
    params = {
        'url': url,
        'output': 'json',
        'limit': limit,
        'fl': 'timestamp,original,statuscode,digest,length'
    }

    try:
        response = requests.get(cdx_url, params=params, timeout=30)
        data = response.json()

        if len(data) < 2:  # 先頭行はヘッダ
            return []

        headers = data[0]
        snapshots = []

        for row in data[1:]:
            snapshot = dict(zip(headers, row))
            snapshot['wayback_url'] = (
                f"https://web.archive.org/web/{snapshot['timestamp']}/{snapshot['original']}"
            )
            snapshots.append(snapshot)

        return snapshots
    except Exception:
        return []

digest(コンテンツのハッシュ)は、同一URLの版が「いつ実際に変わったか」を見分けるのに使える。digestが同じスナップショットは内容が同一で、digestが変わった時点が改変のタイミングを示す。差し替え・訂正の検証に有用。

Archive.today の利用

Archive.today に保存する

import re
import requests
from urllib.parse import quote, unquote, urljoin

def save_to_archive_today(url: str) -> Optional[str]:
    """URLをArchive.todayに送信してアーカイブを依頼する。

    注意:Archive.todayはレート制限とCAPTCHAがある。本関数は
    基本的な保存には機能するが、大量利用では手動対応が必要に
    なることがある。

    運用上の注意(2026):2025年10月にFBIがarchive.todayの登録
    業者へ召喚状を出した。2026年1月に同サイトがDDoS攻撃コードを
    配信したとして、2026年2月にWikipediaが引用元としての受け入れ
    を停止した。Wayback Machineで描画できないコンテンツの取得には
    依然有用だが、法的・引用用途ではWayback / 公的アーカイブを
    優先し、本サービスは補助として扱う。
    """
    submit_url = "https://archive.today/submit/"

    data = {
        'url': url,
        'anyway': '1'  # 直近のスナップショットがあっても再保存する
    }

    try:
        response = requests.post(
            submit_url,
            data=data,
            timeout=60,
            allow_redirects=False,
            headers={'User-Agent': 'Mozilla/5.0 (research-archiver)'},
        )
        # archive.today はスナップショットURLを次の2形態のいずれかで返す:
        #   - 30x で Location: https://archive.today/<snapshot_id>
        #     (Location は RFC 7231 上、相対でもよい)
        #   - 200 で Refresh: 0;url=https://archive.today/<snapshot_id>
        #     (Refresh キーワードはHTML仕様上、大文字小文字を区別しない)
        # 黙ってリダイレクトを追うと /wip/ ページに着地したり正規の
        # スナップショットURLを取り逃すため、両ヘッダを明示的に処理する。
        if response.status_code in (301, 302, 303, 307, 308):
            location = response.headers.get('Location')
            if location:
                return urljoin(response.url, location)
        if response.status_code == 200:
            refresh = response.headers.get('Refresh', '')
            m = re.search(r'\burl\s*=\s*(.+)', refresh, re.IGNORECASE)
            if m:
                target = m.group(1).strip().strip('\'"')
                return urljoin(response.url, target)
        return None
    except Exception:
        return None

def search_archive_today(url: str) -> Optional[str]:
    """既存のArchive.todayスナップショットを検索する。

    /newest/<url> 参照を使う。これは最新スナップショットへ302で
    転送する(レート制限時はCAPTCHAページへ転送される)。
    """
    search_url = f"https://archive.ph/newest/{quote(unquote(url), safe='')}"

    try:
        response = requests.get(
            search_url,
            timeout=30,
            allow_redirects=False,
            headers={'User-Agent': 'Mozilla/5.0 (research-archiver)'},
        )
        if response.status_code in (301, 302, 303, 307, 308):
            location = response.headers.get('Location')
            if location:
                resolved = urljoin(response.url, location)
                # CAPTCHAではなくアーカイブページに着地した場合のみ返す
                if 'archive.' in resolved and '/newest/' not in resolved:
                    return resolved
        return None
    except Exception:
        return None

多重アーカイブによる冗長化

最大限の保全のためのアーカイブ・カスケード

複数サービスに同時保存することで、特定サービスの停止・削除・係争に左右されにくくする。証拠保全の基本は「1か所に頼らない」こと。

from dataclasses import dataclass
from typing import Optional, List
from concurrent.futures import ThreadPoolExecutor, as_completed

@dataclass
class ArchiveResult:
    service: str
    url: str
    archived_url: Optional[str]
    success: bool
    error: Optional[str] = None

class MultiArchiver:
    """冗長化のため、URLを複数サービスにアーカイブする。"""

    def __init__(self):
        self.services = [
            ('wayback', self._save_wayback),
            ('archive_today', self._save_archive_today),
            ('perma_cc', self._save_perma),  # APIキーが必要
        ]

    def archive_url(self, url: str, parallel: bool = True) -> List[ArchiveResult]:
        """URLを全サービスにアーカイブする。

        Args:
            url: アーカイブするURL
            parallel: Trueなら全サービスに同時保存する
        """
        results = []

        if parallel:
            with ThreadPoolExecutor(max_workers=3) as executor:
                futures = {
                    executor.submit(save_func, url): name
                    for name, save_func in self.services
                }

                for future in as_completed(futures):
                    service = futures[future]
                    try:
                        archived_url = future.result()
                        results.append(ArchiveResult(
                            service=service,
                            url=url,
                            archived_url=archived_url,
                            success=archived_url is not None
                        ))
                    except Exception as e:
                        results.append(ArchiveResult(
                            service=service,
                            url=url,
                            archived_url=None,
                            success=False,
                            error=str(e)
                        ))
        else:
            for name, save_func in self.services:
                try:
                    archived_url = save_func(url)
                    results.append(ArchiveResult(
                        service=name,
                        url=url,
                        archived_url=archived_url,
                        success=archived_url is not None
                    ))
                except Exception as e:
                    results.append(ArchiveResult(
                        service=name,
                        url=url,
                        archived_url=None,
                        success=False,
                        error=str(e)
                    ))

        return results

    def _save_wayback(self, url: str) -> Optional[str]:
        return save_to_wayback(url)

    def _save_archive_today(self, url: str) -> Optional[str]:
        return save_to_archive_today(url)

    def _save_perma(self, url: str) -> Optional[str]:
        # Perma.cc のAPIキーが必要
        # APIクレデンシャルがある前提で実装する
        return None

ArchiveBox によるセルフホスト・アーカイブ

外部サービスに依存せず、手元で完全な保存物(HTML・スクリーンショット・PDF・WARC等)を残したいときに使う。取材源の保護や、サービス側の削除・係争リスクを避けたい証拠保全に向く。

ArchiveBox のセットアップ

# 推奨:Docker Compose(v0.8系はChromium、yt-dlp、wget、single-file
# などのキャプチャ用ツールを同梱)。
mkdir ~/web-archives && cd ~/web-archives
curl -O 'https://docker-compose.archivebox.io' && mv docker-compose.archivebox.io docker-compose.yml
docker compose run archivebox init --setup
docker compose up -d                    # :8000 でWeb UIを起動

# pip でのインストールも可能だが、完全なキャプチャには Chromium /
# yt-dlp / wget / single-file を別途入れる必要がある:
# pip install archivebox && archivebox init

# アーカイブするURLを追加(アーカイブ用ディレクトリ内で実行)
docker compose run archivebox add "https://example.com/article"

# ファイルから複数URLを追加
docker compose run archivebox add --depth=0 < urls.txt

# 定期アーカイブをスケジュール
docker compose run archivebox schedule --every=day --depth=1 "https://example.com/feed.rss"

ArchiveBox の Python 連携

import subprocess
from pathlib import Path
from typing import List, Optional

class ArchiveBoxManager:
    """ローカルのArchiveBoxインスタンスを管理する。"""

    def __init__(self, archive_dir: Path):
        self.archive_dir = archive_dir
        self._ensure_initialized()

    def _ensure_initialized(self):
        """必要ならArchiveBoxを初期化する。"""
        if not (self.archive_dir / 'index.sqlite3').exists():
            subprocess.run(
                ['archivebox', 'init'],
                cwd=self.archive_dir,
                check=True
            )

    def add_url(self, url: str, depth: int = 0) -> bool:
        """単一URLをアーカイブする。

        Args:
            url: アーカイブするURL
            depth: 0で単一ページ、1でリンクを1階層たどる
        """
        result = subprocess.run(
            ['archivebox', 'add', f'--depth={depth}', url],
            cwd=self.archive_dir,
            capture_output=True,
            text=True
        )
        return result.returncode == 0

    def add_urls_from_file(self, filepath: Path) -> bool:
        """テキストファイル(1行1URL)からアーカイブする。"""
        with open(filepath) as f:
            result = subprocess.run(
                ['archivebox', 'add', '--depth=0'],
                cwd=self.archive_dir,
                stdin=f,
                capture_output=True
            )
        return result.returncode == 0

    def search(self, query: str) -> List[dict]:
        """アーカイブ済みコンテンツを検索する。"""
        result = subprocess.run(
            ['archivebox', 'list', '--filter-type=search', query],
            cwd=self.archive_dir,
            capture_output=True,
            text=True
        )
        # 出力をパースする…
        return []

証拠としての保全

削除・改変が予想されるページを「いつ・誰が・どんな内容を」取得したかを記録し、後から第三者が検証できる形で残す。日本では米国のような統一的な「ウェブ証拠の認証」制度はないが、(1)取得日時(UTC・JST)、(2)コンテンツのハッシュ(SHA-256)、(3)複数アーカイブのURL、(4)取扱い履歴 を残しておくことが、来歴(プロビナンス)を示す実務上の基本となる。最終的な証拠能力の判断は法務・弁護士と相談すること。

取得・取扱い履歴(チェーン・オブ・カストディ)の記録

import hashlib
import sys
import json
import requests
from datetime import datetime, timezone
from dataclasses import dataclass, asdict
from typing import List

@dataclass
class EvidenceRecord:
    """検証に耐える証拠レコード。"""

    # コンテンツの識別
    original_url: str
    archived_urls: List[str]  # 複数のアーカイブ控え
    content_hash_sha256: str

    # 日時
    capture_time_utc: str
    first_observed: str

    # メタデータ
    page_title: str
    captured_by: str
    capture_method: str
    tool_versions: dict

    # 取扱い履歴(誰がいつアクセスしたか)
    custody_log: List[dict]

    def add_custody_entry(self, accessor: str, action: str, notes: str = ""):
        """証拠へのアクセスを記録する。"""
        self.custody_log.append({
            'timestamp': datetime.now(timezone.utc).isoformat(),
            'accessor': accessor,
            'action': action,
            'notes': notes
        })

    def to_json(self) -> str:
        return json.dumps(asdict(self), indent=2, ensure_ascii=False)

    @classmethod
    def from_capture(cls, url: str, content: bytes, captured_by: str):
        """取得したコンテンツから証拠レコードを生成する。"""
        now = datetime.now(timezone.utc).isoformat()
        py = sys.version_info
        return cls(
            original_url=url,
            archived_urls=[],
            content_hash_sha256=hashlib.sha256(content).hexdigest(),
            capture_time_utc=now,
            first_observed=now,
            page_title="",
            captured_by=captured_by,
            capture_method="automated_capture",
            tool_versions={
                # 'archiver' は自分のツールの実際の __version__ に置き換える
                'archiver': '1.0.0',
                'python': f'{py.major}.{py.minor}.{py.micro}',
                'requests': requests.__version__,
            },
            custody_log=[]
        )

def capture_as_evidence(url: str, captured_by: str) -> EvidenceRecord:
    """証拠としての来歴を完全に記録しつつURLを取得する。"""

    # コンテンツを取得
    response = requests.get(url)
    content = response.content

    # 証拠レコードを作成
    record = EvidenceRecord.from_capture(url, content, captured_by)
    record.page_title = extract_title(content)

    # 複数サービスにアーカイブ
    archiver = MultiArchiver()
    results = archiver.archive_url(url)

    for result in results:
        if result.success:
            record.archived_urls.append(result.archived_url)

    # 初回取得を記録
    record.add_custody_entry(
        captured_by,
        'initial_capture',
        f'{url} から取得、{len(record.archived_urls)} 個のサービスにアーカイブ'
    )

    return record

実務メモ:取得日時はUTCで記録しつつ、記事・報告にはJST(日本時間)を併記すると分かりやすい。SHA-256ハッシュは、後で「保存物が改変されていないこと」を示すための指紋になる。元の生データ(HTML・スクリーンショット・PDF)は加工せずに別途保管し、加工版とは分けて管理する。

Perma.cc(引用の長期保存)

import requests
from typing import Optional

class PermaCC:
    """法的グレードのアーカイブのためのPerma.cc APIクライアント。

    perma.cc のAPIキーが必要(限定利用は無料)。米国の裁判所・
    法律実務で利用されている。日本の裁判手続で同等の地位を持つ
    わけではないが、引用先URLを長期に固定したい場合の選択肢に
    なる。日本ではこれに直接相当する公的サービスはないため、
    Wayback Machine・WARP・ArchiveBox等と組み合わせて来歴を残す。
    """

    def __init__(self, api_key: str):
        self.api_key = api_key
        self.base_url = "https://api.perma.cc/v1"
        self.headers = {
            'Authorization': f'ApiKey {api_key}',
            'Content-Type': 'application/json'
        }

    def create_archive(self, url: str, folder_id: int = None) -> Optional[dict]:
        """新しいPerma.ccアーカイブを作成する。

        guid、作成日時、キャプチャを含むdictを返す。
        """
        data = {'url': url}
        if folder_id:
            data['folder'] = folder_id

        try:
            response = requests.post(
                f"{self.base_url}/archives/",
                json=data,
                headers=self.headers,
                timeout=60
            )

            if response.status_code == 201:
                result = response.json()
                return {
                    'guid': result['guid'],
                    'url': f"https://perma.cc/{result['guid']}",
                    'creation_timestamp': result['creation_timestamp'],
                    'title': result.get('title', '')
                }
            return None
        except Exception:
            return None

    def get_archive(self, guid: str) -> Optional[dict]:
        """GUIDでアーカイブのメタデータを取得する。"""
        try:
            response = requests.get(
                f"{self.base_url}/archives/{guid}/",
                headers=self.headers,
                timeout=30
            )
            return response.json() if response.status_code == 200 else None
        except Exception:
            return None

アーカイブしたPDF・画像のOCR(文字化)

保存したPDFやスクリーンショットには、文字情報が入っていない「画像だけ」のものが多い。プレスリリース、行政のスキャン文書、新聞の切り抜き、チラシ、画像で作られたウェブページなどが典型で、そのままでは本文検索も差分比較もできない。まず埋め込みのテキスト層を確認し、無ければOCR(光学文字認識)で文字に起こす。

┌────────────────────────────────────────────────────────────┐
│              PDF/画像 → テキスト化の判断フロー               │
├────────────────────────────────────────────────────────────┤
│  1. まず埋め込みテキスト層を試す(pypdf 等)               │
│       └─ 文字が取れれば、それが最も正確。OCR不要            │
│                        ↓ ほぼ空(=画像PDF)                │
│  2. ページを画像化(pdftoppm 300dpi)→ tesseract(jpn)      │
│       └─ 日本語は言語データ jpn / 縦書きは jpn_vert         │
│                        ↓ 精度が低い                        │
│  3. 解像度を上げる(400-600dpi)・前処理(二値化/傾き補正)     │
│       新聞切り抜き・複雑レイアウトは誤認識が残る→人が確認   │
└────────────────────────────────────────────────────────────┘

導入(Debian/Ubuntu 系):

sudo apt-get install -y tesseract-ocr tesseract-ocr-jpn tesseract-ocr-jpn-vert poppler-utils
pip install pytesseract pdf2image pypdf pillow
import io, os, re, subprocess, tempfile
from pypdf import PdfReader
import pytesseract
from PIL import Image

def pdf_to_text(pdf_bytes: bytes, dpi: int = 300, max_pages: int = 5) -> tuple[str, str]:
    """PDFを文字化する。まず埋め込みテキスト層、無ければOCR。

    戻り値は (text, method)。method は 'text-layer' か 'OCR'。
    text-layer が取れたらそれを優先する(OCRより正確)。
    """
    # 1. 埋め込みテキスト層
    try:
        reader = PdfReader(io.BytesIO(pdf_bytes))
        layer = "\n".join((p.extract_text() or "") for p in reader.pages)
    except Exception:
        layer = ""
    if len(re.sub(r"\s", "", layer)) >= 25:   # 実質的な文字があれば採用
        return layer, "text-layer"

    # 2. 画像PDF → ページ画像化 → OCR(日本語)
    out = []
    with tempfile.TemporaryDirectory() as d:
        with open(os.path.join(d, "in.pdf"), "wb") as f:
            f.write(pdf_bytes)
        # pdftoppm で各ページをPNGに(-l で先頭 max_pages ページに制限)
        subprocess.run(
            ["pdftoppm", "-r", str(dpi), "-png", "-l", str(max_pages),
             os.path.join(d, "in.pdf"), os.path.join(d, "page")],
            check=False, capture_output=True,
        )
        for png in sorted(f for f in os.listdir(d) if f.endswith(".png")):
            img = Image.open(os.path.join(d, png))
            # 縦書き主体の文書は lang="jpn_vert" を試す
            out.append(pytesseract.image_to_string(img, lang="jpn"))
    return "\n".join(out), "OCR"

Waybackに保存された画像PDFをそのままOCRする例:

import requests

def ocr_archived_pdf(original_url: str, timestamp: str) -> tuple[str, str]:
    """Waybackの保存版PDF(`<ts>id_` で原本取得)を文字化する。"""
    raw = f"https://web.archive.org/web/{timestamp}id_/{original_url}"
    pdf_bytes = requests.get(raw, headers={"User-Agent": "research-archiver"}, timeout=90).content
    return pdf_to_text(pdf_bytes)

実務上の注意:

  • テキスト層があるPDFをOCRしない。埋め込み文字の方が正確で、OCRは固有名詞・数字を誤認識する。まず層を確認。
  • OCR結果は生成物であって原本ではない。金額・日付・氏名など核心部分は、必ず保存した画像(原本)を目視で照合する。数字や固有名詞の誤認識は珍しくない。
  • スキャンした新聞・チラシ・複雑レイアウトは精度が落ちる。解像度を上げる(400〜600dpi)、二値化・傾き補正などの前処理、縦書きなら jpn_vert を併用する。それでも残る誤りは人が直す。
  • OCRした本文は、検索・一次スクリーニングに使い、引用や事実主張には原本を典拠にする。証拠として扱うなら、原本PDFのSHA-256とOCR結果を分けて保管する(証拠としての保全参照)。
  • 画像で作られたウェブページ(本文が画像の広告的サイト)も、スクリーンショットに同じOCRを当てれば本文検索できる。

ブラウザ拡張とブックマークレット

ワンクリック保存ブックマークレット

// Wayback Machineに保存 ─ ブックマークとして登録する
javascript:(function(){
    window.open('https://web.archive.org/save/' + encodeURIComponent(location.href), '_blank');
})();

// Archive.todayに保存
javascript:(function(){
    window.open('https://archive.today/?run=1&url=' + encodeURIComponent(location.href), '_blank');
})();

// 全アーカイブを横断確認(Memento)
javascript:(function(){
    window.open('https://timetravel.mementoweb.org/list/0/' + encodeURIComponent(location.href), '_blank');
})();

消えたページを復元するブックマークレット

// 消えたページを複数アーカイブで試す
// 注:Googleキャッシュ(webcache.googleusercontent.com)は
// 2024年9月に廃止されたため、ここには含めない。
javascript:(function(){
    // location.href をエンコードし、内部の '#' / '?' がパス引数の
    // 一部として渡るようにする。素朴に連結すると、ブラウザが
    // フラグメントを落としクエリ文字列を外側URLに付け替えてしまう。
    var encoded = encodeURIComponent(location.href);
    var archives = [
        'https://web.archive.org/web/*/' + encoded,
        'https://archive.ph/newest/' + encoded,
        'https://timetravel.mementoweb.org/list/0/' + encoded
    ];
    archives.forEach(function(a){ window.open(a, '_blank'); });
})();

アーカイブサービス比較

サービス向いている用途API削除最大サイズ備考
Wayback Machine履歴調査あり(無料)申請により対応無制限匿名SPNは毎分15件程度。S3キー認証で上限が上がる
国立国会図書館 WARP官公庁・自治体・公的機関の過去版検索・閲覧可制度に基づく─公的サイト中心。民間・一般記事は対象範囲が限られる
Archive.todayペイウォール回避・素早い保存非公式しない50MBFBI召喚状(2025/10)、Wikipediaが引用元非推奨(2026/2)。法的・引用用途は避ける
Perma.cc引用の長期固定あり(無料枠)作成者による標準的なページ米国の裁判所で利用。日本での法的地位は別
ArchiveBoxセルフホスト・秘匿性ローカルしないディスク容量次第v0.8はDocker ComposeにChromium / yt-dlp / wget同梱
Browsertrix対話的・JS過多ページの取得あり作成者によるプラン依存Webrecorder.net による Conifer 後継。WARC出力
Conifer対話的コンテンツあり作成者による5GBまで無料旧Webrecorderサービス。現行はBrowsertrix

エラー処理とフォールバック

import requests
from enum import Enum
from typing import Optional
from urllib.parse import quote, unquote

class ArchiveError(Enum):
    NOT_FOUND = "アーカイブが見つからない"
    RATE_LIMITED = "サービスによるレート制限"
    BLOCKED = "アーカイブが拒否されたURL"
    TIMEOUT = "リクエストがタイムアウト"
    SERVICE_DOWN = "アーカイブサービス停止中"

def get_archived_page(url: str) -> tuple[Optional[str], Optional[ArchiveError]]:
    """全アーカイブサービスを、適切なエラー処理付きで試す。"""

    # 1. まずWayback Machineを試す
    try:
        result = check_wayback_availability(url)
        if result and result.get('available'):
            return result['url'], None
    except requests.Timeout:
        pass  # 次のサービスへ
    except Exception:
        pass

    # 2. Archive.todayを試す
    try:
        result = search_archive_today(url)
        if result:
            return result, None
    except Exception:
        pass

    # 3. Memento アグリゲータを試す
    try:
        memento_url = f"https://timetravel.mementoweb.org/api/json/0/{quote(unquote(url), safe='')}"
        response = requests.get(memento_url, timeout=30)
        data = response.json()

        if data.get('mementos', {}).get('closest'):
            return data['mementos']['closest']['uri'][0], None
    except Exception:
        pass

    return None, ArchiveError.NOT_FOUND

実地で踏んだ落とし穴(日本版・検証済みメモ 2026-07)

日本の対象・当環境で繰り返し当たった罠。サンプルを流用する前に必ず対処すること。

  • 官公庁サイトは Shift_JIS で文字化けする。requests の既定デコードや r.text をそのまま使うと化ける。 取得後は r.encoding = r.apparent_encoding(または明示的に cp932)で復号してから本文比較・差分をとる。
    r = requests.get(url, timeout=30)
    r.encoding = r.apparent_encoding   # 総務省統計局等の Shift_JIS ページで必須
    html = r.text
    
  • Save Page Now(SPN)の成否はページ単位。トップページや軽いページは保存できても、 重いJavaScript/SPA/巨大ページは 520 で連続失敗することがある(実測で同一URLに5回連続520)。 「保存を依頼して終わり」にせず、availability/CDX で保存済みURLを必ず確認し、 失敗するページはローカル保全(HTML保存+SHA-256)+ブラウザ描画型(single-file 等)にフォールバックする。
  • archive.today は 429(レート制限)で全試行が通らない環境がある(データセンターIP等)。 引用・証拠用途は Wayback を優先し、archive.today はその場限りの補助に留める。
  • 画像に本文を焼き込んだページはテキスト差分がほぼ空になる。差し替え検知では テキストだけでなく画像参照(src)や画像自体の差分も見る。
  • CDX/availability の並列取得は非JSON応答・TLS一時切断が頻発。 URL一覧は一度取ってローカルにキャッシュ(例 all_urls.json)し、matchType=domain の CDXで最新タイムスタンプを一括取得する方が確実。

ベストプラクティス

いつアーカイブするか

  • 公開前:自分の記事で引用した出典はすべてアーカイブする
  • 速報時:内容が変わる・消える前に直ちにアーカイブする
  • 係争・法的事項:複数アーカイブで日時入りの記録を作る
  • 取材・調査:再現性のため一次情報を保存する
  • SNS:投稿が削除される前にアーカイブする
  • 官公庁の発表・統計:差し替え・削除に備え、WARPとWaybackの両方で確認・保存する

アーカイブの冗長化

最低2つのサービスにアーカイブしておく。

def ensure_archived(url: str) -> bool:
    """URLが最低2つのサービスにアーカイブされていることを確かめる。"""
    archiver = MultiArchiver()
    results = archiver.archive_url(url)

    successful = [r for r in results if r.success]
    return len(successful) >= 2

レート制限と倫理

  • 一括アーカイブでは robots.txt を尊重する
  • リクエスト間に間隔を空ける(最低1〜3秒)
  • 個人・私的なページは本人の同意なくアーカイブしない
  • 利用可能ならAPIキーを使い、適切なレート制限の範囲で動かす
  • 結果をキャッシュし、重複リクエストを避ける
  • 取材で得た情報の保存・利用は、個人情報保護法および自社の取材倫理規程に従う

出典・ライセンス(帰属表示)

本スキルは research-toolkit(by Joe Amditis, MIT License, © Joe Amditis, https://github.com/jamditis/research-toolkit )の skills/web-archiving を 原典の構成(アーカイブ優先順位・Wayback/Archive.today/ArchiveBox/Perma.cc・多重アーカイブ・証拠保全・ブックマークレット・サービス比較・エラー処理)に沿って日本語化したものです。 米国固有のサービス・文脈は日本/汎用に再構成し、国立国会図書館WARP(インターネット資料収集保存事業)など日本の制度・実務を補っています。 コード・スクリプトは機能を保持し、コメントのみ日本語化しています。原典の著作物本文は転載していません。原作者の公認・推奨を受けたものではありません。 MIT ライセンス全文は同梱の LICENSE-MIT.txt を参照。

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

AIが生成しがちな「いかにもAIっぽい」日本語の定型表現・水増し・空疎な形容を取り除き、読者の信頼を損なわない自然な文章に直す。記事・原稿・プレスリリース・告知・SNS投稿など、AIの支援を受けて書いた文章を公開前に点検するときに使う。「AI臭を消したい」「文章が機械っぽい」「人が書いたように直したい」などの依頼で使う。

The English summary is being prepared. Showing the original description.

sgbms1/ja-news-skills3Updated Jul 14, 2026

取材・調査・データ報道で使える無料データ源(API)のカタログ。政府統計(e-Stat)、地域経済(RESAS)、気象(気象庁)、法令(e-Gov)、企業開示(EDINET)、書誌・国会会議録(国立国会図書館)、地理空間(国土地理院)、汎用の国際API(GDELT・Open-Meteo・Wayback等)を用途別に整理し、登録要否・利用条件・選び方を示す。「無料のデータ源が欲しい」「このテーマのAPIは?」「公的データをプログラムで取りたい」といった依頼で使う。

The English summary is being prepared. Showing the original description.

sgbms1/ja-news-skills3Updated Jul 14, 2026

データ報道(データジャーナリズム)の手順を支援する。データセットの分析、グラフや地図の作成、雑然としたデータのクリーニング、統計の算出、データに基づく記事の構築に使う。「e-Statのデータを分析したい」「人口あたりで比較したい」「グラフが誤解を招かないか確認したい」「統計から記事を組み立てたい」などの依頼で使う。記者・編集者・調査担当向け。

The English summary is being prepared. Showing the original description.

sgbms1/ja-news-skills3Updated Jul 14, 2026

取材で集めた資料(開示文書・記事・SNS・反訳など)をデジタルアーカイブ化し、AIで要約・分類・エンティティ抽出・関係づけ(知識グラフ化)して横断検索できる調査基盤を作るための手引き。「取材資料を整理したい」「大量の開示文書をデータベース化したい」「人物・組織・出来事の関係を可視化したい」「調査報道のアーカイブを作りたい」などの依頼で使う。

The English summary is being prepared. Showing the original description.

sgbms1/ja-news-skills3Updated Jul 14, 2026

発言・記事・SNS投稿などの主張を体系的にファクトチェックする。主張の抽出、検証可能な命題への分解、根拠の収集、当事者への確認、判定(レーティング)、記録、訂正対応までを支援する。掲載前の裏取り、ファクトチェック記事の作成、編集部の検証手順づくりに使う。「ファクトチェックして」「この発言は本当か」「裏取りの手順を」などの依頼で使う。

The English summary is being prepared. Showing the original description.

sgbms1/ja-news-skills3Updated Jul 14, 2026

テキスト・記事・ウェブページの内容を、厳密な検証方法論にもとづいてファクトチェックする。検証可能な主張を抽出し、信頼できる一次情報・公的データ・検証機関と突き合わせ、各主張の真偽と確からしさ(確信度)、情報源の信頼度スコアを評価し、根拠つきの検証レポートにまとめる。「この主張を検証して」「記事をファクトチェックして」「情報源の信頼性を見て」「真偽を判定して」「誤情報か確かめて」などの依頼で使う。付属の references(信頼できる情報源/検証方法論)を参照する検証重視版。

The English summary is being prepared. Showing the original description.

sgbms1/ja-news-skills3Updated Jul 14, 2026

See all skills by sgbms1

Report a problem with this skill