2023/12/09_Python_sitemap.xmlを作成するコード

2023/12/13_アップデート

ウィンドウで表示して、URLを指定できるようにした。

GitHub

コード

import tkinter as tk

from tkinter import font

from threading import Thread

import requests

from bs4 import BeautifulSoup

from urllib.parse import urlparse, urljoin

import xml.etree.ElementTree as ET

from datetime import datetime


# 訪れたURLを保持するセット

visited_urls = set()


class SitemapGeneratorApp:

    def __init__(self, root):

        self.root = root

        self.root.title("Sitemap Generator")


        # ウィンドウの大きさを変更できないようにする

        self.root.resizable(width=False, height=False)


        # ラベルとエントリー

        tk.Label(self.root, text="Start URL:").pack(pady=5)

        self.start_url_entry = tk.Entry(self.root, width=80)

        self.start_url_entry.pack(pady=5)


        # 開始ボタン

        self.start_button = tk.Button(self.root, text="Start Crawling", command=self.start_crawling)

        self.start_button.pack(pady=10)


        # ページ数表示ラベル

        self.page_count_label_var = tk.StringVar()

        self.page_count_label_var.set("Pages Crawled: 0")

        self.page_count_label = tk.Label(self.root, textvariable=self.page_count_label_var)

        self.page_count_label.pack(pady=5)


        # メッセージ表示ラベル

        self.message_label_var = tk.StringVar()

        self.message_label_var.set("")

        self.message_label = tk.Label(self.root, textvariable=self.message_label_var, wraplength=500)

        self.message_label.pack(pady=5)



        # フレームを作成

        frame = tk.Frame(self.root)

        frame.pack()


        # URLリストボックス

        self.url_listbox = tk.Text(frame, height=10, width=80, wrap=tk.WORD)

        self.url_listbox.pack(side=tk.LEFT, pady=5)


        # スクロールバー

        scrollbar = tk.Scrollbar(frame, command=self.url_listbox.yview)

        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)


        # URLリストボックスにスクロールバーを結びつける

        self.url_listbox.config(yscrollcommand=scrollbar.set)



        # クラス変数としてstart_urlを追加

        self.start_url = ""

        self.crawling_thread = None  # クロール用のスレッド



    def is_valid_start_url(self, url):

        # スタートURLが空でないことを確認

        if not url:

            self.message_label_var.set("Error: Start URL is empty.")

            self.message_label.config(font=("TkDefaultFont"))

            self.message_label.config(fg="red")

            return False


        # HTTPリクエストを発行してアクセス可能か確認

        try:

            response = requests.get(url)

            response.raise_for_status()  # HTTPエラーレスポンスがあれば例外を発生させる


            # printステートメントを使用してステータスコードを表示

            # print(f"Status Code for {url}: {response.status_code}")


            # スタートURLが404エラーの場合、エラーメッセージを表示して中断

            # ステータスコードが404の場合はエラーメッセージを表示して終了

            if response.status_code == 404 or not response.ok:

                self.message_label_var.set(f"Error accessing Start URL: {response.status_code}")

                self.message_label.config(font=("TkDefaultFont"))

                self.message_label.config(fg="red")

                return


            return True

        except requests.exceptions.RequestException as e:

            self.message_label_var.set(f"Error accessing Start URL: {e}")

            self.message_label.config(font=("TkDefaultFont"))

            self.message_label.config(fg="red")

            return False



    def crawl_site(self, url, xml_element):

        try:


            # HTTPリクエストを発行

            response = requests.get(url)

            response.raise_for_status()  # HTTPエラーレスポンスがあれば例外を発生させる


            # 訪れたURLに追加

            visited_urls.add(url)


            # XML要素を作成して追加

            url_elem = ET.Element("url")

            loc_elem = ET.SubElement(url_elem, "loc")

            loc_elem.text = url


            # 更新日付を追加

            lastmod_elem = ET.SubElement(url_elem, "lastmod")

            lastmod_elem.text = datetime.now().strftime("%Y-%m-%d")


            # url_elem を xml_element に追加

            xml_element.append(url_elem)


            # 成功した場合のみページ数を表示

            self.page_count_label_var.set(f"Pages Crawled: {len(visited_urls)}")

               

            # URLリストボックスに追加

            self.url_listbox.insert(tk.END, url + "\n")

            #スクロールバーを一番下に移動

            self.url_listbox.yview(tk.END)


            # 更新

            self.root.update_idletasks()


            # HTMLを解析

            soup = BeautifulSoup(response.text, 'html.parser')


            # URLを抽出

            links = soup.find_all('a')

            for link in links:

                href = link.get('href')

                if href:

                    # ページ内リンクの場合、無視

                    if href.startswith('#'):

                        continue


                    # 相対パスを絶対URLに変換

                    abs_url = urljoin(url, href)


                    try:

                        # ドメインが一致し、まだ訪れていない場合のみ再帰的にクロール

                        if urlparse(abs_url).netloc == urlparse(self.start_url).netloc and abs_url not in visited_urls:

                            # 再帰的にクロール

                            self.crawl_site(abs_url, xml_element)

                    except requests.exceptions.RequestException as e:

                        # エラー

                        return


        except requests.exceptions.RequestException as e:

            # エラー

            return



    def crawling_thread_func(self):

        try:

            # 訪れたURLを保持するセットを初期化

            global visited_urls

            visited_urls = set()


            # URLリストボックスをクリア

            self.url_listbox.delete("1.0", tk.END)


            # XML要素を作成

            root_xml = ET.Element("urlset")

            root_xml.set("xmlns", "http://www.sitemaps.org/schemas/sitemap/0.9")


            # クロールを実行

            self.crawl_site(self.start_url, root_xml)


            # XMLツリーをファイルに書き込む

            tree = ET.ElementTree(root_xml)

            tree.write("sitemap.xml", encoding="utf-8", xml_declaration=True, method="xml")


        finally:

            # クロール終了後、メッセージを表示

            self.message_label_var.set("Crawling Completed!")

            # クロール終了後、太字のフォントにする

            self.message_label.config(font=("TkDefaultFont", 12, "bold"))

            self.message_label.config(fg="black"# フォントカラーを黒に変更


            # クロール終了後、開始ボタンを有効化

            self.start_button.config(state=tk.NORMAL)


    def start_crawling(self):

        if self.crawling_thread and self.crawling_thread.is_alive():

            # 既にクロール中の場合は何もしない

            return


        # start_urlをクラス変数にセット

        self.start_url = self.start_url_entry.get()


        # スタートURLが無効な場合は中断

        if not self.is_valid_start_url(self.start_url):

            return


        # 開始ボタンを無効化

        self.start_button.config(state=tk.DISABLED)


        # クロール中のメッセージを表示

        self.message_label_var.set("Crawling...")


        # クロール中は普通のフォント

        self.message_label.config(font=("TkDefaultFont"))

        self.message_label.config(fg="black"# フォントカラーを黒に変更


        # クロール用のスレッドを開始

        self.crawling_thread = Thread(target=self.crawling_thread_func)

        self.crawling_thread.start()


if __name__ == "__main__":

    root = tk.Tk()

    app = SitemapGeneratorApp(root)


    # ウィンドウの幅と高さを指定

    width = 600

    height = 400


    # 画面の中央に配置

    screen_width = root.winfo_screenwidth()

    screen_height = root.winfo_screenheight()

    x = (screen_width - width) // 2

    y = (screen_height - height) // 2


    # geometryメソッドでウィンドウの位置とサイズを指定

    root.geometry(f"{width}x{height}+{x}+{y}")


    app.start_url_entry.focus_force()  # フォーカスをエントリーに設定

    root.mainloop()


前のバージョン

下のものはpythonで書いたコード。

VSCodeなどでstart_url にクロールを開始するURLを指定して実行すると、pythonファイルと同じディレクトリにsitemap.xmlが作成される。


グーグルサイトにはxmlは置けないので、GitHub Pages でページを作りsitemap.xmlをアップロード。

グーグルサーチコンソールでサイトマップ送信の成功を確認。


コード

import requests

from bs4 import BeautifulSoup

from urllib.parse import urlparse, urljoin

import xml.etree.ElementTree as ET

from datetime import datetime


# 訪れたURLを保持するセット

visited_urls = set()


def crawl_site(url, xml_element):

    try:

        # 訪れたURLに追加

        visited_urls.add(url)


        # XML要素を作成して追加

        url_elem = ET.Element("url")

        loc_elem = ET.SubElement(url_elem, "loc")

        loc_elem.text = url


        # 更新日付を追加

        lastmod_elem = ET.SubElement(url_elem, "lastmod")

        lastmod_elem.text = datetime.now().strftime("%Y-%m-%d")


        # url_elem を xml_element に追加

        xml_element.append(url_elem)


        # HTTPリクエストを発行

        response = requests.get(url)

        response.raise_for_status()  # HTTPエラーレスポンスがあれば例外を発生させる


        # HTMLを解析

        soup = BeautifulSoup(response.text, 'html.parser')


        # URLを抽出

        links = soup.find_all('a')

        for link in links:

            href = link.get('href')

            if href:

                # ページ内リンクの場合、無視

                if href.startswith('#'):

                    continue

               

                # 相対パスを絶対URLに変換

                abs_url = urljoin(url, href)


                # ドメインが一致し、まだ訪れていない場合のみ再帰的にクロール

                if urlparse(abs_url).netloc == urlparse(start_url).netloc and abs_url not in visited_urls:

                    # 再帰的にクロール

                    crawl_site(abs_url, xml_element)


    except requests.exceptions.RequestException as e:

        print(f"Error fetching URL {url}: {e}")


# クロールを開始するURLとそのドメインを指定

start_url = 'https://sites.google.com/view/roy00227unrealengine/home'


# XML要素を作成

root = ET.Element("urlset")

root.set("xmlns", "http://www.sitemaps.org/schemas/sitemap/0.9")


# クロールを実行

crawl_site(start_url, root)


# XMLツリーをファイルに書き込む

tree = ET.ElementTree(root)

tree.write("sitemap.xml", encoding="utf-8", xml_declaration=True, method="xml")