スナックelve 本店

バツイチ40代女の日記です

1分おきに期間限定無料の漫画をツイートする

DMMのアフィリエイト申し込んでwebサービスでID保存しとく
楽天アフィリエイト申し込んで楽天ウェブサービスでID保存しておく
twitterのなんかID取得しておく。

やることは

  1. DMMから無料本の情報取得
  2. Koboから無料本の情報取得
  3. 1分おきにツイート
  • 生成時のツイート用情報はt_yyyymmddhhmm_ストア名__freeComicInfo.jsonに保存される。
  • ツイート後はyyyymmddhhmm_ストア名__freeComicInfo.jsonにリネームする
import requests
import urllib.parse
import json
import datetime
from twitter_text import parse_tweet
from os import rename
import glob
import json
from time import sleep
import tweepy

# ファイル名生成


def getFilename(kind):
    # 今の日時からファイル名を作成
    dt_now = datetime.datetime.now()
    fName = 't_'+dt_now.strftime('%Y%m%d%H%M%S') + \
        '_' + kind+'_freeComicInfo.json'
    f = open('./'+fName, 'w', encoding='utf-8')
    f.close()
    return fName

# urlパラメータ生成


def getApiUrl(reqUrl, para):
    urlPara = urllib.parse.urlencode(para)
    return reqUrl + urlPara

# 結果文字列の整形


def editFreeBookData(url, kind, sakusya, title, number, jsonItem):
    str = '【{0}】{1}の『{2}』{3} 今 0円'.format(kind, sakusya, title, number)
    # jsonの枝(item)作成
    jsonItem.append({'str': str, 'url': url})
    return jsonItem

# ファイル保存


def saveFile(jsonItem, fName):
    # json追加
    jsonItems = {'items': jsonItem}
    # ファイルに保存
    with open('./'+fName, 'a', encoding='utf-8') as f:
        json.dump(jsonItems, f, ensure_ascii=False)


# API情報を記入
BEARER_TOKEN = ''
API_KEY = ''
API_SECRET = ''
ACCESS_TOKEN = ''
ACCESS_TOKEN_SECRET = ''


# クライアント関数を作成
def ClientInfo():
    print("tweet2")
    client = tweepy.Client(bearer_token=BEARER_TOKEN,
                           consumer_key=API_KEY,
                           consumer_secret=API_SECRET,
                           access_token=ACCESS_TOKEN,
                           access_token_secret=ACCESS_TOKEN_SECRET,
                           )

    return client

# ツイート関数


def CreateTweet(message, client):
    print("tweet1")
    try:
        tweet = client.create_tweet(text=message)
        return tweet
    except tweepy.TweepError as e:
        print(e.message[0]['code'])
        print(e.args[0][0]['code'])
        return


def tweetFiles():
    # 対象ファイル取得(t_で始まるファイル)
    targetFiles = glob.glob('./t_*.json')
    for tFile in targetFiles:
        with open(tFile, encoding='utf-8') as f:
            # jsonからitemsを読み込み
            readJson = json.load(f)
            for item in readJson['items']:
                # itemごとに処理
                # urlは切れたら困るから最初に
                m = item['url'] + '\n' + item['str']
                if parse_tweet(m).valid == False:
                    # 文字が長過ぎたら切り詰めていく
                    i = 280
                    while parse_tweet(m).valid == False:
                        m = m[:i]
                        i -= 1
                    # 最後は省略の「…」
                    i -= 2
                    m = m[:i] + '…'
                    print(m)
                # ツイート
                CreateTweet(m)
                # 60秒待つ
                sleep(60)
        # 処理済みファイルの「t_」を削除
        nfilename = tFile[4:]
        rename(tFile, nfilename)


# メイン処理
kinds = ['DMMブックス', '楽天Kobo']
reqUrls = ['https://api.dmm.com/affiliate/v3/ItemList?',
           'https://app.rakuten.co.jp/services/api/Kobo/EbookSearch/20170426?']
