なんじゃくにっき

プログラミングの話題中心。

Cloudflare D1 大量データ作成/更新はどうすれば良いか?

Cloudflare D1で大量データ作成/更新系を行う方法をいくつか考えてみました。

CLIから実行

1つ目はSQLを生成してCLIから実行する方法。 (結局CLIの実装はAPIを叩いていますが)

npx wrangler d1 execute hogehoge_database --file=./bulk_insert.sql

みたいにして.sqlファイルに書いたSQLを実行する方法ですね。

利点:

  • D1のセットアップが済んでいればファイルにSQL書けば良いだけなので楽ですね。

欠点:

  • 条件によってクエリを変える操作に向かない点。
  • SQLで頑張って条件分岐するようなクエリを書くか、シェルスクリプトの標準出力を読み取って分岐させるコードを書くか、 で出来なくはないですが面倒です。

初期データの投入みたいな条件分岐など必要のない決まったデータを投入するケースはこれで良さそうです。

Workers使う方法

次はWorkerのエンドポイントを用意しておいて、認証をかけてそこから実行する方法。

利点:

  • サービスのWorkerとロジックをある程度共用出来る点。
  • DrizzleなどのORMを利用できる点。
  • UIほしければPages/Workersで作ってしまえる。

欠点

  • Workersの実行時間制限があること。大量実行だとタイムアウトしてしまいそう。
  • 言語はJavaScript/TypeScrypt固定。

APIを使う方法

https://developers.cloudflare.com/api/operations/cloudflare-d1-query-database

最後にD1のqueryエンドポイントにSQLを投げる方法。

利点

欠点

  • SQLを書かないといけない(複雑な場合はクエリビルダを何か使えば良いと思うが)

クエリ分割しよう

いずれのやり方にしても一度に大量にデータを作成しようとすると、クエリがタイムアウトする可能性があるので、

適当な件数毎に区切って分割して作成/更新すると良いと思います。

SQLファイルを分割したり、API叩くプログラムで一定件数毎に処理したり。

Cloudflare D1 使い方

概要

CloudFlareで動くSQLiteであるCloudFlare D1を動かしていきます。

https://developers.cloudflare.com/d1/

注意点

現時点(2024年3月)でD1はベータバージョンです。

今後仕様が変わることもあります。手順もたまに少し変わっていたりします。

公式ドキュメントも読むことをオススメします。(但したまにドキュメント更新追いついてないのか違う挙動するときもあります)

また、データが吹き飛ぶ可能性もあります、と書かれているので本番DBに失って困る情報は書かないほうが良いでしょう。 (一応バックアップは取られてますが)

手順

プロジェクトを作成

wranglerのinstallが済んでいない場合は済ませておく。

Get started · Cloudflare D1 docs

Getting Startedに従っていきます

npx wrangler login
npm create cloudflare@latest project_name_hogehoge

選択肢がいくつか出るので答えていく

  • What type of application do you want to create?  -> "Hello World" Worker

  • Do you want to use TypeScript? -> Yes

  • Do you want to use git for version control? -> Yes

  • Do you want to deploy your application? -> No

deployのところだけはデフォルトYesからNoに変える。(これ紛らわしいのでデフォルトNoにしてほしい)

git

先程作ったプロジェクトと同名のプロジェクト名でgithubにプロジェクトを作る (このときgitignoreは作らないでおく)

remote追加

git remote add origin https://github.com/nanjakkun/project_name_hogehoge.git

localにmasterというブランチが出来ているのでmainに統合する

git fetch origin
git checkout main
git merge --allow-unrelated-histories master
git br -D master

DB作成(本番)

npx wrangler d1 create hogehoge_database

以下のような出力が出るので、手動でwrangler.tomlに追加して上げる必要がある。

[[d1_databases]]
binding = "DB" # i.e. available in your Worker on env.DB
database_name = "hogehoge_database"
database_id = "ddddddd-dddd-8888-aaaa-ffffffffff"

見逃した場合はWebのCloudflareのdashboardでdatabase_nameとdatabase_idを見て記入する。

database_idをリポジトリに上げたくない場合はwrangler.tomlをgitignoreに追加。

(もしかしたらsecretsから読み込ませる方法があるのかも)

SQL実行

schema.sql にファイルを作って

