pythonでqiitaの記事を取得しラジオとして使う

## はじめに
qiitaのAPIをpythonで叩いて一覧を取得し、openAPIで音声データを作るということをしました。

## 背景
前回GPTsでAPI連携してラジオを作ったのですが、取得できるデータ量などの制限が多かったので今回はpythonでAPI連携した後に音声変換するということをしました。
前回は３件しか取得できなかったのですが、今回の方法ならば10件以上取得できます。（gptのAPI料金はかかりますが）

## 使い方
以降紹介するコードを実行すると、mp3で音声ファイル、txtで要約文のファイルが出来上がります。
使い方としては、mp3ファイルでニュースラジオとして流し聞きしてから、気になった記事だけtxtファイルのリンクから飛んで元記事を読みに行く流れになります。

txtファイルはこんな感じ
![スクリーンショット 2024-07-03 193813.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3680875/55d97ee2-6dbf-94d0-227d-9a848b204c1a.png)


## コード
pythonコードは以下です。ベタ書きなのは気にしないでください。
```ruby:qiita_9.py
from itertools import count
import requests
import json

from openai import OpenAI
from langchain.chat_models import ChatOpenAI
from langchain.schema import HumanMessage

client = OpenAI()

#定数
version = 9

chat = ChatOpenAI(
    model="gpt-3.5-turbo",
)

def callAndResponse(orderContext):
    result = chat(
        [
            HumanMessage(content=orderContext),
        ]
    )
    return result.content

# タグでqiita記事の一覧を取得
res = requests.get('https://qiita.com/api/v2/tags/生成AI/items?page=1&per_page=5')
jsonList = json.loads(res.text)
count = 0

# 各記事を処理
for item in jsonList:
    count += 1
    print(count)
    print(item['title'])

    # リンクから記事を要約
    eachUrl = item['url']
    orderContext_syokou = '以下のurlの記事を500文字程度で要約してください'
    orderContext = orderContext_syokou + ' ' + eachUrl
    print(orderContext)

    responseResult = callAndResponse(orderContext)
    print("出力結果取得完了")

    # ファイルに書き込む
    writeFileName = 'qiita_' + str(version) + '_1.txt'
    with open(writeFileName, 'a', encoding='utf-8') as outputFile:
        outputFile.write('記事' + str(count))
        outputFile.write('\n')
        outputFile.write('タイトル：')
        outputFile.write(item['title'])
        outputFile.write('\n')
        outputFile.write('内容：')
        outputFile.write(responseResult)
        outputFile.write('\n')
        outputFile.write('URL：')
        outputFile.write(eachUrl)
        outputFile.write('\n')
        outputFile.write('\n')
    print("----------------------------------------------------------------------------------------------")


# できたファイルを読み込んで、音声に変換
path = 'qiita_' + str(version) + '_1.txt'
with open(path, encoding='utf-8') as f:
    fileResult = f.read()

# テキストが4096文字を超えた場合に備えて、一応4096文字ごとに分割
parts = [fileResult[i:i+4096] for i in range(0, len(fileResult), 4096)]

count = 0
for part in parts:
    response = client.audio.speech.create(
        model="tts-1",
        voice="alloy",
        input=part,
    )
    # 各部分の音声をファイルに書き込む
    count += 1
    radioname = 'qiita_radio_' + str(version) + '_split' + str(count) + '.mp3'
    with open(radioname, "wb") as f:
        f.write(response.content)
    print("音声作成完了_split" + str(count))
```

このコードを実行するにあたって、
```ruby:
chat = ChatOpenAI(
    model="gpt-3.5-turbo",
)
```
などの部分でopenAPIを使ってます。よってOpenAIのAPIの認証はしておく必要があります。
以下の
```ruby:
    response = client.audio.speech.create(
        model="tts-1",
        voice="alloy",
        input=part,
    )
```
あたりでtext to speechで音声に変換しています。APIの認証をしていれば確かできたはずです。
最後にqiitaのAPIですが、
```ruby:
res = requests.get('https://qiita.com/api/v2/tags/生成AI/items?page=1&per_page=5')
```
今回は認証が必要ないものを使っているので特に何も準備は必要ないです。

## 追記
上記コードでは、タイトルと要約文が別のものにすり替わるという事案が発生。
（urlパスだけ指定してgpt-3.5に要約を頼んでいたのが原因。APIで取得したjsonデータのbodyからちゃんと文章だけを取得して要約を頼んだら解決しました。）

