【はてなブログ】投稿記事のURLを一括取得する

とある事情で自分のブログの投稿した記事の全URLのリストアップが必要になったのでリストアップするためのプログラムをC#で書いてみました。

せっかくなのでコードを公開しようと思います。

サイトマップの形式

まずはサイトマップのデータ形式を確認します。はてなブログのサイトマップの形式は2020年7月8日現在以下の通りです。

【サイト全体の概要】sitemap.xml

まずはトップの sitemap.xml です。このURLには各月ごとにまとめられた子サイトマップへのリンク集となっています。

以下のぼくのサイトの例では1か月単位で タグにURLが順番に並んでいます。

<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <sitemap>
    <loc>https://takap-tech.com/sitemap_common.xml</loc>
    <lastmod>2020-07-08T01:50:34+09:00</lastmod>
  </sitemap>
  <sitemap>
    <loc>https://takap-tech.com/sitemap_periodical.xml?year=2020&amp;month=7</loc>
    <lastmod>2020-07-08T01:50:34+09:00</lastmod>
  </sitemap>
  <sitemap>
    <loc>https://takap-tech.com/sitemap_periodical.xml?year=2020&amp;month=6</loc>
    <lastmod>2020-07-08T01:50:34+09:00</lastmod>
  </sitemap>
  <sitemap>
  ... 以下繰り返し

【月ごとの記事一覧】sitemap_periodical.xml

sitemap.xml にあったリンク先は各月ごとの投稿記事の一覧のURLです。

ここに具体的な各記事へのURLが記載されているのでここからURLを抽出します。

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>https://takap-tech.com/entry/2020/06/22/220549</loc>
    <lastmod>2020-06-22T22:13:01+09:00</lastmod>
  </url>
  <url>
    <loc>https://takap-tech.com/entry/2020/06/20/232208</loc>
    <lastmod>2020-06-24T22:26:57+09:00</lastmod>
  </url>
  <url>
  ... 以下繰り返し
</urlset>

各記事のURLをC#で取得する

プログラム言語はC#を使用します。

確認環境

この記事は以下の環境で動作確認しました。

  • C# 8.0
  • VisualStudio 2019
  • .NET Core 3.1

実装コード

C#で記事URLを取得するには HttpClient を使用するのが一番簡単だと思います。

コードは以下の通りです。

まず sitemap.xml の内容を HttpClient で取得し、内容を XDocument でパースします。各月ごとのリンクが取れるのでそれを読みに行って各記事のURLを取得しコンソールに出力します。

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using System.Xml.Linq;
using AngleSharp.Html.Dom;
using AngleSharp.Html.Parser;

internal class AppMain
{
    private static readonly XNamespace ns = "http://www.sitemaps.org/schemas/sitemap/0.9";

    public static void Main(string[] args)
    {
        string domain = args[0]; // コマンドラインからドメイン名を指定
        string rootUrl = $"https://{domain}/sitemap.xml";

        using var client = new HttpClient();

        foreach (var sub in parseRoot(client, rootUrl))
        {
            foreach (var path in parseSub(client, sub))
            {
                Console.WriteLine(path);
                // string title = getTitle(url).Result;
                // Console.WriteLine($"{url}, {title}");
            }
        }
    }

    public static IEnumerable<string> parseRoot(HttpClient client, string url)
    {
        string body = client.GetStringAsync(url).Result;

        var xml = XDocument.Parse(body);
        var e1 = xml.Element(ns + "sitemapindex");
        var e2 = e1.Elements(ns + "sitemap");

        foreach (var e3 in e2)
        {
            yield return e3.Element(ns + "loc").Value;
        }
    }

    public static IEnumerable<string> parseSub(HttpClient client, string url)
    {
        string body = client.GetStringAsync(url).Result;

        var xml = XDocument.Parse(body);
        var e1 = xml.Element(ns + "urlset");
        var e2 = e1.Elements(ns + "url");

        foreach (var e3 in e2)
        {
            yield return e3.Element(ns + "loc").Value;
        }
    }

    // ★★★AngleSharp を利用した記事タイトルの取得
    // using AngleSharp.Html.Dom;
    // using AngleSharp.Html.Parser;
    public static async Task<string> getTitle(string url)
    {
        using var client = new HttpClient();
        using var stream = await client.GetStreamAsync(new Uri(url));
        var parser = new HtmlParser();
        IHtmlDocument doc = await parser.ParseDocumentAsync(stream);
        return doc.Title;
    }
}

実行結果

コマンドラインから以下のようにドメインを指定してプログラムを実行すると以下のように出力されます。

// ドメインを引数にアプリを実行する
> url-listup.exe takap-tech.com

// こんな感じにコンソールに出力される
> https://takap-tech.com/
> https://takap-tech.com/about
> https://takap-tech.com/entry/2020/07/08/015033
> https://takap-tech.com/entry/2020/06/22/220549
> https://takap-tech.com/entry/2020/06/20/232208
> https://takap-tech.com/entry/2020/06/18/002237
> https://takap-tech.com/entry/2020/06/11/003228
> https://takap-tech.com/entry/2020/06/05/005816
> https://takap-tech.com/entry/2020/06/05/001711
> https://takap-tech.com/entry/2020/04/30/152534
> https://takap-tech.com/entry/2020/04/28/211622