DROP TABLE IF EXISTS Customers;
CREATE TABLE IF NOT EXISTS Customers (CustomerId INTEGER PRIMARY KEY, CompanyName TEXT, ContactName TEXT);
INSERT INTO Customers (CustomerID, CompanyName, ContactName) VALUES (1, 'Alfreds Futterkiste', 'Maria Anders'), (4, 'Around the Horn', 'Thomas Hardy'), (11, 'Bs Beverages', 'Victoria Ashworth'), (13, 'Bs Beverages', 'Random Name');
npx wrangler d1 execute hogehoge_database --file=./schema.sql

本番で実行するときは --remoteを付けます。

以前はデフォルトで本番でlocalのときにオプション必要でしたが、デフォで本番はやっぱり危険なので変わったみたいです。

npx wrangler d1 execute hogehoge_database --remote --file=./schema.sql

ファイルじゃなくてSQLを直指定も可。

npx wrangler d1 execute hogehoge_database --command="SELECT 1"

Workersから呼び出す

src/index.tsを変更します

export interface Env {
    DB: D1Database
}

export default {
    async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
      const { pathname } = new URL(request.url);

      var matched = pathname.match(/^\/(\d+)/)

      if (matched) {
            const { results } = await env.DB.prepare(
                "SELECT * FROM Customers WHERE CustomerID = ?"
            )
                .bind(matched[1])
                .all();

            return Response.json(results);
      }

      return new Response("Hello, world");
    },
};

ローカルサーバー立ち上げ

npm run dev

http://localhost:8787/1

にアクセスするとjsonが返ってくればOK!

DreamStudio使ってみた

ずっとブログのヘッダを設定していなくて素っ気なかったけど絵心がないし生成AI製のものを設定してみた。

Stable Diffusionをブラウザで試せるDreamStudioで生成しました。

dreamstudio.ai

サバンナのイメージ。

ブログのイメージとあってない気がするのでそのうち差し替えるかもです。

新しいPC欲しい

ローカルでStable Diffusionを簡単に試せる環境がないので新しいPCが欲しいです。

RTX 4600以上のスペックのGPUを持ったマシンが欲しい!

AMD派だったのにIntel + NVIDIAに浮気しそうです。

S&P 500のグラフをGoogle ColabのAIに描かせる

最近、自然言語処理(とちょっと機械学習)絡みで遊んでいて、 Google Colaboratory使うことがあるんですが、

新規ノートブックで最初に「コーディングを開始するか、AIで生成します」 と出てくるので何かに使えないかと思った次第。(以前AIなんてついてなかったよね?)

で、新NISAもはじまったことだし(てもう2ヶ月以上経ってますが)S&P 500のグラフでも描いてくれるかな?

と思ったので試してみます。

プロンプトは"S&P 500のデータをグラフで出してください。期間は10年、y軸は対数で出してください。"

!pip install yfinance
import yfinance as yf

# Download the historical data for the S&P 500 index
data = yf.download('^GSPC', period='10y')

# Plot the closing prices on a logarithmic scale
data['Close'].plot(logy=True)

# Add a title and axis labels
plt.title('S&P 500 Index: 10-Year Historical Data')
plt.xlabel('Date')
plt.ylabel('Closing Price (Log Scale)')

# Show the plot
plt.show()

実行結果

いやー素晴らしい、タイトルも付けてくれるし今までExcel芸でやってたのが馬鹿みたいですね。

Google検索の結果から特定のドメインを除外する方法2つ

やりたいこと

Google検索の結果の上位に役立たない記事が出てきて、これを出さなくしたい。

ドメイン単位

検索クエリに除外したいドメインを指定する

"-site" 指定でドメインを除外できます。

例えば”はてな”というキーワードで検索するときにhatena.ne.jpのドメインのものを除外したいときはキーワードを以下のようにします。

"はてな -site:hatena.ne.jp"

欠点 - わざわざ毎回入力が面倒(URLを作成するツールを自分で作れば良いんですが・・) - サイトをたくさん入力するとクエリが長くなる

CSSを使って隠す

FirefoxChromeの拡張のStylusを使ってCSSを無理やり当てて隠すことができます。

Stylus – 🦊 Firefox (ja) 向け拡張機能を入手

なお、CSSを当てられれば他の拡張機能でも大丈夫です。

#search > div > div > div > div, .ULSxyf, div[id^="arc-srp"] {
  /* 検索結果から特定ドメインのものを除外 */
  &:has(a[href*="https://twitter.com"]) { display: none; }
}

欠点 - マークアップが変わると効かなくなってしまう - 通信量自体は減らない - 隠しているだけなので1ページ目に何も出てこず、”もっと見る”を押すまで何もないことがある

