自分ブログ

Twitterスクレイピング環境構築

 

Boltを使ったTwitterスクレイピング環境構築

概要

この記事では、AIツール「Bolt」を活用して、Twitterから指定されたアカウントのツイートをスクレイピングするコードを生成し、実行するための環境構築と手順を解説します。


1. Boltへのプロンプト作成

まずは、Boltに入力するプロンプトを用意します。スクレイピングの目的や詳細を伝えることが重要です。

プロンプトの例

以下のプロンプトをBoltに入力してコードを生成します:

### **目的** - Twitterで指定されたアカウントからツイートを収集し、CSV形式で保存する。 ### **要件** 1. **基本動作** - 指定されたTwitterアカウントのツイート本文を取得する。 - 最大取得数: 300~500件。 - ツイートをCSV形式で保存する。 2. **Bot認定を避ける** - WebDriverの自動化検出を回避する設定を追加。 - 操作ごとにランダムな待機時間を挿入(例: 1.5秒~2.5秒)。 - ランダムなスクロール量(例: 200px~500px)を設定。 3. **UI要素** - 保存先フォルダを選択できる。 - ユーザーがTwitterアカウント名を入力できるフィールドを追加。 4. **動作の安定性** - エラーが発生した場合、停止理由を日本語でログ出力する。 - スクロールがページ最下部に到達した場合、取得を終了する。 - 動作ログをコンソールに表示(例: 「ツイートを収集中」「スクロールを実行」など)。 5. **使用ライブラリ** - Puppeteer。 - Node.jsでのUIライブラリ(例: React)。 ### **拡張性** - 必要に応じて、いいね数やリツイート数を取得できるようコメントで示してください。 - 生成されたコードに拡張ポイントをわかりやすくコメントとして挿入してください。

2. Boltが生成するコードの内容

Boltが生成したコードの例です(部分的に修正を加える場合もあります)。

