QiitaのAPI、webスクレイピング、GPTを使用して記事書いてみた！実装手順(後編)

# はじめに
<!-- 発端や概要を記載 -->
こんにちは、エンジニア2年目の嶋田です。
まずは、この記事を開いていただきありがとうございます！
先日以下の記事を投稿しました。

https://qiita.com/shimada_slj/items/bc2cec77e687db95913b

この記事を実際にどのように生成したのかを解説していきたいと思います！
前編と後編の2部構成にしました。
後編では、実際のコードを公開し、コードの内容について説明します。

前編はこちら

https://qiita.com/shimada_slj/items/cfded87a32d4d13c6907

:::note warn
私が作成した記事は、完璧なものではありません。
実際のデータを使用して何か新しいものを作り出す、という基本的な流れのシェアです。
データの精度や記事の正確性に関してはまだまだ改善の余地があると思います。
学習過程の共有であると思いながら読んでいただけるとありがたいです。

もし、読者の皆さんの中で、より良い方法や改善点をお持ちの方がいれば、ぜひ教えてください🙇‍♂️
:::

# 概要
「**QiitaのAPIを利用して記事データを取得し、取得したデータを基にして新しい記事を生成すること**」が今回のゴールです。この過程では、複数の技術が用いられています。`requests`と`beautifulsoup4`を使ってWebスクレイピングを行い、`configparser`で設定ファイルを管理します。さらに、`langchain-community`と`openai`ライブラリを使用して、GPTモデルに基づいたテキスト生成を行っています。

## `/qiita/src/qiita.py`

```python
import requests
import csv
import configparser
import logging

logging.basicConfig(level=logging.INFO)

config = configparser.ConfigParser()
config.read('../config.ini')
access_token = config['Qiita']['access_token']

def fetch_and_save_articles(url, output_csv_file, headers):
    with open(output_csv_file, mode='w', newline='', encoding='utf-8') as file:
        writer = csv.writer(file)
        writer.writerow(['id', 'title', 'body', 'url'])

        response = requests.get(url, headers=headers)
        if response.status_code != 200:
            raise Exception(f"Request returned an error: {response.status_code} {response.text}")

        articles = response.json()

        for article in articles[:20]:
            article_id = article['id']
            title = article['title']
            body = article['body']
            url = article['url']
            body = body.replace('\n', ' ').replace('\r', ' ')
            writer.writerow([article_id, title, body, url])

        logging.info("Processed articles")

if __name__ == '__main__':
    headers = {'Authorization': f'Bearer {access_token}'}

    popular_articles_url = 'https://qiita.com/api/v2/items'
    popular_output_csv_file = '../data/qiita_popular_articles2.csv'
    fetch_and_save_articles(popular_articles_url, popular_output_csv_file, headers)
    username = 'shimada_slj'
    user_articles_url = f'https://qiita.com/api/v2/users/{username}/items'
    user_output_csv_file = '../data/qiita_shimada_articles2.csv'
    fetch_and_save_articles(user_articles_url, user_output_csv_file, headers)

```

## `/qiita/src/langchain-qiita.py`

