URLリストを作成する

サイトリストの詳細設計やTDK取得、見積もり調査のためにはURLのリストが必要になってきます。
このスクリプトはList.txtに記載されたURLまたはローカルHTMLファイルを順次読み込み各ページ内のリンクを抽出し重複排除・正規化の上でソートしてExcelに出力し、そのExcelファイルを自動で開くツールです。

使い方としては最初にドメインのルートだけをList.txtに記載
出力したEXCELからリンクをList.txtに追記する
これを数回繰り返すと全体のURLが取得出来ます。

機能

  • 実行前に確認
  • 必要モジュールの有無を確認し、インストール許可を得て実施
  • List.txtからURLを読み込み
  • URL/ローカルファイルを取得し、リンク抽出
  • 正規化・重複除去
  • 優先順位つけてソート
  • Excelに出力
  • 出力したExcelファイルを自動で開く
# _linklist.py

import os
import sys
import re
import subprocess
import datetime
from urllib.parse import urljoin, urlparse, urlunparse

# 最初に実行の意思確認を行う
def initial_confirmation():
    response = input("このスクリプトを実行しますか? (y/n): ").strip().lower()
    if response != 'y':
        print("キャンセルされました。")
        sys.exit(0)

# 必要な外部モジュールのインストールを確認・実施
def ensure_modules():
    import importlib.util
    # 使用するモジュールとpipパッケージ名の対応
    required = {
        "requests": "requests",
        "bs4": "beautifulsoup4",
        "openpyxl": "openpyxl"
    }
    for module, package in required.items():
        if importlib.util.find_spec(module) is None:
            response = input(f"モジュール「{package}」が見つかりません。インストールしますか? (y/n): ").strip().lower()
            if response == 'y':
                subprocess.check_call([sys.executable, "-m", "pip", "install", package])
            else:
                print(f"モジュール「{package}」が必要です。終了します。")
                sys.exit(1)

# URLの正規化処理(末尾スラッシュ処理、不要パラメータ除去は別途)
def normalize_url(url):
    parsed = urlparse(url)
    # パスの末尾のスラッシュを除去(ルートは残す)
    path = parsed.path.rstrip('/')
    if not path:
        path = '/'
    return urlunparse((parsed.scheme, parsed.netloc, path, '', '', ''))

# HTMLをURLまたはローカルファイルから取得
def fetch_html(path):
    if re.match(r'^https?://', path):
        import requests
        try:
            response = requests.get(path, timeout=10)
            response.raise_for_status()
            return response.text, path
        except Exception as e:
            print(f"取得失敗: {path} → {e}")
            return None, path
    else:
        try:
            with open(path, 'r', encoding='utf-8') as f:
                # ローカルファイルの場合はfile://形式でbase_urlを返す
                return f.read(), f"file://{os.path.abspath(path)}"
        except Exception as e:
            print(f"ファイル読み込み失敗: {path} → {e}")
            return None, path

# BeautifulSoupでHTMLからリンクを抽出
def extract_links(html, base_url=""):
    from bs4 import BeautifulSoup

    soup = BeautifulSoup(html, 'html.parser')
    links = []

    for tag in soup.find_all('a', href=True):
        href = tag['href'].strip()

        # 無効リンクやjavascript系は無視。ただし誤記や変なバックスラッシュなどは
        # 正規表現でhttps://以降を拾う(怪しいものを多少拾う)
        if not href or href.startswith('#') or href.lower().startswith('javascript:') or re.match(r'^https?:\\', href):
            href_match = re.search(r"(https?://[^\s\"'>]+)", href)
            if href_match:
                links.append(href_match.group(1))
            else:
                # onclick属性内にURLがあれば拾う(必要に応じて拡張可能)
                onclick = tag.get("onclick", "")
                onclick_match = re.search(r"(https?://[^\s\"'>]+)", onclick)
                if onclick_match:
                    links.append(onclick_match.group(1))
            continue

        # 相対パスはbase_urlから絶対URLに変換
        if not urlparse(href).scheme:
            if base_url:
                full_url = urljoin(base_url, href)
            else:
                continue
        else:
            full_url = href

        links.append(full_url)

    return links

