たまに困る

たまに昔の記事を確認したいことがありまして、それが 🔗 text の中だと困っちゃうんですよね。
当時は検索用のCGIを入れていましたけど、今は外しちゃったので調べようがないのです。
ちなみにtext→旧ブログ→現ブログ(ここ)の順で移行してきています。

🔗 旧ブログ はWordPressなので検索機能付きで困りませんし、現ブログも大丈夫。

何か良い方法はないものかと思いながら、Notionをガシガシ使っているうちに「あ、解決できるじゃん!」と気付いてしまいました。

textのデータをNotionに放り込んで、Notion内で検索すれば良いのですよ。

アップロードでお仕置き

ファイルをNotionにボンッと放り投げたら、タイムアウトしたのか途中までしか取り込めず。

200を超えるファイルを一気にやったのがいけなかったのか 制限(12時間後に会いましょう) がかけられてしまって、この日の作業は終了・・・。

翌日、Zipにまとめてから投げたら全部取り込めました。

NotionでChatGPT-4.1が使えます

ちょっと話がズレますが、このあとの作業でAIを活用しています。

先日、 NotionでChatGPT-4.1が使えるようになった とXで知ったので、今回利用してみました。

現在はホーム画面でのみ利用可能です。

NotionでChatGPT-4.1
NotionでChatGPT-4.1

整形作業

文字コード変換

Zipで取り込めたんですけど、 きれいに文字化け (変な日本語)しまして、人間の私には読むことができませんでした・・・。

ああ、あなたShift-JISだったもんね・・・ということで文字コードを変換したいのですが、さすがに200を超えるファイルをちまちまと変換したくありません。

このときWindowsを使っていたので、PowerShellで一括変換する方法をNotion AIに教えてもらいました。

⚠️ コードはあくまでもサンプルですので、ご注意ください。

PowerShellで文字コード変換
# 元フォルダ・出力フォルダ
$inputFolder = "C:\\input_html"
$outputFolder = "C:\\output_html"

# htmlファイルをサブフォルダまで再帰で取得
Get-ChildItem $inputFolder -Recurse -Filter *.html | ForEach-Object {
    $inputFile = $_.FullName

    # 出力先の相対パスを作成
    $relativePath = $_.FullName.Substring($inputFolder.Length).TrimStart('\\')
    $outputFile = Join-Path $outputFolder $relativePath

    # 出力先フォルダがなければ作成
    $outputDir = Split-Path $outputFile
    if (!(Test-Path $outputDir)) {
        New-Item -ItemType Directory -Path $outputDir -Force
    }

    # Shift-JISで読み込み
    $content = Get-Content $inputFile -Encoding Default

    # BOM付きUTF-8で書き出し
    [System.IO.File]::WriteAllLines(
        $outputFile,
        $content,
        [System.Text.Encoding]::UTF8
    )
}

html → txt 形式へ変換

文字変換も終わり、再度Notionに取り込みましたら、ところどころでテーブルやデータベースが自動生成されてしまったので、いったん全ファイルからhtmlタグを除去しました。
元ファイルは私が一から手書きのものなので、htmlの書き方がおかしかったりしたのかしら。

変換の仕方はやっぱりNotion AIで。

このとき、唐突に「あ、Goでやろう」と思って、Go用にコードを書いてもらいました。
2年振りに触るGo、すっかり忘れていました。

⚠️ コードはあくまでもサンプルですので、ご注意ください。

Goでhtmlタグの除去
package main

import (
    "io/ioutil"
    "log"
    "os"
    "path/filepath"
    "strings"

    "golang.org/x/net/html"
)

func extractText(n *html.Node, sb *strings.Builder) {
    if n.Type == html.TextNode {
        sb.WriteString(n.Data)
    }
    for c := n.FirstChild; c != nil; c = c.NextSibling {
        extractText(c, sb)
    }
}

func htmlToText(htmlStr string) string {
    doc, err := html.Parse(strings.NewReader(htmlStr))
    if err != nil {
        log.Printf("Parse error: %v", err)
        return ""
    }
    var sb strings.Builder
    extractText(doc, &sb)
    return sb.String()
}

func main() {
    inputDir := "C:\\\\Users\\\\パパ\\\\Documents\\\\htmls"
    outputDir := "C:\\\\Users\\\\パパ\\\\Documents\\\\texts"

    // 出力先ディレクトリを作成
    os.MkdirAll(outputDir, os.ModePerm)

    // htmlファイルをすべて処理
    files, err := filepath.Glob(filepath.Join(inputDir, "*.html"))
    if err != nil {
        log.Fatal(err)
    }
    for _, file := range files {
        data, err := ioutil.ReadFile(file)
        if err != nil {
            log.Printf("Read error: %v", err)
            continue
        }
        text := htmlToText(string(data))
        base := filepath.Base(file)
        outname := strings.TrimSuffix(base, filepath.Ext(base)) + ".txt"
        outpath := filepath.Join(outputDir, outname)
        ioutil.WriteFile(outpath, []byte(text), 0644)
        log.Printf("変換完了: %s → %s", file, outpath)
    }
}

もう少し整形

ムダな改行なんかは、VS Codeの置換機能で一括削除。

Notionにて

データベース化

空のデータベースを用意して、取り込んだデータをドラッグ・アンド・ドロップ。

AI要約と、鑑賞した映画

ファイルごとにAIで要約と、その中で鑑賞した映画を抜き出してもらいました。

映画のほうは大体合っているようですが、CDのタイトルなんかも間違えて取ってきたりしていました。まあいいけどね。

こんな感じ

データベース
データベース

レコード
レコード

まとめ

一連の作業はこんな感じでありました。

この記事を書いていて気付いたのですが、 Notionに入れなくてもVS Codeのフォルダー内検索で全文検索できたわ・・・。

いや、でも、スマホみたいに元データを持たないデバイスだとそれもできないから・・・と自分を慰めております。