AIが生成しがちな「いかにもAIっぽい」日本語の定型表現・水増し・空疎な形容を取り除き、読者の信頼を損なわない自然な文章に直す。記事・原稿・プレスリリース・告知・SNS投稿など、AIの支援を受けて書いた文章を公開前に点検するときに使う。「AI臭を消したい」「文章が機械っぽい」「人が書いたように直したい」などの依頼で使う。
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
ウェブページのアーカイブ(保存)と、キャッシュ・削除済みページの取得を支援する。閲覧できなくなったページへのアクセス、ウェブ内容の保全、証拠としてのアーカイブ作成、多重保存ワークフローの構築に使う。
Wayback Machine、Archive.today、ArchiveBox、国立国会図書館WARP、証拠保全(日時記録・ハッシュ)を扱う。「ページが消える前に保存したい」「削除されたページを取りたい」「証拠として残したい」などの依頼で使う。
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
실행 점수 미검증
설치 방법 보기설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.
調査報道・取材・記録保全のために、アクセスできないウェブページを取得し、ウェブ内容を保存するための手順集。
削除や改変の前にページを押さえることは、調査報道における証拠保全そのものである。取材源が消す前、企業が訂正する前、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等と併用する。
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}"
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
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が変わった時点が改変のタイミングを示す。差し替え・訂正の検証に有用。
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
外部サービスに依存せず、手元で完全な保存物(HTML・スクリーンショット・PDF・WARC等)を残したいときに使う。取材源の保護や、サービス側の削除・係争リスクを避けたい証拠保全に向く。
# 推奨: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"
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)は加工せずに別途保管し、加工版とは分けて管理する。
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/画像 → テキスト化の判断フロー │
├────────────────────────────────────────────────────────────┤
│ 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 | ペイウォール回避・素早い保存 | 非公式 | しない | 50MB | FBI召喚状(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
日本の対象・当環境で繰り返し当たった罠。サンプルを流用する前に必ず対処すること。
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
all_urls.json)し、matchType=domain の
CDXで最新タイムスタンプを一括取得する方が確実。最低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 を尊重する本スキルは 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 を参照。
아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.
로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.
아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.
AIが生成しがちな「いかにもAIっぽい」日本語の定型表現・水増し・空疎な形容を取り除き、読者の信頼を損なわない自然な文章に直す。記事・原稿・プレスリリース・告知・SNS投稿など、AIの支援を受けて書いた文章を公開前に点検するときに使う。「AI臭を消したい」「文章が機械っぽい」「人が書いたように直したい」などの依頼で使う。
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
取材・調査・データ報道で使える無料データ源(API)のカタログ。政府統計(e-Stat)、地域経済(RESAS)、気象(気象庁)、法令(e-Gov)、企業開示(EDINET)、書誌・国会会議録(国立国会図書館)、地理空間(国土地理院)、汎用の国際API(GDELT・Open-Meteo・Wayback等)を用途別に整理し、登録要否・利用条件・選び方を示す。「無料のデータ源が欲しい」「このテーマのAPIは?」「公的データをプログラムで取りたい」といった依頼で使う。
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
データ報道(データジャーナリズム)の手順を支援する。データセットの分析、グラフや地図の作成、雑然としたデータのクリーニング、統計の算出、データに基づく記事の構築に使う。「e-Statのデータを分析したい」「人口あたりで比較したい」「グラフが誤解を招かないか確認したい」「統計から記事を組み立てたい」などの依頼で使う。記者・編集者・調査担当向け。
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
取材で集めた資料(開示文書・記事・SNS・反訳など)をデジタルアーカイブ化し、AIで要約・分類・エンティティ抽出・関係づけ(知識グラフ化)して横断検索できる調査基盤を作るための手引き。「取材資料を整理したい」「大量の開示文書をデータベース化したい」「人物・組織・出来事の関係を可視化したい」「調査報道のアーカイブを作りたい」などの依頼で使う。
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
発言・記事・SNS投稿などの主張を体系的にファクトチェックする。主張の抽出、検証可能な命題への分解、根拠の収集、当事者への確認、判定(レーティング)、記録、訂正対応までを支援する。掲載前の裏取り、ファクトチェック記事の作成、編集部の検証手順づくりに使う。「ファクトチェックして」「この発言は本当か」「裏取りの手順を」などの依頼で使う。
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
テキスト・記事・ウェブページの内容を、厳密な検証方法論にもとづいてファクトチェックする。検証可能な主張を抽出し、信頼できる一次情報・公的データ・検証機関と突き合わせ、各主張の真偽と確からしさ(確信度)、情報源の信頼度スコアを評価し、根拠つきの検証レポートにまとめる。「この主張を検証して」「記事をファクトチェックして」「情報源の信頼性を見て」「真偽を判定して」「誤情報か確かめて」などの依頼で使う。付属の references(信頼できる情報源/検証方法論)を参照する検証重視版。
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.