Next.js (とJavaScript)

Next.js:

 

Node.js

[PM2](https://note.com/mega_gorilla/n/nff477d9f263d)は、Node.jsアプリケーションを本番環境で安定的に運用するための強力なツール
### React

| 用語 | 意味 |
| —————————— | ———————————— |
| `__dirname` | このファイルがあるディレクトリの絶対パス |
| `path.resolve()` | 引数を結合して絶対パスを作る |
| `path.resolve(__dirname, ‘…’)` | このファイルを基準にした絶対パスが作れる → 安定したフォルダ参照に最適 |

`next-intl.config.ts`のとき注意。
#### イベントハンドラ
ブラウザ上で発生するイベント(クリック、入力、フォーム送信など)に反応して処理を行う
– onSubmit → HTML の <form> で送信時に発火するイベント
– handleSubmit → そのイベントを処理するために書く自分の関数(名前は一応自由慣習で、handleナンチャラにする)
| イベント名 | 対象 | 発生タイミング |
| ——————————- | ———————————– | ———- |
| `onClick` | ボタン、リンク、任意要素 | クリックしたとき |
| `onChange` | `<input>`, `<select>`, `<textarea>` | 値が変わったとき |
| `onSubmit` | `<form>` | フォーム送信時 |
| `onKeyDown` / `onKeyUp` | 入力要素、任意要素 | キー押下/キー離し |
| `onMouseEnter` / `onMouseLeave` | 任意要素 | マウスが入った/出た |

### Next.js
Create-next-appの[選択肢について](https://zenn.dev/ikkik/articles/51d97ff70bd0da)
(<- | -> で Enter, 選択肢間違えたらCtrl + c) [ npx create-next-app@latest ]

npm run dev ( yarn dev )は, package.jsonの“scripts“セクションにある
`”dev”: “next dev –turbopack”`
コマンドが実行される.

多言語対応には、[i18next + react-i18next](https://qiita.com/syukan3/items/b4da93fe73cc577012cd)か[next-intl](https://zenn.dev/eju_labs/articles/6ee172b96c5f08)があるらしくて、前者(i18next)はNext.js以外にもReact、Vue、Angularでも使えるらしい。後者は、パフォーマンスとSEO的に良いらしい。

Next.jsはエラー時以外ははSSR(Server Side Rendering)するから、Reactの
> アプリがサーバレンダリングを使用している場合、createRoot() の使用はサポートされていません。代わりに hydrateRoot() を使用してください。
を常に使うっぽい。( エラーページの描画失敗以外: 例えば,
<details>

if (document.documentElement.id === '__next_error__') {
let element = reactEl;
// Server rendering failed, fall back to client-side rendering
if (process.env.NODE_ENV !== 'production') {
const { createRootLevelDevOverlayElement } = require('./components/react-dev-overlay/app/client-entry');
// Note this won't cause hydration mismatch because we are doing CSR w/o hydration
element = createRootLevelDevOverlayElement(element);
}
_client.default.createRoot(appElement, reactRootOptions).render(element);
} else {
_react.default.startTransition(()=>{
_client.default.hydrateRoot(appElement, reactEl, {
...reactRootOptions,
formState: initialFormStateData
});
});
}
// TODO-APP: Remove this logic when Float has GC built-in in development.
if (process.env.NODE_ENV !== 'production') {
const { linkGc } = require('./app-link-gc');
linkGc();
}
}

</details>

Approuterの公式チュートリアルなら, /nextjs-dashboard/node_modules/.pnpm/next@15.3.2_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next/dist/client/app-index.js
Chap.6 でNeonが2日放置してたら、connect切断されてたので注意)
↓参考: [Oracleの資料](https://speakerdeck.com/oracle4engineer/ochacafe-nextjs-basics?slide=33)
| ファイル/フォルダ名 | 説明 |
|—————————-|———————————|
| `app` | App Router |
| `pages` | Pages Router |
| `public` | 静的アセット |
| `next.config.js` | Next.jsのコンフィグ |
| `package.json` | プロジェクトの依存ライブラリとスクリプト |
| `instrumentation.ts` | OpenTelemetryのインストルメント(設定値) |
| `middleware.ts` | Middlewareの記述 |
| `.env` | 環境変数 |
| `.env.local` | ローカル環境の環境変数 |
| `.env.production` | 本番環境の環境変数 |
| `.env.development` | 開発環境の環境変数 |
| `.eslintrc.json` | ESLintのコンフィグ |
| `next-env.d.ts` | TypeScriptの宣言ファイル |
| `tsconfig.json` | TypeScriptのコンフィグ |
| `jsconfig.json` | JavaScriptのコンフィグ |

### Middlewareとは

– クライアントとサーバーの間で動作
– 認証
– バリデーション
– データ整形
– セキュリティチェック
– その他(etc.)*多言語対応もココをいじる。

## tailwind の[解説サイト](https://zenn.dev/yohei_watanabe/books/c0b573713734b9/viewer/215e30)

## prisma
とりあえず、用語載ってる[サイト](https://qiita.com/takekota/items/42f6a9066de60da93161)
prisma.<モデル名>.<操作メソッド>
みたいに、.tsx内で使う。(からデータ拾ってるところ探すときはctrl + shift + Rでprisma.で調べれば気合で見つかる。)
(例:

model Plan {
id Int @id @default(autoincrement())
name String
createdAt DateTime @default(now())
}

// 全件取得

const plans = await prisma.plan.findMany();

“`prisma

datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}

とルートディレクトリ直下の.envファイル(create.next.appすれば勝手に作られるかも?なかったら作成)に

DATABASE_URL="mysql://root:password@localhost:13306/HOGEHOGE

って書く。

詳しそうな[zennのサイト](https://zenn.dev/hayato94087/books/e9c2721ff22ac7/viewer/d00oygw3ghp7jdp)

E2EテストフレームワークのCypress
https://zenn.dev/manalink_dev/articles/manalink-cypress-introduce

## Nuxt

クリーンアーキテクチャ
https://zenn.dev/sre_holdings/articles/a57f088e9ca07d

## ORM(databaseいじるやつ)
[Next.js × ORM 実践比較:Prisma / Drizzle / Kysely をDocker上で動かして違いを体感する](https://shinagawa-web.com/blogs/nextjs-orm-docker-hands-on)

翻訳ファイルの書くとき
JSON5(.json5 : おすすめ)を使うとコメントアウト[書ける](https://qiita.com/jkr_2255/items/026e0fdb4570c88c4f51)し[便利らしい](https://qiita.com/yokra9/items/1ac03876415d7fd47a65)

LINKとroute.push
LINKの場合は、localeが自動判定するが、通常のrouter.pushの場合はlocaleをちゃんとわたさなければいけないというNextIntl仕様があるから、統一的に書く場合はuse.routeをNextintlによりラップされたものを使うようにする。(しないとpath.ts:ページジャンプ用typeScriptとかが猥雑になる。)

CI/CD

### CI/CD
– CI: 継続的インテグレーション: 変更を統合するたびに自動でビルド・テストする。
– CD: 継続的デリバリー: 開発したコードを運用環境(AWSのEC2とか)へ展開する準備を自動で完了させるための仕組み。
### Github Actions
– Github Actions: Git hubが提供するワークフロー(なにかのイベント(pushとか)をトリガーにしてジョブを実行させるもの。)
workflow → 全体の設計図
trigger (on: > push:のこと)→ いつ走るか(きっかけ)
job (jobs: > deploy: > runs-on: # 仮想マシン環境 ubuntu-latestとか)→ 作業机ごとに分けたまとまり
-> step(name: HOGE > run: npm install とか) → 作業机の上での具体的な作業
runs on だと環境しか作ってないから、

steps: # ← step
- name: コードを取得
uses: actions/checkout@v4

でコードを取得する。(@の後ろはversion。これのリポジトリは[ココ](https://github.com/actions/checkout))
定期実行を時間でやると、githubのコンピュータリソースが足りないとできないらしいでも、できなかったことが通知されないから、
`on: /n Schedule: /n -cron:”0 0 * * “`より、重要なやつはPRやPushをTriggerにしたほうが[いいらしい](https://youtu.be/RxcUrg3OO3o?t=528)

App router

Next.js 13で導入されたApp Routerは、デフォルトでReact Server Components(RSC)を使用しており、このRSCは非同期関数として動作します。これにより、データ取得をコンポーネントレベルで直接行うことが可能になりました。

具体的には、async/await構文を使用して、外部APIやデータベースからデータを取得する処理をコンポーネント内で書くことができます。これにより、クライアントサイドでのデータ取得(useEffectなど)とは異なり、サーバーでレンダリングが完了する前に必要なデータをすべて取得できるため、レンダリングブロック(render-blocking)なデータ取得を回避できます。
*App Router の layout.jspage.js は、非同期関数 async に対応するように設計されています。

## **「高階関数(Higher-Order Function, HOF)」+依存注入(Dependency Injection)」**

  • 高階関数を使って翻訳関数を注入することで、コンポーネントは翻訳方法を気にせずに、注入された関数を呼び出すだけでよくなるので便利。
  • データベース接続や外部APIクライアントのような、具体的な実装に依存するロジックを抽象化できます。これにより、開発時と本番環境で異なるデータソース(例:本番ではMongoDB、テストではモックデータ)を簡単に切り替えられる。

Next.js+yjs+BlockNoteでGoogle Docs+Notionのような共同編集エディタを作ろう

git hub メモ

git rebase -i HEAD~2

で直前のコミットと現在のコミットの変更履歴を一つに圧縮できるから便利。

git switch -c HOGEFUGA

で現在のブランチのコピーブランチ名:HOGEFUGAでつくれるけど、IDEAやVSCODEとかならGUI的にいろいろできるかも。

あと、stash, shelf とかで一時退避を使うと、reviewのためとかに別のブランチにいくとかに便利。

git mergeしようとしたときに、conflict しててIDEAで解消するときは、右がもともとのので、真ん中が解消した後の最終結果、左がmerge して取り込もうとしてるブランチのこと。

↓がgit hubのプルリクのコメントで書けるから便利かも。

<details><summary>的はずれなコメントしてたので修正</summary>
これが便利
<del>
\>で引用書ける。
```mermaid graph TD; A-->B; A-->C; B-->D; C-->D; ```
がgit で書ける。
</del>
</detail>

 

git reset --hard (commit番号)

↑神のコマンド。戻れなくなってきたわけわからんくなってきた個人repo.ならアリ。そうじゃない場合は。。。

スクリーンショットの保存場所を変更するbash

スクリーンショットの保存先を指定するコマンドを実行↓

mkdir -p ~/Pictures/Screenshots
defaults write com.apple.screencapture location ~/Pictures/Screenshots
killall SystemUIServer

最後のは、システム設定の変更をすぐに反映させるために、メニューバーを再起動するコマンド。

↓スクリーンショットを日付ごとに振り分けるスクリプト
move_screenshots.sh というファイル名で作成してください。

#!/bin/bash

SRC="$HOME/Pictures/Screenshots"
BASE_DEST="$HOME/Pictures/Screenshots"

find "$SRC" -maxdepth 1 -type f -name "*.png" | while read -r file; do
DATE=$(stat -f "%Sm" -t "%Y-%m-%d" "$file")
DEST="$BASE_DEST/$DATE"
mkdir -p "$DEST"
mv "$file" "$DEST/"
echo "Moved $file to $DEST/"
done

スクリプトの使い方
ファイルを作成後、実行権限を付与します。↓

chmod +x ~/move_screenshots.sh

スクリプトを実行します。

bash ~/move_screenshots.sh

スクリーンショットが保存されるディレクトリは自由に変更可能です。
スクリプト内の SRC と BASE_DEST のパスを書き換えてください。

Java

オーバーライドは「継承してるから使える」ではなく、「Object クラスの機能を自分流にカスタマイズする」こと。

`toString()` のオーバーライドは **継承してなくても使える**!
– なぜなら、**Javaのすべてのクラスは暗黙的に `Object` クラスを継承している**から。

toString() をオーバーライドすると System.out.println(obj); で中身をきれいに出力できるようになる!*equals()もよく使う。

import java.util.Arrays; 
public class Fibonacci { static int callCount = 0; // 再帰呼び出し回数 
public static void main(String[] args) { int[] memo = new int[51]; 
Arrays.fill(memo, -1); int result = fibonacci(memo, 50); 
System.out.println("F(50) = " + result); 
System.out.println("再帰呼び出し回数: " + callCount); } 
//メモ化により **各nにつき再帰1回のみ** → **呼び出し回数 ≒ n回 (O(n))**
public static int fibonacci(int[] memo, int n) { callCount++; 
if (n == 0) return memo[n] = 0; 
if (n == 1) return memo[n] = 1; 
if (memo[n] != -1) return memo[n]; 
return memo[n] = fibonacci(memo, n - 1) + fibonacci(memo, n - 2); } 
}

# 列挙型

public enum Operation {PLUS, MINUS, MULTIPLY, DIVIDE}
をOperation.java

public class OperationSymbol {

public static void main(String[] args) {
System.out.println(Operation.PLUS.name());
System.out.println(Operation.MINUS.ordinal());
}
}
をOperationSymbol.javaで作ると、

PLUS
1
がでてくる。内部にname, ordinalのgetterがあるらしい.

貪欲法はちょっと、hash 再探索っぽい面がある気がする。

Object.equals(A,B)は、nullを考慮した比較ができる。

Hibernateのバージョン上げにも注意する。(Springboot2系から3系にしたらjavax から jakartaになってこれになりがち)

AWSについて

  1. # AWS 始め方

【AWS初心者向け】AWS学習方法まとめ【15時間で達成できる】

↑AWSハンズオン

Hands-on for Beginners で AWS を使いはじめよう ! ~ 目的にあわせてハンズオンをおすすめします ~

AWS skillbuilder
こっちはメアドだけ登録すればできる。

Developers.IO

 

AWS: クラウドサービスプラットフォーム
– EC2: クラウド上にコンピュータを立てる。; CPUやメモリのサイズOSを指定できる。けど、従量課金制だから注意。
– S3:コンピュータ上にファイルを保存できる。
Lambda : イベント駆動でプログラムが実行できる。
– RDS : クラウド上にRDB(関係データベース)を作れる。
また、セキュリティとしてAWS Shieldセキュリティとかがある。

## EC2を使いたい場合;
#1  AWS のトップページに行って, サービス -> コンピューティング ->  EC2 に行く。
#2 キーペア: public key AWS, private key があって、RSA.pemを選ぶ。/Users/<ユーザー名>/.sshになってるか確認。
#3  インスタンスを起動。名: コンピュータ名, マシンイメージ: ubuntuとか(他にも前述のように色々選べる。)
keypair (login)でさっき作ったやつを書く.
#5 ターミナル:

ssh -i <privatekey file(./ssh/HOGEHOGE.pem )のパス> ユーザー名@ホスト名

他にも, VS codeで, remote SSH リモートエクスプローラーを使えば、VS code上でubuntsuをいじれし、ubuntsuの中身(ディレクトリ)が, VS codeの左側にでるから便利かも。

## AWSを終了するには,
インスタンス欄から, インスタンスの状態を選んで, 停止を押せばok .

Tips
### AWS EC2インスタンスのディスク容量(EBSボリュームサイズ)を増やす方法

 

  • Lambda: アプリケーションの実行環境の準備が不要。
  • EC2: EC2 Auto Scalingを使うと、EC2の可用性、 信頼性を上げることができるけど、コストが上がるから注意。
  • VPC: AZ間は跨げるけど、リージョン間は跨げない。そういうときは、リージョン間でVPC接続する。(VPCピアリング)

オンプレミスに接続するふたつの方法

AWS Direct Connect  専用回線で繋ぐから安全。だけど、高価。

Amazon VPN(Virtual Private Network) 安いけど、品質安全性がちょっと落ちる。

Google AI studio ✕ chat bot

Google AI studioくんは、月200万トークンくらいが使えて、お試しには無料で遊べると思うので、遊んでみてください。

import streamlit as st 
import os 
from dotenv import load_dotenv 
import nest_asyncio 

from langchain_community.document_loaders import PyPDFLoader, UnstructuredHTMLLoader 
from langchain.text_splitter import RecursiveCharacterTextSplitter 
from langchain_google_genai import GoogleGenerativeAIEmbeddings, ChatGoogleGenerativeAI 
from langchain_community.vectorstores import FAISS 
from langchain.chains import RetrievalQA 

# Streamlit上でasyncioエラー回避 
nest_asyncio.apply() 

# .envからAPIキー読み込み 
load_dotenv() 
gemini_key = os.getenv("GOOGLE_API_KEY") 
if gemini_key is None: 
st.error(".envに GOOGLE_API_KEY が見つかりません") 
st.stop() 
os.environ["GOOGLE_API_KEY"] = gemini_key 

st.title("📄 PDF / HTML Q&A チャットボット") 

# セッション状態の初期化 
if "messages" not in st.session_state: 
st.session_state.messages = [] 
if "qa_chain" not in st.session_state: 
st.session_state.qa_chain = None 

# 既存メッセージの表示 
for message in st.session_state.messages: 
with st.chat_message(message["role"]): 
st.markdown(message["content"]) 

# ファイルアップロード 
uploaded_file = st.file_uploader( 
"Q&Aを行いたいPDFまたはHTMLファイルをアップロードしてください", 
type=["pdf", "html", "htm"] 
) 

def setup_qa_chain(file_path: str, file_type: str): 
# PDF or HTML の読み込み 
if file_type == "pdf": 
loader = PyPDFLoader(file_path) 
elif file_type in ["html", "htm"]: 
loader = UnstructuredHTMLLoader(file_path) 
else: 
raise ValueError("対応していないファイル形式です") 

documents = loader.load() 
if not documents: 
st.error(f"{file_type.upper()}からテキストを抽出できませんでした。") 
st.stop() 

# テキスト分割 
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200) 
texts = text_splitter.split_documents(documents) 

# 埋め込みとベクトルストア 
embeddings = GoogleGenerativeAIEmbeddings(model="models/embedding-001") 
vector_store = FAISS.from_documents(texts, embeddings) 
retriever = vector_store.as_retriever() 

# GeminiモデルとQAチェーン 
model = ChatGoogleGenerativeAI(model="gemini-1.5-flash") 
qa_chain = RetrievalQA.from_chain_type( 
llm=model, retriever=retriever, return_source_documents=True 
) 
return qa_chain 

# ファイルがアップロードされ、QAチェーンが未作成の場合 
if uploaded_file and st.session_state.qa_chain is None: 
with st.spinner("ドキュメントを読み込み中..."): 
try: 
temp_file_path = f"temp_uploaded.{uploaded_file.name.split('.')[-1]}" 
with open(temp_file_path, "wb") as f: 
f.write(uploaded_file.getbuffer()) 

file_type = uploaded_file.name.split(".")[-1].lower() 
st.session_state.qa_chain = setup_qa_chain(temp_file_path, file_type) 
st.success("ドキュメント準備完了!質問を入力してください。") 
os.remove(temp_file_path) 
except Exception as e: 
st.error(f"エラーが発生しました: {e}") 

# QAチェーンが存在する場合にチャット表示 
if st.session_state.qa_chain: 
if prompt := st.chat_input("ドキュメントの内容について質問してください"): 
st.session_state.messages.append({"role": "user", "content": prompt}) 
with st.chat_message("user"): 
st.markdown(prompt) 

with st.spinner("回答を生成中..."): 
try: 
response = st.session_state.qa_chain.invoke({"query": prompt}) 
answer = response['result'] 

st.session_state.messages.append({"role": "assistant", "content": answer}) 
with st.chat_message("assistant"): 
st.markdown(answer) 
except Exception as e: 
st.error(f"質問処理中にエラーが発生しました: {e}")

みたいなのと、.envファイル

GOOGLE_API_KEY = HOGEHOGE;

を同じ階層に書いて、

streamlit run app.pyで実行すると、streamlitの画面が起動して、アップロードしたファイルを呼んで解釈してくれる感じのchat botが作れます。
(事前に

brew install tesseract 
pip install nest_asyncio 
pip install langchain langchain-community pypdf 
pip install pytesseract pillow

が要る。)

## 一応説明

ファイルアップロード

技術: Streamlit st.file_uploader

役割: ユーザーからPDFやHTMLファイルを受け取る
–>
テキスト分割

技術: RecursiveCharacterTextSplitter

役割: 長文をLLMが扱いやすいチャンクに分割
–>
ベクトル化 (Embeddings)

技術: GoogleGenerativeAIEmbeddings

役割: 各テキストチャンクを数値ベクトルに変換
–>
ベクトルストア & 検索

技術: FAISS (FAISS.from_documents + as_retriever)あるいはコレを見てください

役割: 質問時に関連文書を高速に検索できるように格納
–>
QAチェーン作成

技術: ChatGoogleGenerativeAI, RetrievalQA

役割:

検索結果を参照してLLMが自然言語回答を生成

ユーザー質問 → Retriever → LLM → 回答
–>
質問応答

技術: Streamlit st.chat_input + qa_chain.invoke

役割:

ユーザーの質問を受け取り、回答を生成・表示

会話履歴をセッションに保存
てな感じで動いてます。

## 参考・補足

– [ここ](https://github.com/kaisugi/gpt4_vocab_list)に、トーカナイザーで分割された単語が必ず含まれるボキャブラリー表がおいてある。
トーカナイザーについては、[コレ](https://speakerdeck.com/payanotty/tokunaizaru-men?slide=7)とか[コレ](https://dev.classmethod.jp/articles/road-to-llm-advent-calendar-2023-07/)
– HuggingFaceは、機械学習界のgit hubと主張してるプラットフォーム[らしい](https://qiita.com/ski2_1116/items/f74e7b97008663d0702d)
– 下で使ってるlang.chainは[ココ](https://zenn.dev/chips0711/articles/f4ed8ac37eb3a8)とかに載ってるかも。
– Google AI studioでの[ファインチューニング](https://qiita.com/shun_so/items/0f044047553e2fefa66c)について
– [LangChain で社内チャットボット作ってみた](https://zenn.dev/cloud_ace/articles/19bd3554ac8432)
– [RAGチャットボット開発ガイド](https://zenn.dev/daijobu/articles/c1217e40fdf2a5)

### コマンドの意味

– brew install tesseract

意味: Homebrew(macOS用パッケージ管理ツール)を使って Tesseract OCR をインストールする。

Tesseract OCR: 画像内の文字を認識してテキストに変換するツール。

例: スキャンした書類やPDF画像から文字を読み取るのに使う
– pip install pytesseract pillow

意味: 画像処理関連の Python パッケージをインストールする。

pytesseract: Python から Tesseract OCR を呼び出すためのラッパー。(↑とセットで使う。)

Pillow: Python で画像を扱うライブラリ(PIL の後継)。

用途: 画像やPDFから文字を抽出したり、画像を加工したりする。

– pip install langchain langchain-community pypdf

意味: 以下の Python パッケージをまとめてインストールする。

— langchain: LLM(大規模言語モデル)を活用してチェーン処理やアプリ構築を支援するライブラリ。

— langchain-community: LangChain のコミュニティ拡張パッケージ。追加モデルやツールを提供。

— pypdf: PDF ファイルを読み込んだり操作したりできる Python パッケージ。

#### おまけ

# src/scripts/scrape_site.py 
import requests 
from bs4 import BeautifulSoup 
import json 
import os 
import warnings 
from urllib3.exceptions import NotOpenSSLWarning 
from urllib.parse import urljoin, urlparse 

warnings.filterwarnings("ignore", category=NotOpenSSLWarning) 

BASE_URL = "https://www.HOGEFUGA.co.jp/" 
#引っ張ってくるサイトのurl
OUTPUT_DIR = "scraped_site" 
VISITED = set() 

def fetch_text(url): 
"""1ページのテキストを抽出""" 
try: 
headers = { 
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) " 
"AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/114.0.0.0 Safari/537.36" } 
resp = requests.get(url, headers=headers) 
resp.raise_for_status() 
soup = BeautifulSoup(resp.text, 'html.parser') 
text_content = "" 
for tag in soup.find_all(['p', 'h1', 'h2', 'h3', 'li', 'span']): 
cleaned = tag.get_text(separator=' ', strip=True) 
if cleaned: 
text_content += cleaned + " " 
return text_content.strip() 
except requests.exceptions.RequestException: 
return None 

def save_text(url, text): 
"""URL に対応したディレクトリ/JSON に保存""" 
parsed = urlparse(url) 
path = parsed.path.strip("/") 

# index ページなら "index.json" に 
if path == "": 
path = "index" 

dir_path = os.path.join(OUTPUT_DIR, os.path.dirname(path)) 
os.makedirs(dir_path, exist_ok=True) 
file_path = os.path.join(OUTPUT_DIR, path + ".json") 

with open(file_path, "w", encoding="utf-8") as f: 
json.dump({"url": url, "text": text}, f, ensure_ascii=False, indent=2) 
print(f"保存: {file_path}") 

def crawl(url, max_pages=50): 
"""再帰的にサイト内リンクをクロール""" 
if url in VISITED or len(VISITED) >= max_pages: 
return 
VISITED.add(url) 

text = fetch_text(url) 
if text: 
save_text(url, text) 

# サイト内リンクを取得して再帰 
headers = { 
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) " 
"AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/114.0.0.0 Safari/537.36" } 
try: 
resp = requests.get(url, headers=headers) 
soup = BeautifulSoup(resp.text, 'html.parser') 
for a in soup.find_all('a', href=True): 
link = urljoin(BASE_URL, a['href']) 
if link.startswith(BASE_URL): 
crawl(link, max_pages) 
except requests.exceptions.RequestException: 
pass 

if __name__ == "__main__": 
crawl(BASE_URL) 
print(f"クロール終了。{len(VISITED)} ページ取得")

こういう感じのコード書いて、スクレイピングしてもいいのかも。

Riemann 接続

多様体$M$の$(0,2)$型テンソル場$g$であって, 各点$p\in M$で$g_p$が$T_pM$の内積となっているものをRiemann計量といい, Riemann計量$g$が与えられた多様体$M$のことをRiemann多様体と呼んだことを思い出す。

Definition.
Riemann 多様体は、$(M,g)$の各座標近傍$(U;x^1,\ldots,x^n)$において、
\[ \Gamma_{ij,k} := \Gamma_{ij}^{l}g_{lk}= \frac{1}{2} (\partial_i g_{j,k} + \partial_j g_{k,i} – \partial_k g_{i,j} )\]である$M$のAffine接続をRiemann接続、Levi-Civita接続という。また、この接続が定める$M$上の平行移動をLevi-Civita平行移動という。

各座標近傍において, 上で定義された関数の組$\{ \Gamma_{ij}^{l} \}$がAffine接続を定めること, すなわち座標変換規則をみたす。これは、たとえば服部多様体 p.196に載っている。が、一応昔示したノートがあったので貼っとく。(一応自分でも確かめてみてください)

なんか内積の座標変換表示と逆行列の座標変換行列を用いて、元の座標でどう$\Gamma_{ij,k}$が書けるかをみてる感じです。

$\Gamma_{ij,k}$は$\nabla_{\partial_i}\partial_j$と$\partial_k$の内積
$\Gamma_{ij,k}=\Gamma_{ij}^{l}g_{lk} = g(\nabla_{\partial_i}\partial_j, \partial_k)$である。実際,
\[ g(\nabla_{\partial_i}\partial_j, \partial_k)= g( \Gamma_{ij}^{l}g_{lk}, \partial_k )= \Gamma_{ij}^{l} g(\partial_l, \partial_k )=\Gamma_{ij}^{l} g_{lk}\]

\[ \partial_kg_{ij}-\Gamma_{ki,j}-\Gamma_{kj,i}=0\]

\[ 2(\Gamma_{ki,j}+\Gamma_{kj,i})=\{ (\partial_k g_{j,j} + \partial_j g_{k,j} – \partial_j g_{k,i} )+ (\partial_k g_{j,i} + \partial_j g_{k,i} – \partial_i g_{k,j} ) \}=2\partial_kg_{ij} \]

\[Xg(Y,Z)=\sum_{k=1}^{n}X^k\left( \frac{\partial}{\partial x^k} \right) g(\sum_{i=1}^{n}Y^i \left( \frac{\partial}{\partial x^i} \right) ,\sum_{j=1}^{n}Z^j\left( \frac{\partial}{\partial x^j} \right) )= \sum_{k=1}^{n} \sum_{i=1}^{n} \sum_{j=1}^{n} X^k Y^i Z^j (\Gamma_{ki,j}+\Gamma_{kj,i})\] ここで,
\[ X^k Y^i\Gamma_{ki}^{l}g_{lj}= g(\nabla_{(X^k \frac{\partial}{\partial x^k} )} (Y^i \frac{\partial}{\partial x^i}) , \partial_j) \]

ここで,
\[ X^k Z^j\Gamma_{kj}^{l}g_{li}= g(\nabla_{(X^k \frac{\partial}{\partial x^k} )} (Z^j \frac{\partial}{\partial x^j}) , \partial_i) \] だから、結果として,
\[ Xg(Y,Z)=g(\nabla_X Y,Z) + g(\nabla_X Z,Y) \]

(自己平行)部分多様体

Affine接続$\nabla$をもつ$n$次元多様体$N$, $M$を$N$の$m$次元部分多様体とする。一般に,
\[ (\nabla^N_{X}Y)=(\nabla^N_{X}Y)^{\top} +(\nabla^N_{X}Y)^{\perp} \] と分解できる。この分解した一項目を
$(\nabla^M_{X}Y)$, $(\nabla^N_{X} Y)^{\perp}$を $ B(X,Y) $と書くことにし, $B(X,Y)$を第二基本形式と呼ぶ。つまり,
\[
(\nabla^M_{\partial^{i}}\partial^{j}) = (\nabla^N_{\partial^{i}}\partial^{j})^{\top} = \sum_{k=1}^{m} \Gamma_{ij}^k \, \partial^{k}
\] つまり, 計量を考えると,
任意の$Z\in TN$に対して,
\[g((\nabla^N_{X}Y), Z)=g((\nabla^N_{X}Y)^{\top} +(\nabla^N_{X}Y)^{\perp} , Z)= g((\nabla^M_{X}Y), Z)\] となる. 逆に、この関係から、$(\nabla^M_{X}Y)$を定義づけることも計量があるときはできる。

Remark.
$\iota:M\to N$を用いて誘導される誘導接続を、この場で定義しても良い(cf.  藤岡のchap.6, 幾何学的変分問題/西川 青季 を参照)が、今は別にそこまでいらないのでこういう定義にしておく。
Definition.
Affine接続$\nabla$をもつ多様体$N$, $M$を$N$の$m$次元部分多様体とする。$M$がAffine接続$nabla$に関する$N$の自己平行部分多様体であるとは,
任意の$p\in M$, $X, Y \in \mathcal{X}(M)$に対し,
\[(\nabla_{X}Y) \in T_pM\] であるときに呼ばれる。つまり, 第二基本形式$B(X,Y)$が$0$の場合に、$M$は自己平行部分多様体と呼ばれる.

平坦な多様体とAffine cordinate

Definition.
Affine接続$nabla$をもつ多様体$M$において, 曲率テンソル$R$も捩率テンソル$T$も共に恒等的に$0$となるとき, $M$は$\nabla$-平坦であるという.
Example.
1次元の可微分多様体は、どのようなAffine接続であっても平坦である。実際, 曲率テンソルと捩率テンソルの性質から$R^{l}_{ijk}=-R^{l}_{jik}$, $T^{l}_{ij}=-T^{l}_{ji}$が常に成立することに注意すると、次元が$1$ならば$i=j$になるのだから, $R=0$, $T=0$である.
Definition.
Affine接続$\nabla$をもつ多様体$M$の座標近傍$(U;x^1,\ldots,x^n)$において, 接続係数$\{\Gamma_{ij}^k\}$が全て恒等的にゼロとなるとき, $(U;x^1,\ldots,x^n)$を(局所)$\nabla$-Affine座標近傍という.
Remark.
曲率や捩率はテンソル場だから、それがゼロになるかどうかは座標の取り方に依らない幾何学的性質である。一方、接続係数はテンソルではないから、$\Gamma_{ij}^k(p)=0$が全ての$p\in U$で成り立つという性質は、座標の取り方に依存する。
Theorem.
Affine接続$\nabla$をもつ多様体$M$において, 次の条件は同値である。
$M$は$\nabla$-平坦である
$M$の各点周りに$\nabla$-Affine座標近傍が存在する.
[proof] $\textrm{(ii)} \Longrightarrow \textrm{(i)}$
接続係数が恒等的にゼロとなる局所座標系があったならば、その座標系に関する曲率及び捩率の成分は共にゼロになるから(i)が導かれる。

$\textrm{(i)} \Longrightarrow \textrm{(ii)}$を示す。任意の座標近傍$(U;x^1,\ldots,x^n)$に対し, ある座標近傍$(V;\xi^{1},\ldots,\xi^{n})$が存在して,
$U\cap V$で
\[0=\Gamma_{ab}^c=\frac{\partial x^i}{\partial {\xi}^a}\frac{\partial x^j}{\partial {\xi}^b}\frac{\partial {\xi}^c}{\partial x^k}\Gamma_{ij}^k+\frac{\partial^2 x^l}{\partial {\xi}^a\partial{\xi}^b}\frac{\partial {\xi}^c}{\partial x^l}\quad (1\leq a,b,c\leq n)\] となることを示せばいい。
自明な恒等式(↓コレは$lj$を使って$(l,j)$成分を表している行列であることに注意。)
\[\frac{\partial x^l}{\partial {\xi}^b}\frac{\partial {\xi}^b}{\partial x^j}=\delta_{j}^i\] の両辺を$x^i$で偏微分して
\[\frac{\partial^2 x^l}{\partial {\xi}^a\partial{\xi}^b}\frac{\partial {\xi}^a}{\partial x^i}\frac{\partial {\xi}^b}{\partial x^j} + \frac{\partial x^l}{\partial {\xi}^b}\frac{\partial^2 {\xi}^b}{\partial x^i \partial x^j} = 0\] だから、両辺にJacobi行列$(\frac{\partial {\xi}^a}{\partial x^i})$, $(\frac{\partial {\xi}^b}{\partial x^j})$の逆行列を掛けて
\[\frac{\partial^2 x^l}{\partial {\xi}^a\partial{\xi}^b} = -\frac{\partial x^i}{\partial {\xi}^a}\frac{\partial x^l}{\partial {\xi}^d}\frac{\partial x^l}{\partial {\xi}^b}\frac{\partial^2 {\xi}^b}{\partial x^i \partial x^j} \] この式を最初の式に代入すると,
\begin{align}
0 &=
\frac{\partial x^i}{\partial \xi^a} \frac{\partial x^j}{\partial \xi^b} \frac{\partial \xi^c}{\partial x^k} \Gamma_{ij}^k

\left(
\frac{\partial x^i}{\partial \xi^a} \frac{\partial x^j}{\partial \xi^b}
\frac{\partial x^l}{\partial \xi^d} \frac{\partial^2 \xi^d}{\partial x^i \partial x^j}
\right) \frac{\partial \xi^c}{\partial x^l} \\
&=
\frac{\partial x^i}{\partial \xi^a} \frac{\partial x^j}{\partial \xi^b} \frac{\partial \xi^c}{\partial x^k} \Gamma_{ij}^k

\left(
\frac{\partial x^i}{\partial \xi^a} \frac{\partial x^j}{\partial \xi^b}
\delta_d^c \frac{\partial^2 \xi^d}{\partial x^i \partial x^j}
\right)\\
&=
\frac{\partial x^i}{\partial \xi^a} \frac{\partial x^j}{\partial \xi^b}
\left(
\frac{\partial \xi^c}{\partial x^k} \Gamma_{ij}^k

\frac{\partial^2 \xi^c}{\partial x^i \partial x^j}
\right)
\end{align}

したがって今の計算結果を行列の形で見れば見通せるように、
\[ \frac{\partial \xi^c}{\partial x^k} \Gamma_{ij}^k
=
\frac{\partial^2 \xi^c}{\partial x^i \partial x^j} \] と同値である.
これは, 連立方程式の形で書けば,
\[
\begin{cases}
\dfrac{\partial \xi^c}{\partial x^k} = \theta_k^c \\
\dfrac{\partial \theta_j^c}{\partial x^i} = \theta_k^c \Gamma_{ij}^k
\end{cases}
\]

この連立方程式の可積分条件は,
\[
\begin{cases}
\dfrac{\partial^2 \xi^c}{\partial x^i \partial x^j} = \dfrac{\partial^2 \xi^c}{\partial x^j \partial x^i} \\[1mm] \dfrac{\partial^2 \theta_j^c}{\partial x^i \partial x^k} = \dfrac{\partial^2 \theta_i^c}{\partial x^j \partial x^k}
\end{cases}
\]

1つ目の等式は,
\begin{align}
\dfrac{\partial^2 \xi^c}{\partial x^i \partial x^j}
&= \dfrac{\partial^2 \xi^c}{\partial x^j \partial x^i}
\\[1mm] \iff \dfrac{\partial \theta_j^c}{\partial x^i}
&= \dfrac{\partial \theta_i^c}{\partial x^j}
\\[1mm] \iff \theta_k^c \Gamma_{ij}^k
&= \theta_k^c \Gamma_{ji}^k
\\[1mm] \iff \theta_k^c \underbrace{(\Gamma_{ij}^k – \Gamma_{ji}^k)}_{T^k_{ij}}
&= 0
\end{align}

と変形できる. 他方,
\begin{align}
\dfrac{\partial}{\partial x^i} (\theta_m^c \Gamma_{jk}^m)
&= \sum_{m=1}^{n} \dfrac{\partial}{\partial x^i} (\theta_m^c \Gamma_{jk}^m) \\[1mm] &= \sum_{m=1}^{n} \left( \dfrac{\partial \theta_m^c}{\partial x^i} \Gamma_{jk}^m + \theta_m^c \dfrac{\partial \Gamma_{jk}^m}{\partial x^i} \right) \\[1mm] &= \sum_{m=1}^{n} \left( \sum_{\ell=1}^{n} \theta_\ell^c \Gamma_{im}^\ell \Gamma_{jk}^m + \theta_m^c \dfrac{\partial \Gamma_{jk}^m}{\partial x^i} \right) \\[1mm] &= \sum_{m=1}^{n} \sum_{\ell=1}^{n} \theta_\ell^c \Gamma_{im}^\ell \Gamma_{jk}^m + \sum_{m=1}^{n} \theta_m^c \dfrac{\partial \Gamma_{jk}^m}{\partial x^i}
\end{align}
に注意すると、

\begin{align}
\dfrac{\partial^2 \theta_k^c}{\partial x^i \partial x^j}
&= \dfrac{\partial^2 \theta_k^c}{\partial x^j \partial x^i}
\iff \\[1mm] \dfrac{\partial}{\partial x^i} (\theta_m^c \Gamma_{jk}^m)
&= \dfrac{\partial}{\partial x^j} (\theta_m^c \Gamma_{ik}^m)
\iff \\[1mm] \sum_{m=1}^{n} \sum_{\ell=1}^{n} \theta_\ell^c \Gamma_{im}^\ell \Gamma_{jk}^m
+ \sum_{m=1}^{n} \theta_m^c \dfrac{\partial \Gamma_{jk}^m}{\partial x^i}
&= \sum_{m=1}^{n} \sum_{\ell=1}^{n} \theta_\ell^c \Gamma_{jm}^\ell \Gamma_{ik}^m
+ \sum_{m=1}^{n} \theta_m^c \dfrac{\partial \Gamma_{ik}^m}{\partial x^j}
\iff \\[1mm] \sum_{m=1}^{n} \theta_m^c \Big( \dfrac{\partial \Gamma_{jk}^m}{\partial x^i} – \dfrac{\partial \Gamma_{ik}^m}{\partial x^j}
+ \sum_{\ell=1}^{n} (\Gamma_{im}^\ell \Gamma_{jk}^m – \Gamma_{jm}^\ell \Gamma_{ik}^m) \Big)
&= 0
\iff \\[1mm] \sum_{m=1}^{n} \theta_m^c R^m_{ijk}
&= 0
\end{align}
と変形できる.
仮定により, $T=0$かつ$R=0$だから, 先程の連立方程式は可積分である.
以上により, 任意に固定した$p\in U$における初期条件$(\xi^c)_p$, $(\partial_i \xi^c)_p$を任意に与えると, コレをみたす微分方程式(★)の解$(\xi^c)$が$p$の近傍で存在する。特に初期条件を$\det (\partial_i \xi^c)_p \neq 0$に取っておけば, 逆写像定理により, $(\xi^c)$は$p$の近傍で局所座標近傍系をなす。以上から, $U$の各点$p$の周りにある座標近傍が存在して, 最初の等式が成立する.

Theorem.
多様体$M$に座標近傍$(U;x^1,\ldots,x^n)$と$(V;\xi^{1},\ldots,\xi^{n})$があって、$(x^i)$は$\nabla$-Affine座標近傍系であったとする。このとき, $U\cap V$において, $(\xi^\alpha)$も局所$\nabla$-Affine座標近傍系であるための同値条件は,
座標変換$(x^i)\mapsot (\xi^\alpha)$がAffine trans.で書けること, つまり,
\[ \]
[proof]

\[
\frac{\partial^2 x^l}{\partial \xi^a \partial \xi^b} = 0 \quad \Longleftrightarrow \quad x^l = A^l{}_a \xi^a + b^l
\] [/proof]

Riemann 曲率

Definition.
Affine 接続$\nabla$をもつ多様体$M$上で定義された三重$C^{\infty}$-線形写像
\[R\colon \mathcal{X}(M)\times\mathcal{X}(M)\times \mathcal{X}(M) \to \mathbb{R}; (X,Y,Z) \mapsto \nabla_{X}(\nabla_{Y}Z)- \nabla_{Y}(\nabla_{X}Z) – \nabla_{[X,Y]}Z\] を、接続$\nabla$の曲率テンソル場という。

Lemma.
\[
[X,Y]=-[Y,X] \]
Lemma.
\[X(fg)=(Xf)g+f(Xg)\] 特に,
\[X(1)=0 \] \[ [fX, gY ] = fg[X, Y ] + f(Xg)Y − g(Y f)X. \] 特に,
\[[fX, Y ] = f[X, Y ]  − (Y f)X. \] \[\nabla_{ [fX, Y ] }(Z)=\nabla_{ f[X, Y ] }Z-\nabla_{ (Yf)X }(Z)\] \[\nabla_{ [X, Y ] }(fZ)=([X,Y]f)Z+f\nabla_{ [X, Y ] }(Z)\]
Lemma.
$X, Y,Z\in \mathcal{X}(M)$, $f,g,h \in C^{\infty}$,
\[R(fX,Y,Z)= fR(X,Y,Z)\] \[R(X,Y,Z)= -R(Y,X,Z)\] \[R(X,fY,Z)= fR(Y,X,Z)\] \[\nabla_{ [X, Y ] }(fZ)=([X,Y]f)Z+f\nabla_{ [X, Y ] }(Z)\] \[R(X,Y,fZ)=fR(X,Y,Z)\]
[proof]

\begin{align}
R(fX,Y)Z&= \nabla_{fX}(\nabla_{Y}(Z))- \nabla_{Y}(\nabla_{fX}(Z))-\nabla_{ [fX, Y ] }(Z)\\
&= f\nabla_{X}(\nabla_{Y}(Z))-\nabla_{Y}(f\nabla_{X}(Z))-(\nabla_{ f[X, Y ] }Z-\nabla_{ (Yf)X }(Z))\\
&= f\nabla_{X}(\nabla_{Y}(Z))-((Yf)(\nabla_{X}(Z))+f\nabla_{Y}\nabla_{X}Z)-(\nabla_{ f[X, Y ] }Z-\nabla_{ (Yf)X }(Z))\\
&= f\nabla_{X}(\nabla_{Y}(Z))-((Yf)(\nabla_{X}(Z))+f\nabla_{Y}\nabla_{X}Z)-(f\nabla_{ [X, Y ] }Z-(Yf)\nabla_{ X }(Z))\\
&= f\nabla_{X}(\nabla_{Y}(Z))-(f\nabla_{Y}\nabla_{X}Z)-(f\nabla_{ [X, Y ] }Z)\\
&= f(\nabla_{X}(\nabla_{Y}(Z))-(\nabla_{Y}\nabla_{X}Z)-(\nabla_{ [X, Y ] }Z))\\
&= fR(X,Y,Z)
\end{align}
これで一つ目の等式がわかる。二つ目の等号は、\[
[X,Y]=-[Y,X] \]からすぐわかる。三つ目の等号は、
\begin{align}
R(fX,Y)Z&= \nabla_{fX}(\nabla_{Y}(Z))- \nabla_{Y}(\nabla_{fX}(Z))-\nabla_{ [fX, Y ] }(Z)\\
&= \nabla_{fX}(\nabla_{Y}(Z))- \nabla_{Y}(\nabla_{fX}(Z))-\nabla_{ [fX, Y ] }(Z)\\
&= fR(X,Y,Z)
\end{align}

$ \nabla_{X}(\nabla_{Y}(fZ))= \nabla_{X}((Yf)Z+(\nabla_{fY}(Z)))= [X(Yf)Z+(Yf)\nabla_{X}Z]+[Xf(\nabla_{Y}(Z))+f\nabla_{X}(\nabla_{Y}(Z))]$
同様に,

$ \nabla_{Y}(\nabla_{X}(fZ))=\nabla_{Y}((Xf)Z+(\nabla_{fX}(Z)))= [Y(Xf)Z+(Xf)\nabla_{Y}Z]+[Yf(\nabla_{X}(Z))+f\nabla_{Y}(\nabla_{X}(Z))]$

\begin{align}
& \nabla_{X}(\nabla_{Y}(fZ))- \nabla_{Y}(\nabla_{X}(fZ))\\
&=X(Yf)Z+f\nabla_{X}(\nabla_{Y}(Z))-(Y(Xf)Z+f\nabla_{Y}(\nabla_{X}(Z))) \\
&=[X,Y](fZ)+f( \nabla_{X}(\nabla_{Y}Z)- \nabla_{Y}(\nabla_{X}Z))\\
&=\nabla_{ [X, Y ] }(fZ)-f\nabla_{ [X, Y ] }(Z)+f( \nabla_{X}(\nabla_{Y}Z)- \nabla_{Y}(\nabla_{X}Z)) \\
&=\nabla_{ [X, Y ] }(fZ)+f( \nabla_{X}(\nabla_{Y}Z)- \nabla_{Y}(\nabla_{X}Z)-\nabla_{ [X, Y ] }(Z))\\
&=\nabla_{ [X, Y ] }(fZ)+fR(X,Y,Z)
\end{align}

$f,g,h \in C^{\infty}$,
\[R(fX,gY,hZ)= fghR(X,Y,Z)\]