🌐

数日の手作業が30分に!自治体HP調査を自動化するツール「govpage-snap」を開発した話

に公開

🎯 この記事の対象読者

  • 「DXって言われても、何から手をつけていいか分からない…」と感じている自治体職員の方
  • ウェブサイトの事例調査など、単純な繰り返し業務に時間を奪われ、もっと本質的な仕事に集中したいと思っている方
  • Pythonやプログラミングを使って、身の回りの「ちょっと面倒」を解決してみたいエンジニアの方
  • 「人手不足はデジタルで乗り越える!」という熱い想いを持つすべての方

💡 この記事を読むと得られること

  • プログラミングが、日常業務の「面倒くさい」を解決する強力な武器になるという実感
  • Seleniumを使ったWebサイトのスクリーンショット自動取得の具体的な方法
  • 難しい環境構築なしでPythonツールを動かせるGoogle Colabの便利さ
  • 「自分でもできるかも!」という、業務改善への第一歩を踏み出す勇気

🚀 はじめに:なぜこの記事を書いたか

皆さん、こんにちは!自治体の現場で働く皆さん、そして自治体DXに関心を持つエンジニアの皆さん。

自治体業務にテクノロジーを実用する

言葉にするのは簡単ですが、その具体的なイメージって、なかなか腑に落ちる説明がないと思いませんか? 日々、山のような業務に追われる中で、「DX」という言葉だけが独り歩きしているような…。

例えば、ホームページのリニューアル。企画段階で「他の自治体はどうしてる?」と、近隣自治体のサイトを参考にする、なんてことは日常茶飯事ですよね。でもその調査、どうやってますか?

おそらく、多くの場合は担当者がExcelの自治体リストを片手に、ひとつひとつ検索エンジンに調査対象の自治体名を手で打ち込み、スクリーンショットを撮り、フォルダに名前を付けて保存、という、涙ぐましい「手作業」を繰り返しているのではないでしょうか。

この地道な作業、数日がかりになることも珍しくありません。

「人手不足が深刻な今、こんなことに時間を使っていていいのだろうか?」
「この作業、デジタルでどうにかならないのか?」

そんな課題意識から、今回一つのツールを開発しました。これまで数日かかっていた近隣自治体のホームページ調査が、わずか30分程度で完了する。そんな魔法のようなツールです。

この記事では、実際に動作するツールの実例を通して、「デジタル活用って、こういうことか!」と実感してもらい、皆さんの現場でも役立つヒントを提供できればと思い、筆を執りました。


😫 課題:ホームページの事例調査、その地道すぎる現実

まずは、これまでの「つらい現実」をフローにしてみましょう。きっと「あるある!」と頷いてくれる方も多いはずです。

他自治体のホームページってどんな感じなの?調査フロー

この作業、本当に骨が折れますよね。集中力は途切れるし、単純作業の繰り返しでミスも起こりがち。もっと創造的で、住民のためになる仕事に時間を使いたいのに!


✨ 解決策:自動調査ツール「govpage-snap」

そんなつらい現実を打破するために開発したのが、この「govpage-snap」です!

項目 内容
ツール名 govpage-snap
概要 自治体名とURLが書かれたCSVリストを読み込み、全自治体のホームページのトップ画面を自動で撮影(スクリーンショット)してくれるツールです。
利用シーン ホームページ更改時の他団体事例調査、デザインリサーチなど
GitHubリポジトリ https://github.com/HosoyaYusaku/govpage-snap

このツールを使えば、コーヒーを淹れている間に、面倒な調査作業はすべて完了してしまいます。
作者がテストで動かしたところ182件で32分程度でした。
動作テスト

🛠️ 技術スタックと選定理由

特別なものは使っていません。誰でも手軽に試せることを重視しました。

