walk — サイト巡回(BFS / DFS)

Paprika の walk() / Walker でサイトを BFS/DFS で巡回する完全リファレンス — start_url / target_pages / same_domain / allowed_domains / allow_paths / deny_paths / order / max_depth / per_page_timeout_s / persist_state / host_dedup / recrawl_patterns、Visit のフィールド、ベストプラクティス。

walk() はサイトを BFS / DFS で巡回しながら 1 ページずつ Visit を yield する非同期イテレータです。キュー・重複除去・ドメインとパスのフィルタ・オフスコープ redirect 対応まで内部で処理するので、自前の BFS ループより堅牢です。

全体像は Client インストール / ガイド / 目的別の例は ユースケース

最小例

import asyncio
from paprika_client import paprika, walk

async def main():
    async with paprika() as cli:
        async with cli.session("https://example.com", parent_job_id="crawl") as page:
            async for visit in walk(page, target_pages=50, same_domain=True):
                print(f"[{visit.n}/{visit.target}] depth={visit.depth} {visit.url}")
                await page.save_assets("out/images")

asyncio.run(main())

walk は async のみsync_paprika から直接は使えません(asyncio.run() でラップしてください)。

オプション一覧(完全版)

引数 既定 意味
start_url str 現在 URL 巡回の起点。絶対 URL が必須(省略時は page.url から取る)
target_pages int 100 訪問ページ数の上限(達したら停止)
same_domain bool True 開始 URL と同じドメインだけを巡回
allowed_domains Iterable[str] \| None None 明示的に許可するドメインのリスト(指定すると same_domain より優先)
allow_paths Iterable[str] \| None None パスの正規表現許可リスト
deny_paths Iterable[str] \| None None パスの正規表現除外リスト
deny_defaults bool True 既定の除外パターンを使う(管理画面・ログイン・カートなどを除外)
order "bfs" \| "dfs" \| "random" "bfs" 探索順
max_depth int \| None None 開始 URL からのリンク階層の上限(None = 無制限)
per_page_timeout_s float 30.0 1 ページあたりの上限(秒)
handle_modal_each_page str \| None None 各ページで割り込み画面に対処する agent ゴール
persist_state bool \| str True 進捗を parent job に永続化(attempt 跨ぎで再開可能)。str を渡すと保存キー名になる
host_dedup bool False ホストを跨いだ既訪 URL の重複除去(同じ URL を別ホストで再訪しない)
recrawl_patterns Iterable[str] \| None None host_dedup 有効時に再訪を許可する URL glob パターン(カテゴリ一覧などに使う)

Visit のフィールド

walk() が yield するのは Visit インスタンス。ページの読み込みに成功したときだけ yield されます(失敗・オフドメイン・フィルタ除外は無音でスキップ)。

フィールド 説明
visit.n int 1 始まりの成功訪問番号
visit.target int walker に渡した target_pages(進捗表示用)
visit.url str 実際今いる URL(リダイレクト後)
visit.requested_url str walker が page.goto() に渡した URL
visit.depth int 開始 URL からのリンク階層(start_url は 0)
visit.outline str ページのアウトライン(walker が事前取得済み・再フェッチ不要)
visit.page Page そのページに居る Page ハンドル(ループ外から閉包しなくて良い)

進捗を表示する典型:

async for visit in walk(page, target_pages=200):
    print(f"[{visit.n}/{visit.target}] depth={visit.depth} {visit.url}")

巡回範囲の絞り込み

同じドメインだけ

walk(page, target_pages=200, same_domain=True)

複数ドメインを許可

walk(
    page,
    target_pages=500,
    allowed_domains=["example.com", "cdn.example.com"],
)

パスを許可・除外(正規表現)

walk(
    page,
    target_pages=300,
    allow_paths=[r"^/articles/"],
    deny_paths=[r"^/admin/", r"\.pdf$"],
)

deny_defaults=True(既定)のときは、管理画面・ログイン・カート・印刷ビューなどの経験則的な除外もあわせて適用されます。

階層・順序

walk(page, target_pages=300, max_depth=2, order="dfs")

失敗の扱い

walk()成功した訪問だけ yield します。失敗ページ・オフドメイン redirect・フィルタ除外は walker が無音でスキップするので、ループ内で握りつぶす必要はありません

各ページ内の処理が落ちた場合だけ try で囲ってください:

async for visit in walk(page, target_pages=300):
    try:
        await page.save_assets("out/images")
    except Exception as e:
        print(f"  skip {visit.url}: {e}")

attempt 跨ぎで再開

persist_state=True(既定)のとき、walker の進捗(crawled / queue)は parent job のディスクに保存されます。途中で止まっても、同じ parent_job_id で再開すれば続きから走ります。

async with cli.session("https://example.com", parent_job_id="crawl-001") as page:
    async for visit in walk(page, target_pages=10000, persist_state=True):
        ...

クラス版 Walker

walk() は内部で Walker を作っています。ステートを覗きたい / 別の場所で組み立てたい場合に使います。

from paprika_client import Walker

walker = Walker(page, target_pages=200, same_domain=True)
async for visit in walker:
    ...
print(len(walker.crawled), "pages crawled")
print(len(walker.queue), "still in queue")

ベストプラクティス

関連