下記コードで正しい要約になるように修正したので、下記コードを使ってください。
その他、タグを複数「生成AI、GPTs、githubcopilot、Dify」などにして網羅的にしたり、要点３つを出力したりするような修正を行いました。
```ruby:qiita_23.py
import requests
import json

from openai import OpenAI
from langchain.chat_models import ChatOpenAI
from langchain.schema import HumanMessage

#定数
version = 23
id_stock = []

client = OpenAI()

chat = ChatOpenAI(
    model="gpt-3.5-turbo",
)

def callAndResponse(orderContext):
    result = chat(
        [
            HumanMessage(content=orderContext),
        ]
    )
    return result.content

def getQiitaAndWrite(tag_id, per_page):
    # タグでqiita記事の一覧を取得
    endpointUrl = 'https://qiita.com/api/v2/tags/' + tag_id + '/items?page=1&per_page=' + str(per_page)
    print('endpointUrl = ', endpointUrl)
    res = requests.get(endpointUrl)
    jsonList = json.loads(res.text)
    articleCount = 0

    # 各記事を処理
    for item in jsonList:
        # idをチェックし、重複があればスキップ
        item_string = str(item['id'])
        if item_string in id_stock:
            continue
        id_stock.append(str(item['id']))

        articleCount += 1
        print(articleCount)
        print(item['title'])
        print(item['url'])

        # リンクから記事を要約
        orderContext_syokou = '以下の【文章】を【出力形式】を参考にして150文字程度で要約してください\n\
\n\
【出力形式】：\n\
要点1:\n\
要点2:\n\
要点3:\n\
\n\
\n\
【文章】：「\n\
'
        orderContext = orderContext_syokou + item['body'] + '\n」'

        responseResult = callAndResponse(orderContext)
        print("出力結果取得完了")

        # ファイルに書き込む
        writeFileName = 'qiita_' + str(version) + '_1.txt'
        with open(writeFileName, 'a', encoding='utf-8') as outputFile:
            outputFile.write('タイトル：')
            outputFile.write(item['title'])
            outputFile.write('\n')
            outputFile.write(responseResult)
            outputFile.write('\n')
            outputFile.write('URL：')
            outputFile.write(item['url'])
            outputFile.write('\n')
            outputFile.write('\n')
            outputFile.write('\n')
        print("----------------------------------------------------------------------------------------------")

# 複数タグで網羅的に記事を取得
getQiitaAndWrite('GPTs', 1)
getQiitaAndWrite('githubcopilot', 1)
getQiitaAndWrite('Dify', 1)
getQiitaAndWrite('生成AI', 2)
print('総記事数(被りがないか) = ', len(id_stock))

# できたファイルを読み込んで、音声に変換
path = 'qiita_' + str(version) + '_1.txt'
with open(path, encoding='utf-8') as f:
    fileResult = f.read()

# テキストが4096文字を超えた場合に備えて、一応4096文字ごとに分割
parts = [fileResult[i:i+4096] for i in range(0, len(fileResult), 4096)]

articleCount = 0
for part in parts:
    response = client.audio.speech.create(
        model="tts-1",
        voice="alloy",
        input=part,
    )
    # 各部分の音声をファイルに書き込む
    articleCount += 1
    radioname = 'qiita_radio_' + str(version) + '_split' + str(articleCount) + '.mp3'
    with open(radioname, "wb") as f:
        f.write(response.content)
    print("音声作成完了_split" + str(articleCount))
```

## さらに追記
上記コードを動かすための準備として、LangChainと、APIキーの環境変数の設定を説明し忘れてることに気づきました。
その２つの設定なしで動くコードも下に載せておきます。ただし、APIキーの環境変数の設定をやってないぶん、安全性は劣ります。

```ruby:testCode_08.py
import requests
import json

from openai import OpenAI

# APIキーを直接書く方法。ここのXXX...をAPIキーに書き変える
client = OpenAI(api_key="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")

#定数
version = 8
id_stock = []

def callAndResponse(orderContext):
    response = client.chat.completions.create(
        model="gpt-3.5-turbo",
        messages=[
            {
                "role": "user", 
                "content": orderContext
            }
        ],
        temperature=0
    )
    text = response.choices[0].message.content
    return text

def getQiitaAndWrite(tag_id, per_page):
    # タグでqiita記事の一覧を取得
    endpointUrl = 'https://qiita.com/api/v2/tags/' + tag_id + '/items?page=1&per_page=' + str(per_page)
    print('endpointUrl = ', endpointUrl)
    res = requests.get(endpointUrl)
    jsonList = json.loads(res.text)
    articleCount = 0

    # 各記事を処理
    for item in jsonList:
        # idをチェックし、重複があればスキップ
        item_string = str(item['id'])
        if item_string in id_stock:
            continue
        id_stock.append(str(item['id']))

        articleCount += 1
        print(articleCount)
        print(item['title'])
        print(item['url'])

        # リンクから記事を要約
        orderContext_syokou = '以下の【文章】を【出力形式】を参考にして150文字程度で要約してください\n\
\n\
【出力形式】：\n\
要点1:\n\
要点2:\n\
要点3:\n\
\n\
\n\
【文章】：「\n\
'
        orderContext = orderContext_syokou + item['body'] + '\n」'

        responseResult = callAndResponse(orderContext)
        print("出力結果取得完了")

        # ファイルに書き込む
        writeFileName = 'qiita_' + str(version) + '_1.txt'
        with open(writeFileName, 'a', encoding='utf-8') as outputFile:
            outputFile.write('タイトル：')
            outputFile.write(item['title'])
            outputFile.write('\n')
            outputFile.write(responseResult)
            outputFile.write('\n')
            outputFile.write('URL：')
            outputFile.write(item['url'])
            outputFile.write('\n')
            outputFile.write('\n')
            outputFile.write('\n')
        print("----------------------------------------------------------------------------------------------")

# 複数タグで網羅的に記事を取得
getQiitaAndWrite('GPTs', 1)
getQiitaAndWrite('githubcopilot', 1)
getQiitaAndWrite('Dify', 1)
getQiitaAndWrite('生成AI', 2)
print('総記事数(被りがないか) = ', len(id_stock))

# できたファイルを読み込んで、音声に変換
path = 'qiita_' + str(version) + '_1.txt'
with open(path, encoding='utf-8') as f:
    fileResult = f.read()

# テキストが4096文字を超えた場合に備えて、一応4096文字ごとに分割
parts = [fileResult[i:i+4096] for i in range(0, len(fileResult), 4096)]

articleCount = 0
for part in parts:
    response = client.audio.speech.create(
        model="tts-1",
        voice="alloy",
        input=part,
    )
    # 各部分の音声をファイルに書き込む
    articleCount += 1
    radioname = 'qiita_radio_' + str(version) + '_split' + str(articleCount) + '.mp3'
    with open(radioname, "wb") as f:
        f.write(response.content)
    print("音声作成完了_split" + str(articleCount))

```