paras = [{
    'api_id': 'vVgTxSx9WSzUSKGXCsZt',
    'affiliate_id': 'elve-990',
    'site': 'DMM.com',
    'service': 'ebook',
    'floor': 'comic',
    'hit': 30,
    'offset': 1,
    'keyword': '閲覧期限'
}, {
    'applicationId': '1077688777613086627',
    'affiliateId': '05eff714.c5ceaa1a.05eff716.192055f2',
    'elements': 'title,author,affiliateUrl,itemPrice',
    'formatVersion': 2,
    'koboGenreId': '101904',
    'keyword': '期間限定',
    'hits': 30,
    'sort': 'reviewCount',
    'page': 1
}]
for i in range(len(kinds)):
    fName = getFilename(kinds[i])
    # apiを叩く
    r = requests.get(getApiUrl(reqUrls[i], paras[i]))
    if r.ok:
        ret = r.json()
        jsonItem = []
        # OKだったら/ここはapi分岐するしかない?
        if kinds[i] == 'DMMブックス':
            for item in ret["result"]["items"]:
                if item["prices"]["price"] == 0:
                    authors = item["iteminfo"]["author"]
                    sakusya = ""
                    for author in authors:
                        if sakusya == "":
                            sakusya = author["name"] + "先生"
                        else:
                            sakusya += "、" + author["name"] + "先生"
                    editFreeBookData(
                        item["affiliateURL"], kinds[i], sakusya, item["title"], item["number"], jsonItem)
            saveFile(jsonItem, fName)
        elif kinds[i] == '楽天Kobo':
            for item in ret["Items"]:
                if item["itemPrice"] == 0:
                    sakusya = item["author"] + "先生"
                    editFreeBookData(
                        item["affiliateUrl"], kinds[i], sakusya, item["title"], "", jsonItem)
            saveFile(jsonItem, fName)
# 対象ファイル取得(t_で始まるファイル)
targetFiles = glob.glob('./t_*.json')
client = ClientInfo()
for tFile in targetFiles:
    with open(tFile, encoding='utf-8') as f:
        # jsonからitemsを読み込み
        readJson = json.load(f)
        for item in readJson["items"]:
            # itemごとに処理
            # urlは切れたら困るから最初に
            m = item["url"] + '\n' + item["str"]
            # 文字が長過ぎたら切り詰めていく
            i = 278
            m = m[:i] + "…"
            print(m)
            # ツイート
            CreateTweet(m, client)
            # 60秒待つ
            sleep(60)
    # 処理済みファイルの「t_」を削除
    nfilename = tFile[4:]
    rename(tFile, nfilename)

参考

[解決!Python]バイナリファイルを読み書きするには:pickle編:解決!Python - @IT
[解決!Python]日付や時刻をYYMMDDhhmmssなどの形式に書式化するには:解決!Python - @IT
Python の例外を別の例外として投げるときの話 - サーバーワークスエンジニアブログ
Pythonの2次元配列の使い方!初期化、追加、検索方法まとめ | プログラミングを学ぶならトレノキャンプ(TRAINOCAMP)
【Python】Scheduleモジュールを用いたイベント定期実行|指定時間動作のスケジュール関数作成例と使い方解説
pickle --- Python オブジェクトの直列化 — Python 3.10.6 ドキュメント
【Python】 f-stringの使い方の基本 | Hbk project
Pythonで現在時刻・日付・日時を取得 | note.nkmk.me
Pythonで文字列を分割(区切り文字、改行、正規表現、文字数) | note.nkmk.me
Pythonの例外処理(try, except, else, finally) | note.nkmk.me
pythonでtwitterが扱えるtweepyについて【サンプルコードあり】|python-manブログ
pythonでtwitterが扱えるtweepyについて【サンプルコードあり】|python-manブログ
Python で文字列に変数を埋め込む方法あれこれ - Qiita
Pythonで文字列 <-> 日付(date, datetime) の変換 - Qiita
Pythonでglobモジュールを使う方法【初心者向け】現役エンジニアが解説 | TechAcademyマガジン
python for文を初心者向けに解説!for文基礎はこれで…|Udemy メディア
Python|Udemy メディア
Python3入門 フォルダ内のファイル一覧を取得する方法
Python の辞書の配列またはリスト | Delft スタック
Python | ファイル名またはディレクトリ名を変更する
Python | if文を使った条件分岐
Python | formatメソッドを使った文字列の書式設定
【Python入門】os.renameでファイル名を変更する方法を解説! | 侍エンジニアブログ
python — tweepy例外インスタンスからエラーコードを取得します

結果をツイートしてる垢

よかったらフォローしておくんなまし

肉なし餃子は包むの難しい

今日はこちら
cookpad.com

