読者です 読者をやめる 読者になる 読者になる

clock-up-blog

go-mi-tech

2ちゃんねるの板一覧の属性を取得するクローラー (C#実装)

C# ボット クローラー AdventCalendar スクレイピング

ボット・クローラー Advent Calendar 2016 2日目の記事です。


2ちゃんねるは相変わらず現役で動いていらっしゃるようなのですが、板により微妙に挙動(パラメータ設定)が違うっぽい。

{板のURL}/SETTING.TXT というファイルの内容により板の属性が取得できることを教えてもらったので、とりあえず数ある2ちゃんねるの板のどれが UNICODE 対応しているのか調べてみることにした。

UNICODE対応はとても大事である。🍣を表示できるか否かがここに懸かっている)

作ったプログラム

実装は C#
C# はクロール実装においてもとても楽だ。

ソースコードは以下に公開してある。

得られた結果

以下のように「"unicode": "SUPPORTED"」となっている要素が UNICODE 対応の板である。

"http://hanabi.2ch.net/hawaii/": {
  "url": "http://hanabi.2ch.net/hawaii/",
  "name": "ハワイ州",
  "unicode": "SUPPORTED"
},

ソース解説

ソース全体は上に挙げたリポジトリを見てもらうとして、かいつまんで処理を解説する。

板リストHTLMの取得

まずは板一覧を取得するために2ちゃんねるのメニューページから全リンクを取得する。

http://menu.2ch.net/bbsmenu.html に全板へのリンクが張ってあるのだが、注意点としてこのページは昔ながらの Shift_JIS であることに留意したい。

string menuUrl = "http://menu.2ch.net/bbsmenu.html";
string menuHtml = await HtmlGetter.GetHtml(menuUrl, "shift_jis");

ここで登場する HtmlGetter.GetHtml は自作関数だが、第2引数を省略すれば文字コードは自動判定、明示的に指定すればその文字コードでデコードを行うようになっている。

板リストHTMLから板URLを抽出する

using CsQuery;
....
new CQ(menuHtml).Find("a").Each(_a => // ← jQueryっぽいところ
{
    var a = new CQ(_a);
    var url = a.Attr("href"); // ← jQueryっぽいところ
    var name = a.Text().Trim(); // ← jQueryっぽいところ
    if (Regex.IsMatch(url, "^http://[^/]+/[^/]+/$")) // 板っぽいURLだったら
    {
        Task.Run(async () =>
        {
            await ScrapeBoard(url, name); // 板情報のスクレイピングに進む
        }).Wait();
    }
});

取得できた HTML の DOM 分析する方法はいくつかあるが、今回は CsQuery という NuGet パッケージを用いている。これを使うと jQuery 的な構文で要素解析を行うことができる。

CsQuery の導入は簡単で、Package Manager Console にて

Install-Package CsQuery

を実行するだけで導入できる。(これで CQ クラスが使えるようになる。

SETTING.TXT の解析

各板の SETTING.TXT を解析する。これの文字コードShift_JIS であることに注意。

async Task ScrapeBoard(string boardUrl, string boardName)
{
    // 設定テキスト読み込み -> settings
    Dictionary<string, string> settings = new Dictionary<string, string>();
    string settingUrl = boardUrl + "SETTING.TXT";
    string settingText = await HtmlGetter.GetHtml(settingUrl, "shift_jis");
    settingText = settingText.Replace("\r\n", "\n");
    var lines = settingText.Split('\n');
    foreach(var line in lines)
    {
        var kv = line.Split('=');
        if (kv.Length < 2) continue;
        settings[kv[0]] = kv[1];
    }

    // オブジェクト構築
    var boardInfo = new Models.BoardInfo
    {
        url = boardUrl,
        name = boardName
    };
    if(settings.ContainsKey("BBS_UNICODE") && settings["BBS_UNICODE"] == "pass")
    {
        boardInfo.unicode = "SUPPORTED";
    }
    m_boards[boardUrl] = boardInfo;
}

とりあえず収集できた情報はどんどん m_boards に詰めていく。

JSON出力

m_boards に溜まった情報を最終的に JSON 形式で出力する。
これも簡単で NuGet の Json.NET を用いれば一発である。
(インストール方法は先ほどの CsQuery と同様に Install-Package Newtonsoft.Json みたいな感じ)

using Newtonsoft.Json;
....
string json = JsonConvert.SerializeObject(m_boards, Formatting.Indented);

これで一発である。
result.json のできあがり。

おしまい

C# でモノを組んでいく場合、豊富な NuGet パッケージ(拡張)があるのでなかなか楽できることが多いです。

個人的には LL 系言語でクローラ作るよりもむしろ楽(C# ならインテリセンス効くしブレークポイントも張りやすい等等)に感じます。

});