はてなブログでランダムな記事を表示したい【素人でもできた Phyton編 2】サイトマップがsitemapindex形式の場合

 

今回ChatGPTにコードを相談しながら作りました。
一応そのままコピペしたものですが、利用いただく場合は、自己責任でお願いします。

 

前回、ランダムな記事を作りたいと言って、ChatGTPに相談しながらすすめていたのですが、躓いてしまったところで話が終わっていました。

はてなブログでランダムな記事を表示したい【Phyton編 1】

今回はその続きです。

 

 

手順のおさらい

  1. Pythonでサイトマップを読み取り、記事URLを抽出
  2. それらをJavaScriptの配列形式に整形
  3. その配列をブログの中に貼る

 

前回、私のブログのサイトマップがsitemapindex形式であったために、sitemap.xmlから記事のURLが取得できないという状況になってしまっていました。

 

ということで、それに対応した形のコードをChatGPTに作ってもらいました。

 

サイトマップから記事URLを抽出するためのPythonのコード (サイトマップが sitemapindex 形式の場合)

いろいろパターンを作ってもらったので、一部掲載しておきます。

 

全記事を抽出するパターン

<python>


import requests
import xml.etree.ElementTree as ET

def get_urls_from_sitemap(url):
    res = requests.get(url)
    res.raise_for_status()
    root = ET.fromstring(res.content)

    namespace = root.tag.split('}')[0].strip('{')
    ns = {'ns': namespace}

    urls = []
    # sitemapindexかurlsetか判別
    if root.tag.endswith('sitemapindex'):
        # サブサイトマップのURLを取得
        sitemap_urls = [sitemap.find('ns:loc', ns).text for sitemap in root.findall('ns:sitemap', ns)]
        for sm_url in sitemap_urls:
            urls.extend(get_urls_from_sitemap(sm_url))  # 再帰的に読み込み
    else:
        # urlsetなら記事URLを取得
        for url_elem in root.findall('ns:url', ns):
            loc = url_elem.find('ns:loc', ns)
            if loc is not None:
                urls.append(loc.text)
    return urls

sitemap_url = "https://●●●自分のURL.com/sitemap.xml"
urls = get_urls_from_sitemap(sitemap_url)

print(f"urls = {urls}")