おまけ

Googleの検索結果をつめてみました。

.srp {
  --center-abs-margin: 100px;
}

#rcnt {
    width: 100%;
    display: block;
    
    > div {
        width: 100%;
    }
}

/* 他の検索 */
div[data-smqc="4"] {
    display: none;
}

#taw {
  margin-left: 200px;
  margin-top: -44px;
}

cite, br {
    display: none;
}

.g {
    width: 100% !important;
    margin-bottom: 16px !important;
}

#search h3, .ULSxyf h3, div[id^="arc-srp"] h3
{
    margin-top: 0;
    margin-left: 250px;
    font-size: large;

    & + div {
        width: 246px;
        overflow-x: hidden;
    }
}

#search table h3 {
    margin-left: 0;    
}

#search > div > div > div > div, .ULSxyf, div[id^="arc-srp"] {
  margin-bottom: 0;
  svg { display: none}

  > div {
      margin-bottom: 0;
  }
    
  > div > div > div > div {
    margin-bottom: 0;
  }

  /* 関連性の高い検索 */
  &:has(a[href*="/search"]) { display: none; }

  /* 検索結果から特定ドメインのものを除外 */
  &:has(a[href*="https://twitter.com"]) { display: none; }
}
    

ルーターを買いかえた / エイスース ASUS RT-AX59U

ルーターを買い替えました。ASUSのを買いました。

↓ ヨドバシのリンク。アフィリエイトではありません。

https://www.yodobashi.com/product/100000001008022223/

エイスース ASUS Wi-FiルータWi-Fi 6(11ax)対応 3603+574Mbps AX4200 デュアルバンド Aiメッシュルーター [RT-AX59U]

買い替えた理由

ルーターで広告ブロックをしたくなったからです。

サイト運営の費用を賄うために広告を出してるのはわかるし、広告が目に入るのはしょうがないなーと今まで思ってたんですが、

フィッシング詐欺やスクロール阻害など、広告業界自体に自浄作用がないことは明らかで、健全ではないと思ったのでブロックすることにしました。

スマホChromeなどではブラウザ拡張が使えないのでルーターでブロックすることにしました。

端末複数持っていても一個一個設定せずに一回で設定が済みますし。

で、今まで持っていたルーターがURLブロック機能を持っていなかったからです。

ルーターの探し方

URLブロック機能を持っているルーターに絞って探し始めたんですが、

最近のルーターだとブロック機能は別途有料だったり最初の何年か無料でその後有料だったり。

別に有料のものでもそんなに高くないし良いかなと少し思いましたが、そのうち機種がサービスの対象外になったりしたら嫌だなと思って、別途サービスが必要にないものにしました。

また、AIで判定するのもありましたが別にドメイン直指定で良いな、と。

いうことでASUSのにしました。

ブロック

以前のASUSの製品だとFirewallのURLフィルタのブラックリストに入れられるのは確か10個とか20個とかだったと思うんですが、64個までになってて良かったです。

10個だとちょっと足りないかなーって感じで20個あれば大体良い気もしますが。

でいくつかブロックしてみるとWebページの動作が軽い。ネットワークやらjsの動作やらが減って軽くなったおかげ。

たまにjsの読み込みのところでブロックされて遅くなるサイトもありますがご愛嬌。

-> タイムアウト短くしてみました

Ubuntuで/etc/resolv.confを変更してDNSのタイムアウト時間変更 - なんじゃくにっき

思うところ

私のように悪いことをして広告ブロックする人が増えてくると対策されていくのかもしれないなーっと。

例えば

  • 広告配信自体をコンテンツと同ドメインから配信する
  • 広告配信のドメインを頻繁に(or 動的に切り替える)
  • 広告ブロックされているとコンテンツを見えなくする(これは今でもたまにありますが)

などなど。とはいえ費用対効果もあるので今のところはあまり見かけないですね。

注意点

動作が不安定との口コミがありますが、発売当初はそうだったみたいです。

購入したらまずファームウェアをアップデートしたほうが良いと思います。

今の所自分の環境では問題は発生していません。

言語処理100本ノック を解いてみる

言語処理100本ノックを解いていきます。

言語処理100本ノック 2020 (Rev 2) - NLP100 2020

解くのに使ったプログラミング言語Python 3。

なるべく関数型言語チックに書いているつもりです。再代入はなるたけ避ける。

解答はgithubにも載せておきます。

GitHub - nanjakkun/nlp_100_ans: 言語処理100本ノックの解答