技術 役割 選定理由
Python 開発言語 誰でも手軽に試せるように、特別な環境構築が不要なGoogle Colabで簡単に動かせるPythonを選びました。文法がシンプルで分かりやすいのも魅力です。
Selenium スクリーンショット取得 ブラウザ操作を自動化するためのライブラリです。これを使えば、Pythonコードからブラウザを自在に操り、スクリーンショットを撮ることができます。
CSV データ管理 自治体リストの管理には、多くの職員が使い慣れているCSV形式を採用しました。地方公共団体コードを項目に加えることで、データの管理や後々の活用がしやすくなります。

📜 コードのハイライト

ここでは、このツールの心臓部とも言える、Webサイトにアクセスしてスクリーンショットを保存する部分のコードを少しだけご紹介します。実際にはCSVからURLを読み込む処理などが加わりますが、基本の動きはとてもシンプルです。

import os, pandas as pd, shutil, random, time
from selenium import webdriver
from selenium.common.exceptions import TimeoutException, WebDriverException
from PIL import Image
from tqdm import tqdm

print(">>> Step 3: メイン処理のプログラムを準備します...")

# --- 基本設定 ---
OUTPUT_BASE_DIR = 'output'
SCREENSHOTS_DIR = os.path.join(OUTPUT_BASE_DIR, 'screenshots') # 保存先は単一のフォルダ
RESULT_CSV_PATH = os.path.join(OUTPUT_BASE_DIR, 'results.csv')
ARCHIVE_NAME_BASE = 'municipality_screenshots'
ARCHIVE_PATH = f'{ARCHIVE_NAME_BASE}.zip'
RESIZED_WIDTH, RESIZED_HEIGHT = 1920, 1080
BROWSER_WINDOW_WIDTH, BROWSER_WINDOW_HEIGHT = 1920, 1080
PAGE_LOAD_TIMEOUT = 30

def read_flexible_csv(file_path):
    # (変更なし)
    try:
        with open(file_path, 'r', encoding='utf-8') as f: first_line = f.readline()
        encoding = 'utf-8'
    except UnicodeDecodeError:
        try:
            with open(file_path, 'r', encoding='shift_jis') as f: first_line = f.readline()
            encoding = 'shift_jis'
        except Exception as e: return None, f"ファイルをUTF-8またはShift_JISで読み込めませんでした。{e}"
    has_header = not first_line.split(',')[0].strip().strip('"').isdigit()
    try:
        if has_header:
            df = pd.read_csv(file_path, encoding=encoding, header=0, usecols=[0, 1, 2], names=['code', 'name', 'url'], dtype={'code': str})
        else:
            df = pd.read_csv(file_path, encoding=encoding, header=None, usecols=[0, 1, 2], names=['code', 'name', 'url'], dtype={'code': str})
        return df, f"ファイル情報を検出: [文字コード: {encoding}, ヘッダー: {'あり' if has_header else 'なし'}]"
    except Exception as e: return None, f"CSVの解析中にエラーが発生しました: {e}"

def setup_driver():
    # (変更なし)
    options = webdriver.ChromeOptions()
    options.add_argument('--headless'); options.add_argument('--no-sandbox'); options.add_argument('--disable-dev-shm-usage')
    options.add_argument(f'--window-size={BROWSER_WINDOW_WIDTH},{BROWSER_WINDOW_HEIGHT}')
    options.add_argument('--ignore-certificate-errors'); options.add_argument('--allow-running-insecure-content')
    driver = webdriver.Chrome(options=options); driver.set_page_load_timeout(PAGE_LOAD_TIMEOUT)
    return driver

