goo blog サービス終了のお知らせ 
見出し画像

Retro-gaming and so on

実践的プログラミング入門 画像ダウンローダを作ろう


今回から2NTブログの方を本体にしようと思う。
goo blogの方は2NTからHTMLをコピーするだけ、にしておく。
じゃないと画像の移動がメンドイ事になるから、だ。

いまだにgoo blogからは「画像ダウンロード機能提供」の話は一切無い。
一体どうする気なんだろうか。
そしてgoo blogは「終了する」のに相変わらずこんなトンマな広告を管理画面に晒している。


スクリーンショット_2025-05-03_20-38-58


「画像ダウンロード」は提供しないのに、新規広告は打つ。
goo blogは最後までユーザーをバカにしてる模様だ。

ところで、「画像ダウンロード機能提供」が無い、と言う事は、あからさまにユーザーは「はてなブログ」か「アメーバブログ」の二者択一を迫られてる、って事だ。「移転先のブログ」を自由に選べる権利さえ、goo blogユーザーには無い、って話になる。
要は、「はてな」や「アメブロ」以外に行った人は、基本的に、今までアップロードした写真を「放棄」せざるを得なくなる。
この、とことん「ユーザーの自由を制限する」ってのが残念ながらgoo blogの特徴だ。
そしてこの「自由の制限」は別の問題を引き起こす。ユーザーに「現役プログラマ」や「現役エンジニア」が一切いない、と言う弊害だ。
プログラマやエンジニアは「ブログに自由を求める」。結果、goo blogには「現役プログラマ」や「現役エンジニア」が作成したブログが一切無い。驚くことなかれ、本当に0%なんだ。こんなブログ他には無い。
そしてプログラマやエンジニアの存在率が0%ってのは一体何を意味するのか。
通常、「ブログサービス側」が今回みたいに「画像ダウンロードを提供しない」とかなった場合、腕に覚えのある現役プログラマや現役エンジニアが腕をふるって、「サービス提供側が何もしないなら、俺がダウンローダを作って提供してやるよ」とか言って、フリーの画像ダウンローダを無償公開してくれたりする。一方、goo blogの場合、現役プログラマや現役エンジニアが一切いない、のでそういう事が起こらない。
詰んでる。

そこでここでは、「画像ダウンローダの作り方」を紹介しよう。「誰も画像ダウンローダを提供してくれないなら、自分で作っちゃえばいいじゃない」って事だな。
なお、最近では、このテのプログラミングにはPythonが使われるのが流行りだが、ここではそういう時流には逆らってRacketを用いて作る。是非とも手持ちのパソコンにインストールして欲しい。
また、「Lisp」と言うプログラミング言語名を聞いた事がある人は、同時に、「Lispと言うプログラミング言語にはあまり実用性が無い」と言う噂も聞いたことがあるかもしんない。
「そんな事はない」と言う実例となるだろう。Lisp系言語は実用性もある、んだ。
ここではLisp系言語では最強、と言って良いRacketを用いて「画像ダウンローダ」を作ってみよう。

コマンドラインでの写真のダウンロード方法:


ところで、通常、写真をダウンロードする、等と言った場合、ブラウザで表示された写真をマウスで右クリックして・・・と言った操作になる。
しかし、その「右クリックで操作して・・・」ってのをプログラミングで定式化するのは非常に困難だ。
一方、DOS窓でも実は写真自体はダウンロード可能だ。Windows 10以降ではcurl.exeと言われるダウンローダが標準装備されている(※1)。
DOS窓でのcurlのコマンドラインの書式は次のようになっている。

curl.exe --ssl-no-revoke -L 写真のアドレス -o ダウンロードパス(ファイル名)

例えば、https://blog-imgs-159.fc2.com/u/r/a/urapicpic/20241226115426d12.jpgと言う写真をWindowsのピクチャフォルダにダウンロードしたいとしよう。その場合は次のようにコマンドラインで入力して、リターンキーを叩けばいい。

curl.exe --ssl-no-revoke -L https://blog-imgs-159.fc2.com/u/r/a/urapicpic/20241226115426d12.jpg -o "C:\Users\PCUSER\pictures\瀬戸環奈.jpg"

