pixivのフォローユーザーの漫画・イラストを一括DL

# やりたかったこと
pixivでフォローしている全ての絵師さんの漫画・イラストをまとめてダウンロードしたかったんです。

１枚１枚手動で保存するのは面倒ですよね。
できれば自動で保存したい。

そんな時に見つけたのが、[pixivpyライブラリ](https://github.com/upbit/pixivpy)。
非公式のpixiv APIみたいで、これで今回やりたかったことに近いスクリプトを書いてらっしゃる方もいました。

- [pixivからイラストを無限に集めてみる[python] - MATHGRAM](http://www.mathgram.xyz/entry/scraping/pixiv)

ただ、上記のスクリプトだと、漫画が１ページ目しか取れなかったので、少しスクリプトの改造が必要でした。
また、pixivpyには自分のフォローユーザーの情報を取得するメソッドがなかったので、そのスクリプトも別で用意する必要がありました。

# やったこと
以下の３つのスクリプトを書きました。

1. **自分のフォローユーザー**の絵師IDを取得する
2. 絵師IDから**全ての漫画・イラスト**をDLする
3. **自分のフォローユーザー**の**全ての漫画・イラスト**をDLする

## 実行環境
- OS: macOS High Sierra
- python: 3.6.3
- pixivpy: 3.3.3 -> 3.3.6

## 事前準備①: 外部ライブラリのインストール
必要なライブラリをインストールします。

```terminal:Terminal
$ pip install pixivpy beautifulsoup4 robobrowser lxml
```


## 事前準備②: 認証情報をまとめる
コードに直書きしてもいいのですが、外部ファイルにまとめておくと管理しやすいです。

```json:client.json
{
	"pixiv_id": "自分のpixiv ID",
	"password": "自分のpixiv パスワード",
	"user_id":  "自分のユーザID"
}
```

## ファイル構造
```
作業ディレクトリ
├── client.json
├── get_following_userID.py
├── get_illustrations.py
├── get_all_illustrations.py
└── pixiv_images
```


## ソースコード
### 1. **自分のフォローユーザー**の絵師IDを取得する

フォローユーザー一覧ページより、絵師さんのユーザIDを抽出するスクリプトです。

#### ▷ ソースコード本体
```python:get_following_userID.py
# -*- coding: utf-8 -*-

from pixivpy3 import *
import json
from time import sleep
import sys, io, re, os
from robobrowser import RoboBrowser
from bs4 import BeautifulSoup

f = open("client.json", "r")
client_info = json.load(f)
f.close()

# pixivpyのログイン処理
api = PixivAPI()
api.login(client_info["pixiv_id"], client_info["password"])
aapi = AppPixivAPI()

# フォローユーザーの総数を取得
self_info = aapi.user_detail(client_info["user_id"])
following_users_num = self_info.profile.total_follow_users

# フォローユーザー一覧ページのページ数を取得
if(following_users_num%48 != 0):
    pages = (following_users_num//48)+1
else:
    pages = following_users_num//48

#タグ除去用
p = re.compile(r"<[^>]*?>")
# [jump:1]形式除去用
jump = re.compile(r"\[jump:.+\]")
#ファイルエンコード設定用
character_encoding = 'utf_8'

# Webスクレイパーのログイン処理
pixiv_url = 'https://www.pixiv.net'
browser = RoboBrowser(parser='lxml', history=True)
browser.open('https://accounts.pixiv.net/login')
form = browser.get_forms('form', class_='')[0]
form['pixiv_id'] = client_info["pixiv_id"]
form['password'] = client_info["password"]
browser.submit_form(form)

# フォローユーザー一覧ページのURLを設定
target_url = 'https://www.pixiv.net/bookmark.php?type=user&rest=show&p='

# 全てのフォローユーザーのユーザIDを取得
following_users_id = []
for i in range(1, pages+1):
    print(target_url + str(i))
    browser.open(target_url + str(i))
    following_users = browser.find(class_='members')
    for user in following_users.find_all("input"):
        following_users_id.append(user.get("value"))
    sleep(3) # ページを移動したら一時待機する（マナー）

print(following_users_id)
```

#### ▷ 実行例
```terminal:Terminal
$ python get_following_userID.py
https://www.pixiv.net/bookmark.php?type=user&rest=show&p=1
https://www.pixiv.net/bookmark.php?type=user&rest=show&p=2
https://www.pixiv.net/bookmark.php?type=user&rest=show&p=3
['2246509', '1203800', '220531', ... , '11']
```

### 2. 絵師IDから**全ての漫画・イラスト**をDLする

絵師さんのユーザIDより全ての漫画・イラストをダウンロードするスクリプトです。

#### ▷ ソースコード本体
```python:get_illustrations.py
# coding: utf-8

from pixivpy3 import *
import json
from time import sleep
import os

# ログイン処理
f = open("client.json", "r")
client_info = json.load(f)
f.close()
api = PixivAPI()
api.login(client_info["pixiv_id"], client_info["password"])
aapi = AppPixivAPI()
aapi.login(client_info["pixiv_id"], client_info["password"])

# 入力された絵師IDから絵師情報を取得
illustrator_pixiv_id = int(input("Type illustrator pixiv id number:\n>>>"))

# ユーザ情報（作品数、絵師名）を取得
user_info_json = aapi.user_detail(illustrator_pixiv_id)
total_illusts = user_info_json.profile.total_illusts
total_manga = user_info_json.profile.total_manga
illustrator_name = user_info_json.user.name

# イラスト情報を取得（とりあえず300作品取得）
works_info = api.users_works(illustrator_pixiv_id, page=1, per_page=300)

# # 絞り込み条件
# target_tag = "target_tag"
# lower_bookmarks = 0

# 渡されたディレクトリが存在しない場合に作成するLambda関数
mkdirExceptExist = lambda path: "" if os.path.exists(path) else os.mkdir(path)

mkdirExceptExist("./pixiv_images") # 保存用フォルダがない場合は生成

saving_direcory_path = "./pixiv_images/" + illustrator_name + "/"
mkdirExceptExist(saving_direcory_path) # 保存用フォルダがない場合は生成

separator = "============================================================"
print("Artist: %s" % illustrator_name)
print("Works: %d" % works_info.pagination.total)
print(separator)

for i, work_info in enumerate(works_info.response):

    work_type = "manga" if work_info.is_manga else "illust"

    mkdirExceptExist(saving_direcory_path + work_type) # 保存用フォルダがない場合は生成

    # # DL画像を絞り込む場合
    # if target_tag not in work_info.tags:	#タグによる絞り込み
    # 	continue
    # works_bookmarks = work_info.stats.favorited_count.private + work_info.stats.favorited_count.public
    # if works_bookmarks < lower_bookmarks:  #ブックマーク数による絞り込み
    # 	continue

    # ダウンロード
    work_title = work_info.title.replace("/", "-") # '/'はPathとして扱われるため回避

    print("Procedure: %d/%d" % (i + 1, works_info.pagination.total))
    print("Title: %s" % work_title)
    print("URL: %s" % work_info.image_urls.large)
    print("Caption: %s" % work_info.caption)
    print(separator)

    # 漫画の場合
    if work_info.is_manga:
        mkdirExceptExist(saving_direcory_path + "manga/" + work_title) # 保存用フォルダがない場合は生成
        
        manga_info = api.works(work_info.id)
        for page_no in range(0, manga_info.response[0].page_count):
            page_info = manga_info.response[0].metadata.pages[page_no]
            num = str(page_no) if len(str(page_no)) > 1 else "0" + str(page_no)
            aapi.download(page_info.image_urls.large, path=saving_direcory_path + "manga/" + work_title, name=num+".jpg")
            sleep(3)
    # イラストの場合
    else:
        aapi.download(work_info.image_urls.large, path=saving_direcory_path + "illust", name=work_title+".jpg")
        sleep(3)

print("\nThat\"s all.")
```

<details><summary>pixivpy 3.3.3 の場合（クリックで表示）</summary><div>

```python:get_following_userID.py
# -*- coding: utf-8 -*-

from pixivpy3 import *
import json
from time import sleep
import sys, io, re, os
from robobrowser import RoboBrowser
from bs4 import BeautifulSoup

f = open("client.json", "r")
client_info = json.load(f)
f.close()

# pixivpyのログイン処理
api = PixivAPI()
api.login(client_info["pixiv_id"], client_info["password"])
aapi = AppPixivAPI()

# フォローユーザーの総数を取得
self_info = aapi.user_detail(client_info["user_id"])
following_users_num = self_info.profile.total_follow_users

# フォローユーザー一覧ページのページ数を取得
if(following_users_num%48 != 0):
    pages = (following_users_num//48)+1
else:
    pages = following_users_num//48

#タグ除去用
p = re.compile(r"<[^>]*?>")
# [jump:1]形式除去用
jump = re.compile(r"\[jump:.+\]")
#ファイルエンコード設定用
character_encoding = 'utf_8'

# Webスクレイパーのログイン処理
pixiv_url = 'https://www.pixiv.net'
browser = RoboBrowser(parser='lxml', history=True)
browser.open('https://accounts.pixiv.net/login')
form = browser.get_forms('form', class_='')[0]
form['pixiv_id'] = client_info["pixiv_id"]
form['password'] = client_info["password"]
browser.submit_form(form)

# フォローユーザー一覧ページのURLを設定
target_url = 'https://www.pixiv.net/bookmark.php?type=user&rest=show&p='

# 全てのフォローユーザーのユーザIDを取得
following_users_id = []
for i in range(1, pages+1):
    print(target_url + str(i))
    browser.open(target_url + str(i))
    following_users = browser.find(class_='members')
    for user in following_users.find_all("input"):
        following_users_id.append(user.get("value"))
    sleep(3) # ページを移動したら一時待機する（マナー）

print(following_users_id)
```
</div></details>

#### ▷ 実行例
```terminal:Terminal
$ python get_illustrations.py
Type illustrator pixiv id number:
>>>2246509
Artist: カナヘイ
Works: 234
============================================================
Procedure: 1/234
Title: つねー
URL: https://i.pximg.net/img-original/img/2018/02/01/15/19/01/67061760_ugoira0.jpg
Caption: ねーねーねこの動くスタンプ出ましたー！
1と2の中から人気のものを動かしたグレードアップ版です✨動くねーねー達をぜひ見てみてください☺
https://t.co/PX4UFAf72k
============================================================
Procedure: 2/234
Title: ただ単に勢い余ったうさぎ
URL: https://i.pximg.net/img-original/img/2018/02/01/15/17/31/67061749_p0.jpg
Caption: ただ単に勢い余ったうさぎ。
...
..
.
That's all.
```

### 3. **自分のフォローユーザー**の**全ての漫画・イラスト**をDLする

1と2の機能を統合したスクリプトです。

#### ▷ ソースコード本体
```python:get_all_illustrations.py

# -*- coding: utf-8 -*-

from pixivpy3 import *
import json
from time import sleep
import sys, io, re, os

from robobrowser import RoboBrowser
from bs4 import BeautifulSoup

f = open("client.json", "r")
client_info = json.load(f)
f.close()


##### 全てのフォローユーザーのユーザIDを取得 #####

# pixivpyのログイン処理
api = PixivAPI()
api.login(client_info["pixiv_id"], client_info["password"])
aapi = AppPixivAPI()
aapi.login(client_info["pixiv_id"], client_info["password"])

# フォローユーザーの総数を取得
self_info = aapi.user_detail(client_info["user_id"])
following_users_num = self_info.profile.total_follow_users

# フォローユーザー一覧ページのページ数を取得
if(following_users_num%48 != 0):
    pages = (following_users_num//48)+1
else:
    pages = following_users_num//48

#タグ除去用
p = re.compile(r"<[^>]*?>")
# [jump:1]形式除去用
jump = re.compile(r"\[jump:.+\]")
#ファイルエンコード設定用
character_encoding = 'utf_8'

# Webスクレイパーのログイン処理
pixiv_url = 'https://www.pixiv.net'
browser = RoboBrowser(parser='lxml', history=True)
browser.open('https://accounts.pixiv.net/login')
form = browser.get_forms('form', class_='')[0]
form['pixiv_id'] = client_info["pixiv_id"]
form['password'] = client_info["password"]
browser.submit_form(form)

# フォローユーザー一覧ページのURLを設定
target_url = 'https://www.pixiv.net/bookmark.php?type=user&rest=show&p='

# 渡されたディレクトリが存在しない場合に作成するLambda関数
mkdirExceptExist = lambda path: "" if os.path.exists(path) else os.mkdir(path)

# 全てのフォローユーザーのユーザIDを取得
following_users_id = []
for i in range(1, pages+1):
    print(target_url + str(i))
    browser.open(target_url + str(i))
    following_users = browser.find(class_='members')
    for user in following_users.find_all("input"):
        following_users_id.append(user.get("value"))
    sleep(3) # ページを移動したら一時待機する（マナー）

print("\n▽▽▽\n")


##### ダウンロード処理 #####

mkdirExceptExist("./pixiv_images/") # 保存用フォルダがない場合は生成

# 絵師IDから絵師情報を取得
for user_id in following_users_id:

    # ユーザ情報（作品数、絵師名）を取得
    user_info_json = aapi.user_detail(int(user_id))
    total_illusts = user_info_json.profile.total_illusts
    total_manga = user_info_json.profile.total_manga
    illustrator_name = user_info_json.user.name

    # イラスト情報を取得（とりあえず300作品取得）
    works_info = api.users_works(int(user_id), page=1, per_page=300)

    # # 絞り込み条件
    # target_tag = "target_tag"
    # lower_bookmarks = 0

    mkdirExceptExist("./pixiv_images/" + illustrator_name + "/") # 保存用フォルダがない場合は生成

    separator = "============================================================"
    print("Artist: %s" % illustrator_name)
    print("Works: %d" % works_info.pagination.total)
    print(separator)

    # ダウンロード
    for i, work_info in enumerate(works_info.response):

        work_type = "manga" if work_info.is_manga else "illust"

        saving_direcory_path = "./pixiv_images/" + illustrator_name + "/"
        mkdirExceptExist(saving_direcory_path + work_type) # 保存用フォルダがない場合は生成

        # # DL画像を絞り込む場合
        # if target_tag not in work_info.tags:  #タグによる絞り込み
        #   continue
        # works_bookmarks = work_info.stats.favorited_count.private + work_info.stats.favorited_count.public
        # if works_bookmarks < lower_bookmarks:  #ブックマーク数による絞り込み
        #   continue

        # ダウンロード
        work_title = work_info.title.replace("/", "-") # '/'はPathとして扱われるため回避

        print("Procedure: %d/%d" % (i + 1, works_info.pagination.total))
        print("Title: %s" % work_title)
        print("URL: %s" % work_info.image_urls.large)
        print("Caption: %s" % work_info.caption)
        print(separator)

        # 漫画の場合
        if work_info.is_manga:
            mkdirExceptExist(saving_direcory_path + "manga/" + work_title) # 保存用フォルダがない場合は生成
            
            manga_info = api.works(work_info.id)
            for page_no in range(0, manga_info.response[0].page_count):
                page_info = manga_info.response[0].metadata.pages[page_no]
                num = str(page_no) if len(str(page_no)) > 1 else "0" + str(page_no)
                aapi.download(page_info.image_urls.large, path=saving_direcory_path + "manga/" + work_title, name=num+".jpg")
                sleep(3)
        # イラストの場合
        else:
            aapi.download(work_info.image_urls.large, path=saving_direcory_path + "illust", name=work_title+".jpg")
            sleep(3)

print("\nThat\'s all.")
```

<details><summary>pixivpy 3.3.3 の場合（クリックで表示）</summary><div>

```python:get_all_illustrations.py
#!/usr/bin/env PYTHONIOENCODING=UTF-8 python3
# -*- coding: utf-8 -*-

from pixivpy3 import *
import json
from time import sleep
import sys, io, re, os

from robobrowser import RoboBrowser
from bs4 import BeautifulSoup

f = open("client.json", "r")
client_info = json.load(f)
f.close()


##### 全てのフォローユーザーのユーザIDを取得 #####

# pixivpyのログイン処理
api = PixivAPI()
api.login(client_info["pixiv_id"], client_info["password"])
aapi = AppPixivAPI()

# フォローユーザーの総数を取得
self_info = aapi.user_detail(client_info["user_id"])
following_users_num = self_info.profile.total_follow_users

# フォローユーザー一覧ページのページ数を取得
if(following_users_num%48 != 0):
    pages = (following_users_num//48)+1
else:
    pages = following_users_num//48

#タグ除去用
p = re.compile(r"<[^>]*?>")
# [jump:1]形式除去用
jump = re.compile(r"\[jump:.+\]")
#ファイルエンコード設定用
character_encoding = 'utf_8'

# Webスクレイパーのログイン処理
pixiv_url = 'https://www.pixiv.net'
browser = RoboBrowser(parser='lxml', history=True)
browser.open('https://accounts.pixiv.net/login')
form = browser.get_forms('form', class_='')[0]
form['pixiv_id'] = client_info["pixiv_id"]
form['password'] = client_info["password"]
browser.submit_form(form)

# フォローユーザー一覧ページのURLを設定
target_url = 'https://www.pixiv.net/bookmark.php?type=user&rest=show&p='

# 全てのフォローユーザーのユーザIDを取得
following_users_id = []
for i in range(1, pages+1):
    print(target_url + str(i))
    browser.open(target_url + str(i))
    following_users = browser.find(class_='members')
    for user in following_users.find_all("input"):
        following_users_id.append(user.get("value"))
    sleep(3) # ページを移動したら一時待機する（マナー）

print("\n▽▽▽\n")


##### ダウンロード処理 #####

# 絵師IDから絵師情報を取得
for user_id in following_users_id:

    json_result = api.users_works(int(user_id), per_page=300) # とりあえず１ユーザにつきmax300作品ダウンロードする
    total_works = json_result.pagination.total
    illust = json_result.response[0]

    # # 絞り込み条件
    # target_tag = "target tag"
    # lower_score = 0

    if not os.path.exists("./pixiv_images"): # 保存用フォルダがない場合は生成
        os.mkdir("./pixiv_images")
    saving_direcory_path = "./pixiv_images/" + illust.user.name + "/"
    separator = "------------------------------------------------------------"

    # ダウンロード
    print("Artist: %s" % illust.user.name)
    print("Works: %d" % total_works)
    print(separator)
    if not os.path.exists(saving_direcory_path):
        os.mkdir(saving_direcory_path)
    for work_no in range(0, total_works):
        # # DLする画像を絞り込む場合
        # if target_tag not in illust.tags: #タグによる絞り込み
        #   continue
        # if illust.stats.score < lower_score:#スコアによる絞り込み
        #   continue

        illust = json_result.response[work_no]
        print("Procedure: %d/%d" % (work_no + 1, total_works))
        print("Title: %s" % illust.title)
        print("URL: %s" % illust.image_urls["large"])
        print("Caption: %s" % illust.caption)
        # 漫画の場合
        if illust.is_manga:
            work_info = api.works(illust.id)
            for page_no in range(0, work_info.response[0].page_count):
                page_info = work_info.response[0].metadata.pages[page_no]
                aapi.download(page_info.image_urls.large, saving_direcory_path)
                sleep(3)
        # イラストの場合
        else:
            aapi.download(illust.image_urls.large, saving_direcory_path)
            sleep(3)
        print(separator)

print("\nThat\'s all.")
```
</div></details>

#### ▷ 実行例
```terminal:Terminal
$ python get_all_illustrations.py
https://www.pixiv.net/bookmark.php?type=user&rest=show&p=1
https://www.pixiv.net/bookmark.php?type=user&rest=show&p=2
https://www.pixiv.net/bookmark.php?type=user&rest=show&p=3

▽▽▽

Artist: カナヘイ
Works: 234
============================================================
Procedure: 1/234
Title: つねー
URL: https://i.pximg.net/img-original/img/2018/02/01/15/19/01/67061760_ugoira0.jpg
Caption: ねーねーねこの動くスタンプ出ましたー！
1と2の中から人気のものを動かしたグレードアップ版です✨動くねーねー達をぜひ見てみてください☺
https://t.co/PX4UFAf72k
============================================================
Procedure: 2/234
Title: ただ単に勢い余ったうさぎ
URL: https://i.pximg.net/img-original/img/2018/02/01/15/17/31/67061749_p0.jpg
Caption: ただ単に勢い余ったうさぎ。
...
..
.
That's all.
```
（※ソースコードを載せましたが、無茶な利用をするとBANされるのでご注意ください）

# 今後の展望
このスクリプトで好きなイラストを集めて、自分の好みのイラスト判別器でも作ろうかと。
判別器を使って、Tumblrで自動リブログとかしたいです。

でも、まずはシンプルに、似たキャラクターの分類器から始めようと思います。
積んであった[ゼロ本](https://www.amazon.co.jp/dp/4873117585)を読み始める機会がやってきたぞ...。

# 参考にしたサイト
- [pixivからイラストを無限に集めてみる[python] - MATHGRAM](http://www.mathgram.xyz/entry/scraping/pixiv)
- [PythonでWebスクレイピング：Pixivのブクマ情報をファイルで記録](http://pie001.hatenablog.com/entry/2017/12/21/210356)

## 余談
プログラミングが面倒な人のために、こんな便利ツールもあるみたいです。
▷ [pixivダウンローダ](http://shimarisu.webcrow.jp/pixiv.html)
<br />

【Updated: 2019-07-08（pixivpy 3.3.3 -> 3.3.6 に移行）】