def main_process(input_csv_path):
    """スクリーンショット取得の全工程を管理するメイン関数"""
    df, message = read_flexible_csv(input_csv_path)
    print(message);
    if df is None: return print(f"❌ 処理を中断します。({message})"), False

    print(f"合計 {len(df)} 件のデータを読み込みました。")

    driver = setup_driver()
    os.makedirs(SCREENSHOTS_DIR, exist_ok=True) # 保存用フォルダを一つだけ作成
    results = []

    print("スクリーンショットの取得を開始します。件数によっては長時間かかります...")

    for _, row in tqdm(df.iterrows(), total=len(df), desc="スクリーンショット取得中"):
        code, name, url = row['code'], row['name'], row['url']
        base_info = {'code': code, 'name': name, 'url': url} # 結果記録用の基本情報

        # ファイルパスは screenshots フォルダ直下に設定
        temp_path = os.path.join(SCREENSHOTS_DIR, f"{code}_temp.png")
        final_path = os.path.join(SCREENSHOTS_DIR, f"{code}_{name}.png")

        try:
            # --- シンプルなアクセス処理 ---
            # 提供されたURLに一度だけアクセスを試みる
            tqdm.write(f"  -> {name} ({url}) にアクセス中...")
            driver.get(url)
            time.sleep(random.uniform(2, 5)) # ページ読み込み待機

            # 表示された画面をそのまま撮影(サイト本体でもエラー画面でも)
            driver.save_screenshot(temp_path)
            with Image.open(temp_path) as img:
                if img.mode in ('RGBA', 'P'): img = img.convert('RGB')
                img.resize((RESIZED_WIDTH, RESIZED_HEIGHT), Image.Resampling.LANCZOS).save(final_path)
            os.remove(temp_path)

            # アクセスと撮影に成功したので「成功」として記録
            results.append({'status': '成功', 'error': '', **base_info})

        except (TimeoutException, WebDriverException) as e:
            # タイムアウトなどでアクセス自体に失敗した場合
            error_message = str(e).split('\n')[0]
            tqdm.write(f"  -> 失敗: {name} ({error_message})")
            results.append({'status': '失敗', 'error': error_message, **base_info})

    driver.quit(); print("\n全てのスクリーンショット処理が完了しました。")

    pd.DataFrame(results).to_csv(RESULT_CSV_PATH, index=False, encoding='utf-8-sig')
    print(f"処理結果レポートを '{RESULT_CSV_PATH}' に保存しました。")
    print("成果物をZIPファイルに圧縮しています..."); shutil.make_archive(ARCHIVE_NAME_BASE, 'zip', OUTPUT_BASE_DIR)
    return True

print("✅ メイン処理の準備が完了しました。次のステップに進んでください。")

💻 使い方

「プログラミングとか、黒い画面とか、ちょっと怖い…」という方もご安心を!Googleアカウントさえあれば、誰でも簡単に動かせます。

① 環境構築

環境構築は不要です!
Google Colaboratoryにアクセスし、ノートブックを新規作成するだけ。あとはGitHubにあるコードをコピペすれば準備完了です。

② ツールの実行

  1. 調査したい自治体のリスト(地方公共団体コード、自治体名、URLが記載されたCSVファイル)を準備します。
  2. Google Colabにgovpage-snapのコードを貼り付けます。
  3. CSVファイルをアップロードし、コードを実行!

これだけで、あとはツールが全自動でスクリーンショットを撮り続けてくれます。

③ 出力結果(Before / After)

【Before】
ファイル名もバラバラ、サイズもバラバラ…。手作業で集めたことが一目瞭然です。
Before

【After】
ツールが生成した画像は、「(地方公共団体コード)_(自治体名).png」というように統一されたファイル名で、指定したフォルダにズラリと並びます。これだけでも作業がグッと楽になりますよね!
After


⚖️ ご利用にあたっての注意点(大切なこと)

このツールは非常に便利ですが、Webサイトへ自動でアクセスする際には、技術的な作法と法律への配慮が不可欠です。皆さんに安心して&利用するテクノロジーに責任を持って使っていただくために、いくつか大切な点をお伝えします。

著作権について

公開情報であっても、著作物として保護されています。特に以下の行為は、著作権法違反のリスクがあります。

  • 画像や文章を無断でブログ等に掲載 → 公衆送信権(著作権法(以下「法」といいます。)第23条)
  • 収集データを販売・有料配布 → 譲渡権(法第27条)
  • 取得物を改変して公開/社内配布 → 同一性保持権(法第20条)

