2013年2月21日木曜日

裏サンデーの画像をダウンロード

※) 2014年4月25日追記。

今の裏サンデー用のツールはこちら。

====================================

1ヶ月ほど前?から裏サンデーのサーバがやたらと重くなっています。

その対策のためでしょうか? 携帯用の画像とpc用の画像、2つが用意されるようになりました。 javascriptで場合分けされています。

私はできるだけjavascriptは無効にするようにしています。 youtubeとか、有名サイトでも普段は無効にしていて見るときだけ許可です。 面倒に思えるかもしれませんが、それに慣れてしまってるんで不便は感じません。 思いもよらないところで動画が再生されたりトラッキングされたりというリスクは減らせるかなぁ、と思っていたり。 あまり意味は無いかもしれないですけどね。

有用なサイトでjavascriptが使われていて、その有効/無効が面倒なのは自分の方針なんでいいんですよ。 でも、javascriptが不要なはずのサイトでjavascriptが無ければ表示されないようになっていたらちょっとイラっとしますね。 裏サンデーの場合は、回線のためとは分かっていてもちょっとネガティブな感想を持ちました。

裏サンデーならjavascriptを有効にしてもいいとは思ったんですが、ページのソースを見たら携帯用とpc用の場合分けは単純でした。 img要素のリンク先をif文で判定しているだけです。 リンク先が普通に見えているので画像をダウンロードするツールとかは簡単に作れそう。 というわけで作ってみました。 マンガの「第??話」のurlをクリップボードにコピーした状態で読み込みボタンを押すとページの画像がダウンロードできます。

裏サンデーの方のアクセス集中対策も場当たり的な感じがするのでいつまで使えるかわかりませんが、ツールを作るのにも時間がかかってないので、サイトの方が変更されたらツールのコードを書き換えるという方針です。 htmlソースの解析とかメチャクチャ適当だけど気にしない。 テストとかはまともにしていません。 短時間で作った、ザックリ手抜きツールですな。

Visual Studio Express 2012 for Windows DesktopのC#で作成。 wpfプロジェクトです。

MainWindow.xamlのコードはこうなってます。

<Window x:Class="GetUrasundayImage.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <DockPanel>
        <Button DockPanel.Dock="Top" Name="button" Click="ButtonClickHandler" ToolTip="クリップボードに掲載ページのurlをコピーした状態で押すこと">読み込み</Button>
        <ListBox Name="listBox"/>
    </DockPanel>
</Window>

MainWindow.xaml.csのコードはこんな感じです。

// 参照System.Net.Httpを追加

using System;
using System.IO;
using System.Net.Http;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows;

namespace GetUrasundayImage
{
    public partial class MainWindow : Window
    {
        private const int RETRY_COUNT_MAX = 3;
        private const string SAVE_DIR = 保存先のディレクトリを書く;

        private HttpClient _httpClient;

        public MainWindow()
        {
            InitializeComponent();

            _httpClient = new HttpClient();
        }

        private async void ButtonClickHandler(object sender, RoutedEventArgs e)
        {
            button.IsEnabled = false;

            try
            {
                var url = Clipboard.GetText();
                Log("url  =>  " + url);
                var page = Encoding.UTF8.GetString(await Get(url));

                await SaveImages(url, page);

                Log("♪ 全取得成功");
            }
            catch (Exception exc)
            {
                Log("× 取得失敗で途中終了 ... " + exc.Message);
            }
            finally
            {
                button.IsEnabled = true;
            }
        }

        private async Task SaveImages(string url, string page)
        {
            var regexServerDir = new Regex("http://urasunday.com/(?<serverDir>.*?)/.*", RegexOptions.IgnoreCase | RegexOptions.Singleline);
            var serverDir = regexServerDir.Match(url).Groups["serverDir"].Value;

            var regexTitle = new Regex(@"<title>\s*裏サンデー\s*\|\s*(?<title>\S*)\s*\|\s*(?<number>\S*)\s*</title>", RegexOptions.IgnoreCase | RegexOptions.Singleline);
            var titleMatcher = regexTitle.Match(page);
            var title = titleMatcher.Groups["title"].Value;
            var number = titleMatcher.Groups["number"].Value;

            string dir = SAVE_DIR + title + '_' + number + '\\';
            Directory.CreateDirectory(dir);

            var regexAnchor = new Regex("src=\"\\.\\./\\.\\./(?<imageUrl>comic/" + serverDir + "/pc/.*?jpg)\"", RegexOptions.IgnoreCase | RegexOptions.Singleline);

            int i = 0;
            for (var m = regexAnchor.Match(page); m.Success; m = m.NextMatch())
            {
                string imageUrl = "http://urasunday.com/" + m.Groups["imageUrl"].Value;
                var imageData = await Get(imageUrl);
                File.WriteAllBytes(dir + i.ToString("D3") + ".jpg", imageData);
                i++;
            }
        }

        private async Task<byte[]> Get(string url)
        {
            for (int i = 0; i < RETRY_COUNT_MAX; i++)
            {
                try
                {
                    var res = await _httpClient.GetByteArrayAsync(url);
                    Log("データ取得成功  ==>  " + url);
                    return res;
                }
                catch (Exception exc)
                {
                    Log("データ取得失敗(" + (i + 1) + "回目)");
                    Log("    url  =>  " + url);
                    Log("    msg  =>  " + exc.Message);
                }
            }

            return null;
        }

        private void Log(string message)
        {
            var t = DateTime.Now;
            listBox.Items.Add(t.ToString("HH:mm ss\"'\"fff") + "  " + message);
        }
    }
}