1章の答えと解説だけこの記事にも載せます。

全部解説書こうと思ったけど力尽きたのでgithub見てください・・

(3/24現在 40問まで終了)

1章 準備運動の解答

00

文字列”stressed”の文字を逆に(末尾から先頭に向かって)並べた文字列を得よ.

str = "stressed"[::-1]
print(str)

sliceの使い方を知っていれば一瞬。[start:stop:step]。

他言語から来るとstrにreverseってメソッドが欲しくなるかも。

01

「パタトクカシーー」という文字列の1,3,5,7文字目を取り出して連結した文字列を得よ.

str = "パタトクカシーー"[::2]
print(str)

これも前問と同様sliceの使い方知ってれば。

02

「パトカー」+「タクシー」の文字を先頭から交互に連結して文字列「パタトクカシーー」を得よ.

itr = map(lambda x: x[0] + x[1], zip("パトカー", "タクシー"))
print("".join(itr))

zip使いましょう。

03

“Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics.”という文を単語に分解し, 各単語の(アルファベットの)文字数を先頭から出現順に並べたリストを作成せよ.

import re

str1 = "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."
itr1 = map(lambda str: len(str), re.split(r'[\s,\.]+', str1))
itr2 = filter(lambda x: x > 0, itr1)
print(list(itr2))

04

"Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can.” という文を単語に分解し,1, 5, 6, 7, 8, 9, 15, 16, 19番目の単語は先頭の1文字, それ以外の単語は先頭の2文字を取り出し, 取り出した文字列から単語の位置(先頭から何番目の単語か)への連想配列(辞書型もしくはマップ型)を作成せよ.

import re

str1 = "Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can."

def to_elemnt_symbol(itr) -> str:
  idx = itr[0]
  if (idx in [1, 5, 6, 7, 8, 9, 15, 16, 19]):
    return itr[1][0], idx
  else:
    return itr[1][0:2], idx

itr1 = filter(lambda x: len(x) > 0, re.split(r'[\s,\.]+', str1))
itr2 = map(to_elemnt_symbol, enumerate(itr1, 1))

print(dict(itr2))

他言語だとmapWithIndexみたいなのを使うところはPythonではenumerate。

05

与えられたシーケンス(文字列やリストなど)からn-gramを作る関数を作成せよ. この関数を用い,”I am an NLPer”という文から単語bi-gram,文字bi-gramを得よ.

def word_n_gram(text, num=2):
  words = text.split(" ")
  return list(map(lambda i: words[i:i+num], range(len(words))))

def char_n_gram(text, num=2):
  words = text.replace(" ", "")
  return list(map(lambda i: words[i:i+num], range(len(words))))

text1 = "I am an NLPer"

print(word_n_gram(text1, 2))
print(char_n_gram(text1, 2))

06

paraparaparadise”と”paragraph”に含まれる文字bi-gramの集合を, それぞれ, XとYとして求め,XとYの和集合,積集合,差集合を求めよ. さらに,’se’というbi-gramがXおよびYに含まれるかどうかを調べよ.

# 前回の問題で使ったのと同じ関数
def char_n_gram(text, num=2):
  words = text.replace(" ", "")
  return list(map(lambda i: words[i:i+num], range(len(words))))

text1 = "paraparaparadise"
text2 = "paragraph"

X = set(char_n_gram(text1))
Y = set(char_n_gram(text2))

print("union: ", X | Y)
print("intersect: ", X & Y)
print("diff: ", X - Y)

if "se" in X:
  print("X contains se")

if "se" in Y:
  print("Y contains se")

Set使いましょう。

07

引数x, y, zを受け取り「x時のyはz」という文字列を返す関数を実装せよ. さらに,x=12, y=”気温”, z=22.4として,実行結果を確認せよ.

def weather(x: int, y: str, z: float) -> str:
  return f'{x}時の{y}は{z}'

print(weather(12, "気温", 22.4))

文字列に変数展開する方法はいくつかあるけどその1つ。

08

与えられた文字列の各文字を,以下の仕様で変換する関数cipherを実装せよ. - 英小文字ならば(219 - 文字コード)の文字に置換 - その他の文字はそのまま出力 この関数を用い,英語のメッセージを暗号化・復号化せよ.

def cipher(text: str) -> str:
  def transform_char(ch):
    if (97 < ord(ch) & ord(ch) < 122):
      return chr(219 - ord(ch))
    else:
      return ch

  return "".join(list(map(transform_char, text)))

