ChatGPTを使ってGoogle Patentから自動で最新の特許を要約して、メールを送る

お世話になります。@tmiyamanさんの記事を参考にして、GooglePatentから最新の特許が自動で送られてくる、GASを作成しました。
参考に下リンクは以下の通り、
https://qiita.com/tmiyama/items/646595ac76dab039b6ec

# GASスクリプトの全文

```
// API keys
const OPENAI_API_KEY = "chatgpt API";
var SERPAPI_API_KEY = "SERP API";
// 結果メールの送信先
const EMAIL_RECIPIENT = "メールアドレル";
// Key word
const keyword = '検索キーワード';
// 結果メールのタイトル
const EMAIL_SUBJECT = `Google Patents 要約 (${keyword}) `;
// 結果メールの送信者の名前
const EMAIL_SENDER = "要約bot";
// 最大特許数
const MAX_PATENT_COUNT = 5;
// 最新から検索対象日数
const DAYS = 365;

function main() {
  var results = getMostRecentPatentUrlSerpApi(keyword);
  
  if (results) {
    var output = "新着特許のお知らせ\n\n";
    var patentCount = 0;

    for (let i = 0; i < results.length; i++) {
      var title = results[i].title;
      var patentId = results[i].patent_id;
      var url = `https://patents.google.com/patent/${patentId.split('/')[1]}/en`;
      
      // 特許ページのHTMLを取得
      var htmlContent = UrlFetchApp.fetch(url).getContentText();
      
      // 出願日を取得
      var filingDate = extractFilingDate(htmlContent);
      
      // メタタグのdescriptionを抽出
      var metaDescription = extractMetaDescription(htmlContent);

      // ChatGPTで日本語200字程度に要約
      const input = `以下のメタタグdescriptionの内容を日本語で200字程度に要約してください。出願日: ${filingDate}\n\n${metaDescription}`;
      const res = callChatGPT(input);

      if (res && res.choices && res.choices.length > 0) {
        const paragraphs = res.choices.map((c) => c.message.content.trim());
        output += `Title: ${title}\n` + `URL: ${url}\n` + `出願日: ${filingDate}\n` + `${paragraphs.join("\n")}\n\n\n`;
      } else {
        output += `Title: ${title}\n` + `URL: ${url}\n` + `出願日: ${filingDate}\n` + `要約を取得できませんでした。\n\n\n`;
        Logger.log("要約の取得に失敗しました。レスポンス: " + JSON.stringify(res));
      }
      
      patentCount += 1;
      if (patentCount == MAX_PATENT_COUNT) break;
    }

    if (patentCount == 0) {
      output = "No new patents found \n ";
    } 
  } else {
    output = "No patents found (check keyword) \n ";
  }

  sendEmail(output);
}

function getMostRecentPatentUrlSerpApi(keyword) {
  var searchUrl = 'https://serpapi.com/search.json?q=' + encodeURIComponent(keyword) +
                  '&engine=google_patents' +
                  '&api_key=' + SERPAPI_API_KEY +
                  '&sort=new';  // ソートを最新順に設定
  
  var options = {
    'method': 'get',
    'muteHttpExceptions': true,
    'followRedirects': true
  };
  
  var response = UrlFetchApp.fetch(searchUrl, options);
  var content = JSON.parse(response.getContentText());
  
  if (content.organic_results && content.organic_results.length > 0) {
    return content.organic_results;
  } else {
    Logger.log("No patents found.");
    return null;
  }
}

function extractFilingDate(htmlContent) {
  // HTMLから出願日を抽出
  var filingDateStart = htmlContent.indexOf('<time itemprop="date" datetime="');
  if (filingDateStart === -1) return "出願日情報なし";

  filingDateStart = htmlContent.indexOf('datetime="', filingDateStart) + 10; // datetime=" の位置を特定
  var filingDateEnd = htmlContent.indexOf('"', filingDateStart); // " で閉じられる位置を特定
  if (filingDateEnd === -1) return "出願日情報なし";

  var filingDate = htmlContent.substring(filingDateStart, filingDateEnd).trim();

  return filingDate;
}

function extractMetaDescription(htmlContent) {
  // HTMLから<meta name="description" content="...">の内容を抽出
  var metaTagStart = htmlContent.indexOf('<meta name="description"');
  if (metaTagStart === -1) return "メタタグdescriptionが見つかりませんでした。";

  var contentStart = htmlContent.indexOf('content="', metaTagStart) + 9; // content=" の位置を特定
  var contentEnd = htmlContent.indexOf('"', contentStart); // " で閉じられる位置を特定
  if (contentEnd === -1) return "メタタグdescriptionの内容が見つかりませんでした。";

  var description = htmlContent.substring(contentStart, contentEnd).trim();

  return description;
}

