Apache Web サーバのセキュリティ設定と管理ガイド
【概要】 Apache Webサーバのセキュリティ強化と管理に必要な手順を示す。パッケージ更新、バージョン情報隠蔽、OCSP Stapling、HSTS、CSP、HTTPからHTTPSへのリダイレクト設定を解説する。これらの設定によりWebサーバのセキュリティレベルが向上し、様々な攻撃から保護できる。サイトマップ生成プログラムも参考情報として提供する。
Apache Web サーバのメンテナンスとセキュリティ設定
パッケージのアップデートとApacheの更新
サーバを安全かつ最新の状態に保つために、定期的なパッケージのアップデートが重要である。Ubuntu 24.04 LTSでは、Apache 2.4.58が標準バージョンとして提供され、継続的なセキュリティアップデートが適用される。以下の手順でシステム全体のパッケージとApacheを更新する。
端末を開き,次のように操作を実行する(端末を開くには,右クリックメニューが便利である)。
- パッケージ情報の更新:
# パッケージリストの情報を更新 sudo apt update
- システム全体のパッケージをアップグレード:
(-V オプションで更新されるパッケージの詳細を表示する)sudo apt -yV upgrade
- Apache設定ファイルのバックアップ:
(更新前に設定ファイルをバックアップしておくと、問題発生時に復元しやすくなる。ファイル名には実行日時が含まれる。)cd /etc sudo tar -cvpf /var/tmp/apache.$(date +%Y%m%d%H%M%S).tar ./apache2
- Apache のバージョン確認:
apache2 -v
- Apache の再起動:
(設定変更を反映させるために再起動する)sudo systemctl restart apache2
- 設定変更後の確認:
再起動後、Webサイトが正常に表示・機能するかを必ず確認すること。
Apache 2 サーバのバージョン情報を公開しない
サーバのバージョン情報を公開すると,攻撃者に脆弱性を突くヒントを与えてしまう可能性がある。Ubuntu 24.04 LTSでもこの対策は有効である。そのため,次の設定により,Apache 2 サーバのバージョン情報を公開しないようにする。
設定は、Apacheのメイン設定ファイル (`/etc/apache2/apache2.conf`) や、セキュリティ関連の設定をまとめたファイル (例: `/etc/apache2/conf-available/security.conf` を作成し `sudo a2enconf security` で有効化) に記述することを推奨する。
- 設定ファイルに,次の2行を追加する。
ServerTokens Prod
: HTTPレスポンスヘッダーに"Apache"とだけ表示し、詳細なバージョン番号やOS情報を隠す。ServerSignature Off
: Apacheが生成するエラーページ(例:404 Not Found)に表示されるサーバ情報を非表示にする。
ServerTokens Prod ServerSignature Off
- 設定ファイルの構文テストを実行する。エラーメッセージが出ないことを確認する。このコマンドはUbuntu 24.04 LTSでも有効である。
sudo apache2ctl configtest
- Apache を再起動して設定を反映させる。
sudo systemctl restart apache2
- 確認方法:
curl -I https://yourdomain.com
コマンドやブラウザの開発者ツール(ネットワークタブ)でレスポンスヘッダーを確認し、Server
ヘッダーがApache
のみになっていること、エラーページにサーバ情報が表示されないことを確認する。
Apache2でOCSP Staplingを有効にする
OCSP (Online Certificate Status Protocol) Stapling は、SSL/TLS証明書の失効情報を確認する際の応答速度とプライバシーを向上させる技術である。サーバが認証局に代わって証明書の有効性情報(OCSPレスポンス)をクライアント(ブラウザ)に提供する。
この設定は、SSL/TLSを使用するバーチャルホストの設定ファイル(例: `/etc/apache2/sites-available/your-site-ssl.conf` 内の
ディレクティブ)に記述する。Ubuntu 24.04 LTS上のApache 2.4でも有効な設定である。
- SSLモジュールが有効であることを確認する。(通常は有効になっている)
sudo a2enmod ssl
- SSL/TLS設定を行っている VirtualHost セクション内に,次の2行を追加する。
SSLUseStapling on SSLStaplingCache shmcb:logs/stapling-cache(150000)
SSLUseStapling on
: OCSP Staplingを有効にする。SSLStaplingCache shmcb:logs/stapling-cache(150000)
: OCSPレスポンスを共有メモリにキャッシュする。shmcb
は共有メモリキャッシュタイプ、logs/stapling-cache
はキャッシュの名前、(150000)
はキャッシュサイズ(バイト単位)を指定する。推奨サイズは150000バイトである。 - 設定ファイルの構文テストを実行する。
sudo apache2ctl configtest
- Apache を再起動する。
sudo systemctl restart apache2
- OCSP Stapling の確認を行う。「mydomain.com」のところは,自分のドメイン名に変更して実行し,
OCSP Response Data:
セクションが表示され、OCSP Response Status: successful
となっていることを確認する。openssl s_client -connect mydomain.com:443 -status | grep -A 10 'OCSP Response Data'
(出力が長い場合は
| grep -A 10 'OCSP Response Data'
で関連部分を抽出できる)
Apache2でHTTP Strict Transport Security (HSTS) を設定
HTTP Strict Transport Security (HSTS) は,一度HTTPSでアクセスしたサイトには、以降必ずHTTPSでアクセスするようにブラウザに強制する仕組みである。これにより、通信が暗号化されていないHTTPにダウングレードされる攻撃(SSLストリッピングなど)を防ぐ。
この設定は、SSL/TLSを使用するバーチャルホストの設定ファイル(例: `/etc/apache2/sites-available/your-site-ssl.conf` 内の
ディレクティブ)に記述する。Ubuntu 24.04 LTS上のApache 2.4でも有効な設定である。
- レスポンスヘッダーを操作するために
headers
モジュールを有効にする。このモジュールはApache 2.4.58で標準搭載されており、セキュリティヘッダー実装に必須である。sudo a2enmod headers
- SSL/TLS設定を行っている VirtualHost セクション内に,次の1行を追加する。これにより,ブラウザに対して,指定した期間(以下の例では約1年間)、サブドメインを含めて常にHTTPSを使用するように指示する。
includeSubDomains
とpreload
オプションの使用が推奨される。preload
はHSTSプリロードリストへの登録申請を可能にするが、リストから削除するのは困難なため、十分にテストした上で追加すること。Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
- 設定ファイルの構文テストを実行する。
sudo apache2ctl configtest
- Apache を再起動する。(
headers
モジュールを初めて有効にした場合は再起動が必要である)sudo systemctl restart apache2
- 確認方法:
curl -I https://yourdomain.com
コマンドやブラウザの開発者ツールでレスポンスヘッダーを確認し、Strict-Transport-Security
ヘッダーが正しく設定されていることを確認する。
HTTP接続からHTTPSへの自動リダイレクト設定
Webサイトへのアクセスを常にHTTPSで行わせるために、HTTP (80番ポート) へのアクセスを自動的にHTTPS (443番ポート) へリダイレクトする設定が推奨される。これはHSTSと組み合わせて使用することで、よりセキュアな通信を保証する。
この設定は、HTTPを使用するバーチャルホストの設定ファイル(例: `/etc/apache2/sites-available/your-site.conf` 内の
ディレクティブ)に記述する。以下の設定はUbuntu 24.04 LTS上のApache 2.4.58で有効である。
rewrite
モジュールが有効であることを確認する。sudo a2enmod rewrite
- HTTP設定を行っている VirtualHost セクション内に、次の3行を追加する。
RewriteEngine On RewriteCond %{HTTPS} off RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
RewriteEngine On
: Rewrite機能を有効にする。RewriteCond %{HTTPS} off
: HTTPS接続でない場合に条件を適用する。RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
: 上記条件を満たす場合、元のパスとクエリ文字列を保持したまま、スキームをHTTPSに変更して永続的 (301) にリダイレクトする。 - 設定ファイルの構文テストを実行する。
sudo apache2ctl configtest
- Apache を再起動して設定を反映させる。(
rewrite
モジュールを初めて有効にした場合は再起動が必要である)sudo systemctl restart apache2
- 確認方法: ブラウザでサイトのHTTP (例:
http://yourdomain.com
) にアクセスし、自動的にHTTPS (例:https://yourdomain.com
) にリダイレクトされることを確認する。curl -I http://yourdomain.com
コマンドでもリダイレクトヘッダーを確認できる。
X-Frame-Optionsヘッダーの設定
X-Frame-Optionsヘッダーは,他のサイトの <frame>
, <iframe>
, <object>
要素内に自サイトのページが表示されることを制御する機能である。これにより、ユーザーを騙して意図しない操作を行わせるクリックジャッキング攻撃を防止する。
この設定は、Apacheのメイン設定ファイル、セキュリティ関連の設定ファイル、またはバーチャルホストの設定ファイルに記述できる。サイト全体で一貫したポリシーを適用する場合が多い。Ubuntu 24.04 LTS上のApache 2.4でも有効である。
- (
headers
モジュールが有効になっていることを確認すること。HSTS設定で有効にしていれば不要である) - 設定ファイル(例: `/etc/apache2/conf-available/security.conf` や VirtualHost セクション内)に,次の1行を追加する。
SAMEORIGIN
は、同一オリジン(同一ドメイン)からのフレーム内でのみ表示を許可する。完全に表示を禁止する場合はDENY
を指定する。Header always set X-Frame-Options "SAMEORIGIN"
- 設定ファイルの構文テストを実行する。
sudo apache2ctl configtest
- Apache を再起動する。
sudo systemctl restart apache2
- 確認方法:
curl -I https://yourdomain.com
コマンドやブラウザの開発者ツールでレスポンスヘッダーを確認し、X-Frame-Options
ヘッダーが設定されていることを確認する。
X-Content-Type-Options ヘッダーの設定
このヘッダーは、ブラウザが Content-Type
ヘッダーで指定されたMIMEタイプを無視して、ファイルの内容からタイプを推測(MIMEスニッフィング)する動作を抑制する。これにより、例えば画像ファイルとしてアップロードされたファイルがスクリプトとして実行されるといったセキュリティリスクを低減する。Ubuntu 24.04 LTS上のApache 2.4でも有効である。
この設定は、Apacheのメイン設定ファイル、セキュリティ関連の設定ファイル、またはバーチャルホストの設定ファイルに記述できる。
- (
headers
モジュールが有効になっていることを確認すること) - 設定ファイル(例: `/etc/apache2/conf-available/security.conf` や VirtualHost セクション内)に,次の1行を追加する。
nosniff
を指定することで、MIMEスニッフィングを抑止する。Header always set X-Content-Type-Options "nosniff"
- 設定ファイルの構文テストを実行する。
sudo apache2ctl configtest
- Apache を再起動する。
sudo systemctl restart apache2
- 確認方法:
curl -I https://yourdomain.com
コマンドやブラウザの開発者ツールでレスポンスヘッダーを確認し、X-Content-Type-Options
ヘッダーがnosniff
で設定されていることを確認する。
Content-Security-Policy (CSP) ヘッダーの設定
Content-Security-Policy (CSP) は,Webサイトが読み込むリソース(スクリプト、スタイルシート、画像など)の取得元を厳密に制限する機能である。これにより、クロスサイトスクリプティング (XSS) 攻撃やデータインジェクション攻撃などのリスクを大幅に軽減できる。従来のX-XSS-Protectionヘッダーよりも強力で柔軟な対策として推奨される。Ubuntu 24.04 LTS上のApache 2.4でも有効である。
CSPの設定は非常に強力であるが、設定を間違えるとサイトの表示や機能が損なわれる可能性がある。慎重に設定し、十分にテストすること。この設定は、SSL/TLSを使用するバーチャルホストの設定ファイル(例: `/etc/apache2/sites-available/your-site-ssl.conf` 内の <VirtualHost *:443>
ディレクティブ)に記述することが一般的である。
- (
headers
モジュールが有効になっていることを確認すること) - SSL/TLS設定を行っている VirtualHost セクション内に,CSPディレクティブを追加する。以下の設定はあくまで一例であり、あなたのWebサイトの構成に合わせて適切に調整する必要がある。
設定例:
default-src 'self'
: デフォルトでは、自分自身のオリジン(ドメイン)からのリソースのみを許可する。これが基本となる。script-src 'self' https://trusted-cdn.com
: スクリプトは自身のオリジンと、信頼するCDN (例:https://trusted-cdn.com
) からのみ許可する。外部のJavaScriptライブラリなどを使用している場合は、そのドメインを追加する。style-src 'self' https://fonts.googleapis.com
: スタイルシートは自身のオリジンと、Google Fonts (https://fonts.googleapis.com
) からのみ許可する。img-src 'self' data:
: 画像は自身のオリジンとdata:
スキーム(インライン画像)からのみ許可する。object-src 'none'
:<object>
,<embed>
,<applet>
といったプラグイン要素の読み込みを禁止する。frame-ancestors 'none'
: このページがフレーム内に埋め込まれることを禁止する (クリックジャッキング対策。X-Frame-Optionsの代替としても機能する)。- 設定ファイルの構文テストを実行する。
sudo apache2ctl configtest
- Apache を再起動する。
sudo systemctl restart apache2
- 確認方法: ブラウザの開発者ツール(ネットワークタブやコンソール)でレスポンスヘッダーとエラーメッセージを確認する。サイトの表示や機能が損なわれていないか、意図しないリソースがブロックされていないかを十分にテストする。外部のCSP評価ツール(例:
observatory.mozilla.org
)を利用するのも有効である。
# Content-Security-Policy ヘッダーの設定例 (環境に合わせて要調整)
# ※ 最初は Content-Security-Policy-Report-Only でテストすることを推奨します。
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' https://trusted-cdn.com; style-src 'self' https://fonts.googleapis.com; img-src 'self' data:; object-src 'none'; frame-ancestors 'none';"
注意: https://trusted-cdn.com
や https://fonts.googleapis.com
の部分は、あなたのサイトが実際に使用している外部リソースのオリジンに合わせて記述すること。不要な場合は削除する。インラインスクリプト (<script>...</script>
) やインラインスタイル (style="..."
) を使用している場合は、script-src
や style-src
に 'unsafe-inline'
を追加する必要があるが、セキュリティリスクが高まるため推奨されない。可能であれば外部ファイル化するか、nonceやhashを使用する方式を検討すること。
テストの推奨: Ubuntu 24.04 LTSでも、最初からポリシーを適用するのではなく、Header always set Content-Security-Policy-Report-Only "..."
のように Content-Security-Policy-Report-Only
ヘッダーを使用し、ブラウザの開発者コンソールで違反レポートを確認しながらポリシーを調整することを強く推奨する。問題がないことを確認してから Content-Security-Policy
に変更すること。
補足: サイトマップ sitemap.xml の作成プログラム
Webサイト管理に役立つツールとして、Pythonによるサイトマップ生成プログラムを紹介する。
次のプログラムを a.py のような名前で保存し,「python a.py https://www.kkaneko.jp/index.html」のように実行することにより,サイトマップ sitemap.xml を作成することができる.
このプログラムを実行するには、Ubuntu 24.04 LTSのデフォルトであるPython 3.12と以下のライブラリが必要である。pipまたはaptでインストールすること。
pip install requests beautifulsoup4
sudo apt update && sudo apt install python3-requests python3-bs4
import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin, urlparse
import sys
import re
import xml.etree.ElementTree as ET
import xml.dom.minidom # 整形のために追加
def is_valid_url(url, target_domain):
""" URLが指定されたドメイン内で有効かチェック """
parsed_url = urlparse(url)
return parsed_url.netloc and parsed_url.scheme and parsed_url.netloc == target_domain
def check_image_links(page_soup, page_url, target_domain, session, broken_images):
""" ウェブページ内の画像リンクをチェック """
for img_tag in page_soup.find_all("img"):
img_src = img_tag.get("src")
if img_src:
full_img_url = urljoin(page_url, img_src)
if is_valid_url(full_img_url, target_domain) and not is_url_accessible(full_img_url, session):
report_broken_link(full_img_url, page_url, broken_images)
def is_url_accessible(url, session):
""" URLがアクセス可能かチェック """
try:
response = session.head(url)
return response.status_code == 200
except requests.RequestException:
return False
def report_broken_link(url, source_url, broken_urls):
""" 壊れたリンクを報告 """
print(f"切れたリンク: {url}, 元のページ: {source_url}")
broken_urls.add((url, source_url))
def remove_hash_fragment(url):
# URLからハッシュフラグメント (#以降) を削除する。
return re.sub(r"#.*", "", url)
def remove_query_parameter(url):
# URLからクエリパラメータ (?以降) を削除する。同一コンテンツへの重複URLを避けるためである。
return url.split('?')[0]
def get_file_size(url, session):
""" URLのファイルサイズを取得 """
try: # HEADリクエストが失敗する場合も考慮
response = session.head(url, timeout=5) # タイムアウト設定
response.raise_for_status() # エラーがあれば例外発生
return int(response.headers.get('content-length', 0))
except requests.RequestException as e:
print(f"ファイルサイズ取得エラー: {url}, エラー: {e}")
return 0 # エラー時は 0 を返す
def get_all_links(page_url, target_domain, session, visited_urls, broken_urls, broken_images):
""" HTML ファイル内のリンクを取得 """
urls = set()
# HTML, PDF, Office文書以外のファイルタイプはサイトマップに含めないため、巡回対象外とする。
# 必要に応じて対象/除外リストを調整すること。
excluded_extensions = ('.zip', '.ZIP', '.accdb', '.csv', '.txt', '.jpg', '.JPG', '.png', '.MOV', '.mp4', '.MP4', '.gz', '.z', '.blend', '.ply', '.wav', '.xls', 'xlsx', '.MTS', '.iso', '.tif', '.tiff', '.bmp', '.mp3', '.avi', '.obj', '.cgi', '.spam', '.sh', '.tgz', '.ova', '.IFO', '.VOB', '.BUP', '.pcd', '.gif', '.xml', '.fbx', '.m4a', '.odp', '.mht', '.ttf', '.rb', '.tex')
allowed_extensions = ('.html', '.pdf', '.ppt', '.pptx', '.doc', '.docx') # サイトマップに含める拡張子
parsed_page_url = urlparse(page_url)
# 除外リストにある拡張子で終わるURLはスキップ
if any(parsed_page_url.path.lower().endswith(ext) for ext in excluded_extensions):
print(f"除外された拡張子: {page_url}")
return urls
# 許可リストにない拡張子のURLもスキップ (ただしディレクトリのような拡張子なしは許可)
if '.' in parsed_page_url.path.split('/')[-1] and not any(parsed_page_url.path.lower().endswith(ext) for ext in allowed_extensions):
print(f"許可されない拡張子: {page_url}")
return urls
# ファイルサイズのチェック (10MBを超えるファイルはスキップ)
file_size = get_file_size(page_url, session)
if file_size > 10000000:
print(f"大きすぎるファイル (サイズ: {file_size}): {page_url}")
return urls
try:
response = session.get(page_url, timeout=10) # タイムアウト設定
response.raise_for_status() # エラーがあれば例外発生
# Content-Typeがtext/htmlでない場合はスキップ
content_type = response.headers.get('content-type', '').lower()
if 'text/html' not in content_type:
# PDFなどは別途処理するためここではスキップしない
if not any(page_url.lower().endswith(ext) for ext in allowed_extensions if ext != '.html'):
print(f"HTMLでないコンテンツタイプ: {content_type}, URL: {page_url}")
return urls
if response.history: # リダイレクトが発生した場合
# リダイレクト先のURLがターゲットドメイン外なら処理しない
if not is_valid_url(response.url, target_domain):
print(f"外部へのリダイレクト: {page_url} -> {response.url}")
return urls
print(f"リダイレクト検出: 元 {page_url}, 先 {response.url}")
# リダイレクト先を処理対象に追加 (ただし、既に訪問済みでないかチェック)
final_url = remove_query_parameter(remove_hash_fragment(response.url))
if final_url != page_url and final_url not in visited_urls:
# crawl_website内で追加されるのでここでは追加しない
pass # 必要であればここで追加処理
# HTMLの場合のみパースしてリンクを抽出
if 'text/html' in content_type:
page_soup = BeautifulSoup(response.content, "html.parser")
# 画像リンク切れチェック (オプション)
# check_image_links(page_soup, page_url, target_domain, session, broken_images)
for a_tag in page_soup.find_all("a"):
href = a_tag.get("href")
if href:
# URLの正規化 (相対パスを絶対パスに、フラグメントとクエリを除去)
full_url = urljoin(page_url, href)
cleaned_url = remove_query_parameter(remove_hash_fragment(full_url))
# 有効なURLか、ターゲットドメイン内か、訪問済みでないか、許可された拡張子かをチェック
if is_valid_url(cleaned_url, target_domain) and cleaned_url not in visited_urls:
# 許可された拡張子で終わるかチェック
parsed_cleaned_url = urlparse(cleaned_url)
if any(parsed_cleaned_url.path.lower().endswith(ext) for ext in allowed_extensions) or '.' not in parsed_cleaned_url.path.split('/')[-1]: # 拡張子なし(ディレクトリ等)も許可
try:
# リンク先が存在するか確認してから追加 (HEADリクエスト)
link_response = session.head(cleaned_url, timeout=5)
link_response.raise_for_status()
# リダイレクトがあれば最終的なURLを使用
final_link_url = remove_query_parameter(remove_hash_fragment(link_response.url))
if is_valid_url(final_link_url, target_domain) and final_link_url not in visited_urls:
print(f"追加候補: {final_link_url} (元リンク: {href})")
urls.add(final_link_url)
except requests.RequestException as link_e:
print(f"リンク先アクセスエラー: {cleaned_url}, 元ページ: {page_url}, エラー: {link_e}")
report_broken_link(cleaned_url, page_url, broken_urls) # broken_urls を使用
except (requests.RequestException, Exception) as e:
print(f"ページ取得/処理エラー: {page_url}, エラー詳細: {e}")
report_broken_link(page_url, "Crawl Start", broken_urls) # broken_urls を使用
return urls
def crawl_website(start_url, target_domain, session, visited_urls, broken_urls, broken_images):
""" ウェブサイトを再帰的に巡回 (幅優先探索) """
to_visit = [start_url] # これから訪れるURLのリスト
while to_visit:
current_url = to_visit.pop(0) # FIFOで幅優先探索
# 正規化してからチェック
current_url = remove_query_parameter(remove_hash_fragment(current_url))
if current_url in visited_urls or not is_valid_url(current_url, target_domain):
continue
print(f"巡回中: {current_url}")
visited_urls.add(current_url)
# 現在のURLからリンクを取得
try:
# get_all_links関数に渡すbroken_urls引数の変数名を修正 (report_broken_link関数で使用する引数名と合わせる)
links = get_all_links(current_url, target_domain, session, visited_urls, broken_urls, broken_images)
# 新しく見つかった、未訪問のリンクを訪問リストに追加
for link in links:
cleaned_link = remove_query_parameter(remove_hash_fragment(link))
if cleaned_link not in visited_urls and cleaned_link not in to_visit:
to_visit.append(cleaned_link)
except Exception as e:
print(f"巡回エラー ({current_url}): {e}")
# report_broken_link関数に渡すbroken_urls引数の変数名を修正
report_broken_link(current_url, "Crawl Loop", broken_urls)
def create_sitemap(links):
""" サイトマップをXML形式で作成 """
urlset = ET.Element("urlset", xmlns="http://www.sitemaps.org/schemas/sitemap/0.9")
# HTMLページのみをサイトマップに追加 (オプション: 必要なら他のタイプも)
html_links = [link for link in links if urlparse(link).path.lower().endswith('.html') or '.' not in urlparse(link).path.split('/')[-1]]
for link in sorted(html_links): # HTMLリンクのみソートして追加
url = ET.SubElement(urlset, "url")
ET.SubElement(url, "loc").text = link
ET.SubElement(url, "changefreq").text = "monthly" # または weekly/daily など
# lastmod も追加可能 (例: ET.SubElement(url, "lastmod").text = "YYYY-MM-DD")
return ET.ElementTree(urlset)
def save_sitemap(sitemap, filename):
""" サイトマップを整形してファイルに保存 """
xml_str = ET.tostring(sitemap.getroot(), encoding='utf-8')
# minidomを使って整形
dom = xml.dom.minidom.parseString(xml_str)
pretty_xml_str = dom.toprettyxml(indent=" ", encoding='utf-8') # インデントを指定
# ファイルに保存 (バイト列として書き込む)
with open(filename, "wb") as file: # バイナリモードで開く
file.write(pretty_xml_str)
# --- メインの実行部分 ---
if __name__ == "__main__": # スクリプトとして実行された場合のみ動作
if len(sys.argv) > 1:
start_url = sys.argv[1]
# 開始URLを正規化
start_url = remove_query_parameter(remove_hash_fragment(start_url))
parsed_start_url = urlparse(start_url)
target_domain = parsed_start_url.netloc
# ドメインがなければエラー
if not target_domain:
print(f"エラー: 無効な開始URLである。ドメイン名が含まれていない: {start_url}")
sys.exit(1)
# スキームがなければ https を仮定 (httpも可)
if not parsed_start_url.scheme:
start_url = "https://" + start_url
print(f"スキームを追加した: {start_url}")
session = requests.Session()
# User-Agentを設定 (偽装が必要な場合)
session.headers.update({'User-Agent': 'My Sitemap Generator Bot'})
visited_urls, broken_urls, broken_images = set(), set(), set()
print(f"巡回開始: {start_url}, 対象ドメイン: {target_domain}")
# crawl_website関数に渡すbroken_urls引数の変数名を修正
crawl_website(start_url, target_domain, session, visited_urls, broken_urls, broken_images)
# サイトマップの作成と保存 (HTMLページのみ)
sitemap_xml = create_sitemap(visited_urls)
save_sitemap(sitemap_xml, "sitemap.xml")
print(f"\n--- 処理完了 ---")
print(f"巡回したURL数: {len(visited_urls)}")
# findallの名前空間指定を修正 (元コードと同じ形式を維持)
print(f"サイトマップ (sitemap.xml) に含まれるURL数: {len(sitemap_xml.getroot().findall('{http://www.sitemaps.org/schemas/sitemap/0.9}url'))}")
print(f"サイトマップが作成され、sitemap.xml として保存された。")
# 壊れたリンクの報告
if broken_urls:
print("\n--- 検出された壊れた可能性のあるリンク ---")
for url, source in sorted(list(broken_urls)):
print(f"リンク: {url} (参照元: {source})")
else:
print("\n壊れたリンクは見つからなかった。")
else:
print("使い方: python a.py <開始URL>")
print("例: python a.py https://www.example.com")
生成された sitemap.xml
ファイルは、Webサイトのルートディレクトリに配置することが最も一般的である。配置後、robots.txt
ファイルに Sitemap: [サイトマップのURL]
という行を追加することで、検索エンジンにサイトマップの場所を知らせることができる。また、Google Search Consoleなどの検索エンジン提供ツールに直接サイトマップを登録することも推奨される。