瀬戸環奈、っつーのは現在(2025年5月2日現在)でのFANZAの一番人気のAV女優だが(謎



コマンドラインを実行し終わればピクチャフォルダに画像がダウンロードされてる筈だ。
curl.exeにテキトーに、自分のブログの写真のアドレスでも貼っつけて試してみて欲しい。
今回の「画像ダウンロード」プログラムはこのcurl.exeを利用して作る。

RacketからDOSにコマンドを送り込む関数をsystemと言う。

;; systemの書式
(system コマンド文字列)
コマンド文字列を生成する関数を次のようにして定義する。

;; DOSコマンド生成関数
(define (command url p)
 (format "curl.exe --ssl-no-revoke -L \"~a\" -o \"~a\"" url (path->string (build-path p (last (string-split url "/"))))))

format関数は書式文字列と呼ばれるモノを扱う関数で、「文字列の穴」を後続の引数で埋めて任意の文字列を生成する。~aがその「穴」を意味し、これが書式指定子と呼ばれるモノだ。command関数の引数urlは写真のアドレスを意図し、引数pはダウンロードフォルダのパスを意図している。(path->string(build-path p (last (string-split url "/"))))は生成したパスを一旦文字列に直し、結果~aにはめ込んでいる。

> (command "https://blog-imgs-159.fc2.com/u/r/a/urapicpic/20241226115426d12.jpg" "C:\\Users\\PCUSER\\pictures\\瀬戸環奈.jpg")
"curl.exe --ssl-no-revoke -L \"https://blog-imgs-159.fc2.com/u/r/a/urapicpic/20241226115426d12.jpg\" -o \"C:\\Users\\PCUSER\\pictures\\瀬戸環奈.jpg\\20241226115426d12.jpg\""



goo blogの写真名の謎:


ところで、我々にとって、写真の一括ダウンロードが何故難しいのか。
当然、まぁ、goo blogが本来なら提供すべき機能なのは確かなんだが、要は「記事」と「写真」が関係ないフォルダに置かれてるのが大きな理由だ。
そして、写真名が何だか良く分からんモノに置き換わっている。例えばhttps://blogimg.goo.ne.jp/user_image/5f/56/b41c9977a716cc317b68a89949e74a8d.jpgのように、だ。「b41c9977a716cc317b68a89949e74a8d」とか言われても意味はサッパリだろう。
この「写真名」は数値とアルファベットが混在しているが、良く見てみるとf以降の文字は使われていない。実はこれ、十六進数なんだ。
これは恐らくハッシュ値だ。

以前、ハッシュテーブルの話を書いたが、原理的にはハッシュ関数と言われるモノを使って、写真のタイトルから特定の十六進数を作り出している。理由は、貴方が持ってる写真を、goo blogの外部から「名前で検索」させない為、だ。
ある種のセキュリティ目的だな。
このハッシュ値から元の「写真のタイトル」を復元するのは結構難しい。また、使ったハッシュ関数も公開されていない。
困るのは、一括ダウンロード目的だと、

  1.  記事が格納されているフォルダと写真が格納されているフォルダは特に関係がない
  2.  写真の十六進数からは元の写真の名前が想像出来ない


と言う2つの理由によって、「どんな写真があるか」を想像してアドレスを特定出来ない事だ。
これはなかなか厄介だ。

引っ越しデータを使って写真のアドレスを特定する:


と言うわけで、ブログの外側から「写真を特定する」のは結構メンド臭い、って事になる。
そこで、goo blogからダウンロードした「引っ越しデータ」を利用する事にしよう。ここにはブログで使われた全写真のアドレス情報が記述されている。
以前書いたが、「引っ越しデータ」はMovable Type形式と言われるフォーマットで記述されたテキストファイルだ。
その仕様は、

  • AUTHOR: ブログ運営者名
  • TITLE: 記事のタイトル
  • DATE: 投稿時間
  • PRIMARY CATEGORY: カテゴリー名
  • STATUS: 公開(Publish) / 下書き(Draft)
  • ALLOW COMMENTS: コメント受付 有効(1) / 無効(0)
  • CONVERT BREAKS: 自動改行 有効(default) / 無効(0)
  • 区切り(-----): ハイフン5つ
  • BODY: 記事本文
  • 区切り(-----): ハイフン5つ
  • 区切り(--------): ハイフン8つ

と言う順番で記述されてる「らしい」。・・・少なくとも、僕のブログの「引っ越しデータ」を調べる限り、この内容で、この順番で記載されている。
単純に言うと、goo blogでの一つの記事はBODY部分にHTML(の一部)情報として記述されていて、写真のアドレスもそこに含まれている。
そして、そこから写真のアドレスを抜いてくれば、上で書いたcurl.exeでダウンロードが可能だ、っちゅーこっちゃ。
考え方自体は簡単だろ?
つまり、全体としては「写真ダウンローダ」は「引っ越しデータ」ファイルから一行一行情報を調べていって、BODYを見つけた時にそこから、(HTMLでの)imgタグを見つけ出し、その中からsrc属性を探し、そこで結び付けられた写真のアドレスを引っこ抜いてリストへ纏める、と。そして上のcurl.exeのコマンドラインの書式にそれら写真のアドレスと自分のPCのダウンロード先を指定し、順次ダウンロードしていけばいい、って事だ。
とまぁ、あからさまに行順の逐次実行のプログラムをRacketで組めば一丁上がり、ってわけだ(※2)。
ただ、それだけじゃちと芸が無いんで、もうひと工夫、ふた工夫加えよう。

ブログの記事毎にフォルダを作ろう:


上で書いた通り、単純には、「引っ越しデータ」から写真の全アドレスを抜けば、そのままcurl.exeを使って一括ダウンロード自体は可能だが、一方、可能だけれど「整理されてない」状態でダウンロードしたら、特定の写真を探したりするのが困難になる。
何つったって、上にも書いたけど、goo blogでは「写真の名前がハッシュ化」されている。人間の目で読んでも意味がある「名前」になっていない。これら相手に「整理」とか言われても困っちゃうだろう。
そこで、だ。幸いな事にブログの記事にはタイトルが付けられていて、そもそも写真はブログに掲載される為にアップロードされたわけだ。
要は「ブログの記事毎に」纏めておけば整理はしやすい、って事になる。皆、一記事に何枚写真を貼っつけるのか知らないが、そこまで一記事が写真塗れ、って事もねぇだろう。大体、写真を貼っつけるには上で書いたようなimgタグ記述が必要だし、goo blogだと字数制限がある。つまり無制限に写真をアップロードして記事内に組み込めます、ってわけじゃないんだ(※3)。
と言うわけで、ブログの記事毎に写真を纏めておけば、記事名から参照はしやすいだろう。
加えると、僕みたいにテキトーなヤツは、「同タイトルで複数記事を書く」って事をやっている。
そこで「タイトル」フォルダの下に「投稿日時」フォルダを作ってそこにブログの記事で使われた写真をダウンロードする。
「投稿日時」は原則ユニークだ。と言うのも、予約投稿をしなければ、「完全に同じ(分数/秒数を含む)投稿日時」にはならない。また、2つ以上の記事を「全く同じタイミングで」投稿する意味も通常はない。
結果、投稿日時は「ID」としては極めて優秀だ、と言う事になる。
また、「投稿日時」フォルダの下に「EyeCatch」フォルダも作成する。
EyeCatchはgoo blogの使用テンプレートにも依るが、記事の見出しに付けられる画像の事だ。

screenshot202505031035.png
EyeCatch画像の例。

MT形式のファイルだと、BODY部分でEyeCatch画像は一番最初に記述されている。
つまり、「画像アドレス」のリストを作成した際に、一番最初の写真はEyeCatch、残りは記事本文で使われた画像、って事になる。
従って、画像アドレスのリストの最初の画像だけは「EyeCatch」フォルダにダウンロードして、残りは「投稿日時」フォルダ直下へとダウンロードすれば事足りるわけだ。
なお、Racketでは「フォルダ作成」関数はmake-directoryとなる。

;; make-directoryの書式
(make-directory パス)

パス(path)ってのはWindowsだと、Cドライブから始まった、目的のアイテムまでの経路の事だ。
例えば、今回、画像ダウンロード先のフォルダはデフォルトだと「ピクチャ」傘下の「goo_blog」フォルダにしよう、と思っている。
従って、パスは

C:\Users\PCUSER\pictures\goo_blog

となる(※4)。
Racketでそこにフォルダを作る際には

(make-directory (string->path "C:\\Users\\PCUSER\\pictures\\goo_blog"))

が基本コマンド、となる(※5)。string->pathはWindowsでパス名を扱いやすくする為に、単なる文字列をパスオブジェクトへと変換する関数だが、まぁ、その辺はいいだろう。
また、フォルダを作成する際には、既に同名のフォルダがあるかどうか、安全の為に調べていた方がいい。それをチェックする関数をdirectory-exists?と言う。
以下が「保険を含めての」make-directoryの使い方だ。

;; C:\Users\PCUSER\pictures\goo_blogと言うフォルダの作り方
(let ((p (string->path "C:\\Users\\PCUSER\\pictures\\goo_blog")))
 (unless (directory-exists? p)
  (make-directory p)))

これが「同名フォルダが既に存在した場合も考慮した」記述パターンになる。要は同名フォルダが既にあった場合、フォルダ作成はしない。
letは次のような意味だ。

スクリーンショット_2025-05-03_11-35-24

要は、パスなんかの長ったらしいモノを何度も書いてらんないんで、それを変数pと束縛している。
また、unlessは次のような意味だ。

スクリーンショット_2025-05-03_11-39-38
要は単に「指定されたパスにフォルダが無い時に限ってフォルダを作成せよ」って言ってるだけ、だ。何も難しい事はない。

なお、最初に次のようにパスとダウンロードフォルダ名の設定をしておく。

;; パス設定
(define *base*
 (path->directory-path
 ;; 以下は変更してO.K.
 ;; 特に、PCUSERはWindowsのユーザー名に書き換える事
 "C:\\Users\\PCUSER\\pictures"))

;; デフォルトフォルダ名
(define *sub*
 (path->directory-path
 ;; 以下は変更してO.K.
 "goo_blog"))

Windowsのファイル名の「お約束」と使用ライブラリ:


ところで、「ブログ記事のタイトル」及び「投稿日時」からフォルダを作る際に気をつけておかなければならない事がある。
Windowsのフォルダの場合、「名付けで使ってはいけない文字」がいくつかあるから、だ。
「タイトル」及び「投稿日時」にそういう文字が含まれてる場合、ちとややこしい事になる。
例えば「投稿日時」はMT形式ファイル内では例えば「11/11/2020 22:42:07」と記録されているが、フォルダ名の一部としては"/"や":"は使えない。
また、全角の空白文字は「使える」けれどもあまり望ましくない。
そんなわけで、「タイトル」及び「投稿日時」を利用してフォルダ名を作る際に、そのテの「禁止文字」を削除した方がいいわけだ。

スクリーンショット_2025-05-03_14-59-00

SRFI-13string-deleteは次のようにして、文字セットに含まれる文字を文字列から削除する。

(string-delete 文字セット 文字列)

ここで、文字セットはSRFI-14char-set-adjoinchar-set:whitespaceを利用して作り出す。

;; Windowsのフォルダ名で使えない文字 + 空白文字の文字セット定義
(define cs
 (char-set-adjoin char-set:whitespace
       #\\ #\/ #\: #\* #\? #\" #\< #\> #\|))

char-set:whitespaceは空白文字集合だ。それにWindowsのフォルダ名で使えない文字をchar-set-adjoinで追加して文字列からそれら文字を削除する。

> (string-delete cs "11/11/2020 22:42:07")
"11112020224207"

フォルダ名に付いての問題はこれで終わりだ。

他には、今回のプログラムで使うライブラリをインストールする。
DOS窓から次のコマンドを実行してhtml-parsingsxmlと言うRacket外部ライブラリをダウンロード/インストールする。

raco pkg install html-parsing sxml

自分でHTMLを解析するライブラリを書くのは面倒くさいが(※6)、html-parsingはHTMLで書かれた文字列をLispで扱いやすいS式へと変換してくれる。

スクリーンショット_2025-05-03_16-13-11

何度か書いてるが、極論、プログラミングに必要なデータ形式はS式だけ、なんだ。JSONもXMLもMT形式も、言っちゃえばHTMLさえ要らない。S式、ってデータ形式さえあれば本当は他に何も要らないんだ。
貴方がLispプログラミングを覚えれば、そう言う真理に気づく事になる・・・Lispを知らない人はデータ形式の劣った「再発明」ばっか行う事になるんだ・・・・・・・・。
SXMLと言うのは(HTMLを含む)XMLのS式版だ。sxmlライブラリはこのSXML相手に自在に目標のデータを抜いてくる・・・そうだな、HTML情報から写真のアドレスを抜いてくるのがこのライブラリの役割だ。ちとクセはあるが、HTMLが構成する木構造から自在に目的の要素を抜いてくれる。

画像ダウンロードプログラムの冒頭で、これらライブラリを使うように宣言しておく。

(require html-parsing
    sxml
    (only-in srfi/13 string-delete)
    (only-in srfi/14 char-set-adjoin char-set:whitespace))


画像ダウンロードプログラムのロジック:


では、Movable Typeの書式に合わせたダウンローダのロジックを改めて整理しよう。

  1.  引っ越しデータファイルを開く
  2.  ファイルの末尾に到達するまで次を繰り返す。
    •  ファイルを一行づつ読む
      1.  先頭がAUTHORの場合はスキップして次の処理へ
      2.  先頭がTITLEの時はタイトルフォルダを作成してパスを更新、次の処理へ
      3.  先頭がDATEの時はDATEフォルダを作成してパスを更新、次の処理へ
      4.  先頭がPRIMARY CATEGORY場合はスキップして次の処理へ
      5.  先頭がSTATUSの場合はスキップして次の処理へ
      6.  先頭がALLOW COMMENTSの場合はスキップして次の処理へ
      7.  先頭がCONVERT BREAKSの場合はスキップして次の処理へ
      8.  先頭が-----の場合はスキップして次の処理へ
      9.  先頭がBODYの時はスキップして次の処理へ(残りは実は無い)
      10.  先頭が--------の場合、一つの記事が終了してるんで、EyeCatchフォルダを作り、HTMLを解析して写真アドレスのリストを作成、所定のフォルダへ写真をダウンロードし、htmlを掃除、パスを初期値へ戻して次の処理へ
      11. 他はHTML情報なんで、htmlにその情報を積み、次の処理へ

実際、MT形式ファイルに対して殆どのケースでは「スキップする」になる。
上のロジックをそのまま書けばこういうコードになる。これが写真ダウンローダの本体だ。
重要構文及び関数は以下の5つだ。

スクリーンショット_2025-05-04_01-10-18
繰り返し構文

スクリーンショット_2025-05-04_01-16-52
ファイルの読み書き

スクリーンショット_2025-05-04_01-33-01
ファイル末尾チェック

スクリーンショット_2025-05-04_01-21-19
場合分け

スクリーンショット_2025-05-04_01-45-21
ファイルからの一行読み込み



他に重要な構文としては例外処理があるが、それはここに書いてある。

あとは、いくつか使用関数のちょっとした説明を書いておこう(※7)。

  • build-path: パスオブジェクト/文字列を連結して新しいパスを生成する
  • string-split: 文字列を指定した文字列で分割し、リストとして返す
  • string-trim:  文字列の先頭、及び末尾に空白文字、あるいは改行文字があったら削除する
  • string-join: 文字列のリストを受け取り、それを一つの文字列へと結合する。

プログラムの殆どが、一種の文字列操作、となる。

caseによる「場合分け」で漏れたケースが「HTML情報」、つまり、ブログ記事本文、となる。それをリストである変数htmlに随時追加(cons)していき、追加し終わったら、string-joinで連結したHTMLをhtml->xexpで解析し、それからsxpathでimgタグを探し、src属性から写真のアドレスを抜き出す。
あとはRacketからcurl.exeを呼び出して、所定のフォルダへと写真をダウンロードすれば終了、だ。

残りはmodule+を使用してエントリポイント(main関数)を作るだけ、だ。

;; エントリポイント
(module+ main
 (let ((p (build-path *base* *sub*)))
  (unless (directory-exists? p)
   (make-directory p)))
 (downloader (vector-ref (current-command-line-arguments) 0)))

基礎ダウンロードフォルダ(ピクチャフォルダ内のgoo_blogフォルダ)を作成して、あとはプログラム本体を呼び出す。
current-command-line-argumentsはDOS窓から引数ベクタを受け取る。引数は「引っ越しデータ」を想定している(export_blog_1.txt等)。複数引数を受け取れるが、必要なのは先頭情報「だけ」なんで、vector-refで0番目の情報以外は全て捨てる。
これでプログラムは完成、となる。

あとはWindowsでお馴染みの(と言うかMS-DOSパソコンでお馴染み「だった」?)BATファイルを作っておこう。
これを作っておけばDOS窓を画像ダウンローダのインターフェースとして使える。

@echo off
racket photo_downloader.rkt %*
exit /b

これは

「出力表示を控え、photo_downloader.rktを引数付きで呼び出し、Racketにそれを渡して実行しろ。終わったらそのまま閉じろ。」

と言ったような意味になる。

プログラムは結果、全体で2ファイルになる。


プログラミングに明るくない人は、これら2ファイルをどっかテキトーなフォルダにダウンロードし、名前をキチンと付けて(photo_downloader.某)、一番簡単には、「引っ越しデータ」を解凍したモノ(export_blog_1.txt等)も同じフォルダへと入れる。
あとはDOS窓から、

photo_downloader.bat export_blog_1.txt

とでも打てば、写真のダウンロードが始まるだろう。
回線等の環境にも依るが、試してみたところ、光回線で、大体goo blogが言ってる「1GB」容量で、ダウンロード時間は1時間程度、ってトコだ。僕の写真は2.235GBってトコなんで、3時間せずに全写真のダウンロードは終了してる。と思う(笑)。
確認が面倒なんだよ(そもそも、goo blogでの「写真検索」が使い物にならん・笑)。

とまぁ、今回の記事は以上で終了だ。
一応お約束なんで言っておく。「このプログラムは完全無保証なんで、このプログラム使用に於ける損害には一切責任を負いません」と。
また、「このプログラムはコピーレフト品なんで、改造、再配布共にご自由に」とも。

※1: curlは元々、UNIX系OSのプログラムだ。もちろん、写真以外もダウンロード出来る。
また、Windows10以前のWindowsを使っていてもダウンロード/インストールは可能だ。

※2: このロジックは別にLisp系言語だけじゃなく、どのプログラミング言語でも同じだ。
興味がある人は、自分でVisual BasicやPythonなんかを使って書いてみてもいいだろう。
俺は今回はやらんが(笑)。

※3: 当然HTMLは文字列なんで、写真貼り付けで使用されるimgタグの文字数は、写真のアドレス(かなり長い)を考慮すると結構大きい。

※4: PCUSERはWindows上でのデフォルトアカウント名。通常は貴方が設定したアカウント名が入っているだろうから、そこは必要なら変えるべきだ。

※5: バックスラッシュ/円記号(\)が2個に増えてる事に違和感を覚えるだろう。
これは、プログラミング言語だと通常、\と言うのは特殊な文字(エスケープシーケンス)を作成する際に使われ、単独では使用出来ないから、だ。
結果、\をそのまま使いたい場合はこのように2個掛けにする。

※6: Pythonで有名なHTMLの構文解析器にbeautiful soupがある。

※7: carmapcadrcons等のLisp系言語での基礎機能の説明は省いてるが、それらに付いてはScheme(Lispの一種)の仕様書を参考にして欲しい。
ここでは、Racket特有の機能に絞って解説している。
  • Xでシェアする
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

最近の「プログラミング」カテゴリーもっと見る

最近の記事
バックナンバー
人気記事