餃子の皮が30枚だったので餡が結構残ってしまった。
↓こちらのレシピで下味付けた鶏肉と一緒に炒めたらピリ辛で美味しかったヽ(=´▽`=)ノ
erecipe.woman.excite.co.jp

中身がポロポロで包みにくい(´;ω;`)

美味しゅうございましたが野菜のみじん切りは色々ロスが大きいのであまりしたくないなぁと思いました(´;ω;`)

phpを捨てて僕は走り出す、pythonと

また後で検索しにくいタイトルにしてしまったwww

ほら、phpAPI叩いて情報取得できたので、phpのページ作って、そこにアクセスすると

情報取得

ファイルに保存

ファイルを読み込み

ツイート

って処理するのを作ろうと思ったわけよ。
なんで情報取得ですぐツイートしないかって? んと・・・なんかアレだよ・・・。えーっと・・・。
あ、そうそう、ほら、ファイルに保存するところをアフィリエイトサイトごとに作れば、ツイートの部分は共有できると思ったんやね。*1
後ファイルに保存して取っておけたら後々面白いかも? そうでもない?

で、ツイートする部分を作ろうと思ったんですけどもね・・・認証がなんか・・・。
developer.twitter.com
もう何一つわからん

13時位までは多分phpのことを調べていたんすが・・・分かんねぇな、こりゃ、と。ニンゲン諦めも肝心ですよね。
途中でpostmanとかってアプリも入れたけどさっぱりわからんかった。日本語・・・わしゃ日本語にうえておるんじゃ・・・。

コピペで行けるよ、そう、Pythonならね
di-acc2.com

ありがとうございます。tweepy神!!!
あとtwitter側でもちょっと設定しないと。

よくわからんけどコールバック関数とか埋めて、Access Token、Access Token Secretをゲットするんじゃ(吐血)
zenn.dev

URL入れるところは全部twitterのURL入れたわ・・・。わからん。

ソース

はい、コードです。
基本的に期間限定とタイトルに入っているモンを検索して、0円のだけ保存してます。
実行するたびに出力ファイルクリアするので注意。
保存する時ほっておくと文字コードが確かSーJISだったのでちゃんと指定してあげてね。

DMMブックス読み込み

これは新しい方から100件取得してますね・・・(統一性がないw)

from ast import Return
import requests
import urllib.parse
import json

f = open('./freeComicInfo.json', 'w', encoding='utf-8')
f.close()

reqUrl = "https://api.dmm.com/affiliate/v3/ItemList?"
para = {
    "api_id": "vVgTxSx9WSzUSKGXCsZt",
    "affiliate_id": "elve-990",
    "site": "DMM.com",
    "service": "ebook",
    "floor": "comic",
    "hit": 100,
    "sort": "date",
    "keyword": "期間限定"
}
urlPara = urllib.parse.urlencode(para)

r = requests.get(reqUrl + urlPara)
if r.ok:
    ret = r.json()
    jsonItem = []
    for item in ret["result"]["items"]:
        if item["prices"]["price"] == 0:
            authors = item["iteminfo"]["author"]
            sakusya = ""
            for author in authors:
                if sakusya == "":
                    sakusya = author["name"] + "先生"
                else:
                    sakusya += author["name"] + "先生"
            str = "{0}の『{1}』{2} 今 {3}円".format(
                sakusya, item["title"], item["number"], item["prices"]["price"])
            print(str)
            url = item["affiliateURL"]
            jsonItem.append({'str': str, 'url': url})
    jsonItems = {'items': jsonItem}

with open('./freeComicInfo.json', 'a', encoding='utf-8') as f:
    json.dump(jsonItems, f, ensure_ascii=False)

んー無料お試しじゃなくて期間限定でちゃんと読めるやつ取りたいなぁ

楽天コボ

こっちは何順のソートなんだろ? よくわからんスタンダードw
maxが30件だったので30件取ってます。

from ast import Return
import requests
import urllib.parse
import json

f = open('./freeComicInfo.json', 'w', encoding='utf-8')
f.close()

reqUrl = "https://app.rakuten.co.jp/services/api/Kobo/EbookSearch/20170426?"
para = {
    "applicationId":"1077688777613086627",
    "affiliateId":"05eff714.c5ceaa1a.05eff716.192055f2",
#    "elements":"title,author,affiliateUrl",
    "formatVersion":2,
    "koboGenreId":"101904",
    "keyword": "期間限定",
    "hits":30,
    "sort":"standard",

}
urlPara = urllib.parse.urlencode(para)
print(reqUrl + urlPara)
r = requests.get(reqUrl + urlPara)
if r.ok:
    ret = r.json()
    jsonItem = []
    for item in ret["Items"]:
        if item["itemPrice"] == 0:
            sakusya = item["author"] + "先生"
            str = "{0}の『{1}』今{2}円".format(
                sakusya, item["title"], item["itemPrice"])
            print(str)
            url = item["affiliateUrl"]
            jsonItem.append({'str': str, 'url': url})
    jsonItems = {'items': jsonItem}

    with open('./freeComicInfo.json', 'a', encoding='utf-8') as f:
        json.dump(jsonItems, f, ensure_ascii=False)

精度的にどうなんでしょうなぁ?(;´Д`)