また、“社内利用だからOK”は誤りです。たとえ社内資料であっても、例えば複製や社内チャットによる共有は許諾がない限り複製権(法第21条)を侵害します。
現実的な話にブレークダウンをしますと、著作権者の利益を不当に害しない限定的な範囲(例:担当部署内の数名での検討)での利用まで、直ちに問題視される可能性は低いと考えられますが、最終的な判断は、各組織のコンプライアンスに関する方針をご確認ください。
(参考情報:文化庁 著作権テキスト)

サーバーへの負荷について

このツールは、相手のサーバーに過度な負荷をかけないよう、アクセスごとに数秒間の待機時間を設けています。しかし、ご自身で改造して利用する場合や、極端に短い間隔で大量のサイトにアクセスするような使い方は、相手の業務を妨害してしまう恐れがあり、絶対に避けるべきです。

優しく、迷惑をかけずに、情報をいただく」という紳士的な姿勢を忘れないでください。

利用規約の確認

ウェブサイトによっては、利用規約でプログラムによる自動的な情報収集(スクレイピング)を明示的に禁止している場合があります。公的機関のサイトでは稀ですが、念のため対象サイトの利用規約を確認するよう心がけましょう。


🧗 開発でつまづいた点と解決策

開発は順風満帆!…とはいかず、いくつか壁にぶつかりました。

  • 課題1:そもそも、どうやって自動でスクリーンショットを撮るんだ?

    • 原因・解決策: 「そういえば昔、Seleniumっていうのでブラウザを自動で動かしたことがあるな…」と思い出しました。ただ、具体的な使い方はうろ覚え。そこでAIアシスタントに「SeleniumでWebページのスクリーンショットを撮る方法を教えて!」と相談したところ、的確なコードを提示してくれて、一気に道が開けました。まさにAI様様です!
  • 課題2:スクリーンショットも文字が□□□□??

    • 原因・解決策: 初回のテスト時に採取した画像データは、文字を描画できず、「□」が表示されていました。これは、ウェブサイト側が日本語フォントを指定していても、システム側がその文字を描画できないということ。Colabの環境へ日本語フォントをインストールすることで解消しました。
      例
  • 課題3:撮った画像の管理がしづらい…

    • 原因・解決策: 当初、取得した画像は都道府県ごとにフォルダ分けして保存していました。一見、整理されているように見えますが、いざ閲覧しようとすると、いちいちフォルダを開くのが面倒くさい。「ワンクリックで見たい!」というズボラな思いから、ファイル名自体に情報を持たせることにしました。CSVに「地方公共団体コード」の列を追加し、ファイル名を(地方公共団体コード)_(自治体名).pngという形式にしたことで、ソートもしやすく、一目でどの自治体のものか分かるようになり、閲覧性が劇的に向上しました。

🔭 今後の展望と野望

この「govpage-snap」、今は平時のホームページ調査に使えると思っていますが、ふと、こんな可能性を思いつきました。

「大規模災害発生時の、各自治体の初動対応の把握に使えないか?」

大規模な災害が起きると、多くの自治体のホームページは、トップページが災害対策モードに切り替わります。このツールを災害発生時に実行することで、どの自治体がいつ災害対応ページに切り替えたか、どんな情報を発信しているかを、観測できるのではないか。

まだまだアイデア段階ですが、有事の際の情報収集ツールとしても活用できるよう、今後さらに検討を深めていきたいと考えています。


🔚 おわりに:小さな一歩が、大きな変化を生むと信じて

今回は、自治体の現場にある「ちょっとした面倒」を解決するために開発したツールをご紹介しました。

「DX」や「業務効率化」というと、何か壮大なシステムを導入しないといけないように感じてしまうかもしれません。でも、本当はそうじゃない。

現場の職員一人ひとりが「これ、面倒だな」「もっとこうなったら良いのに」と感じる小さな課題意識。それを、手軽なツールを使って、自分の手で解決していく。

そんな「小さな一歩の積み重ねこそが、やがて組織全体の大きな変化を生み出すのだと、私は信じています。

この記事が、あなたの職場をより良くするための「小さな一歩」を踏み出す、何かのきっかけになれば、これ以上嬉しいことはありません。

最後まで読んでいただき、ありがとうございました!

Discussion