# Excelに出力する処理(罫線・ゼブラ行・列幅調整含む)
def write_to_excel(links):
    from openpyxl import Workbook
    from openpyxl.styles import PatternFill, Border, Side

    wb = Workbook()
    ws = wb.active
    ws.title = "Link List"

    thin_border = Border(left=Side(style='thin'),
                         right=Side(style='thin'),
                         top=Side(style='thin'),
                         bottom=Side(style='thin'))
    zebra_fill = PatternFill(start_color='DDDDDD', end_color='DDDDDD', fill_type='solid')

    # ヘッダー
    ws.cell(row=1, column=1, value="No.")
    ws.cell(row=1, column=2, value="URL")

    # データ書き込み+罫線+ゼブラ塗りつぶし
    for i, link in enumerate(links, start=1):
        no_cell = ws.cell(row=i+1, column=1, value=i)
        url_cell = ws.cell(row=i+1, column=2, value=link)
        no_cell.border = thin_border
        url_cell.border = thin_border
        if i % 2 == 0:
            no_cell.fill = zebra_fill
            url_cell.fill = zebra_fill

    # 列幅調整(URL列はかなり広めに)
    ws.column_dimensions['B'].width = 80
    ws.column_dimensions['A'].width = 6

    # ファイル名生成(実行時刻_リンクリスト.xlsx)
    filename = datetime.datetime.now().strftime("%Y%m%d%H%M") + "_linklist.xlsx"
    wb.save(filename)
    print(f"\n✅ Excel出力完了: {filename}")

    # ExcelファイルをOSに応じて自動で開く
    if sys.platform.startswith("win"):
        os.startfile(filename)
    elif sys.platform == "darwin":
        subprocess.call(["open", filename])
    else:
        subprocess.call(["xdg-open", filename])

# リンクの優先順位付け用キー(netloc+pathを小文字で取得)
def get_key_parts(url):
    parsed = urlparse(url)
    return (parsed.netloc + parsed.path).rstrip('/').lower()

def main():
    # 最初に実行確認
    initial_confirmation()

    # 必要モジュールの確認とインストール(必要あれば)
    ensure_modules()

    # List.txtはスクリプトと同じフォルダにある想定
    list_path = os.path.join(os.path.dirname(__file__), "List.txt")
    if not os.path.exists(list_path):
        print("List.txt が見つかりません。")
        sys.exit(1)

    all_links = []
    # List.txtからURL/ファイルパスを読み込み
    with open(list_path, 'r', encoding='utf-8') as f:
        targets = [line.strip() for line in f if line.strip()]

    total = len(targets)
    if total == 0:
        print("List.txt に処理対象がありません。")
        sys.exit(1)

    # List.txtのURLのnetloc+pathを優先キーとして先にExcelに出したいURLの判定に使用
    priority_keys = set()
    for t in targets:
        parsed = urlparse(t)
        key = (parsed.netloc + parsed.path).rstrip('/').lower()
        priority_keys.add(key)

    # 順次URLまたはローカルHTMLを読み込みリンクを抽出
    for index, target in enumerate(targets):
        print(f"[{index + 1}/{total}] 対象: {target}")
        html, base_url = fetch_html(target)
        if html:
            links = extract_links(html, base_url)
            print(f" → 抽出リンク数: {len(links)}")
            all_links.extend(links)
        else:
            print(f" → スキップ")

    # リンクを正規化しつつ重複除去
    print("\nリンクを正規化して重複を除外中...")
    normalized_set = {}
    for link in all_links:
        norm = normalize_url(link)
        if norm not in normalized_set:
            normalized_set[norm] = link

    unique_links = list(normalized_set.values())

    # 優先順位付け関数
    # List.txtのURLに近いものを先に並べる(netloc+pathが先頭一致すれば優先)
    def priority(link):
        link_key = get_key_parts(link)
        for pkey in priority_keys:
            if link_key.startswith(pkey) or pkey.startswith(link_key):
                return 0
        return 1

    # 優先度→ドメイン→パスの順でソート
    unique_links.sort(key=lambda x: (priority(x), urlparse(x).netloc, urlparse(x).path))

    print(f"重複除去後リンク数: {len(unique_links)}")

    # Excelに出力して自動で開く
    write_to_excel(unique_links)

if __name__ == "__main__":
    main()