import puppeteer from 'puppeteer'; import fs from 'fs'; (async () => { const browser = await puppeteer.launch({ executablePath: 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe', headless: true, }); const page = await browser.newPage(); const targetAccount = 'your_target_account'; // 取得したいTwitterアカウント const tweets = []; await page.goto(`https://twitter.com/${targetAccount}`); console.log(`Navigating to ${targetAccount}'s Twitter page...`); let previousHeight; while (tweets.length < 500) { const tweetElements = await page.$$('article [data-testid="tweetText"]'); for (let tweet of tweetElements) { const text = await tweet.evaluate(node => node.innerText); if (!tweets.includes(text)) tweets.push(text); } previousHeight = await page.evaluate(() => document.body.scrollHeight); await page.evaluate(() => window.scrollBy(0, window.innerHeight)); await new Promise(r => setTimeout(r, Math.random() * (2500 - 1500) + 1500)); // ランダム待機 const currentHeight = await page.evaluate(() => document.body.scrollHeight); if (currentHeight === previousHeight) break; } fs.writeFileSync('tweets.csv', tweets.join('\n')); console.log(`Saved ${tweets.length} tweets to tweets.csv`); await browser.close(); })();

3. 必要ツールのダウンロードとインストール

以下のツールをインストールします。

3.1 ChromeDriver

用途: PuppeteerやSeleniumChromeブラウザを操作する際に必要。

  1. 公式サイトからChromeDriverをダウンロードします:
  2. Chromeのバージョンに対応するドライバをダウンロードします。
  3. ダウンロード後、C:\Program Files\chromedriverに配置。

3.2 JDK

用途: Puppeteerと関連ツールで必要になる場合があるJavaランタイム。

  1. OpenJDKをインストール:
  2. インストール先ディレクトリ例:
    C:\Program Files\Java\jdk-21.0.5
  3. 環境変数JAVA_HOMEを設定:
    • JAVA_HOMEの値: C:\Program Files\Java\jdk-21.0.5
    • Path%JAVA_HOME%\binを追加。

3.3 Node.jsとnpm

用途: Puppeteerやその他のJavaScriptライブラリの管理。

  1. Node.js公式サイトからLTSバージョンをダウンロード。
  2. インストール後に以下で確認:
    node -v npm -v

4. 環境設定の手順

  1. ディレクトリの作成:

    mkdir "C:\Users\suui1\Document\Poject\java\bolt_scraper" cd "C:\Users\suui1\Document\Poject\java\bolt_scraper"
  2. Puppeteerのインストール:

    npm install puppeteer@latest
  3. Boltが生成したコードを配置:

    • scraper.jsファイルを作成し、Boltが生成したコードを貼り付けます。
  4. スクリプト実行:

    node scraper.js

5. 教育用ポイント

  1. つまずきやすい箇所:

    • 環境変数設定(JAVA_HOMEPath)。
    • Puppeteerのブラウザダウンロードエラー。
    • ESモジュールとCommonJSの違い(import vs require)。
  2. トラブルシューティング:

    • キャッシュ削除:
      npm cache clean --force
    • PUPPETEER_SKIP_DOWNLOADの設定:
      $env:PUPPETEER_SKIP_DOWNLOAD="true"

Puppeteerを使ったスクレイピングの学びポイント

Puppeteerを使ったスクレイピング環境構築と動作確認(詳細手順)

概要

この記事では、Puppeteerを使用したスクレイピング環境を構築し、動作確認を行う手順を解説します。特に初めての方でも理解しやすいよう、つまずいたポイントや解決方法を詳しく説明します。


1. 必要なツールと環境

1.1 インストールするもの

  • Node.js: JavaScriptランタイム。
  • npm: Node.jsに付属するパッケージマネージャー。
  • Puppeteer: Google Chromeを操作するためのライブラリ。
  • Chromeブラウザ: Puppeteerが操作するブラウザ。

1.2 作業環境


2. 初期設定手順

2.1 Node.jsのインストール

  1. Node.js公式サイトからLTSバージョンをダウンロードし、インストールします。
  2. インストール後、PowerShellで以下のコマンドを実行し、バージョン確認:
    node -v npm -v
    結果: バージョンが表示されれば成功。

2.2 Puppeteerのインストール

  1. プロジェクトディレクトリを作成します。
    mkdir "C:\Users\suui1\Document\Poject\java\bolt_scraper" cd "C:\Users\suui1\Document\Poject\java\bolt_scraper"
  2. Puppeteerをインストール:
    npm install puppeteer@latest
    注意点:
    • エラーが出た場合はキャッシュをクリア:
      npm cache clean --force

3. Puppeteerの動作確認

3.1 テストコード作成

  1. scraper_test.jsを作成し、以下のコードを記述:

    import puppeteer from 'puppeteer'; (async () => { const browser = await puppeteer.launch({ executablePath: 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe', }); const page = await browser.newPage(); await page.goto('https://example.com'); console.log('Page title is:', await page.title()); await browser.close(); })();
  2. ファイルをプロジェクトディレクトリ内に保存:

    C:\Users\suui1\Document\Poject\java\bolt_scraper\scraper_test.js

3.2 スクリプト実行

  1. PowerShellで以下を実行:
    cd "C:\Users\suui1\Document\Poject\java\bolt_scraper" node scraper_test.js
  2. 期待される結果:
    csharp
    Page title is: Example Domain

つまずいたポイント:

  • requireが使えないエラー:ESモジュール環境下ではimportを使用。
  • scraper_test.jsが見つからないエラー:ファイルの場所と拡張子を再確認。

4. キャッシュ削除と修正

  • キャッシュ削除:
    Remove-Item -Recurse -Force C:\Users\suui1\.cache\puppeteer
  • Puppeteerが自動でダウンロードしたブラウザを使用できない場合、ローカルのChromeパスを指定。

5. 動作確認後の後始末

  1. テスト用ファイル(scraper_test.js)は削除して問題ありません。
    Remove-Item scraper_test.js
  2. 必要に応じて、Boltが生成したスクレイピングコードの実行に進みます。

6. 学びポイント

  • エラーの種類と対処法:
    • ESモジュール対応の方法。
    • キャッシュ削除で解決した事例。
  • 動作確認の重要性:
    • 環境設定が正しいか確認しながら、一歩ずつ進める。

まとめ

これでPuppeteerを使ったスクレイピング環境が完成しました。この手順を基に、次は実際のスクレイピングコードの作成に進めます。特に初心者の方は、一つずつ動作確認を行い、エラーが出たら原因を特定することを意識してください。

要点qrea

要点まとめ

 

1. 基本設定とファイル配置:

index.htmlを適切なディレクトリ(public_html/vision-rr.com/など)に配置し、ドメインのルートとして設定します。

CSSや画像ファイルはindex.htmlからの相対パスを確認し、適切な場所に配置します。

2. パーミッション設定:

一般的なファイル(HTML、CSS、画像):644

PHPファイル:755

パーミッション設定が不適切だと、ファイルの読み込みや実行が制限される可能性があります。

3. 相対パス絶対パス:

サイト構造が変更される場合、相対パスが影響を受けるため、特定のケースでは絶対パスを使用することを検討します。

4. .htaccessの活用:

URLのリダイレクトやアクセス制限にRewriteRuleAllow/Denyディレクティブを使用し、柔軟なアクセス管理を実現できます。

5. FTPとファイル管理:

FTPクライアントを使って、ファイルを適切に管理し、定期的なバックアップを取ります。

CSSJavaScriptのキャッシュ問題を防ぐため、CSSリンクにバージョン番号を追加します(例:<link rel="stylesheet" href="css/style.css?v=1.0">)。

6. ドメイン共有とセキュリティ:

複数ユーザーでドメインを共有する際は、FTPアカウントやパーミッションを適切に設定し、アクセス権を制御します。

.htaccessを用いて、特定のIPアドレスからのみアクセスを許可する設定を行い、セキュリティを強化します。

7. トラブルシューティング:

ファイルのパスやリンクが正しく設定されていないと、CSSや画像が表示されないことがあります。

権限設定が間違っている場合、ページが読み込まれない、またはアクセス権限のエラーが発生します。

 

zipは日本語名前だとアップロードされない

xrea設定

タイトル: XREAでホームページを作成する際の注意点と効率的な運用方法

 

ウェブサイトの作成は、シンプルなHTMLとCSSのファイルから始まりますが、実際にXREAのようなホスティングサービスで運用する際には、いくつかのつまづきポイントがあります。本記事では、XREAでのウェブサイト作成から運用に至るまで、初心者がつまずきやすいポイントを詳細に解説し、効率的な運用方法を紹介します。

 

1. ウェブサイト作成のプロンプト

 

はじめてHTMLとCSSを使ってサイトを作成する際の指示は以下のように詳細にすることで効率的です:

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>サイトタイトル - 事業内容と問い合わせ</title>
    <link rel="stylesheet" href="css/style.css">
  </head>
  <body>
    <!-- ヘッダー -->
    <header>
      <h1>会社名</h1>
      <img src="image/logo.png" alt="会社ロゴ" class="header-logo">
    </header>
    <!-- メインコンテンツ -->
    <section id="main-content">
      <h2>事業内容</h2>
      <p>詳細情報...</p>
    </section>
    <!-- フッター -->
    <footer>
      <p>&copy; 2024 会社名. All rights reserved.</p>
    </footer>
  </body>
</html>

 

css

body {

  font-family: Arial, sans-serif;

  background-color: #f0f0f0;

  color: #333;

  margin: 0;

  padding: 0;

}

 

header {

  background-color: #004080;

  color: #ffffff;

  padding: 20px;

  text-align: center;

}

 

.header-logo {

  max-width: 150px;

  margin: 0 auto;

}

 

footer {

  background-color: #333;

  color: #fff;

  text-align: center;

  padding: 10px;

}

 

 

 

2. XREAでのつまづいたポイントと対策

 

つまづきポイント:

 

ファイルのアップロードとディレクトリ構造:

初心者が陥りやすいのが、HTMLファイルやCSSファイル、画像を適切なディレクトリに配置しないことでアクセスできないこと。public_htmlフォルダに配置されていないと、404エラーが発生することがあります。

対策:

すべての主要なファイルはpublic_html内、またはそのドメイン用のサブフォルダ内に配置する。

ファイルパスは常に相対パスを確認し、HTML内でリンクされているリソースが正しく参照されるかテストする。

パーミッションの設定:

CSSやHTMLファイルが表示されない原因の一つはパーミッションの設定。644(読み取り専用)に設定することが基本ですが、時に755が必要な場合もあります。

注意:

PHPファイルは通常755が推奨されます。

画像ファイルやCSS、JSファイルは644に設定して問題ありません。

.htaccessファイルの設定:

.htaccessファイルを使用してリダイレクトやアクセス制御を行うと、設定次第でサイト全体が見えなくなることがあります。リダイレクトやURL書き換えを行う際には、慎重に設定しましょう。

RewriteEngine On
RewriteRule ^$ /LP_vision/ [L]

 

 

3. 実際の運用で気をつけるポイント

 

キャッシュの問題:

XREAや他のCDNサービスを利用している場合、サイトの更新が反映されないことがあります。ブラウザのキャッシュをクリアし、再読み込みすることで問題が解決します。

 

アクセス制限の設定:

管理パネルでアクセス制限を設定すると、思わぬエラーが発生することがあります。セキュリティは重要ですが、制限を加えすぎると自分自身がサイトにアクセスできなくなることもあります。

 

ドメインとフォルダ構造の一致:

特定のディレクトリにサイトを配置している場合、ドメインとそのディレクトリの構造が一致しているか確認してください。間違った場所にファイルを置くと、正しく表示されません。

 

4. 簡単にできることと注意点

 

バックアップの取り方:

定期的にFTPクライアントを使ってサイト全体をバックアップする習慣をつけましょう。重要なデータの損失を防ぐために、データベースのバックアップも含めます。

パスの管理:

全てのリソースは相対パスでリンクされていることを確認し、ファイル構成が変わったときにはリンクが正しく機能しているかをテストします。

 

まとめ

 

XREAでのサイト運用は、最初の設定や構造の理解が鍵です。ファイルの配置やパーミッション設定、index.htmlの場所など、基本的な要素をしっかりと確認することで、スムーズなサイト構築と運用が可能になります。問題が発生した場合は、サーバーログやエラーメッセージを参考に迅速に対応しましょう。

 

この内容を元にブログ記事として公開することで、他のウェブ制作者にとっても非常に役立つ情報になるでしょう。

 

5. ドメインの共有方法

 

ドメインを複数のユーザーと共有したり、外部の開発者にアクセス権限を付与したりすることは、共同開発や運用の効率化に役立ちます。XREA.comドメインを共有する際の手順と注意点を以下に示します。

 

ドメイン共有の目的と準備

 

複数の開発者での共同管理: チームでWebサイトの開発・運用を行う場合、ドメインの共有設定が必要です。

セキュリティ確保: ドメイン共有時はアクセス権限の範囲を明確にし、不必要な権限を与えないように注意します。

 

共有の具体的な手順

 

1. DNS設定の確認:

XREAのコントロールパネルにログインし、DNS設定から共有したいドメインのレコードを確認・編集します。

必要に応じて、サブドメインを作成してアクセス権限を分けることも可能です。

2. FTPアクセス権の設定:

FTPアカウントを追加: コントロールパネルで新規FTPアカウントを作成し、開発者ごとに異なるアカウントを発行します。

パーミッションの設定: 各アカウントのアクセス範囲を設定し、必要なディレクトリのみアクセス可能にします。

3. ファイル・ディレクトリの共有:

必要なディレクトリを他のユーザーと共有する場合、アクセス権限を確認し、644755など適切なパーミッションを設定します。

4. 管理者権限の調整:

管理者アカウントでの監視: 管理者はすべてのアクセスを監視し、設定変更やアクセス権の見直しを定期的に行います。

.htaccessによる制御: 特定のIPアドレスからのみアクセスを許可する設定を.htaccessファイルで行い、セキュリティを強化します。

自動TwitterBOTデプロイ編

Twitterボット開発プロジェクト: デプロイから現在までの歩みと今後の展望


はじめに

Twitter APIを利用して、人間らしい動作を模倣するTwitterボットを作成し、Herakuにデプロイしています。 この記事では、開発の全体像、検討した課題、解決策、現在の進捗状況について整理しています。


プロジェクトの目的

目的: Twitter APIを利用して、人間らしい動作を模倣するTwitterボットを作成する。

機能の概要:

  1. 自動ツイート:

    • 1日に最大3件までランダムな間隔でツイートする。
    • コンテンツは指定されたもの、あるいはランダムに生成される。
  2. 自動DM返信:

    • 受信したDMに対して自動で返信し、1日200件までの返信に制限します。
    • 返信には少しランダムな要素を取り入れます。
  3. タグ検索&自動フォロー:

    • 指定されたハッシュタグを1時間に1回検索し、そのタグを使ったアカウントをフォローする。
    • 1日のフォロー数を100件までに制限します。
  4. 自動いいね:

    • 1日に50件までツイートに「いいね」をします。
    • いいねするツイートはタグ検索結果からランダムに選び、ランダムなタイミングで行います。

開発の流れ

1. 環境設定

まず、Pythonと必要なライブラリをインストールして開発環境を準備しました。

バッシュ
pip install tweepy requests

次に、プロジェクトの構造を以下のように設定しました。

メイクファイル
C:\Users\suui1\.vscode\PROGRAM ├── 13Twitter │ └── twitter_bot.py ├── requirements.txt └── Procfile

requirements.txtに必要なライブラリを書きます。

文章
tweepy requests

Procfileは、Herakuでプロジェクトを動かすための設定です。

文章
worker: python 13Twitter/twitter_bot.py
2. Botのコード作成

次に、Twitter APIを使ってツイートを投稿する基本的なコードを作成しました。

パイソン
import requests # Twitter APIエンドポイント url = "https://api.twitter.com/2/tweets" # Bearer Tokenの設定 bearer_token = 'YOUR_BEARER_TOKEN' # ヘッダーの設定 headers = { "Authorization": f"Bearer {bearer_token}", "Content-Type": "application/json" } # ツイート内容 payload = { "text": "Hello from Twitter API v2!" } # POSTリクエストでツイートを投稿 response = requests.post(url, headers=headers, json=payload) # 結果を表示 print(f"Response status code: {response.status_code}") print(f"Response data: {response.json()}")

このコードを実行することで、テストツイートを投稿することができました。


デプロイ時のトラブルと解決策

Herokuへのデプロイ中に、いくつかのエラーが発生しました。

エラー1: 「デフォルトの言語が検出されませんでした」

問題: Heroku が言語を自動検出できず、ビルドパックが適用されませんでした。

解決策: ビルドパックを手動で設定します。

バッシュ
heroku buildpacks:set heroku/python -a twitter1012
エラー2: 「参照のプッシュに失敗しました」

問題: Git でリモートリポジトリに意見する際にエラーが発生しました。

解決策: キャッシュをクリアして解決。

バッシュ
heroku repo:purge_cache -a twitter1012

現在の進捗

  • Heroku に正しくデプロイされ、アプリケーションが正常に動作していることを確認しました。
  • Pythonコードを使用して、Twitterへのツイート投稿が成功しました。

シフト

  1. 自動DM返信機能の実装
  2. タグ検索&自動フォロー機能の実装
  3. 自動いいね機能の追加

この記事では、Heraku でのデプロイまでの流れとトラブル解決について紹介しました。次回の記事では、自動 DM 返信機能の実装について詳しく説明します。


こちらが現在の進捗状況です。この内容をブログ記事としてまとめていただければ、今後の進捗状況にも考えられます。

gmailライン通知 URL摘出

使えるコード
だめなコード例
理由はソースコードに空白や欲しい情報以外にコードが入ってします。なので下記のextractUsingRegex関数:を追加すると不要なコードを削除してくれる

追記9/29がスペースも除去してる

gmailの設定方法

設定のすべての設定を選択してフィルタのところで設定

 

追記9・29
改行と整理完璧

改行がうまくいかなかったら何か記号をつけての後に/n

を入れるとうまくいく

 

var lineToken = "  "; // LINE Notifyのトーク

function send_line(Me) {
    var payload = { 'message': Me };
    var options = {
        "method": "post",
        "payload": payload,
        "headers": { "Authorization": "Bearer " + lineToken }
    };
    UrlFetchApp.fetch("https://notify-api.line.me/api/notify", options);
}

function fetchContactMail() {
    var ISREAD_LABEL = GmailApp.getUserLabelByName('通知済み');
    var strTerms = ('label:通知対象 -label:通知済み');

    var myThreads = GmailApp.search(strTerms);
    var myMsgs = GmailApp.getMessagesForThreads(myThreads);

    for (var i = 0; i < myMsgs.length; i++) {
        var msg = myMsgs[i].slice(-1)[0];
        if (!msg) continue;  // メッセージが空の場合を回避

        var body = msg.getPlainBody();
        
        // URLを検索
        var urlMatch = body.match(/https:\/\/assist[^\s]*/);
        if (urlMatch) {
            var url = urlMatch[0];

            try {
                // URLのコンテンツを取得
                var response = UrlFetchApp.fetch(url);
                var html = response.getContentText();

                // <tr>内の<th>と<td>のペアを抽出し、電話番号や依頼内容も含める
                var items = [];
                var trMatch = html.match(/<tr>\s*<th>(.*?)<\/th>\s*<td>(.*?)<\/td>\s*<\/tr>/g);
                if (trMatch) {
                    trMatch.forEach(function(row) {
                        var matches = row.match(/<th>(.*?)<\/th>\s*<td>(.*?)<\/td>/);
                        if (matches) {
                            var key = matches[1].trim();
                            var value = matches[2].replace(/<[^>]+>/g, '').trim(); // HTMLタグを除去してテキスト部分のみ取得
                            if (key === '電話番号' && value.includes('tel:')) {
                                // 電話番号は<a>タグ内にあるので特別処理
                                var phoneNumber = value.match(/tel:(\d+)/);
                                if (phoneNumber) {
                                    value = phoneNumber[1];
                                }
                            }
                            items.push(key + ": " + value);
                        }
                    });
                }

                // 必要な項目をLINEに送信
                if (items.length > 0) {
                    send_line("案件情報:\n" + items.join("\n"));
                } else {
                    send_line("必要な項目が見つかりませんでした。");
                }
            } catch (e) {
                send_line("URLの取得に失敗しました: " + e.message);
            }
        } else {
            send_line("URLが見つかりませんでした。");
        }
    }

    // メールを「通知済み」にラベル付け
    myThreads.forEach(function (t) {
        t.addLabel(ISREAD_LABEL);
    });
}

function main() {
    fetchContactMail();
}

ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー

 

var lineToken = " "; // LINE Notifyのトーク

// LINEにメッセージを送信する関数
function sendToLine(message) {
    var options = {
        "method": "post",
        "headers": { "Authorization": "Bearer " + lineToken },
        "payload": { "message": message }
    };
    UrlFetchApp.fetch('https://notify-api.line.me/api/notify', options);
}

// 正規表現を使ってHTMLからデータを抽出する関数
function extractUsingRegex(html, regex) {
    var match = html.match(regex);
    return match ? match[1].replace(/&nbsp;/g, ' ').replace(/&gt;/g, '>').replace(/<[^>]+>/g, '').trim() : 'データなし';
}

// 案件情報を抽出し、LINEに送信する関数
function extractData(html) {
    // 正規表現で案件情報を抽出
    var 案件番号 = extractUsingRegex(html, /案件番号<\/th>\s*<td>\s*(.*?)\s*<\/td>/s);
    var 屋号 = extractUsingRegex(html, /屋号<\/th>\s*<td>\s*(.*?)\s*<\/td>/s);
    var お名前 = extractUsingRegex(html, /お名前<\/th>\s*<td>\s*(.*?)\s*<\/td>/s);
    var 電話番号 = extractUsingRegex(html, /電話番号<\/th>\s*<td>\s*<a.*?>\s*(.*?)\s*<\/a>/s);
    var 住所 = extractUsingRegex(html, /住所<\/th>\s*<td>\s*(.*?)\s*<\/td>/s);
    var email = extractUsingRegex(html, /Email<\/th>\s*<td>\s*(.*?)\s*<\/td>/s);
    var 依頼日時 = extractUsingRegex(html, /依頼日時<\/th>\s*<td>\s*(.*?)\s*<\/td>/s);
    var 依頼内容 = extractUsingRegex(html, /依頼内容<\/th>\s*<td>\s*(.*?)\s*<\/td>/s);

    // LINE通知の内容
    var message = `
    案件番号: ${案件番号}
    屋号: ${屋号}
    お名前: ${お名前}
    電話番号: ${電話番号}
    住所: ${住所}
    Email: ${email}
    依頼日時: ${依頼日時}
    依頼内容: ${依頼内容}
    `;

    sendToLine(message);  // LINE通知の関数呼び出し
}

function fetchContactMail() {
    var ISREAD_LABEL = GmailApp.getUserLabelByName('通知済み');
    var strTerms = ('label:通知対象 -label:通知済み');

    var myThreads = GmailApp.search(strTerms);
    var myMsgs = GmailApp.getMessagesForThreads(myThreads);

    for (var i = 0; i < myMsgs.length; i++) {
        var msg = myMsgs[i].slice(-1)[0];
        if (!msg) continue;  // メッセージが空の場合を回避

        var body = msg.getPlainBody();
        
        // URLを検索
        var urlMatch = body.match(/https:\/\/assist[^\s]*/);
        if (urlMatch) {
            var url = urlMatch[0];

            try {
                // URLのコンテンツを取得
                var response = UrlFetchApp.fetch(url);
                var html = response.getContentText();

                // 案件情報を抽出してLINEに送信
                extractData(html);

            } catch (e) {
                sendToLine("URLの取得に失敗しました: " + e.message);
            }
        } else {
            sendToLine("URLが見つかりませんでした。");
        }
    }

    // メールを「通知済み」にラベル付け
    myThreads.forEach(function (t) {
        t.addLabel(ISREAD_LABEL);
    });
}

function main() {
    fetchContactMail();
}

追記9/29


var lineToken = "  "; // LINE Notifyのトーク

// LINEにメッセージを送信する関数
function sendToLine(message) {
    var options = {
        "method": "post",
        "headers": { "Authorization": "Bearer " + lineToken },
        "payload": { "message": message }
    };
    UrlFetchApp.fetch('https://notify-api.line.me/api/notify', options);
}

// 正規表現を使ってHTMLからデータを抽出する関数
function extractUsingRegex(html, regex) {
    var match = html.match(regex);
    return match ? match[1].replace(/&nbsp;/g, ' ').replace(/&gt;/g, '>').replace(/<[^>]+>/g, '').replace(/\s+/g, ' ').trim() : 'データなし';
}

// 案件情報を抽出し、LINEに送信する関数
function extractData(html) {
    // 正規表現で案件情報を抽出
    var 案件番号 = extractUsingRegex(html, /案件番号<\/th>\s*<td>\s*(.*?)\s*<\/td>/s);
    var 屋号 = extractUsingRegex(html, /屋号<\/th>\s*<td>\s*(.*?)\s*<\/td>/s);
    var お名前 = extractUsingRegex(html, /お名前<\/th>\s*<td>\s*(.*?)\s*<\/td>/s);
    var 電話番号 = extractUsingRegex(html, /電話番号<\/th>\s*<td>\s*<a.*?>\s*(.*?)\s*<\/a>/s);
    var 住所 = extractUsingRegex(html, /住所<\/th>\s*<td>\s*(.*?)\s*<\/td>/s);
    var email = extractUsingRegex(html, /Email<\/th>\s*<td>\s*(.*?)\s*<\/td>/s);
    var 依頼日時 = extractUsingRegex(html, /依頼日時<\/th>\s*<td>\s*(.*?)\s*<\/td>/s);
    var 依頼内容 = extractUsingRegex(html, /依頼内容<\/th>\s*<td>\s*(.*?)\s*<\/td>/s);

    // LINE通知の内容(無駄なスペースを取り除く)
    var message = [
        `案件番号: ${案件番号}`,
        `屋号: ${屋号}`,
        `お名前: ${お名前}`,
        `電話番号: ${電話番号}`,
        `住所: ${住所}`,
        `Email: ${email}`,
        `依頼日時: ${依頼日時}`,
        `依頼内容: ${依頼内容.replace(/\s+/g, ' ').trim()}` // 特に依頼内容の余計なスペースを削除
    ].map(line => line.trim()).join("\n");

    sendToLine(message);  // LINE通知の関数呼び出し
}

function fetchContactMail() {
    var ISREAD_LABEL = GmailApp.getUserLabelByName('通知済み');
    var strTerms = ('label:通知対象 -label:通知済み');

    var myThreads = GmailApp.search(strTerms);
    var myMsgs = GmailApp.getMessagesForThreads(myThreads);

    for (var i = 0; i < myMsgs.length; i++) {
        var msg = myMsgs[i].slice(-1)[0];
        if (!msg) continue;  // メッセージが空の場合を回避

        var body = msg.getPlainBody();
       
        // URLを検索
        var urlMatch = body.match(/https:\/\/assist[^\s]*/);
        if (urlMatch) {
            var url = urlMatch[0];

            try {
                // URLのコンテンツを取得
                var response = UrlFetchApp.fetch(url);
                var html = response.getContentText();

                // 案件情報を抽出してLINEに送信
                extractData(html);

            } catch (e) {
                sendToLine("URLの取得に失敗しました: " + e.message);
            }
        } else {
            sendToLine("URLが見つかりませんでした。");
        }
    }

    // メールを「通知済み」にラベル付け
    myThreads.forEach(function (t) {
        t.addLabel(ISREAD_LABEL);
    });
}

function main() {
    fetchContactMail();
}

 

追記9・29

var lineToken = "dwTIX9rNyiHReGKMuJc9XpBkEJu5SVia6YigmDCQKos"; // LINE Notifyのトーク

// LINEにメッセージを送信する関数
function sendToLine(message) {
    var options = {
        "method": "post",
        "headers": { "Authorization": "Bearer " + lineToken },
        "payload": { "message": message }
    };
    UrlFetchApp.fetch('https://notify-api.line.me/api/notify', options);
}

// 正規表現を使ってHTMLからデータを抽出する関数
function extractUsingRegex(html, regex) {
    var match = html.match(regex);
    return match ? match[1].replace(/&nbsp;/g, ' ').replace(/&gt;/g, '>').replace(/<[^>]+>/g, '').replace(/\s+/g, ' ').trim() : 'データなし';
}

// 依頼内容を見やすく整形する関数
function formatRequestContent(content) {
    return content
        .replace(/\s+/g, ' ')  // 余計なスペースを削除
        .replace(/\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\]/g, '\n[$1]') // 日時部分を改行
        .replace(/(01_建物種別は?:)/, '\n$1')  // 「建物種別」の前に改行
        .replace(/(99_その他ご依頼内容:)/, '\n$1')  // 「その他ご依頼内容」の前に改行
        .replace(/。/g, '。\n')  // 句点で改行
        .replace(/kana:/, '\nkana:'); // kanaの前で改行
}

// 案件情報を抽出し、LINEに送信する関数
function extractData(html) {
    // 正規表現で案件情報を抽出
    var 案件番号 = extractUsingRegex(html, /案件番号<\/th>\s*<td>\s*(.*?)\s*<\/td>/s);
    var 屋号 = extractUsingRegex(html, /屋号<\/th>\s*<td>\s*(.*?)\s*<\/td>/s);
    var お名前 = extractUsingRegex(html, /お名前<\/th>\s*<td>\s*(.*?)\s*<\/td>/s);
    var 電話番号 = extractUsingRegex(html, /電話番号<\/th>\s*<td>\s*<a.*?>\s*(.*?)\s*<\/a>/s);
    var 住所 = extractUsingRegex(html, /住所<\/th>\s*<td>\s*(.*?)\s*<\/td>/s);
    var email = extractUsingRegex(html, /Email<\/th>\s*<td>\s*(.*?)\s*<\/td>/s);
    var 依頼日時 = extractUsingRegex(html, /依頼日時<\/th>\s*<td>\s*(.*?)\s*<\/td>/s);
    var 依頼内容 = extractUsingRegex(html, /依頼内容<\/th>\s*<td>\s*(.*?)\s*<\/td>/s);

    // 依頼内容を整形
    依頼内容 = formatRequestContent(依頼内容);

    // LINE通知の内容(各項目の先頭に■を追加し、案件番号の前に改行を追加)
    var message = [
        `₋\n■案件番号:${案件番号}`,
        `■屋号:${屋号}`,
        `■お名前:${お名前}`,
        `■電話番号:${電話番号}`,
        `■住所:${住所}`,
        `■Email: ${email}`,
        `■依頼日時:${依頼日時}`,
        `■依頼内容:${依頼内容}`
    ].map(line => line.trim()).join("\n");

    sendToLine(message);  // LINE通知の関数呼び出し
}

function fetchContactMail() {
    var ISREAD_LABEL = GmailApp.getUserLabelByName('通知済み');
    var strTerms = ('label:通知対象 -label:通知済み');

    var myThreads = GmailApp.search(strTerms);
    var myMsgs = GmailApp.getMessagesForThreads(myThreads);

    for (var i = 0; i < myMsgs.length; i++) {
        var msg = myMsgs[i].slice(-1)[0];
        if (!msg) continue;  // メッセージが空の場合を回避

        var body = msg.getPlainBody();
       
        // URLを検索
        var urlMatch = body.match(/https:\/\/assist[^\s]*/);
        if (urlMatch) {
            var url = urlMatch[0];

            try {
                // URLのコンテンツを取得
                var response = UrlFetchApp.fetch(url);
                var html = response.getContentText();

                // 案件情報を抽出してLINEに送信
                extractData(html);

            } catch (e) {
                sendToLine("URL取得失敗");  // エラーメッセージを簡潔に修正
            }
        } else {
            sendToLine("URL無し");
        }
    }

    // メールを「通知済み」にラベル付け
    myThreads.forEach(function (t) {
        t.addLabel(ISREAD_LABEL);
    });
}

function main() {
    fetchContactMail();
}