PythonでPixivのブックマークした画像を一括取得したいともがくお話

#重要
このドキュメントで利用している手法がPixiv側のログイン手法の変更により利用できなくなりました。
現時点での代替案はpixivpyのGithubのissue#158をご覧ください。
落ち着いたら別記事で書きます。
#　はじめに
本投稿が初であり、完全にプログラミング等の経験が皆無でありますゆえ、
かなりお見苦しいところがございますが、ご自愛ください。（自分用メモも兼ねております。）
(どのくらいかというと、Markdownって何というレベルでございます。)
#1.制作の経緯（読み飛ばしてもらって構いません。）
普段から、 [pixiv](https://www.pixiv.net/ "https://www.pixiv.net/")を利用して、様々な素晴らしい作品を眺めているのですが、
ある日ふと、ブックマークした画像たちをスマホ（Android）の背景に交互に使いたいと思ったわけです。

ですが、ブックマークした画像数は、300以上あるため、
流石に手動でダウンロードするのは難しいと思ったわけです。
その結果、スクレイピングで画像を取得できると知ったので、
Androidにも導入できる、Pythonで作成することにしました。
#2.開発環境
OS：Windows10 64bit　1903
CPU/Memory:i5-7200U/8GB
python 3.6.8 64bit版
エディター（？）：VSCode version 1.38.1
#3.Pythonのインストール
[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
#4.パターンA：普通にURLから画像を取ろうとする。
参考先[PythonでWeb上の画像などのファイルをダウンロード（個別・一括）](https://note.nkmk.me/python-download-web-images/"https://note.nkmk.me/python-download-web-images/")様を参考に挑戦していきました。

このサイトを参考にしたわけは、追加でライブラリがいらないのが決め手です。
とりあえず一枚画像を取得しようと言うことで、下記コードを作成。

```python:get_image.py
import os
import pprint
import time
import urllib.error
import urllib.request

url="https://i.pximg.net/img-master/img/2019/08/05/12/33/14/76088417_p0_master1200.jpg"
try:
    with urllib.request.urlopen(url_list) as web_image:
            image=web_image.read()
            with open ("./image/"+url_list+".jpg",mode="wb") as local_image:
                local_image.write(image)
except urllib.error.URLError as e:
    print(e)
```

結果は、

```
HTTP Error 403: Forbidden
```

うーん。ブラウザ上では表示できるのですが、Pythonはこのエラーで駄目。
グーグル先生曰く、アクセス権限がない模様。
もしかして、と思ってChromeのシークレットモードでURLを開くと、
![SnapCrab_No-0014.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498105/fc58916c-cbef-b338-eb6c-8e7c693e9764.jpeg)
同じエラーを発見しました。

多分ブラウザ上で見れたのはログイン情報がブラウザ上に保存されていて、
認証情報があるから行けたのではないかと思います。

はじめからつまずいていて、”もう手動でやろうかなと”、思ってしまうほどでした。
その時、アレの存在を知ってしまうのです。
#5.PixivPyを知ってしまう。
4の悩みを解決するために検索をかけまくっていたら、こんなものを見つけてしまいました。
参考：[pixivからイラストを無限に集めてみる[python]](https://www.mathgram.xyz/entry/scraping/pixiv"https://www.mathgram.xyz/entry/scraping/pixiv")
非公式のAPI **pixivpy** を利用するというもの。
Python初体験の人間に、APIが何かはさっぱりわかりませんが、とりあえず挑戦してみます。
#注意事項
公式のAPIではありませんので、利用の際は十分に注意を払ってください。
また、下記で出てくるコードは、Python歴皆無なものが作ったため、
こちらの想定していない動作がされた場合、当然のようにエラーが出ます。
修正したほうがいいところがあれば、ぜひコメントいただけると幸いです。
#6.制作編（多分本編）
1：pipでPixivpyをインストール

```py
pip install pixivpy
```

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

3.pythonのファイルを作る。
とりあえず、”pixiv_download_CLI.py”に決定。

4.ログイン部分を作成

```py:pixiv_download_CLI
from pixivpy3 import *

#api login
aapi=AppPixivAPI()
aapi.login("Mail", "Pass")
```
MailにはあなたのPixivのメールアドレス（OR：PixivID）、Passはパスワードを入力してください。
参考にしたとこでは、Jsonに書き込んで、読み込んでいたのですが、やり方がうまく理解できず、
とりあえず直書きしました。

heroku等で常時回すなら、環境変数等に書き込んで覗かれないように注意が必要です。

```py
from pixivpy3 import *
```
が何かはわかりませんでしたが、
とりあえずライブラリの追加、ログイン処理は完成です。
ログインすると、Pixivからメールが届いたので多分いいはず。

5.フォルダがあるか確認
if 文でできるらしいので、ファイル操作に使えると聞いた、OSライブラリを加えてみます。

```py:pixiv_download_CLI
from pixivpy3 import *

#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
aapi=AppPixivAPI()
aapi.login("Mail", "Pass")
```

os.pathはカレントディレクトリを示してるらしいので、
相対パスで画像保存用フォルダを作成。

```py
os.mkdir("./pixiv_images")
```
また、拡張のためにブックマーク専用のフォルダを作成しています。

最初、ifの順序を逆にしたため、
フォルダが見つからないエラーが山程出てしまいましたが、なんとかできました。

6.ブックマークの数を取得したい。
bookmarkの数を取得したかったのですが、
あいにく英語は中３レベルすらないので、APIのリファレンスが読めません。
そもそもブックマーク数をどこで取るのかわからないのと、（それらしい参考文献がない）
ブックマークが増えたときに増えた分だけ取得できるようにしたいという言い訳の元、こんなコードを作成。

```py
bookmark_count=input("your bookmark count number please.\n only public bookmark:")
bookmark_count=int(bookmark_count)//30+1
```

input関数を使って、ユーザーに数を入力してもらいます。
ちなみにこのAPIで非公開のものが取れる設定があるのか、はわかりませんが、
公開のものについては取れることがわかったので、
説明文に”only public bookmark”と書いておきました。
下の計算式では、このAPIで取得できる枚数が一回につき30だということがわかったので、
30で割って＋１してページ数を合わせています。

7.ダウンロードとループ部分。
まずはコードを見てみます。

```py
        json_user_collect = aapi.user_bookmarks_illust("user-id", restrict='public')
        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
                aapi.download(illust.image_urls.large,path=savepath,name=str(illust.title.replace("/","-"))+".png")
                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)
```
いかにも初心者丸出しのコードに見えませんか？
とりあえず解説します。

```py
json_user_collect = aapi.user_bookmarks_illust("user-id", restrict='public')
```

user-idには自分のプロフィールページのURLである。
https://www.pixiv.net/member.php?id=xxxxxxxxx
のxxxxxxxxxを入れてください。

これをするとAPIから、JSONのデータみたいなのが送られてきます。
<details><summary>内容例</summary><div>

```json
{'illusts': [{'id': 76088417, 'title': 'NOSTALGIC 2', 'type': 'illust', 'image_urls': {'square_medium': 'https://i.pximg.net/c/540x540_10_webp/img-master/img/2019/08/05/12/33/14/76088417_p0_square1200.jpg', 'medium': 'https://i.pximg.net/c/540x540_70/img-master/img/2019/08/05/12/33/14/76088417_p0_master1200.jpg', 'large': 'https://i.pximg.net/c/600x1200_90_webp/img-master/img/2019/08/05/12/33/14/76088417_p0_master1200.jpg'}, 'caption': 'C96 夏コミ新刊「NOSTALGIC 2」表紙です。<br /><br />とらのあなさんで予約開始し 
てますよろしくお願いします！<br />委託 - <a href="https://ec.toranoana.shop/tora/ec/item/040030764021/" target="_blank">https://ec.toranoana.shop/tora/ec/item/040030764021/</a><br /><br />3日目(日) 西Ｊ-22a 「Octopus jelly」 <br />コミケWeb Catalog - <a href="https://webcatalog.circle.ms/Perma/Circle/10349499/" target="_blank">https://webcatalog.circle.ms/Perma/Circle/10349499/</a>', 'restrict': 0, 'user': {'id': 702789, 'name': 'Gilse[3日目-J22a]', 'account': 'gilse1024', 'profile_image_urls': {'medium': 'https://i.pximg.net/user-profile/img/2013/05/15/01/38/39/6236767_4a10bef9a4a421078d4c715b95cb9666_170.gif'}, 'is_followed': False}, 'tags': [{'name': '女の子', 'translated_name': None}, {'name': '雨', 'translated_name': None}, {'name': '朝', 'translated_name': None}, {'name': 'お団子', 'translated_name': None}, {'name': 'C96', 'translated_name': None}, {'name': 'ぺたん座り', 'translated_name': None}, {'name': 'キャミソールワンピース', 'translated_name': None}, {'name': 'オリジナル3000users入り', 'translated_name': None}], 'tools': ['Photoshop'], 'create_date': '2019-08-05T12:33:14+09:00', 'page_count': 1, 'width': 700, 'height': 1000, 'sanity_level': 2, 'x_restrict': 0, 'series': None, 'meta_single_page': {'original_image_url': 'https://i.pximg.net/img-original/img/2019/08/05/12/33/14/76088417_p0.jpg'}, 'meta_pages': [], 'total_view': 25353, 'total_bookmarks': 5107, 'is_bookmarked': True, 'visible': True, 'is_muted': False}, {'id': 70992672, 'title': '天使の園', 'type': 'illust', 'image_urls': {'square_medium': 'https://i.pximg.net/c/540x540_10_webp/img-master/img/2018/10/03/00/56/51/70992672_p0_square1200.jpg', 'medium': 'https://i.pximg.net/c/540x540_70/img-master/img/2018/10/03/00/56/51/70992672_p0_master1200.jpg', 'large': 'https://i.pximg.net/c/600x1200_90_webp/img-master/img/2018/10/03/00/56/51/70992672_p0_master1200.jpg'}, 'caption': 'K-BOOKS様創業24周年記念のス
ペシャルジークレーに看板娘のミントを描かせていただきました！ 少しお値段ははりますが、通常より2倍サイズのジークレーなのでこの機会に是非！ 受付は10月8日（月）23:59まで。よろし
くお願いします！ <a href="https://shop1.k-books.co.jp" target="_blank">https://shop1.k-books.co.jp</a>', 'restrict': 0, 'user': {'id': 44234, 'name': '夜ノみつき', 'account': 'nanairo', 'profile_image_urls': {'medium': 'https://i.pximg.net/user-profile/img/2016/02/02/19/20/17/10471965_90168a3c7b491269f8fb0c5215f7b4ae_170.jpg'}, 'is_followed': False}, 'tags': [{'name': 'オリジナル', 'translated_name': None}, {'name': '仕事絵', 'translated_name': None}, {'name': '猫耳', 'translated_name': None}, {'name': '天使', 'translated_name': None}, {'name': '素足履き', 'translated_name': None}, {'name': 'オリジナル10000users入り', 'translated_name': None}], 'tools': ['SAI', 'Photoshop'], 'create_date': '2018-10-03T00:56:51+09:00', 'page_count': 1, 'width': 711, 'height': 1000, 'sanity_level': 2, 'x_restrict': 0, 'series': None, 'meta_single_page': {'original_image_url': 'https://i.pximg.net/img-original/img/2018/10/03/00/56/51/70992672_p0.jpg'}, 'meta_pages': [], 'total_view': 72347, 'total_bookmarks': 12413, 'is_bookmarked': True, 'visible': True, 'is_muted': False}, {'id': 73092535, 'title': 'イラスト展のお知らせ', 'type': 'illust', 'image_urls': {'square_medium': 'https://i.pximg.net/c/540x540_10_webp/img-master/img/2019/02/10/00/32/15/73092535_p0_square1200.jpg', 'medium': 'https://i.pximg.net/c/540x540_70/img-master/img/2019/02/10/00/32/15/73092535_p0_master1200.jpg', 'large': 'https://i.pximg.net/c/600x1200_90_webp/img-master/img/2019/02/10/00/32/15/73092535_p0_master1200.jpg'}, 'caption': 'このたびとらのあな様にて初個展を開催することにな
りました！ご興味のある方は遊びに来ていただけると嬉しいです！<br /><a href="https://news.toranoana.jp/82826" target="_blank">https://news.toranoana.jp/82826</a><br />��日時
2月15日（金)～2月24日(日)\u3000全日12:00～20:00<br />��入場：無料料<br /��場所：とらのあな秋葉原原店店C\u300階イベントスペーースス', 'restrict': 0, 'user': {'id': 795196, 'name
DSマイル', 'account': 'kd998', 'profile_image_urls': {'medium': 'https://i.pximg.net/user-profile/img/2016/10/13/23/06/34/11616539_2aaeb931caba959fa5012895d692c2e8_170.png'}, 'is_followed': False}, 'tags': [{'name': 'オリジナル', 'translated_name': None}, {'name': '着物', 'translated_name': None}, {'name': '和傘', 'translated_name': None}, {'name': 'ふつくしい', 'translated_name': None}, {'name': '女の子', 'translated_name': None}, {'name': '銀髪ロング', 'translated_name': None}, {'name': '紫眼', 'translated_name': None}, {'name': '蛇の目傘', 'translated_name': None}, {'name': 'オリジナル30000users入り', 'translated_name': None}], 'tools': [], 'create_date': '2019-02-10T00:32:15+09:00', 'page_count': 1, 'width': 800, 'height': 1134, 'sanity_level': 2, 'x_restrict': 0, 'series': None, 'meta_single_page': {'original_image_url': 'https://i.pximg.net/img-original/img/2019/02/10/00/32/15/73092535_p0.png'}, 'meta_pages': [], 'total_view': 183103, 'total_bookmarks': 31326, 'is_bookmarked': True, 'visible': True, 'is_muted': False}, {'id': 71116873, 'title': 'ラッキースケベななちゃん', 'type': 'illust', 'image_urls': {'square_medium': 'https://i.pximg.net/c/540x540_10_webp/img-master/img/2018/10/10/17/47/16/71116873_p0_square1200.jpg', 'medium': 'https://i.pximg.net/c/540x540_70/img-master/img/2018/10/10/17/47/16/71116873_p0_master1200.jpg', 'large': 'https://i.pximg.net/c/600x1200_90_webp/img-master/img/2018/10/10/17/47/16/71116873_p0_master1200.jpg'}, 'caption': 'こちら後日メロンブックス様のうりぼうざっか店にて<br />タペストリーとして販 
売される予定です！', 'restrict': 0, 'user': {'id': 544479, 'name': '宮瀬まひろ＠4日目南ナ-23a', 'account': 'mahiro_miyase', 'profile_image_urls': {'medium': 'https://i.pximg.net/user-profile/img/2018/11/30/16/08/56/15073985_eda03ac10451ed9d252da6e7f8688b5d_170.jpg'}, 'is_followed': False}, 'tags': [{'name': 'オリジナル', 'translated_name': None}, {'name': 'セーラー服', 'translated_name': None}, {'name': 'ぱんつ', 'translated_name': None}, {'name': '高品質パンツ', 'translated_name': None}, {'name': 'M字開脚', 'translated_name': None}, {'name': '黒ニーソ', 'translated_name': None}, {'name': '尻神様', 'translated_name': None}, {'name': 'オリジナル10000users入り', 'translated_name': None}, {'name': 'スリチラ', 'translated_name': None}], 'tools': ['SAI', 'Photoshop'], 'create_date': '2018-10-10T17:47:16+09:00', 'page_count': 1, 'width': 627, 'height': 887, 'sanity_level': 6, 'x_restrict': 0, 'series': None, 'meta_single_page': {'original_image_url': 'https://i.pximg.net/img-original/img/2018/10/10/17/47/16/71116873_p0.jpg'}, 'meta_pages': [], 'total_view': 81746, 'total_bookmarks': 12109, 'is_bookmarked': True, 'visible': True, 'is_muted': False}, {'id': 73426979, 'title': 'さがしもの', 'type': 'illust', 'image_urls': {'square_medium': 'https://i.pximg.net/c/540x540_10_webp/img-master/img/2019/02/28/01/59/07/73426979_p0_square1200.jpg', 'medium': 'https://i.pximg.net/c/540x540_70/img-master/img/2019/02/28/01/59/07/73426979_p0_master1200.jpg', 'large': 'https://i.pximg.net/c/600x1200_90_webp/img-master/img/2019/02/28/01/59/07/73426979_p0_master1200.jpg'}, 'caption': 'セーラー＋カーディガン。( ・ㅂ・)و ̑̑', 'restrict': 0, 'user': {'id': 491688, 'name': '風那', 'account': 'rocolis', 'profile_image_urls': {'me
dium': 'https://i.pximg.net/user-profile/img/2018/08/27/07/27/06/14695981_41bd55e10a21c4351c7e4d3c8e966997_170.jpg'}, 'is_followed': False}, 'tags': [{'name': 'オリジナル', 
'translated_name': None}, {'name': 'MOE2019', 'translated_name': None}, {'name': '制服', 'translated_name': None}, {'name': '金髪', 'translated_name': None}, {'name': 'カー 
ディガン+セーラー服', 'translated_name': None}, {'name': 'オリジナル1000users入り', 'translated_name': None}], 'tools': ['SAI'], 'create_date': '2019-02-28T01:59:07+09:00', 
'page_count': 1, 'width': 1880, 'height': 2660, 'sanity_level': 2, 'x_restrict': 0, 'series': None, 'meta_single_page': {'original_image_url': 'https://i.pximg.net/img-original/img/2019/02/28/01/59/07/73426979_p0.jpg'}, 'meta_pages': [], 'total_view': 13219, 'total_bookmarks': 2985, 'is_bookmarked': True, 'visible': True, 'is_muted': False}, {'id': 74189031, 'title': 'Little artist III', 'type': 'illust', 'image_urls': {'square_medium': 'https://i.pximg.net/c/540x540_10_webp/img-master/img/2019/04/14/01/53/06/74189031_p0_square1200.jpg', 'medium': 'https://i.pximg.net/c/540x540_70/img-master/img/2019/04/14/01/53/06/74189031_p0_master1200.jpg', 'large': 'https://i.pximg.net/c/600x1200_90_webp/img-master/img/2019/04/14/01/53/06/74189031_p0_master1200.jpg'}, 'caption': '', 'restrict': 0, 'user': {'id': 6662895, 'name': 'ATDAN-', 'account': '869054445', 'profile_image_urls': {'medium': 'https://i.pximg.net/user-profile/img/2016/01/11/21/46/50/10371466_80f6ad67eab3b8abd44a2fb74ddd1ba1_170.jpg'}, 'is_followed': False}, 'tags': [{'name': '女の子', 'translated_name': None}, {'name': '海伊', 'translated_name': None}, {'name': '美脚', 'translated_name': None}, {'name': 'オリジナル5000users入り', 'translated_name': None}, {'name': 'オリジナル10000users入り', 'translated_name': None}, {'name': '足の裏', 'translated_name': None}, {'name': 'マニキュア', 'translated_name': None}], 'tools': ['SAI', 'Photoshop'], 'create_date': '2019-04-14T01:29:13+09:00', 'page_count': 1, 'width': 1193, 'height': 1686, 'sanity_level': 2, 'x_restrict': 0, 'series': None, 'meta_single_page': {'original_image_url': 'https://i.pximg.net/img-original/img/2019/04/14/01/53/06/74189031_p0.jpg'}, 'meta_pages': [], 'total_view': 150369, 'total_bookmarks': 22391, 'is_bookmarked': True, 'visible': True, 'is_muted': False}, {'id': 76541087, 'title': 'Miku - 12th', 'type': 'illust', 'image_urls': {'square_medium': 'https://i.pximg.net/c/540x540_10_webp/img-master/img/2019/08/31/01/03/10/76541087_p0_square1200.jpg', 'medium': 'https://i.pximg.net/c/540x540_70/img-master/img/2019/08/31/01/03/10/76541087_p0_master1200.jpg', 'large': 'https://i.pximg.net/c/600x1200_90_webp/img-master/img/2019/08/31/01/03/10/76541087_p0_master1200.jpg'}, 'caption': '', 'restrict': 0, 'user': {'id': 6662895, 'name': 'ATDAN-', 'account': '869054445', 'profile_image_urls': {'medium': 'https://i.pximg.net/user-profile/img/2016/01/11/21/46/50/10371466_80f6ad67eab3b8abd44a2fb74ddd1ba1_170.jpg'}, 'is_followed': False}, 'tags': [{'name': 'VOCALOID', 'translated_name': None}, {'name': '初音ミク', 'translated_name': None}, {'name': 'Miku', 'translated_name': None}, {'name': '初音ミク生誕祭2019', 'translated_name': None}, {'name': 'VOCALOID5000users入り', 'translated_name': None}, {'name': 'VOCALOID10000users入り', 'translated_name': None}], 'tools': ['SAI', 'Photoshop'], 'create_date': '2019-08-31T01:03:10+09:00', 'page_count': 1, 'width': 1000, 'height': 1414, 'sanity_level': 2, 'x_restrict': 0, 'series': None, 'meta_single_page': {'original_image_url': 'https://i.pximg.net/img-original/img/2019/08/31/01/03/10/76541087_p0.jpg'}, 'meta_pages': [], 'total_view': 105082, 'total_bookmarks': 23014, 'is_bookmarked': True, 'visible': True, 'is_muted': False}, {'id': 76317731, 'title': 'Little Artist - V', 'type': 'illust', 'image_urls': {'square_medium': 'https://i.pximg.net/c/540x540_10_webp/img-master/img/2019/08/18/02/32/07/76317731_p0_square1200.jpg', 'medium': 'https://i.pximg.net/c/540x540_70/img-master/img/2019/08/18/02/32/07/76317731_p0_master1200.jpg', 'large': 'https://i.pximg.net/c/600x1200_90_webp/img-master/img/2019/08/18/02/32/07/76317731_p0_master1200.jpg'}, 'caption': '', 'restrict': 0, 'user': {'id': 6662895, 'name': 'ATDAN-', 'account': '869054445', 'profile_image_urls': {'medium': 'https://i.pximg.net/user-profile/img/2016/01/11/21/46/50/10371466_80f6ad67eab3b8abd44a2fb74ddd1ba1_170.jpg'}, 'is_followed': False}, 'tags': [{'name': '女の子', 'translated_name': None}, {'name': '海伊', 'translated_name': None}, {'name': 'オリジナル10000users入り', 'translated_name': None}], 'tools': ['SAI', 'Photoshop'], 'create_date': '2019-08-18T02:03:34+09:00', 'page_count': 1, 'width': 960, 'height': 1340, 'sanity_level': 2, 'x_restrict': 0, 'series': None, 'meta_single_page': {'original_image_url': 'https://i.pximg.net/img-original/img/2019/08/18/02/32/07/76317731_p0.jpg'}, 'meta_pages': [], 'total_view': 158284, 'total_bookmarks': 30519, 'is_bookmarked': True, 'visible': True, 'is_muted': False}, {'id': 76372256, 'title': '外、暑かったね', 'type': 'illust', 'image_urls': {'square_medium': 'https://i.pximg.net/c/540x540_10_webp/img-master/img/2019/08/21/00/43/58/76372256_p0_square1200.jpg', 'medium': 'https://i.pximg.net/c/540x540_70/img-master/img/2019/08/21/00/43/58/76372256_p0_master1200.jpg', 'large': 'https://i.pximg.net/c/600x1200_90_webp/img-master/img/2019/08/21/00/43/58/76372256_p0_master1200.jpg'}, 'caption': '夏コミ有難うございました！', 'restrict': 0, 'user': {'id': 10292, 'name': 'ふーみ', 'account': 'tokitoruo', 'profile_image_urls': {'medium': 'https://i.pximg.net/user-profile/img/2018/12/05/01/15/26/15093709_d317aa489a6bfbf056842c58fbd5e01c_170.png'}, 'is_followed': True}, 'tags': [{'name': 'オリジナル', 'translated_name': None}, {'name': '女の子', 'translated_name': None}, {'name': 'おへそ', 'translated_name': None}, {'name': 
'茶髪ロング', 'translated_name': None}, {'name': 'スクールリボン', 'translated_name': None}, {'name': 'オリジナル10000users入り', 'translated_name': None}, {'name': '制服', 
'translated_name': None}], 'tools': ['Photoshop'], 'create_date': '2019-08-21T00:43:58+09:00', 'page_count': 1, 'width': 699,
```
</div></details>

```py

        while bookmark_count > 0:
            print("#"+str(bookmark_count))
            num=len(json_user_collect.illusts)
            bookmark_count=bookmark_count-1
```

while文で30以上の値が入ったときでもページを移動できるように、
bookmark_countを確認するようにしています。
numではlen関数？というのを利用して、そのページの作品数を取得しています。
これが原因で30以下の値でも30個は取得してしまうのですが、、、
とりあえずこれで行きます。

#ラスボス現る
そして最後のダウンロード部分。
改めてコードを見ると、

```py
            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
                aapi.download(illust.image_urls.large,path=savepath,name=str(illust.title.replace("/","-"))+".png")
                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)
```

こんな感じでまあひどそうです。
まずfor文で1つずつのillusts内のデータをとっていきます。

```py

 writer=illust.user.name.replace("/","-")
```

で作成ユーザー名を取得します。
.replace("/","-")で名前に/がついて、フォルダを作成するときにエラーになるのを防ぎます。
（他に絶対だめな文字があればぜひ教えて下さい）

```py

                if not os.path.exists("./pixiv_images/bookmark/"+writer):
                    os.mkdir("./pixiv_images/bookmark/"+writer)
                savepath="./pixiv_images/bookmark/"+writer
```

ここでは5と同様にフォルダを確認しておきます。
ブックマークした画像は作者別に分けられるので、意外と便利です。
最後の行で保存先を指定します。

```py
                aapi.download(illust.image_urls.large,path=savepath,name=str(illust.title.replace("/","-"))+".png")
                print("#"+str(writer)+":"+str(illust.title))
                sleep(1)
```

ついにダウンロードのときです。
忘れていると思うので確認ですが、

```py
aapi=AppPixivAPI()
```

です。このはじめの行がダウンロードを実行します。引数として、

```py

AppPixivAPI().download("URL",path="保存先",name="ファイル名（拡張子含む）")
```

他にもありますが、今回はこれらに絞っています。（すいません。使い方がわからないだけです。）

”URL”には前に示した、APIから得たJsonの中にある、画像のURLを参照しています。
こんなふうになっています。

```
json_user_collect.illusts[:num].image_urls.large
```

なお画像サイズは、他にもありますが、今回は一番大きいものにしました。

"保存先"は先程用意した、savepathを利用します。

”ファイル名（拡張子含む）”については、色々問題が残ったままですが、

```py

str(illust.title.replace("/","-"))
```

で作品名を文字列で取得した上で、更にファイル名に使えないであろう/をーに置き換えておきましたが、
実際問題、他にも＊なども使えなかったことから、どうするか考え中です。
こちらもよかったら、使えない文字列をご存知でしたら教えていただけると幸いです。
拡張子は、なんとなくでpngとなりました。

後ろ2行では、作者名、作品名をプロント表示し、
アクセス負荷防止の為、気休め程度のスリープを入れています。

7の最後はこちら、

```py
            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)
```

この処理で次のブックマークページへのURL等を取得しています。
これについてはAPIリファレンスの例を参考に改変しているだけです。

2行目で、URLを参照し、
3行目についてはちょっと良くわかりませんが、なにかしています。
4行目で次のページのJson取得をして、冒頭のwhile文へと戻ります。

#完成品

```py:pixiv_download_CLI.py
from pixivpy3 import *
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
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
json_user_collect = aapi.user_bookmarks_illust("user-id", restrict='public')
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
        aapi.download(illust.image_urls.large,path=savepath,name=str(illust.title.replace("/","-"))+".png")
        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("終了しました。")
```

最後に終了メッセージを出して完成です。

インシデントがおかしいかもですが修正して使っていただけたら幸いです。
#終わりに
ここまで読んでいただきありがとうございました。
初投稿でとてつもなく、拙い文章となってしまいましたが、
皆様の小さなきっかけになれば幸いです。

#参考文献
[PythonでWeb上の画像などのファイルをダウンロード（個別・一括）](https://note.nkmk.me/python-download-web-images/"https://note.nkmk.me/python-download-web-images/")
[pixivからイラストを無限に集めてみる[python]](https://www.mathgram.xyz/entry/scraping/pixiv"https://www.mathgram.xyz/entry/scraping/pixiv")
API：[https://github.com/upbit/pixivpy](https://github.com/upbit/pixivpy)

#感謝
300pvありがとうございます。
2019/12/18

500pvありがとうございます。
[ot0eu](https://qiita.com/ot0eu)様にご指摘いただいた部分（見出し５のタイプミス）を修正しました。
2020/02/21

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

このシリーズの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")
[ランキング編](https://qiita.com/haraday/items/a2c5ea461224d95c04f9
"https://qiita.com/haraday/items/a2c5ea461224d95c04f9")