2018年11月22日

Wikiart で公開されている作品を自動でダウンロードする


絵画作品を閲覧できるサイト Wikiart から Tampermonkey を使って特定の作家の作品データをダウンロードする方法をメモメモ…



Wikiart というサイトがあります。一言でいうと芸術作品のWikipediaのようなもので、世界中のユーザーが協力しながら、古今東西さまざまな著名アーティストの作品情報を収集、データベース化し公開しているサイトです。2018年11月現在で3000人以上の芸術家たちによる250,000点以上もの作品が公開されています。単純に好きな作家の作品を鑑賞するために使われるだけでなく、膨大な数の芸術作品を利用してディープラーニングなどの機械学習を行う目的で使われることも多いと思います。

そこで、Wikiartに登録されている特定の作家の作品の画像データを自動でダウンロードし、ローカルのフォルダに保存するスクリプトを作成してみました。たぶんこういう「画像だけを延々と取得していく」処理は他の言語でもっと適しているものがあると思うのですが、今回は Tampermonkey と JavaScript だけで頑張りました。

注意

このページで紹介するスクリプトは、作品一覧から各作品のページを読み込んで画像データのURLを取得していくものです。この方法とは別に、WikiartのAPIを使ったもう少し効率の良いスクリプトも作成したので、もしこのページのスクリプトが使えなかった場合にはそちらをお試しください。

Wikiart で公開されている作品を自動でダウンロードする (API編)


というわけで、こちらが作成したスクリプトです。

// ==UserScript==
// @name         Wikiart Image Downloader
// @version      1
// @match        https://www.wikiart.org/en/*/all-works/text-list
// @grant        GM_download
// ==/UserScript==

'use strict';


// 作品のダウンロードが完了してから次の作品のダウンロードを開始するまでの時間
var interval = 2000;
// ダウンロードが失敗したあともう一度ダウンロードをやり直すまでの時間
var retryInterval = 10000;
// ダウンロードが失敗したあと再試行する回数
var maxRetry = 2;

var imageData = [];
var failedData = [];
var currentDownloadIndex = 0;
var currentRetry = 0;

(function() {
    getImageData();
    startDownload();
})();

// 作品一覧ページのDOMを読み込んで各情報を配列にブチ込む
function getImageData()
{
    const elements = document.querySelectorAll(".painting-list-text-row a[href]");
    if(!elements) return;

    for(let i=0; typeof(elements[i])!='undefined'; i++)
    {
        const artist = elements[i].getAttribute('href').match(/\/en\/(.+)\/.+/)[1];
        const paintingTitle = elements[i].textContent;
        const paintingId = elements[i].getAttribute('href').match(/\/en\/.+\/(.+)/)[1];
        const yearElement = elements[i].parentNode.querySelector("span");
        const year = yearElement ? yearElement.textContent.replace(/[^0-9]/g, "") : "";
        imageData.push({ artist: artist, year: year, paintingTitle: paintingTitle, paintingId: paintingId });
    }
}