ツイートする部分

#https://di-acc2.com/system/rpa/9748/

from nturl2path import url2pathname
import tweepy
from pprint import pprint
import schedule
from time import sleep
import json
# API情報を記入
BEARER_TOKEN = "なんか長いやつ"
API_KEY = "ちょい短め"
API_SECRET = "50文字くらい?"
ACCESS_TOKEN = "50文字くらい?"
ACCESS_TOKEN_SECRET = "50文字くらい?"


# クライアント関数を作成
def ClientInfo():
    client = tweepy.Client(bearer_token=BEARER_TOKEN,
                           consumer_key=API_KEY,
                           consumer_secret=API_SECRET,
                           access_token=ACCESS_TOKEN,
                           access_token_secret=ACCESS_TOKEN_SECRET,
                           )

    return client


# 関数
def CreateTweet(message):
    tweet = ClientInfo().create_tweet(text=message)
    return tweet

# 関数実行・結果出力
# pprint(CreateTweet(message))


with open('./freeComicInfo.json', encoding='utf-8') as f:
    readJson = json.load(f)
    for item in readJson["items"]:
        m = item["str"] + "\n" + item["url"]
        CreateTweet(m)

参考

【Python×Twitter】自動ツイート(投稿)を定期実行する方法|APIとtweepyを用いたbot開発支援
[解決!Python]エンコーディングを指定して、シフトJISなどのファイルを読み書きするには:解決!Python - @IT
PythonでJSON出力する際、日本語が文字化けする件 - Qiita
Pythonの辞書(dict)に要素を追加する方法まとめ | HEADBOOST
[解決!Python]リスト(配列)に要素を追加するには(+演算子/+=演算子/append/extend/insertメソッド):解決!Python - @IT
【Python入門】インクリメント演算子は使えない!?対処法まとめ | 侍エンジニアブログ
Python | 数値を文字列に変換して文字列と連結する
【Python】TypeError: string indices must be integers, not str - S氏はたまにblogを更新してます
Pythonで定数を定義する方法を現役エンジニアが解説【初心者向け】 | TechAcademyマガジン
【Python】文字列に変数を入れて文字列を動的に変更させる(format、f_strings) | OFFICE54
Pythonでファイルの読み込み、書き込み(作成・追記) | note.nkmk.me
JSONで配列の入れ子構造や値の取得方法などをPythonを使って説明! | 侍エンジニアブログ
【Python】open()のmodeについて - プログラミング勉強の備忘録
【Python入門】foreachをPythonで使ってみよう - Qiita
Python | if文を使った条件分岐
PythonでURLのクエリ文字列(パラメータ)を取得・作成・変更 | note.nkmk.me
【Python】jsonファイルを出力する | 鎖プログラム
PythonでURLエンコード・デコード(urllib.parse.quote, unquote) | note.nkmk.me
PythonでWeb APIを叩く - Qiita
Ruby、PHP、Python、JavaScriptの特徴とコードを比較してみた(言語選定と言語チェンジのための参考資料) - Qiita
【Python×Twitter】自動ツイート(投稿)を定期実行する方法|APIとtweepyを用いたbot開発支援
【Python】Scheduleモジュールを用いたイベント定期実行|指定時間動作のスケジュール関数作成例と使い方解説
Python で副業(?) Twitter API を使ってみる 〜その2:OAuth 2.0 で Twitter API curl で取得〜 - akiblog

*1:こういうのはおおよその勘で動き始めますよねw

よくわからないまま走り出す

というわけでTwitterAPI V2とやらを触ってみるよ。

JSON body parameters