text1 = "lorem ipsum"
print(cipher(text1))
print(cipher(cipher(text1)))

decodeとencodeが同じ関数なんですねえ。2回同じのを作用させると元に戻る。

09

スペースで区切られた単語列に対して,各単語の先頭と末尾の文字は残し, それ以外の文字の順序をランダムに並び替えるプログラムを作成せよ. ただし,長さが4以下の単語は並び替えないこととする. 適当な英語の文(例えば”I couldn’t believe that I could actually understand what I was reading : the phenomenal power of the human mind .”)を与え, その実行結果を確認せよ.

import random

def typoglycemia(text) -> str:
  def shuffle(word) -> str:
    if (len(word) <= 4):
      return word
    else:
      substr = word[1:-1:]
      return word[0] + "".join(random.sample(substr, len(substr))) + word[-1]

  words = text.split(" ")
  return " ".join(map(shuffle, words))

text1 = "I couldn’t believe that I could actually understand what I was reading : the phenomenal power of the human mind ."
print(typoglycemia(text1))

以前流行ったアレですね。

この ぶんょしう は イリギス の ケブンッリジ だがいく の けゅきんう の けっか 、 にんんげ は もじ を にしんき する とき その さしいょ と さいご の もさじえ あいてっれば じばんゅん は めくちちゃゃ でも ちんゃと よめる という けゅきんう に もづいとて わざと もじの じんばゅん を いかれえて あまりす。

余談

ChatGPT(3.5)に解けるのかと思って問題を与えてみたら、大体解けますね。ちょっと間違ってたりするけど。

と言ってもこれは解答をネットにアップしている人は他にもいるので、まあ、って感じではありますが。

速度的には自分で書くより向こうのほうがずっと速いのでこちらの完敗でございます。

reCAPTCHAとか画像認証をやめてほしい

題意のとおりreCAPTCHAや画像認証をやめてほしい。

Webサービスで何かのステップの先に進もうとすると人力で画像を読み取って入力しないといけないやつね。

読みづらくした文字を読み取って入力させたり、この◎◎枚の絵の中から●が描かれたものを選べ、みたいなやつ。

正直あの手のものが苦手で、何度も失敗することがよくある。

セキュリティを担保するんだったら他段階認証とかにしてほしい。ログインが必要なサービスだったらそっちのほうが安全だし利用者に負担がかからないと思う。

画像認証なんて攻撃者がちょっと本気を出せば突破できるものだし。

ログイン不要でコメントできるサービスでbotを防ぐのには多少効果があるのかはしれない。

VSCodeのextention Ruby LSP

VSCodeRuby用ExtentionとしてはVSCode Ruby を使っていたんですが、Ruby LSPへの乗り換えを勧められるので乗り換えました。

VSCode Ruby - Visual Studio Marketplace

Ruby LSP

Ruby LSP - Visual Studio Marketplace

VSCodeのextention画面からRuby LSPインストールします。

Multi-root workspaces

Ruby LSPをインストールしてもそのままでは動きません。

なんか”Lockfileがプロジェクト直下に見当たらないよ”的なエラーメッセージが出ます。

VSCodeのMulti-root workspacesを有効にしろと言ってきます。

(なお、1 Workspaceに1 root = 1 projectの場合にはそのまま動くと思います)

workspacesをMulti-rootにするには、workspace直下の.code-workspaceを編集します。

{
    "folders": [
        {
            "path": "."
        }
    ],
}

上記のようにrootが直下1つになっているのを

{
    "folders": [
        {
            "path": "project_a"
        },
        {
            "path": "project_b"
        },
    ],
}

のようにします。

気を付ける点

Ruby 3.0以降でないと使えないです。

Ruby 2.7以前のプロジェクトを触らないときは効いてくれません・・

また当然ですがプロジェクトの.ruby-versionで指定されたバージョンと同じバージョンのRubyがローカルに必要です。

dockerで開発しているといなかったりするんですよねえ。

IRBの色を変えたい

なーんかRubyのREPLの色が見づらくて変えられないかなーと思っていたら変えられるとのこと。

reline/doc/reline/face.md at master · ruby/reline · GitHub

$HOME/.irbrc に

if defined? Reline::Face
  Reline::Face.config(:completion_dialog) do |conf|
    conf.define :default, foreground: :white, background: :blue
    conf.define :enhanced, foreground: :white, background: :magenta
    conf.define :scrollbar, foreground: :white, background: :blue
  end
end

relineがない環境でirbが落ちないようにinstallされているか調べています