PythonでPixivのランキング上位画像を一括取得したいともがくお話

#はじめに
本投稿はプログラミング経験が非常に浅いものが作っておりますゆえ、
かなりお見苦しいところがございます。ご自愛ください。

また少々宣伝になってしまいますが、
本投稿は前投稿の続きになっておりますので、
もしよろしければ御覧ください。

#1.制作の経緯
普段から [pixiv](https://www.pixiv.net/ "https://www.pixiv.net/")を利用して、様々な素晴らしい作品を眺めているのですが、
ある日ランキングの上位100作品ぐらい一気に見てみたいと思ったわけです。
（普通ないとは思いますが）
前回の経験を活かし、ランキングの作品を取得し眺めようと思いました。
#2.開発環境
OS：Windows10 64bit　1909
CPU/Memory:i5-7200U/8GB
python 3.6.8 64bit版
エディター（？）：VSCode version 1.41.0
#3.pythonのインストール
ここではインストール済みと考えスキップします。
<details><summary>方法</summary><div>
[Python公式のwindows用ダウンロードページ](https://www.python.org/downloads/windows/ "hhttps://www.python.org/downloads/")から、
お好みのバージョンをインストーラーを利用して、入れる。
私は、よくわからなかったので、適当に選びました。[Download Windows x86-64 web-based installer](https://www.python.org/ftp/python/3.6.8/python-3.6.8-amd64-webinstall.exe "https://www.python.org/ftp/python/3.6.8/python-3.6.8-amd64-webinstall.exe")
pathは通す設定にして、あとはインストーラーを任せでしたので、
必要な方は、ググってください。
インストーラーが正常に実行できればOK
</div></details>
#注意事項
公式のAPIではありませんので、利用の際は十分に注意を払ってください。
また、下記で出てくるコードは、Python歴皆無なものが作ったため、
こちらの想定していない動作がされた場合、当然のようにエラーが出ます。
修正したほうがいいところがあれば、ぜひコメントいただけると幸いです。
#4.制作編
前回準備したとこは省きますが見たい方は確認してくださいね。
<details><summary>方法</summary><div>
1：pipでPixivpyをインストール

```py
pip install pixivpy
```

2.作業用フォルダを作って、VSCodeでそのフォルダを開く。

</div></detail>

#1.ファイルの作成。いや作成しない。
私はふとこんなことを思いつきました。
**前作ったやつとセットにできるのでは？**
前回のファイルに書き加えてしまえば、管理するファイルも一個でよくなる。
**が、**
機能をどう使い分ければいいんだこれ？

とりあえず現状把握すると、下のコードになってるのですが、
<details><summary>現状のコード</summary><div>

```py 
from pixivpy3 import PixivAPI
from pixivpy3 import AppPixivAPI
from pixivpy3 import PixivError
from time import sleep
import json
import os

#folder check
if not os.path.exists("./pixiv_images"):
    os.mkdir("./pixiv_images")
if not os.path.exists("./pixiv_images/bookmark"):
    os.mkdir("./pixiv_images/bookmark")
#api login
mail=input("APIログインのために\npixivのmailアドレスを入力:")
password=input("pixivのパスワードを入力")
aapi=AppPixivAPI()
aapi.login("Mail", "Pass")
#main
bookmark_count=input("your bookmark count number please.\n only public bookmark:")
bookmark_count=int(bookmark_count)//30+1
myself_user_id=int(aapi.user_id)
try:
    json_user_collect = aapi.user_bookmarks_illust(myself_user_id, restrict=bookmark_type)
except:
    print("error")
    sleep(10)
    quit()
while bookmark_count > 0:
    print("#"+str(bookmark_count))
    num=len(json_user_collect.illusts)
    bookmark_count=bookmark_count-1
    for illust in json_user_collect.illusts[:num]:
        writer=illust.user.name.replace("/","-")
        if not os.path.exists("./pixiv_images/bookmark/"+writer):
            os.mkdir("./pixiv_images/bookmark/"+writer)
        savepath="./pixiv_images/bookmark/"+writer
        try:
            aapi.download(illust.image_urls.large,path=savepath,name=str(illust.title.replace("/","-"))+".png")
        except:
            print("error")
            print("#"+str(writer)+":"+str(illust.title))
            sleep(1)
            continue
        print("#"+str(writer)+":"+str(illust.title))
        sleep(1)
    if bookmark_count>0:
        next_url=json_user_collect.next_url
        next_qs=aapi.parse_qs(next_url)
        json_user_collect=aapi.user_bookmarks_illust(**next_qs)
print("終了しました。")
```

修繕後のファイルです。
</div></details>

ここに加えていくにはどうすればいいのでしょう。
プログラミング経験がある方なら知っての通り、
普通に考えて、下にコードを書き足すだけでは機能しません。

そこで、私はある結論に至りました。

**while文書けばよくね**（初心者の発言です。こいつ何いってんだ状態です。）

とりあえずまずは、ランキング部分の実装です。

#2.ファイルは結局作るんです

while文作戦はうまくいくかわからないのでとりあえずファイルを作成します。
ファイル名は”**rankingかっこかり**”とでもしておきます。
さて、ここから書いていきますが、前回同様初心者なので、
とりあえず思いつきで書いていきます。

#3.各種import・ログイン処理
この部分は、前回のものを流用していきます。
まずはimport部分

```py

from pixivpy3 import PixivAPI
from pixivpy3 import AppPixivAPI
from pixivpy3 import PixivError
from time import sleep
import json
import os
import datetime
import time

```

pixivpy以外は最初から入っているパッケージなので問題ないはずです。
次にapiのログイン処理部分を書いておきます。

```py

mail="your mail "
password="password"
api = PixivAPI()
aapi=AppPixivAPI()
aapi.login(mail,password)
api.login(mail,password)
```

こんな感じですね。
次はフォルダの確認ですが、前回のコードでは、

```py

if not os.path.exists("./pixiv_images"):
    os.mkdir("./pixiv_images")
if not os.path.exists("./pixiv_images/bookmark"):
    os.mkdir("./pixiv_images/bookmark")
```

と書いてあったので、つなげることを前提にこの処理に付け加えることにしましょう。

```py
if not os.path.exists("./pixiv_images"):
    os.mkdir("./pixiv_images")
if not os.path.exists("./pixiv_images/bookmark"):
    os.mkdir("./pixiv_images/bookmark")
if not os.path.exists("./pixiv_images/ranking"):
    os.mkdir("./pixiv_images/ranking")
```

こんなふうですね。
変わったことはしてないはずです。

#4.ランキングの種類を分けたい。
pixivにはランキングが複数個あり、
**デイリー/ウィークリー/マンスリー/ルーキー/オリジナル/男子に人気/女子に人気**(あとR18コンテンツもあるらしいですが...)
のようになっていて、
今回のAPIには引数として、選べるようなので選ぶ仕組みも作ってしまいます。
APIのドキュメントより

```py

	# 排行榜/过去排行榜
	# mode:
	#   daily - 每日
	#   weekly - 每周
	#   monthly - 每月
	#   male - 男性热门
	#   female - 女性热门
	#   original - 原创
	#   rookie - Rookie
	#   daily_r18 - R18每日
	#   weekly_r18 - R18每周
	#   male_r18
	#   female_r18
	#   r18g
	# page: 1-n
	# date: '2015-04-01' (仅过去排行榜)
	def ranking_all(self, mode='daily', page=1, per_page=50, date=None):
```

中国語と英語なのでまあわかります。（英語だけですが。）
とりあえず、これを参考にコードを組むと、

```py
ranking_type=input("want to ranking type enter.\nmode:daily/weekly/monthly/male/female/original/rookie\ndaily_r18/weekly_r18/male_r18/female_r18/r18g\nplese here:")
ranking_max=input("ランキングで取得する最大順位を入力(MAX:300):")
ranking_day=ranking_day_set()
json_ranking_result=api.ranking_all(mode=ranking_type, page=1, per_page=ranking_max,date=ranking_day)
```

ranking_day_set()については後でコードを示しますが、
1行目ではランキングの種類を聞いています。
普通にinputを使っています。
懸念事項としてはエラー処理がないので、
みなさんが参考にしてくださる際は、組み込んだほうが安心できます。
2行目では取得数の話になりますが、自分は頭が硬いので、とりあえずinputで数値を取得しているだけですね。
当然こちらもエラー処理が必要ですね。
（ログインの時点で必要だろって話ですが。
自分にはそんなもの書けないし、ローカルで自分しか使わないのでまあ大丈夫...なはずです。）
最大枚数があるのは、pixivのランキングが500位までしかないからです。
（スクロールして確かめましたが、少なくともデイリーでは。）
さて、私を今回一番苦しめたのは、3行目、
日付指定ができるのですが、これが鬼門でした。

#5.日付指定の闇
日付指定なんて簡単じゃーん。
そう思っていたときもありました。
これはPixivのランキングを正確に理解していなかった私のせいでありますが...
まずコードを見てみましょう。

```py
def ranking_day_set():
    date = str(input("日付を2000-01-01のように設定してください(間違えるor空白時は昨日になります。"))
    conected_time=datetime.datetime.fromtimestamp(time.time())
    try:
        temp=datetime.datetime.strptime(date, '%Y-%m-%d')
    except:
        if conected_time.hour<12:
            temp=datetime.date.today()-datetime.timedelta(days=2)
        else:
            temp=datetime.date.today()-datetime.timedelta(days=1)
    return datetime.date(temp.year,temp.month,temp.day)
```
このシリーズ初めてのユーザー定義関数（？）ですね。
まず、1行目でinputで日付データを要求します。
これはユーザーが日付指定したい場合に使うので、空白時の説明も付けときました。
2行目では、現在時刻を取得しているはずです。
（思い出しながら書いちゃったので忘れました。）
3行目以降はtry文ですが、
4行目で、文字列を日付にし、
失敗時には、日付が必要なので、6行目からの処理が必要です。

そう、**ここに**落とし穴があったのです。

#6.落とし穴の訳
落とし穴とは、**pixivのランキング更新タイミングを知らなかった**ということです。
pixivでは本コード製作時時点では正午にランキング更新が行われており、
私は当初、入力されていないときの挙動を、今日の日付を入れるだけの処理にしていました。
ところが、それでは正午より前の時間では
ランキングは生成されていないのでエラーを吐いてしまいます。
そこで、if文を使い日付を調整することにしました。
6行目では、現在時刻が12時を超えているかどうかで判別し、
超えていたら昨日の日付を（9行目）
超えていなければ一昨日の日付を入れるようにしました。（7行目）
そして最後に日付を返してやっておしまいです。（10行目）

#7.取得処理
先にコードを出しときます。

```py

json_ranking_result=api.ranking_all(mode=ranking_type, page=1, per_page=ranking_max,date=ranking_day)
try:
    illust_list=json_ranking_result.response[0].works
except:
    print("error")
    quit()
num=len(illust_list)
name=str(ranking_day)
if not os.path.exists("./pixiv_images/ranking/"+ranking_type):
    os.mkdir("./pixiv_images/ranking/"+ranking_type)
if not os.path.exists("./pixiv_images/ranking/"+ranking_type+"/"+name):
    os.mkdir("./pixiv_images/ranking/"+ranking_type+"/"+name)
savepath="./pixiv_images/ranking/"+ranking_type+"/"+name+"/"
for illust in illust_list[:num]:
    aapi.download(illust.work.image_urls.large,path=savepath,name="#"+str(illust.rank)+"-"+str(illust.work.title.replace("/","-"))+"by"+str(illust.work.user.name.replace("/","-"))+".jpg")
    print("#"+str(illust.rank)+":"+str(illust.work.title))
    sleep(1)
print("end.")
```
1行目は先程のコード部分とかぶっていますが、
ここでランキングをjsonで取得します。
ここではエラーは出ないのですが（間違っていてもjsonは取得できるため）
次の作品情報取得でエラーが出るのでtryで処理しときます。（2~6行目）
7行目でイラスト数の取得、8行目でランキングの日付を文字列変換しておきます。
9~13行目でランキングで取得した画像の保存先を作成しておきます。
今回はranking>ランキングの種類>日付の順にフォルダにしてしまいます。
そして14行目以降は画像取得処理で、ここらへんは、エラー処理をしてないこと以外は前回の取得処理と大して変わりません。
強いていうと、先頭に順位が書かれていることぐらいですね。
これで処理部分は完成です。
#8.くっつけよう。関数にして。
ふと、関数にしてくっつけてしまえばいいのではと思ったのでそのまま関数にしてくっつけてしまいます。

結果....

```py

from pixivpy3 import PixivAPI
from pixivpy3 import AppPixivAPI
from pixivpy3 import PixivError
from time import sleep
import json
import os
import datetime
import time
import csv

#api login


#関数
def selector(id):
    modeset=input("enter the mode.\n discover/ranking/bookmark/quit:")
    if modeset=="bookmark":
        bookmark_getter(id)
    elif modeset=="ranking":
        ranking_getter()
    

def ranking_day_set():
    date = str(input("日付を2000-01-01のように設定してください(間違えるor空白時は昨日になります。"))
    conected_time=datetime.datetime.fromtimestamp(time.time())
    try:
        temp=datetime.datetime.strptime(date, '%Y-%m-%d')
    except:
        if conected_time.hour<12:
            temp=datetime.date.today()-datetime.timedelta(days=2)
        else:
            temp=datetime.date.today()-datetime.timedelta(days=1)
    return datetime.date(temp.year,temp.month,temp.day)

def bookmark_getter(id):
    bookmark_type_check=input("非公開のブックマークをダウンロードしますか？y/n:")
    if bookmark_type_check=="y":
        bookmark_type="private"
    else:
        bookmark_type="public"
    bookmark_count=input("your bookmark count number please.\n only public bookmark:")
    bookmark_count_original=int(bookmark_count)
    bookmark_count=int(bookmark_count)//30+1
    if input("他の人のブックマークをダウンロードしますか？y/n")=="y":
        myself_user_id=int(input("スキなユーザーIDを入力"))
    else:
        myself_user_id=id
    try:
        json_user_collect = aapi.user_bookmarks_illust(myself_user_id, restrict=bookmark_type)
    except:
        print("error")
        selector(id)
    while bookmark_count > 0:
        if bookmark_count_original<30:
            num=bookmark_count_original
        else:
            num=len(json_user_collect.illusts)
        bookmark_count=bookmark_count-1
        for illust in json_user_collect.illusts[:num]:
            writer=illust.user.name.replace("/","-")
            if not os.path.exists("./pixiv_images/bookmark/"+writer):
                os.mkdir("./pixiv_images/bookmark/"+writer)
            savepath="./pixiv_images/bookmark/"+writer
            try:
                aapi.download(illust.image_urls.large,path=savepath,name=str(illust.title.replace("/","-"))+".png")
            except:
                print("error")
                print("#"+str(writer)+":"+str(illust.title))
                sleep(1)
                continue
            print("#"+str(bookmark_count))
            print("#"+str(writer)+":"+str(illust.title))
            sleep(1)
        if bookmark_count>0:
            next_url=json_user_collect.next_url
            next_qs=aapi.parse_qs(next_url)
            json_user_collect=aapi.user_bookmarks_illust(**next_qs)
    print("end.")

def ranking_getter():
    ranking_type=input("want to ranking type enter.\nmode:daily/weekly/monthly/male/female/original/rookie\ndaily_r18/weekly_r18/male_r18/female_r18/r18g\nplese here:")
    ranking_max=input("ランキングで取得する最大順位を入力(MAX:300):")
    ranking_day=ranking_day_set()
    json_ranking_result=api.ranking_all(mode=ranking_type, page=1, per_page=ranking_max,date=ranking_day)
    try:
        illust_list=json_ranking_result.response[0].works
    except:
        print("error")
        selector(myself_user_id)
    num=len(illust_list)
    name=str(ranking_day)
    if not os.path.exists("./pixiv_images/ranking/"+ranking_type):
        os.mkdir("./pixiv_images/ranking/"+ranking_type)
    if not os.path.exists("./pixiv_images/ranking/"+ranking_type+"/"+name):
        os.mkdir("./pixiv_images/ranking/"+ranking_type+"/"+name)
    savepath="./pixiv_images/ranking/"+ranking_type+"/"+name+"/"
    for illust in illust_list[:num]:
        aapi.download(illust.work.image_urls.large,path=savepath,name="#"+str(illust.rank)+"-"+str(illust.work.title.replace("/","-"))+"by"+str(illust.work.user.name.replace("/","-"))+".jpg")
        print("#"+str(illust.rank)+":"+str(illust.work.title))
        sleep(1)
    print("end.")

#メイン処理ここから
mail="your mail "
password="password"
api = PixivAPI()
aapi=AppPixivAPI()

aapi.login(mail,password)
api.login(mail,password)
try:
    myself_user_id=int(aapi.user_id)
except:
    print("error")
#フォルダ確認
if not os.path.exists("./pixiv_images"):
    os.mkdir("./pixiv_images")
if not os.path.exists("./pixiv_images/bookmark"):
    os.mkdir("./pixiv_images/bookmark")
if not os.path.exists("./pixiv_images/ranking"):
    os.mkdir("./pixiv_images/ranking")

selector(myself_user_id)

print("終了しました。")
```
という感じになりました。
当初はwhile文で行く予定でしたが、関数用意して選択することにしました。

#9.終わりに
長文にお付き合いいただきありがとうございました。
私のコードはエラー処理省きまくった、
一種の**爆弾**ナノではと思っていますが、
皆様はぜひともエラー処理を書いてください。
（自家用で、エラー原因わかりきっているので私は面倒くさがって書きませんが。）
実は、このあとまた追加で機能を作ってしまったのですが、
それはまた次のお話にしたいと思います。

また、皆様のご指摘等がいただけますと私の学びとなりますので、よろしくお願いいたします。

#各種リンク
[pixiv](https://www.pixiv.net/ "https://www.pixiv.net/")
[pixivpy](https://github.com/upbit/pixivpy "https://github.com/upbit/pixivpy")

このシリーズのURL
[ブックマーク編](https://qiita.com/haraday/items/add722167d0d6f2342f9
"https://qiita.com/haraday/items/add722167d0d6f2342f9")
[改修編](https://qiita.com/haraday/items/a2c3f8fd2d57a14dbba2
"https://qiita.com/haraday/items/a2c3f8fd2d57a14dbba2")