// 作品のダウンロードを開始する
function startDownload()
{
    if(!imageData.length)
    {
        console.log("Can't find image data!");
        return;
    }

    // 最後まで完了したらダウンロードに失敗した作品をコンソールに表示して終了
    if(currentDownloadIndex < 0 || currentDownloadIndex >= imageData.length)
    {
        console.log("Done!");
        console.log("Fails: " + failedData.length);
        if(failedData.length) console.log(failedData);
        return;
    }

    const image = imageData[currentDownloadIndex];
    const altURL = "https://uploads0.wikiart.org/images/" + image.artist + "/" + image.paintingId + ".jpg"; // 画像URL取得に失敗したとき用の保険URL

    // ファイル名は "発表年 作品名.jpg"
    let filename = image.paintingTitle + ".jpg";
    if(image.year) filename = image.year + " " + filename;
    filename = filename.replace(/[\/\\?%*:|"<>]/g, ""); // 禁止文字は消去

    // 実際に個別の作品ページを見に行く→DOMを分析して画像のURLを取得
    fetch("https://www.wikiart.org/en/" + image.artist + "/" + image.paintingId)
        .then(response => {
            response.text().then(text => {
                const parser = new DOMParser();
                const htmlDoc = parser.parseFromString(text, "text/html");
                const imgElement = htmlDoc.querySelector("img[itemprop='image']");
                const imageURL = imgElement.getAttribute('src').replace(/!.+/, "");
                start(imageURL);
            })
            .catch(() => { start(altURL); })
    })
    .catch(() => { start(altURL); });

    function start(url)
    {
        console.log("Download Start!: " + image.paintingTitle + "(" + currentDownloadIndex + ")");
        download(url, filename);
    }
}

// GM_download()を使ってローカルフォルダに指定したファイルをダウンロードする
function download(url, filename)
{
    const arg = { url: url,
                  name: filename,
                  saveAs: false,
                  onerror: onError,
                  onload: onLoad,
                  ontimeout: onTimeout
                };
    GM_download(arg);
}

// 作品のダウンロードに成功したら次の作品に進む
function onLoad()
{
    console.log("Download Complete!: " + imageData[currentDownloadIndex].paintingTitle + "(" + currentDownloadIndex + ")");
    console.log("--------------------");

    currentDownloadIndex++;
    currentRetry = 0;
    setTimeout(startDownload, interval);
}

// ダウンロードに失敗したときに再試行する
function retry()
{
    currentRetry++;
    // 規定回数ダウンロードを繰り返す
    if(currentRetry <= maxRetry)
    {
        console.log("Retry! " + currentRetry);
        setTimeout(startDownload, retryInterval);
    }
    // それでもダメだった場合はあとで見つけやすいように登録しておく
    else
    {
        const index = currentDownloadIndex;
        const title = imageData[currentDownloadIndex].paintingTitle;
        const year = imageData[currentDownloadIndex].year;
        failedData.push({ index: index, title: title, year: year });

        console.log("--------------------");

        // 続行
        currentDownloadIndex++;
        currentRetry = 0;
        setTimeout(startDownload, interval);
    }
}

function onError(err)
{
    console.log("*** Error! *** " + imageData[currentDownloadIndex].paintingTitle + "(" + currentDownloadIndex + ") was not downloaded! Reason: " + err.error);
    retry();
}

function onTimeout()
{
    console.log("*** Timeout! ***" + imageData[currentDownloadIndex].paintingTitle + "(" + currentDownloadIndex + ") was not downloaded!");
    retry();
}


あくまで趣味の範囲で「作ってみた」という感じなので、参考程度に見てもらえれば嬉しいです。ちなみにこのスクリプトでは Tampermonkey の GM_download というAPIを使っているため、Tampermonkey 以外(Greasemonkeyとか)では動きません。GM_download については前回の投稿 (JavaScript Tampermonkeyでファイルを保存する)にまとめました。

使い方

  1. Tampermonkeyに上記のスクリプトを登録する
  2. 「名前を付けて保存」の確認ダイアログが表示されないよう設定変更する
    Chromeの場合であればブラウザ設定から「ダウンロード前に各ファイルの保存場所を確認する」をオフに、Tampermonkey設定から「ダウンロードのモード」を「ブラウザーAPI」にしておく(詳しくは前回の投稿を参照のこと)
  3. ダウンロードしたい作者の作品一覧ページ(/all-works/text-list)を開く
    たとえばエル・グレコの作品一覧であれば
    https://www.wikiart.org/en/el-greco/all-works/text-list
    写楽であれば https://www.wikiart.org/en/toshusai-sharaku/all-works/text-list
  4. 自動でダウンロードが始まるのでブラウザのデベロッパーツールを起動(F12)してコンソールを眺める
  5. "Download Start!"や"Download Complete!"などの表示がずらずらと流れていれば成功
  6. 完了するまでひたすら待つ
こんな感じでログが出力されていれば成功


保存先のディレクトリはブラウザで設定した場所になるので、実行する前にどこに保存されるのかしっかり確認しておいてください。

途中で一時停止する機能はありません。停止したい場合は別のページに移動するかスクリプトを無効にしてください。最初からではなく特定の番号の作品から再開させたい場合は20行目の currentDownloadIndex に任意の番号を指定してください。

保存するときのファイル名のルールを変更したい場合は66~69行目あたりを修正してください。ただしファイル名として無効な文字列が含まれているとダウンロードに失敗するので注意しましょう。

注意事項

  • スクリプトを実行する場合は自己責任でお願いします。不利益や損害が生じても責任は負えません
  • ページの読み込みと画像の取得を連続で行っていくスクリプトなので、サイト側に少なからず負荷をかけることになります。一応利用規約をざっと確認したところスクリプト使用に関しては特に記述がなかったのでおそらく大丈夫だとは思いますが、濫用して規制されても責任は負いかねます
  • ダウンロードする作品の著作権とその取扱いには十分注意してください。古い作品は基本的にパブリックドメインになっていますが、最近の作品だとまだ著作権が切れていなかったり、特定の国でのみ著作権が消滅していたりします。著作権情報はそれぞれの作品ページで必ず確認するようにしてください。まあ、Wikiというものの特性上、その画像の著作権情報の確証を得るのは非常に難しいとは思いますが…
  • 作品ごとにHTMLを読み込んで情報を取得しているため、サイトのデザインが変わったりすると使用できなくなる可能性があります
  • その他もろもろ、エラーや不具合が出て意図しない動作になることがあります。エラーなどでうまくダウンロードできない場合はAPIを使ったバージョン(Wikiart で公開されている作品を自動でダウンロードする (API編))も試してみてください

0 件のコメント:

コメントを投稿