POST /2/tweets | Docs | Twitter Developer Platform

の自分用メモ。今回はtextだけ有ればいい。

Name Type Description
direct_message_deep_link string Tweets a link directly to a Direct Message conversation with an account.
アカウントとのダイレクト メッセージの会話へのリンクを直接ツイートします。なにそれ怖い
Example: {"text": "Tweeting a DM deep link!", "direct_message_deep_link": "https://twitter.com/messages/compose?recipient_id=2244994945"}
for_super_followers_only boolean Allows you to Tweet exclusively for Super Followers.
スーパー フォロワー専用のツイートを許可します。だれ?それ。(なんか投げ銭的な?)
Example: {"text": "Hello World!", "for_super_followers_only": true}
geo object A JSON object that contains location information for a Tweet. You can only add a location to Tweets if you have geo enabled in your profile settings. If you don't have geo enabled, you can still add a location parameter in your request body, but it won't get attached to your Tweet
プロフィールで位置情報を有効にしてる時ツイートに場所を追加できます
geo.place_id string Place ID being attached to the Tweet for geo location.
場所ID
Example: {"text": "Tweeting with geo!","geo": {"place_id": "5a110d312052166f"}}
media object A JSON object that contains media information being attached to created Tweet. This is mutually exclusive from Quote Tweet ID and Poll.
メディア情報
media.media_ids array A list of Media IDs being attached to the Tweet. This is only required if the request includes the tagged_user_ids.
メディア情報ID
Example: {"text": "Tweeting with media!", "media": {"media_ids": ["1455952740635586573"]}}
media.tagged_user_ids array A list of User IDs being tagged in the Tweet with Media. If the user you're tagging doesn't have photo-tagging enabled, their names won't show up in the list of tagged users even though the Tweet is successfully created.
タグ付けされたユーザーID
Example: {"text": "Tagging users in images!", "media": {"media_ids": ["1455952740635586573"], "tagged_user_ids": ["2244994945","6253282"]}}
poll object A JSON object that contains options for a Tweet with a poll. This is mutually exclusive from Media and Quote Tweet ID.
投票
poll.duration_minutes number Duration of the poll in minutes for a Tweet with a poll. This is only required if the request includes poll.options.
投票時間
Example: {"text": "Tweeting with polls!", "poll": {"options": ["yes", "maybe", "no"], "duration_minutes": 120}}
poll.options array A list of poll options for a Tweet with a poll. For the request to be successful it must also include duration_minutes too.
投票の時必要っぽい感じ
Example: {"text": "Tweeting with polls!", "poll": {"options": ["yes", "maybe", "no"], "duration_minutes": 120}}"
quote_tweet_id string Link to the Tweet being quoted.
引用するツイートのID
Example: {"text": "Yay!", "quote_tweet_id": "1455953449422516226"}
reply object A JSON object that contains information of the Tweet being replied to.
リプライする時
reply.exclude_reply_user_ids array A list of User IDs to be excluded from the reply Tweet thus removing a user from a thread.
リプライするユーザーID
Example: {"text": "Yay!", "reply": {"in_reply_to_tweet_id": "1455953449422516226", "exclude_reply_user_ids": ["6253282"]}}
reply.in_reply_to_tweet_id string Tweet ID of the Tweet being replied to. Please note that in_reply_to_tweet_id needs to be in the request if exclude_reply_user_ids is present.
返信ツイートID。リプライするなら必須
Example: {"text": "Excited!", "reply": {"in_reply_to_tweet_id": "1455953449422516226"}}
reply_settings string Settings to indicate who can reply to the Tweet. Options include "mentionedUsers" and "following". If the field isn’t specified, it will default to everyone.
リプライ権限みたいなやつ。クソ
Example:{"text": "Tweeting with reply settings!", "reply_settings": "mentionedUsers"}
text string Text of the Tweet being created. This field is required if media.media_ids is not present.
ツイート内容。メディアが無いなら必須よ。
Example: {"text": "Hello World!"}

想像力の限界

危うくカズに騙されるところでした。
絵面に説得力があり過ぎで、思わず電車マニア(オタクまで行ってないと思う)の阿豆らいち (id:AzuLitchi)さんに確認。

そこで杭が出てきてもいいこと一つもないぞ!!
つんのめる人、轟く急ブレーキ、大破する車体、大量のけが人。
すぐ突っ込めなくて残念です・・・。