```python
import csv
import random
import openai
from openai import OpenAI
import configparser
import os
import logging

logging.basicConfig(level=logging.INFO)

config = configparser.ConfigParser()
config.read('../config.ini')
OPENAI_API_KEY = config['OpenAI']['api_key']
client = OpenAI(api_key=OPENAI_API_KEY)

def load_csv_data(csv_file_path):
    articles = []
    with open(csv_file_path, mode='r', encoding='utf-8') as file:
        csv_reader = csv.DictReader(file)
        for row in csv_reader:
            articles.append(row)
    return articles

def choose_theme_from_popular_articles(popular_articles):
    titles = [article['title'] for article in popular_articles]
    return random.choice(titles)

def generate_inspired_theme(articles, theme):
    inspiration = "以下のテーマに基づいて新しい記事を作成してください:\n"
    inspiration += f"テーマ: {theme}\n"
    inspiration += "このテーマに触発された内容や書き方を踏まえて、"
    inspiration += "技術初学者をターゲットにし、実践的なガイド、コードスニペットを提供し、"
    inspiration += "読者がトレンドを理解し、自身のプロジェクトに活用できるようにすることを目指します。"
    return inspiration

def generate_article_with_openai(theme):
    response = client.chat.completions.create(
        model="gpt-3.5-turbo",
        messages=[
            {"role": "system", "content": f"{theme}\n"}
        ]
    )
    return response.choices[0].message.content

def save_article_to_txt(article, output_txt_file_path):
    with open(output_txt_file_path, 'w', encoding='utf-8') as file:
        file.write(article)

def load_generated_article(file_path):
    with open(file_path, 'r', encoding='utf-8') as file:
        return file.read()

def identify_sections(article_content):
    sections = {}
    current_section = None
    for line in article_content.split("\n"):
        if line.startswith("# "):
            current_section = line[2:]
            sections[current_section] = ""
        elif current_section:
            sections[current_section] += line + "\n"
    return sections

def generate_detailed_content_for_sections(sections):
    detailed_sections = {}
    for title, content in sections.items():
        prompt = f"以下のセクションについて詳細な記事を書いてください:\nセクション: {title}\n{content}\n詳細な内容:"
        detailed_content = generate_article_with_openai(prompt)
        detailed_sections[title] = detailed_content
    return detailed_sections



def assemble_article(detailed_sections):
    article = ""
    for title, content in detailed_sections.items():
        article += f"# {title}\n{content}\n\n"
    return article
def generate_detailed_article_with_openai(sections):
    prompt = "以下のセクションに基づいて詳細な記事を作成してください:\n"
    for title, content in sections.items():
        prompt += f"セクション: {title}\n概要: {content}\n\n"
    prompt += "詳細な内容:"

    response = client.chat.completions.create(
        model="gpt-3.5-turbo",
        messages=[
            {"role": "system", "content": prompt}
        ]
    )
    detailed_article = response.choices[0].message.content
    return detailed_article

# main関数内での使用例
def main():
    generated_article_path = '../generated/generated_article99.txt'
    article_content = load_generated_article(generated_article_path)
    sections = identify_sections(article_content)
    detailed_article = generate_detailed_article_with_openai(sections)
    save_article_to_txt(detailed_article, '../generated/detailed_generated_article.txt')

    logging.info("詳細な記事が保存されました: ../generated/detailed_generated_article.txt")

if __name__ == '__main__':
    main()

```

# コードの解説

## `/qiita/src/qiita.py`の解説

`qiita.py`では、Qiita APIを使って最新の人気記事や特定ユーザーの記事を取得し、それらをCSVファイルに保存しています。

- `configparser`を使用して、設定ファイルからQiitaのアクセストークンを読み込んでいます。
- `requests`ライブラリを使って、QiitaのエンドポイントにHTTP GETリクエストを送信しています。
- 応答から得られた記事データをCSV形式で保存しています。

これにより、記事生成のための生データとして使用できる情報を得ることができます。

## `/qiita/src/langchain-qiita.py`の解説

`langchain-qiita.py`では、取得した記事データを基にしてOpenAI GPT-3.5-turboモデルを使用し、新しい記事の生成を行っています。

- `load_csv_data`関数でCSVデータを読み込み、記事のリストを取得します。
- `choose_theme_from_popular_articles`関数でランダムにテーマを選択します。
- `generate_inspired_theme`関数で、選ばれたテーマを基に記事の概要を生成します。
- `generate_article_with_openai`関数で、GPT-3.5-turboモデルを用いて詳細な記事を生成します。
- `save_article_to_txt`関数で生成した記事をテキストファイルに保存します。

これにより、AIが生成した記事の初稿を作成し、さらにその内容を拡充することができます。

## 記事生成プロセスの最終段階

最後に、`main`関数では上記のステップをすべて組み合わせて、記事の生成から保存までのプロセスを自動化しています。

- `load_generated_article`関数で、GPT-3.5-turboによって生成された記事を読み込みます。
- `identify_sections`関数で、読み込んだ記事の中から各セクションを特定します。
- `generate_detailed_content_for_sections`関数で、各セクションに詳細な内容を追加します。
- `assemble_article`関数で、すべてのセクションを組み立てて完成記事を形成します。
- 最後に、`save_article_to_txt`関数でこの完成記事をファイルに保存し、プロセスを完了します。

# 最後に

以上でコードの解説を終えます。ここに示したコードは、実際に私が使用したものであり、QiitaのAPIからデータを取得して新しい記事を生成する一連のプロセスを自動化しています。

ぜひ皆さんもこのコードを実際に動かしてみて、自動生成を試してみてください！

もし、このコードや記事に関して質問や提案があれば、お気軽にコメントしてください。
最後までお付き合いいただき、ありがとうございました。