6
Help us understand the problem. What are the problem?

posted at

updated at

pixivpyでログインできなくなったので、新しいログイン方法の紹介(番外編3)

要約

こちらのスレッドの内容の紹介

ログイン方法が

original.py
api = AppPixivAPI()
api.login("username", "password")
aapi = AppPixivAPI()
aapi.login("username", "password")

だったものを

new.py
api = AppPixivAPI()
api.auth(refresh_token=REFRESH_TOKEN)
aapi = AppPixivAPI()
aapi.auth(refresh_token=REFRESH_TOKEN)

に変える。
なおREFRESH_TOKEN

こちらのスクリプトを使用して取得した。

このスクリプトをは有志が作ったものであり、公式のものではありません。 そのためログイン時のユーザーid、passwordは流出してもいいように捨て垢を使うなどの方法を使うことをお勧めします。

また何か問題が起きても___自己責任___でお願いします。

はじめに

2021年2月上旬ごろにpixiv.pyを使ったスクリプトが動かなくなってしまった。

PixivError: [ERROR] auth() failed! check username and password.
HTTP 400: {"has_error":true,"errors":{"system":{"message":"Invalid grant_type parameter or parameter missing","code":1508}},"error":"invalid_grant"}

こちらによると、pixivpyはもともとandroidアプリを通してログインをしていましたが、アップデートでログイン方法が変わったため、現在ログインできなくなったそうです。

有志によっていろいろ検討がなされた結果、リフレッシュトークンを使用することで今まで通り使用できるようになりました。
しかしトークンを一定期間ごとに手動で取得しなければならない可能性があり、完全自動化から半自動化に変わってしまう恐れがあるとのことです。
まだスレッドの内容を完全に理解していないので、間違いなどありましたらご指摘ください。

前準備

1.chromeが必要なので、ダウンロードしてください。

2.seleniumをダウンロードしてください

pip install selenium

seleniumとは、pythonでchromeを動かすライブラリです。
Python + Selenium で Chrome の自動操作を一通り
こちらを参考にしてください。

3.chromedriver.exeをダウンロードしてください。

chromedriver.exeはseleniumでchromeを動かすために必要なものです。
検索するとバイナリでダウンロードする方法などがあるそうですが、
今回はホームページから直接zipをダウンロードします。

ここからchromeのバージョンとosにあったものをダウンロード、
そして解凍

バージョンについては

こちらを参考に

4.REFRESH_TOKENの取得

こちらに従って実行してください。

ディレクトリ

このような配置にして、

image.png

プログラム

サイトのプログラムコピペです。

内容は理解していないうえ、実行してログインしなければならないので、何が起きるかわかりません。
そのためログイン時のユーザーid、passwordは流出してもいいように捨て垢を使うなどの方法を使うことをお勧めします。
自己責任でお願いします。

pixiv_auth.py
#!/usr/bin/env python

import time
import json
import re
import requests

from argparse import ArgumentParser
from base64 import urlsafe_b64encode
from hashlib import sha256
from pprint import pprint
from secrets import token_urlsafe
from sys import exit
from urllib.parse import urlencode
from selenium import webdriver
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities


# Latest app version can be found using GET /v1/application-info/android
USER_AGENT = "PixivAndroidApp/5.0.234 (Android 11; Pixel 5)"
REDIRECT_URI = "https://app-api.pixiv.net/web/v1/users/auth/pixiv/callback"
LOGIN_URL = "https://app-api.pixiv.net/web/v1/login"
AUTH_TOKEN_URL = "https://oauth.secure.pixiv.net/auth/token"
CLIENT_ID = "MOBrBDS8blbauoSck0ZfDbtuzpyT"
CLIENT_SECRET = "lsACyCD94FhDUtGTXi3QzcFE2uU1hqtDaKeqrdwj"


def s256(data):
    """S256 transformation method."""

    return urlsafe_b64encode(sha256(data).digest()).rstrip(b"=").decode("ascii")


def oauth_pkce(transform):
    """Proof Key for Code Exchange by OAuth Public Clients (RFC7636)."""

    code_verifier = token_urlsafe(32)
    code_challenge = transform(code_verifier.encode("ascii"))

    return code_verifier, code_challenge