function callChatGPT(input) {
    const messages = [
        {
            role: "user",
            content: input,
        },
    ];

    const url = "https://api.openai.com/v1/chat/completions";

    const options = {
        "method": "post",
        "headers": {
            "Authorization": `Bearer ${OPENAI_API_KEY}`,
            "Content-Type": "application/json",
        },
        "payload": JSON.stringify({
            model: "gpt-4o-mini",  // モデルをGpt4o-miniに設定
            messages: messages,
            max_tokens: 200,  // レスポンスのトークン数を制限
        }),
        "muteHttpExceptions": true  // 詳細なエラーメッセージを取得
    };

    try {
        const response = UrlFetchApp.fetch(url, options);
        const statusCode = response.getResponseCode();

        if (statusCode === 429) {
            // TPM制限に達した場合、30秒待機して再試行
            Logger.log("TPM制限に達しました。30秒待機して再試行します。");
            Utilities.sleep(30000);  // 30秒待機
            return callChatGPT(input);  // 再試行
        }

        const jsonResponse = JSON.parse(response.getContentText());

        // チェック: レスポンスが適切か
        if (jsonResponse && jsonResponse.choices && jsonResponse.choices.length > 0) {
            return jsonResponse;
        } else {
            Logger.log("ChatGPTレスポンスに問題があります。レスポンス: " + JSON.stringify(jsonResponse));
            return null;
        }
    } catch (e) {
        Logger.log("Error: " + e.message);
        return null;
    }
}

function sendEmail(body) {
    const options = { name: EMAIL_SENDER };
    GmailApp.sendEmail(EMAIL_RECIPIENT, EMAIL_SUBJECT, body, options);
}
```


# 以下はステップ・バイ・ステップでのスクリプトの概要です

このスクリプトは、特許情報を自動的に検索し、要約を生成し、メールで送信するプロセスを自動化します。

## 1. APIキーの設定
最初に、OpenAI APIキーとSERP APIキーを設定します。これにより、後でAPIリクエストを行う際に認証情報を提供します。

## 2. 検索条件とメール設定
次に、以下のパラメータを設定します。
- 検索するキーワード (`keyword`)
- 結果のメールを送信するアドレス (`EMAIL_RECIPIENT`)
- メールの件名 (`EMAIL_SUBJECT`)
- 検索結果の上限数 (`MAX_PATENT_COUNT`)
- 検索対象の日数 (`DAYS`)

## 3. `main` 関数
この関数はスクリプトのエントリーポイントで、以下のステップを順に実行します。

1. **特許の検索**:  
   `getMostRecentPatentUrlSerpApi` 関数を呼び出して、指定されたキーワードに基づいて最新の特許を検索します。

2. **検索結果の処理**:  
   取得した特許結果をループで処理します。各特許について、タイトル、特許ID、URLを取得し、特許の詳細ページを取得します。

3. **出願日の抽出**:  
   `extractFilingDate` 関数を使用して、特許ページから出願日を抽出します。

4. **メタタグdescriptionの抽出**:  
   `extractMetaDescription` 関数を使用して、特許ページのメタタグに含まれるdescriptionを抽出します。

5. **ChatGPTを使用した要約の生成**:  
   抽出したdescriptionと出願日を含めて、ChatGPTに要約を依頼します。要約の結果を保存します。

6. **メールの送信**:  
   すべての特許を処理した後、要約をメールで送信します。

## 4. `getMostRecentPatentUrlSerpApi` 関数
この関数は、指定されたキーワードを使用してSERP APIにリクエストを送り、最新の特許結果を取得します。取得した結果はJSON形式で解析され、特許の情報を返します。

## 5. `extractFilingDate` 関数
この関数は、特許ページのHTMLから出願日を抽出します。HTMLタグ `<time itemprop="date" datetime="...">` から出願日を取得し、返します。

## 6. `extractMetaDescription` 関数
この関数は、特許ページのHTMLからメタタグ `<meta name="description" content="...">` の内容を抽出します。これにより、特許の概要を取得します。

## 7. `callChatGPT` 関数
この関数は、ChatGPT APIを呼び出し、指定された入力テキストに基づいて要約を生成します。APIリクエストが失敗した場合、一定時間待機して再試行する処理も含まれています。

## 8. `sendEmail` 関数
最後に、この関数はGmailAppを使用して生成された要約を指定されたメールアドレスに送信します。送信者名も指定できます。

---

これらのステップに従ってコードが実行され、検索キーワードに基づく最新の特許情報を取得し、ChatGPTを使用して要約を生成し、それをメールで送信します。