# JS出力
print('

') 

 

 

上記の●●自分のURL●●のところを、自分のURLに置き換えます。

 

ただ、これは全記事のURLを抽出しているので、例えば現時点でこのブログは500記事以上あるので、URLがとんでもないことに…(゚∀゚)

▲ 全記事のURLが出てくる…

 

 

少しカスタマイズ

記事数が多い場合は、ランダムに指定した数の記事を選んで、そこから更にランダムで記事URLを描出するという方法をとることもできます。記事URL数が多すぎると、読込速度が若干落ちる原因にもなるようなので、今回はその方法を使うことにしました。

 

更に欲がでてきたので、

  • サイトマップからすべての記事URLを取得
  • その中から ランダムに100件を選ぶ
  • 100件の記事の タイトルとサムネイル画像を取得
  • JavaScriptで その100件の中から2件をランダムに選んで表示する

この条件でコードを作ってもらうことにしました。

 

<python>

def main():
    sitemap_url = "https://自分のURL.com/sitemap.xml"
    all_urls = get_urls_from_sitemap(sitemap_url)

    # ランダムに100件だけ選ぶ(記事が100未満なら全件)
    sample_urls = random.sample(all_urls, min(100, len(all_urls)))

    articles = []
    for url in sample_urls:
        title, img = get_title_and_ogp_image(url)
        articles.append({
            'url': url,
            'title': title,
            'img': img
        })

    # JavaScriptとして出力(2件ランダムに表示)
    print('<script>')
    print('const articles = [')
    for art in articles:
        print(f'  {{url: "{art["url"]}", title: "{art["title"].replace(\'"\', "\\\"")}", img: "{art["img"]}"}},')
    print('];')
    print('const shuffled = articles.sort*1;')
    print('const selected = shuffled.slice(0, 2);')
    print('selected.forEach(article => {')
    print('  document.write(`')
    print('    <div style="margin-bottom:10px;">')
    print('      <a href="${article.url}" style="display:flex; align-items:center; text-decoration:none;">')
    print('        <img src="${article.img}" alt="${article.title}" style="width:60px; height:60px; object-fit:cover; margin-right:10px; border-radius:5px;" loading="lazy">')
    print('        <span style="color:#333;"> ${article.title}</span>')
    print('      </a>')
    print('    </div>')
    print('  `);')
    print('});')
    print('</script>')

※自分のURL.comと書いていますが、ここは https://www.mikanusagi.com とか、そういう意味です。この部分を書き換えてください。

※このコード、一部コードが外に出てしまい、この記事の末尾に表示されています。このまま貼り付けても当該箇所が動きません…。

 

最初にランダムに選ぶ数を変更したい時は、

 sample_urls = random.sample(all_urls, min(100, len(all_urls)))

の100を他の数に変更します。例えば30件なら、min(30, len…)。

 

表示件数をもっと増やしたい場合は、sliceのところの数字を、slice(0, 3) のように増やすことで対応できます。

 

さらにカスタマイズ

できた~と喜んでいたら、さらに素敵な提案をしてくれました。

カテゴリ別に表示する方法 (条件あり)

はてなブログの場合、カテゴリを作るとこんな感じになります。

https://www.mikanusagi.com/archive/category/100

これを使ってカテゴリ別に抽出することも理論上は可能。

 

ただし、sitemap.xml にあるのは実際の記事のURL(エントリ)なので、
URLの中に /category/レシピ などが含まれていない場合はフィルターできません。

でも、代わりに「レシピ」という単語がURLやタイトルに含まれていれば、それでフィルターできます。

ChatGPTのコメントより

 

URLに「100」や「100円」が含まれる記事だけに絞る場合

<python>

# 100件ランダム抽出する前に、URLでフィルタリング
filtered_urls = [url for url in all_urls if '100' in url or '100円' in url]
sample_urls = random.sample(filtered_urls, min(100, len(filtered_urls)))

 

 タイトルに「100円」が含まれる記事だけを残す

<python>

# 全記事の中からタイトルを取得してフィルタリング
for url in sample_urls:
    title, img = get_title_and_ogp_image(url)
    if '100円' in title:
        articles.append({
            'url': url,
            'title': title,
            'img': img
        })

 

 

タイトルの文字数を調整する方法

タイトルが長すぎるときは「...」で省略

Pythonを使う場合

<python>

def truncate_title(title, max_length=40):
    return title if len(title) <= max_length else title[:max_length - 3] + '...'
articles.append({
    'url': url,
    'title': truncate_title(title),  # ← ここでタイトル省略
    'img': img
})

 

 

いったんまとめ

  • サイトマップから記事URLを取得
  • その中から ランダムに100件を選ぶ
  • 各記事の タイトル・画像を取得
  • タイトルは40文字までに制限し、省略記号(...)を付ける
  • JavaScriptで その100件の中からさらに2件をランダム表示

この条件を反映させたコードが以下です。

<python>


import requests
import xml.etree.ElementTree as ET
from bs4 import BeautifulSoup
import random

# タイトルが長すぎるときに省略
def truncate_title(title, max_length=40):
    return title if len(title) <= max_length else title[:max_length - 3] + '...'

# サイトマップからURL一覧を取得(名前空間対応)
def get_urls_from_sitemap(url):
    res = requests.get(url)
    res.raise_for_status()
    root = ET.fromstring(res.content)

    namespace = root.tag.split('}')[0].strip('{')
    ns = {'ns': namespace}

    urls = []
    if root.tag.endswith('sitemapindex'):
        sitemap_urls = [sitemap.find('ns:loc', ns).text for sitemap in root.findall('ns:sitemap', ns)]
        for sm_url in sitemap_urls:
            urls.extend(get_urls_from_sitemap(sm_url))
    else:
        for url_elem in root.findall('ns:url', ns):
            loc = url_elem.find('ns:loc', ns)
            if loc is not None:
                urls.append(loc.text)
    return urls

# 各記事からタイトルとOGP画像を取得
def get_title_and_ogp_image(url):
    try:
        res = requests.get(url)
        res.raise_for_status()
        soup = BeautifulSoup(res.text, 'html.parser')

        title = soup.title.string.strip() if soup.title else 'タイトルなし'

        og_img_tag = soup.find('meta', property='og:image')
        og_image = og_img_tag['content'] if og_img_tag and og_img_tag.get('content') else ''

        return title, og_image
    except Exception as e:
        print(f"Error fetching {url}: {e}")
        return 'タイトル取得失敗', ''

# メイン処理
def main():
    sitemap_url = "https://●●●自分のURL.com/sitemap.xml"
    all_urls = get_urls_from_sitemap(sitemap_url)

    # ランダムに100件抽出(記事数が少なければ全件)
    sample_urls = random.sample(all_urls, min(100, len(all_urls)))

    articles = []
    for url in sample_urls:
        title, img = get_title_and_ogp_image(url)
        articles.append({
            'url': url,
            'title': truncate_title(title),
            'img': img
        })

    # JavaScriptとして出力(2件ランダム表示)
    print('

') if __name__ == "__main__": main() 

 

※自分のURL.comと書いていますが、ここは https://www.mikanusagi.com とか、そういう意味です。この部分を書き換えてください。

 

 

表示されるブログ名を削除したい場合

大分かたまってきたのですが、もう少しだけ…。

はてなブログの場合、タイトル後ろにブログ名がつきます。

 

「美味しいごはんの話 - そんな日もあるさ」←こんなかんじ。

 

これが表示されないようにしたいんだよな…。

 

ということで、この方法も教えてもらいました。

 

<python> 一部抜粋 「-そんな日もあるさ」を削除したい場合

def get_title_and_ogp_image(url):
    try:
        res = requests.get(url)
        res.raise_for_status()
        soup = BeautifulSoup(res.text, 'html.parser')

        title = soup.title.string.strip() if soup.title else 'タイトルなし'

        # 柔軟にブログ名を削除
        BLOG_NAME_SUFFIX = ' - そんな日もあるさ'
        if title.endswith(BLOG_NAME_SUFFIX):
            title = title[:-len(BLOG_NAME_SUFFIX)]

        og_img_tag = soup.find('meta', property='og:image')
        og_image = og_img_tag['content'] if og_img_tag and og_img_tag.get('content') else ''

        return title, og_image
    except Exception as e:
        print(f"Error fetching {url}: {e}")
        return 'タイトル取得失敗', ''

赤字の部分を「いったんまとめ」の項で記載しているコード内に追加します。

 

「いったんまとめ」のコードにタイトル名を削除するコードを追加した状態が、2025/6/10時点で表示されている状態です。

 

▲ こんな感じ。ランダムに2個抽出されています。

 

 

なんか長くなってしまっているので目次をいったんはさみます。(=゚ω゚)

 

ラストスパート

Python のファイルが完成してからすること

  1. Pythonのファイルが完成したら、コマンドプロンプト経由で実行します。
  2. するとコマンドプロンプト内にJavaScript形式のコードが作成されます。
  3. これをはてなブログのHTML形式の場所に貼り付けたら完成。

 

やり方はこちらの記事内にありますが、一応以下にも記載します。

はてなブログでランダムな記事を表示したい【Phyton編 1】

 

こんなのが生成されるので、<script>~</script>をまるっとコピーして、はてなブログに貼り付けます。これで完成。

(JavaScriptコードの一例)

<script>
 const urls = [
  "https://●●URL.com/entry1",
  "https://●●URL.com/entry2",
  // ここに全URLリストが続く
 ];
 const randomIndex = Math.floor(Math.random() * urls.length);
 const randomUrl = urls[randomIndex];
 document.write('<a href="' + randomUrl + '"> ランダムな記事を読む</a>');
</script>

 

 

はてなブログのサイドバーに JavaScript コードを貼る

一応貼り方も簡単に。

管理画面の「デザイン」>「カスタマイズ」 を選択。

 「サイドバー」 >「モジュールを追加」>「HTML」を選択。

そこに、先ほど生成されたJavaScriptコードを貼り付けます。

 

 

保存して完成。

私のブログは、2025年6月10日時点ではこうなっています。

 

 

 

最後に

お気づきかと思いますが、このやり方だと、新しく作成した記事のURLは反映されません。また、最初にランダムに100記事とか選ぶので、その時点で選ばれなかった記事も反映されません。

つまり、更新は今回と同じ手順で手動で行う必要があります。

 

もちろん、自動で記事を取得しにいく方法もあるらしいのですが、外部ツールを使うなどでちょっとややこしそうなので今回はそれについてはやってみませんでした。

 

個人的には、「最初に100件選ぶだけでもしばらくは訪問くださった方に新しいものが表示されるからたまに更新するのでも大丈夫な気がする…」と思ったので、この手法を選びました。

 

記事が私にしては異様に長くなってしまいましたが(コードを貼り付けているせいなのと、折り畳みにしてないから。笑)、自分の備忘録も兼ねているので、今回はこのまま掲載します。

 

繰り返しになりますが、素人のメモに近いものなので、ご利用いただく際は自己責任でお願いします。

 

それでは今回はこのへんで。(*'▽')/

 

といいつつ

おまけ カテゴリ絞り込みのコード

  • カテゴリ絞り込みは、URLかタイトルに含まれるキーワードで判定
  • OGP画像が空文字(ない場合)は除外
  • ランダムに100件抽出の前にフィルター

この条件の時のコードも作ってくれました。

(以下補足)

  • TARGET_KEYWORDS にカテゴリ判定したいキーワードを自由に追加できる。
  • is_target_article() でURLかタイトルにキーワードが含まれるかチェック。
  • 画像が空 ('') の記事は除外。
  • フィルター済み記事から100件をランダム抽出。
  • JSでさらに2件ランダムに選んで表示。

<python>


import requests
import xml.etree.ElementTree as ET
from bs4 import BeautifulSoup
import random

def truncate_title(title, max_length=40):
    return title if len(title) <= max_length else title[:max_length - 3] + '...'

def get_urls_from_sitemap(url):
    res = requests.get(url)
    res.raise_for_status()
    root = ET.fromstring(res.content)

    namespace = root.tag.split('}')[0].strip('{')
    ns = {'ns': namespace}

    urls = []
    if root.tag.endswith('sitemapindex'):
        sitemap_urls = [sitemap.find('ns:loc', ns).text for sitemap in root.findall('ns:sitemap', ns)]
        for sm_url in sitemap_urls:
            urls.extend(get_urls_from_sitemap(sm_url))
    else:
        for url_elem in root.findall('ns:url', ns):
            loc = url_elem.find('ns:loc', ns)
            if loc is not None:
                urls.append(loc.text)
    return urls

def get_title_and_ogp_image(url):
    try:
        res = requests.get(url)
        res.raise_for_status()
        soup = BeautifulSoup(res.text, 'html.parser')

        title = soup.title.string.strip() if soup.title else 'タイトルなし'

        og_img_tag = soup.find('meta', property='og:image')
        og_image = og_img_tag['content'] if og_img_tag and og_img_tag.get('content') else ''

        return title, og_image
    except Exception as e:
        print(f"Error fetching {url}: {e}")
        return 'タイトル取得失敗', ''

# 「レシピ」カテゴリなどのキーワードリスト
TARGET_KEYWORDS = ['レシピ', 'recipe']

def is_target_article(url, title):
    # URLまたはタイトルにキーワードが含まれていたらTrue
    for kw in TARGET_KEYWORDS:
        if kw in url or kw in title:
            return True
    return False

def main():
    sitemap_url = "https://www.mikanusagi.com/sitemap.xml"
    all_urls = get_urls_from_sitemap(sitemap_url)

    articles = []
    for url in all_urls:
        title, img = get_title_and_ogp_image(url)

        # カテゴリ判定と画像の有無でフィルター
        if is_target_article(url, title) and img != '':
            articles.append({
                'url': url,
                'title': truncate_title(title),
                'img': img
            })

    # ランダムに100件選択(記事数少なければ全件)
    sample_articles = random.sample(articles, min(100, len(articles)))

    # JS出力(2件ランダムに表示)
    print('

') if __name__ == "__main__": main() 

 

※自分のURL.comと書いていますが、ここは https://www.mikanusagi.com とか、そういう意味です。

 

※なんか一部のコードが外に出ちゃってます😢「少しカスタマイズ」のコード分です。

---------------------------------------------

*1:) => 0.5 - Math.random(