1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ブラウザコンソールから、リンク一覧を再帰的に取得する

1
Posted at

概要

ウェブサイトのリンクを再帰的に取得するには、専用のツールやプログラミング(Python、Bashなど)を利用することが多いと思いますが、中規模以下のサイトであればブラウザのコンソールから、簡単に取得できます。

ブラウザ内で完結するので、いわゆる脆弱性問題は少ないと思いますが、サイトのインフラが脆弱な場合、攻撃とみなされないとも限らないので、ご注意ください。
タイムアウト、深さ制限、最大ページ数制限を初期値で設定するようにしているので、適切な値でご利用ください。

ほぼ生成AIが作りましたが、ちょっとした作業には便利かと思いますので、ご紹介します。

手順

  • 今回試したのはChromeとEdgeのみです。ほかのブラウザでも動くと思いますが未確認です。
  • リンク一覧を取得したいページでF12などで開発者ツールを開き、Console に以下のコードを貼り付けます。
  • その際、以下の警告が出た場合は、

Warning: Don’t paste code into the DevTools Console that you don’t understand or haven’t reviewed yourself. This could allow attackers to steal your identity or take control of your computer. Please type “allow pasting” below and press Enter to allow pasting.

> allow pasting

と打ち込むと、ペーストできるようになります。

  • 成功すると、extracted_links.txt というファイルがダウンロードされ、リンク一覧が確認できます。
(async () => {
    const startUrl = location.href;

    const MAX_PAGES = 50; // 最大ページ数(初期値50)
    const CONCURRENCY = 5; // 並列数(初期値5)
    const FETCH_TIMEOUT = 5000; // タイムアウト(初期値5000ms)
    const MAX_DEPTH = 5; // 深さ制限(初期値 5)
  // Set で重複排除
    const visited = new Set();
    const results = new Set();

    // queue は { url, depth } のオブジェクトを積む
    const queue = [{ url: startUrl, depth: 0 }];

    // URL正規化
    function normalize(urlObj) {
        urlObj.hash = '';
        urlObj.searchParams.sort();
        if (urlObj.pathname.endsWith('/')) {
            urlObj.pathname = urlObj.pathname.slice(0, -1);
        }
        return urlObj.toString();
    }

    // fetch with timeout
    async function fetchWithTimeout(url, timeout = FETCH_TIMEOUT) {
        const controller = new AbortController();
        const timer = setTimeout(() => controller.abort(), timeout);

        try {
            return await fetch(url, { signal: controller.signal });
        } catch (e) {
            if (e.name === 'AbortError') {
                console.warn(`タイムアウト: ${url}`);
            }
            throw e;
        } finally {
            clearTimeout(timer);
        }
    }

    // worker
    async function worker() {
        while (queue.length) {
            if (visited.size >= MAX_PAGES) break;

            const item = queue.shift();
            if (!item) continue;

            const { url, depth } = item;

            // 深さ制限
            if (depth > MAX_DEPTH) continue;

            if (visited.has(url)) continue;
            visited.add(url);

            if (visited.size % 5 === 0) {
                console.log(`進捗: ${visited.size}`);
            }

            const baseUrl = new URL(url);

            try {
                const res = await fetchWithTimeout(url);
                if (!res.ok) continue;

                const contentType = res.headers.get('content-type') || '';
                if (!contentType.includes('text/html')) continue; // htmlに制限(画像やPDFなどは取得しない)

                const html = await res.text();
                const doc = new DOMParser().parseFromString(html, 'text/html');

                const anchors = doc.querySelectorAll('a');

                for (const a of anchors) {
                    const rawHref = a.getAttribute('href');
                    if (!rawHref) continue;
                    if (rawHref.startsWith('#')) continue;

                    try {
                        const linkUrl = new URL(rawHref, baseUrl.href);

                        if (
                            (linkUrl.protocol === 'http:' || linkUrl.protocol === 'https:') &&
                            linkUrl.origin === baseUrl.origin
                        ) {
                            const normalized = normalize(linkUrl);
                            results.add(normalized);

                            // 次の深さへ
                            const nextDepth = depth + 1;

                            if (
                                nextDepth <= MAX_DEPTH &&
                                !visited.has(normalized) &&
                                !queue.some(q => q.url === normalized) &&
                                visited.size + queue.length < MAX_PAGES
                            ) {
                                queue.push({ url: normalized, depth: nextDepth });
                            }
                        }
                    } catch {
                        // URL パース失敗は無視
                    }
                }
            } catch (e) {
                console.error(`Fetch エラー: ${url}`, e);
            }
        }
    }

    // 並列実行
    await Promise.all(
        Array.from({ length: CONCURRENCY }, () => worker())
    );

    console.log('--- 抽出完了 ---');

    const list = Array.from(results).sort().join('\n');
    console.log(list);

    const blob = new Blob([list], { type: 'text/plain' });
    const link = document.createElement('a');
    link.href = URL.createObjectURL(blob);
    link.download = 'extracted_links.txt';
    link.click();
})();
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?