def print_auth_token_response(response):
    data = response.json()

    try:
        access_token = data["access_token"]
        refresh_token = data["refresh_token"]
    except KeyError:
        print("error:")
        pprint(data)
        exit(1)

    print("access_token:", access_token)
    print("refresh_token:", refresh_token)
    print("expires_in:", data.get("expires_in", 0))


def login():
    caps = DesiredCapabilities.CHROME.copy()
    caps["goog:loggingPrefs"] = {"performance": "ALL"}  # enable performance logs

    driver = webdriver.Chrome("./chromedriver", desired_capabilities=caps)

    code_verifier, code_challenge = oauth_pkce(s256)
    login_params = {
        "code_challenge": code_challenge,
        "code_challenge_method": "S256",
        "client": "pixiv-android",
    }

    driver.get(f"{LOGIN_URL}?{urlencode(login_params)}")

    while True:
        # wait for login
        if driver.current_url[:40] == "https://accounts.pixiv.net/post-redirect":
            break
        time.sleep(1)

    # filter code url from performance logs
    code = None
    for row in driver.get_log('performance'):
        data = json.loads(row.get("message", {}))
        message = data.get("message", {})
        if message.get("method") == "Network.requestWillBeSent":
            url = message.get("params", {}).get("documentURL")
            if url[:8] == "pixiv://":
                code = re.search(r'code=([^&]*)', url).groups()[0]
                break

    driver.close()

    print("[INFO] Get code:", code)

    response = requests.post(
        AUTH_TOKEN_URL,
        data={
            "client_id": CLIENT_ID,
            "client_secret": CLIENT_SECRET,
            "code": code,
            "code_verifier": code_verifier,
            "grant_type": "authorization_code",
            "include_policy": "true",
            "redirect_uri": REDIRECT_URI,
        },
        headers={"User-Agent": USER_AGENT},
    )

    print_auth_token_response(response)


def refresh(refresh_token):
    response = requests.post(
        AUTH_TOKEN_URL,
        data={
            "client_id": CLIENT_ID,
            "client_secret": CLIENT_SECRET,
            "grant_type": "refresh_token",
            "include_policy": "true",
            "refresh_token": refresh_token,
        },
        headers={"User-Agent": USER_AGENT},
    )
    print_auth_token_response(response)


def main():
    parser = ArgumentParser()
    subparsers = parser.add_subparsers()
    parser.set_defaults(func=lambda _: parser.print_usage())
    login_parser = subparsers.add_parser("login")
    login_parser.set_defaults(func=lambda _: login())
    refresh_parser = subparsers.add_parser("refresh")
    refresh_parser.add_argument("refresh_token")
    refresh_parser.set_defaults(func=lambda ns: refresh(ns.refresh_token))
    args = parser.parse_args()
    args.func(args)


if __name__ == "__main__":
    main()



実行

python pixiv_auth.py login

実行するとchromeが立ち上がり、ログイン画面が出てくるので、入力してログインしてください。
image.png

結果

❯ python3 pixiv_auth.py login
[INFO] Get code: 3s3Xc075wd7njPLJBXgXc4qS-...
access_token: Fp9WaXhNapC8myQltgEn...
refresh_token: uXooTT7xz9v4mflnZqJ...
expires_in: 3600

そうするとこのように表示されるので、
refresh_token:
に続くuXooTT7xz9v4mflnZqJ...をコピーしてください

注意

refresh_tokenがあれば、だれでもログイン(?)できる状態になってしまいます。
具体的にはフォローを外したり、作品をお気に入りにしたりできます。
詳しくは

のfunctionsを中国語から日本語に訳して見てみてください。
よって、不用意にほかの人の目に触れないようにしましょう。
今回はそのためにリフレッシュトークンの後半を...に変えています。

プログラムの書き換え

original.py
api = AppPixivAPI()
api.login("username", "password")
aapi = AppPixivAPI()
aapi.login("username", "password")

だったものを

new.py
api = AppPixivAPI()
api.auth(refresh_token="uXooTT7xz9v4mflnZqJ")
aapi = AppPixivAPI()
aapi.auth(refresh_token="uXooTT7xz9v4mflnZqJ")

に変える。

最後に

有志が集まって新しいログイン方法を見つけだしていたので、まとめました。

何度も言いますが、___自己責任___でよろしくお願いします。

特に中国語の翻訳はgoogleさんにお願いしているので、何か間違っている可能性があります。
間違いなどありましたらご指摘お願いします。m(__)m

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
6
Help us understand the problem. What are the problem?