19日に更新してた

アフィリエイトはないよ

【golang】SOURCENEXT 取り扱いの SpeakBuddy の値段をチェックしてみる

こないだ、セールで買い逃したのでチェックするアプリを作ってみました。

package main

import (
	"context"
	"fmt"
	"log"
	"strings"

	"github.com/chromedp/chromedp"
)

func main() {

	allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), []chromedp.ExecAllocatorOption{
		chromedp.NoFirstRun,
		chromedp.NoDefaultBrowserCheck,
		chromedp.Headless,
	}...)
	defer cancel()

	ctx, cancel := chromedp.NewContext(allocCtx, chromedp.WithLogf(log.Printf))
	defer cancel()

	url := "https://www.sourcenext.com/product/0000013977/"
	xpath := `//*[@id="app-cart"]/div/div/section/div[2]/div/div[3]/div[1]/div[1]/div[2]/span[1]`
	xpath2 := `//*[@id="app-cart"]/div/div/section/div[2]/div/div[3]/div[1]/div[2]/div[2]/span[2]`
	var res string
	var res2 string

	err := chromedp.Run(ctx,
		chromedp.Navigate(url),
		chromedp.Text(xpath, &res),
		chromedp.Text(xpath2, &res2),
	)
	if err != nil {
		log.Fatalln(err)
	}

	if res2 != "" {
		res = res2
	}
	res = strings.Replace(res, ",", "", 1)
	t := "今日のスピークバディの価格は " + res + " 円です。"
	fmt.Println(t)

タスクスケジューラに入れておいて、通知送るようにすればセールでの買い逃しがないかも。

copilotで作った"SOURCENEXTのSpeakBuddyの値段をチェックするgolangのアプリのブログのイラスト"

【python & golang】Yahoo!ファイナンスVIP倶楽部のCSV、下り最速

どこかの豆腐屋みたいなタイトルにしておりますが、実態はへっぽこでして。

東証にあるエクセルファイルをダウンロードして、リストを作ろうと python ノリで golang をいじってみたのですが、130A.Tとかアルファベット表記が入っている番号を golang で取り込むのが僕には無理だった*1ので python pandas を使って print したのを取り込む形にしました。golang のアプリと同じディレクトリにおいておきます。

pro market のものはヤフーファイナンスにて出てこなかったので、取り除いてあります。

>>tosho.py
import pandas as pd
a=pd.read_excel("https://www.jpx.co.jp/markets/statistics-equities/misc/tvdivq0000001vg2-att/data_j.xls")
print(",".join([str(s) for s in a["コード"][a["市場・商品区分"]!="PRO Market"]]))

ここからが難儀の始まり

  • tab を複数開くと処理時間がかかるだろうと思い、1枚だけにして更新。
  • SSD 上にユーザープロファイルもダウンロードファイルも置いて実行するとまとまった数が落とせるようになる。
  • エラー率が高かったので、クリックしてダウンロードが終了したら次に画面読み込みに行くようにする。
  • ヘッドレスにしたらうまくいくかと思うも、動かないので表示を出す。
  • 650-700 ファイルくらいダウンロードすると止まる。平均ダウンロード時間が sleep 抜きで2秒を超えると進まなくなる。chrome の設定では変化なし。
  • 1ファイルごとに sleep を 0.5 秒いれると 1000程度落とせるようになる。平均ダウンロード時間が sleep 抜きで2秒を超えると進まなくなるのは変わりない。
  • どうにもならないのでダウンロード検出、sleep は消し、1ファイル1tab にて落とすようにしたらダウンロードできるようになった。1ファイルあたりの平均ダウンロード時間も早くなる。tab の close() を明示的にやらないといけないがある程度の時間を取っておかないと行けないので、goroutine にて閉めるようにする。
  • 電源設定を変えてないのでスリープすると落ちるのでマウスジグラー的なものを入れてみる。自分の環境だと座標指定してもよくわからない動きをするのでランダムで動かす。
  • 動くには動くが3000ファイルくらい落とすと、画面表示は出るがクリックまでの速度が遅くなる。600ファイル位から10ファイルに1つから2つくらい3-7秒かかるようになる。
  • 並列ダウンロードできるように組んだらいいのかとやってみるがループだけ回って全くダウンロードしない。
  • ガベージコレクション動かしたらいいのか?と runtime.GC() やってみるも変わらず。page.ClearCompilationCache()も同じく。
  • go get -u github.com/chromedp/chromedp して go: upgraded github.com/chromedp/chromedp v0.9.3 => v0.9.5 変わらず。
  • なぜかポートフォリオが作られていたので削除。変化なし。
  • chrome の設定のメモリセーバーをオンにする。変わらず。
  • 単なるループだとある程度ダウンロードできるようになる。
  • chrome を バージョン: 123.0.6312.123(Official Build) (64 ビット)にしたらある程度うまくいくようになったが、広告の表示が出るときに時間がかかっているので adblock をいれた。うまくいきそうだったので放置して寝たら、あと1ファイルというところでアプリが落ちて終わっていた。\r\n の改行が入っていた模様。
  • 画面描画に時間がかかるのでは?と考え描画が終わったあたりで次を読み込むといいのかもと、sleep を入れて goroutine を使ってみる。950ms 位を入れないとうまくダウンロードできないが、まれにダウンロード数が足りないのでそのあたりを補足。
  • 時間帯によってダウンロードスピードが変化する。夜ダウンロードすると速い。
package main

import (
	"context"
	"fmt"
	"log"
	"math/rand"
	"net/url"
	"os"
	"os/exec"
	"path/filepath"
	"slices"
	"strings"
	"sync"
	"time"

	"github.com/chromedp/cdproto/browser"
	"github.com/chromedp/cdproto/cdp"
	"github.com/chromedp/chromedp"
	"github.com/go-vgo/robotgo"
)

// ランダムで動かしている。
func mouseziggler() {
	robotgo.MouseSleep = 100
	x, y := rand.Intn(1920), rand.Intn(1080)
	robotgo.Move(x, y)
}

func main() {
	s := time.Now()

	p, _ := os.UserHomeDir()

	// download directory
	wd := filepath.Join(p, "Downloads", "csv")

	// userdatadir
	UserDataDir := filepath.Join(p, "AppData", "Local", "Google", "Chrome")

	// tosho.pyからの読み込み
	stdout, err := exec.Command("python", "tosho.py").CombinedOutput()
	if err != nil {
		log.Fatalln(err)
	}
	tmpIds := strings.TrimRight(string(stdout), "\r\n")
	ids := strings.Split(tmpIds, " ")

	const yf string = "https://finance.yahoo.co.jp"

	// 新しいコンテキストを作成
	allcCtx, cancel := chromedp.NewExecAllocator(context.Background(), []chromedp.ExecAllocatorOption{
		// headlessいれると動かない
		// chromedp.Flag("headless", true),
		chromedp.NoFirstRun,
		chromedp.NoDefaultBrowserCheck,
		chromedp.UserDataDir(UserDataDir),
	}...)
	defer cancel()

	ctx, cancel := chromedp.NewContext(allcCtx)
	defer cancel()

	// ブラウザを起動
	err = chromedp.Run(ctx,
		chromedp.Navigate(yf),
		// ダウンロードディレクトリを設定
		// browser.SetDownloadBehaviorBehaviorAllowAndName にしない
		browser.
			SetDownloadBehavior(browser.SetDownloadBehaviorBehaviorAllow).
			WithDownloadPath(wd).
			WithEventsEnabled(true),
	)
	if err != nil {
		log.Fatalln(err)
	}

	// (NEXT FUNDS)ロシア株式指数上場投信【1324.T】
	l := []string{"1324"}

	var wg sync.WaitGroup

	const concurrency = 2
	limit := make(chan struct{}, concurrency)

	for i, id := range ids {
		// test用
		// if i >= 100 {
		// 	break
		// }

		if slices.Contains(l, id) {
			continue
		}

		time.Sleep(950 * time.Millisecond)

		rap := time.Since(s).Seconds()
		wg.Add(1)

		f := func(i int, id string, ctx context.Context, rap float64) {

			if i%100 == 0 {
				// マウスジグラー的なもの
				mouseziggler()
			}

			id = strings.ReplaceAll(id, " ", "")
			id += ".T"
			url, _ := url.JoinPath(yf, "quote", id, "history")

			idf := id + ".csv"

			tabctx, cancel := chromedp.NewContext(ctx)

		L:
			limit <- struct{}{}
		L2:
			var nodes []*cdp.Node

			// タブでダウンロードを行う
			err = chromedp.Run(tabctx,
				chromedp.Navigate(url),
				chromedp.Nodes("#csv_dl", &nodes, chromedp.NodeVisible, chromedp.AtLeast(0)),
			)
			if err != nil {
				log.Fatal(err)
			}

			if len(nodes) > 0 {
				chromedp.Run(tabctx,
					chromedp.Click("#csv_dl > a", chromedp.NodeVisible),
				)
				<-limit
			} else {
				goto L2
			}

			time.Sleep(5 * time.Second)

			if _, err := os.Stat(filepath.Join(wd, idf)); err != nil {
				goto L
			} else {
				wg.Done()
				cancel()
				avr := time.Since(s).Seconds() / float64(i+1)
				dif := time.Since(s).Seconds() - rap
				rem := avr * float64(len(ids)-(i+1))
				fmt.Printf("%v, %v, %.2f, %.4f, %.2f, %.2f \n", i, id, time.Since(s).Seconds(), avr, dif, rem)
			}

		go f(i, id, ctx, rap)

	}

	wg.Wait()

	fmt.Println("Done.")

	dir, _ := os.ReadDir(wd)


	fmt.Printf("time\t\t: %.1f sec\ntime per ID\t: %.2f sec \n", time.Since(s).Seconds(), time.Since(s).Seconds()/float64(len(dir)))

	fmt.Printf("download files\t: %d files\nlost files\t: %d files\n", len(dir), len(ids)-len(dir)-len(l))
}

で結果が

time            : 4068.3 sec
time per ID     : 0.96 sec
download files  : 4251 files
lost files      : 0 files

こんな感じ。

*1:xls のライブラリ2つ試してみたのですができませんでした

R604あたりのチラ裏

  • 東証のリストからダウンロードするファイルリストを golang で作るために xml のライブラリを2つ試してみたけれど、どちらも 130A のようなアルファベットが入ったものを拾ってくれない & 自分でライブラリ書いてゴリ押しするほどプログラミング能力がないので、 python pandas で抽出して stdout からの読込みすることにした。pandas ありがたい。
  • ELSA speak のスコアが久しぶりに 80%になった。1回でいけるもの、2,3回でいけるもの、なかなかうまくいかないものが未だあるので、通じる英語のスコアという話ではあるがなかなか全部で出てはくれないので、まだまだ練習。
  • 身近で強風被害が出た。今年の春一番は強いな。
  • オオタニサン結婚おめでとうとか思ってたら、大変なことになってるな。

www.moranbong.co.jp

  • 勝訴おめでとうございます。

  • ソースネクストのサイトで売ってたスピークバディの一年版”完売しました”になっている。扱いやめた?と思っていたら復活してた。予定販売数があるのかな?
  • ELSA speak 100日超えて毎日の練習によるスコアが、上がって戻って下がって戻って80%。X(元twitter)だと英語系インフルエンサーはすごいけれど、中々ああはなられへんよなぁ。発音はなんとかですが、流暢性とか抑揚があかんなぁ。
  • abceed を使って target1900 をやってたのですが、おすすめと出てきたので英検3級の単語集に乗り換えてみたら優しめでバリバリ行けていい感じなので、準2級、2級と進めています。
  • 今更ながら chromedp 癖強。大量のファイルをダウンロードすると、なかなか思った通りに動いてくれない。pythonselenium とか挙動が安定していて楽だった記憶ガガガ
  • 新年度なので NHK ラジオ英会話とかはじめてみる。かつては何言っているのかわからなかったが、きれいな発音でゆっくりでとても聞き取りやすいに変わったのは ELSA speak のおかげ。
  • ロクに使いもしないのに golang のバージョンが上がると入れ替えるようにしていて、1.22.2 にした。
  • vast.ai の広告が結構出てくるけどなんでだ?最近そういうのやってないからあんまり広告踏んでないんだけれどなぁ。

cloud.vast.ai

  • NHK ラジオのニュースで学ぶ「現代英語」の音声ファイルをダウンロードするときに英文だけ metadata の lyrics に書き込むようにしてみた。ついでに mp3 や aac につける ffmpeg のサムネイルをネットにあるもので試してみたら、ローカルじゃなくても OK だった。再生時に画像が出るだけで結構アガる。
  • 転職で保険証の手続きをしているけれど手元に新しい保険証がないような場合、マイナンバーカードを持っていくと面倒なく受診できるって聞いた。めちゃめちゃ便利なのね。

news.mynavi.jp

  • ネットのダイエットとかのまとめを見ているとトマトジュースってのが目についたので食品成分データベースで調べてみたら、推定値ながらグルタミン酸アスパラギン酸が桁違いに多い。*1

fooddb.mext.go.jp

  • ちょっと前まで景気がいい話といえば中国だったのに最近なんかしょぼくれた感じの報道しかされなくなって、世界中どこも景気のいい話がない気がしていたけれど、日経平均最高値更新したり今年度日本は賃金引き上げ減税があるから、今年度末になったら景気の良さを実感するだろうか?
copilot で作った"ちょっと前まで景気がいい話といえば中国だったのに最近なんかしょぼくれた感じの報道しかされなくなって、世界中どこも景気のいい話がない気がしていたけれど、今年度日本は賃金引き上げ減税があるから、今年度末になったら実感するだろうか?"の画像

*1:アミノ酸可食部100g

今月のチラ裏

  • 暇空茜氏の本が出ると知ったのと取り扱い停止と知ったのが同時で、本が買えない?テロ予告した奴らふざけやがって。
  • ELSA speak premium 年会員 50% off sale 2/20-3/20
  • 少しはうまくなったかなと、ELSA speak のテストをやったら一番最初にやったときと同じ点数だった。この2ヶ月はあんまり活きていないのねとガッカリした。
  • 毎日の練習で、単語とか5-7語くらいまでの短文だとなんとかなるのだけれど、長くなるとどんどん駄目になっていくので、もうちょっとゆっくり文を伸ばしてくれるレッスンが欲しい。後、文の発音練習の時、抑揚の表示も欲しいし、流暢もどれくらい理想と離れているかの目安を表示してくれると嬉しい。
  • 個人的に一番いいと思っている機能は、発音練習の自分の発音が発音記号でどのような音かを表示してくれる機能*1。よく英語サイトで 自分の発音を聞き返して お手本の発音との違いを確認して練習しましょう とかあるけれど、違いがわからないから この機能はとても重宝してます。一人でも音の違いを客観的に判断でき 口の動かし方がこういうふうに異なっているのだなと 理解し意識して練習できるのはメリットだと思うけど、なかなかうまくいかないのな。
  • メルカリ、本人確認済みなのにアプリ開くごとに 本人確認済み云々と通知とメールを送ってくるのはなんでや?もう10回くらい送られてきていて、公式スパム化してきている。
  • なんかバイデン大統領、ノムタンぽくなってきていて、アメリカ大丈夫かって心配になってくる。
  • groq.com 謳い文句通りにすごく表示が早い。model は Mixtral 8x7B-32K と Llama 2 70B-4k が選べるようですが、以下デフォルトになっていた前者でのお話。
  • 日本語は文字数とばかりに切れて「続きをお願いします」というと出てくるけど章分けされていて章の途中で文が切れていても次の章から出てきた。
  • 回答が英語だと
中東戦争の経緯について教えて

中東戦争(The Middle East War)は、20th 世紀にわたり複数の軍事的な conflicting claims to territory, and political and ideological differences have led to a number of significant conflicts in the Middle East. Here are some of the key events and factors that have contributed to the outbreak of war in the region:

1. The fall of the Ottoman Empire: The Ottoman Empire, which once ruled much of the Middle East, began to decline in the late 19th century. During and after World War I, the empire collapsed, leading to the creation of new states in the region, including Iraq, Syria, Lebanon, Jordan, and Palestine. These new states were created through a series of agreements between the Allied powers, including Britain and France, and were often drawn along arbitrary borders that did not take into account the region's complex ethnic and religious diversity.
2. The establishment of Israel: The establishment of the state of Israel in 1948 was a major flashpoint in the Middle East. Many Arab states rejected the creation of Israel and launched a series of wars to try to destroy it. Israel emerged victorious in these wars, leading to the displacement of hundreds of thousands of Palestinians and fueling resentment and anger towards Israel and the West.
3. The Iranian Revolution: The 1979 Iranian Revolution, which overthrew the US-backed Shah and established an Islamic republic, was another significant event in the region. The revolution had a profound impact on the Middle East, inspiring other Islamic movements and leading to a rise in anti-Western sentiment.
4. The Gulf War: The Gulf War of 1990-91, which was triggered by Iraq's invasion of Kuwait, was another major conflict in the region. The war saw a coalition of Western and Arab states led by the United States intervene to expel Iraqi forces from Kuwait. The war had significant consequences for the region, including the destabilization of Iraq and the rise of Islamist groups.
5. The US invasion of Iraq: The US-led invasion of Iraq in 2003, which was justified on the grounds of removing weapons of mass destruction, has been widely criticized as a major factor in the destabilization of the region. The invasion led to a power vacuum that was filled by various militia groups and terrorist organizations, including ISIS.

These are just a few of the key events and factors that have contributed to the outbreak of war in the Middle East. The region's complex history, ethnic and religious diversity, and strategic importance have made it a flashpoint for conflict for many years, and it remains a volatile and unstable region today.
  • 日本語だと
中東戦争の経緯について日本語で教えて

中東戦争とは、20 世紀にわたり、中東地域で起きた複数の軍事的な conflicting claims to territory, 政治的・思想的な違いに起因する戦争の総称です。中東地域は、複雑なエチーニック・リライジョンの多様性と、戦略的に重要な地域であるため、歴史的には多くの戦争の焦点となってきました。以下に、中東戦争の主要な起因要因をいくつか挙げます。

1. オトマン帝国の崩壊:オトマン帝国は20 世紀にわたり中東を支配していた帝国でしたが、20 世紀にわたり徐々に衰退し始め、第一次世界大戦中に崩壊しました。この後、イギリスやフランスなどの連合国は新しい国を中東に設立し、それらの国境はオトマン帝国の複雑なエチーニック・リライジョンの多様性を考慮して描かれていませんでした。その結果、イラク、シリア、レバノン、ジョーダン、パレスチナなどの新しい国が生まれ、これらの国の間で紛争は継続されていました。
2. イスラエル国の設立:1948 年イスラエル国の設立は、中東での主要な火災地となりました。アラブな国々はイスラエルの設立を拒否し、それを破壊するために一連の戦争を起こしました。しかし、イスラエルはこれらの戦争で勝利し、パレスチナ人の数百万人が追放され、これによってイスラエルと西方への憤りや怒りを煽る要因となりました。
3. イラン革命:1979 年のイラン革命は、イランを世界的に大きく影響を与えた主要なイベントの1つでした。革命は、イスラム的な運動をイスパイアし、西方に対する反感を増し、アメリカ合衆国に対する反感を高めました。
4. クWAIT侵略戦:1990-91 年のクWAIT侵略戦は、中東での別の主要な戦争でした。この戦争は、イラクがクWAITを侵略したことに起因し、アメリカ合衆国を率いる西方とアраб国が参加し、クWAITをイラク軍から取り除くことに成功しました。この戦争は、イラクの不安定化やイスラム

続きをお願いします

1. イラク侵略:2003 年、アメリカ合衆国によるイラク侵略は、武器の破棄を理由に行われましたが、後からその理由は虚偽であることが判明しました。この侵略は、イラクの不安定化やテロリスト団の増加など、中東地域の不安を繰り広げました。
2. シリア・イラクの国境地域:シリアとイラクの国境地域は、過去数年間にわたり、テロリスト団や武装団体が活動する領域となっていました。ISISやアルカイーダなどのテロリスト団が、この地域を拠点として活動し、中東地域を不安定にしています。
3. サウジアラビアとイランの関係:サウジアラビアとイランは、宗教的・政治的な違いに基づいて、数え切れないほどの争いを持っていました。これらの国の間での緊張は、中東地域の不安を維持しています。

これらの要因は、中東地域での戦争の主要な原因となっています。中東地域は、複雑な歴史、エチーニック・リライジョンの多様性、戦略的に重要な地域であるため、継続的に不安定な状況を生み出しています。政治的・軍事的・経済的な解決策を考えることは、中東地域の安定を確実にするために不可欠です。
  • copilot でも Bard 改め Gemini でもうまく行かない回文はgroqでもやっぱり全然駄目。
日本語の回文の例を上げてください

日本語の回文の例としては、以下のようなものがあります。

1. アイデア
2. アメリカ
3. おおさか
4. いいね
5. おかあさん
6. おかえり
7. れれた
8. おろお
9. くるく
10. サイタマ

これらは、文字列の先頭と末尾が同じ文字であるため、日本語の回文となっています。回文は、文字列が左右反転して同じ文字列となることを意味し、数学的には対称性を持っている文字列です。回文は、文字列の中心を中心として左右対称であることから、数学的に興味深い研究対象となっています。

groq.com

  • やった、ELSA score が一瞬だけ80になった。うれしい。
  • abceed で単語の総復習モードで一日前とか二日前とかのところ、今日やったものとか何日以内って選択肢が欲しい。

www.youtube.com

  • 暇空茜氏の本がアマゾンで出ていた。テロはなかったし、とりあえず入手できてよかった。けど、テロ予告した奴らは許せん。
  • 多様性に配慮した聖書とかあるのだろうか?
  • "多言語の習得は「音」から 脳領域特定、文法理解早く" というニュース記事が出ていた。英語頑張るか。しかし、プログラミング言語はどうなるんだろう?

news.livedoor.com

  • 自分用に作っているしょうもないプログラムでも色々面倒なのに、売り物のソフトはバグ対応だけじゃなくフールプルーフまでやらなきゃいけないから大変だろうな。
  • 3coins で売ってたマルチローラーボールってマッサージ用のボールがとてもいい。5 cm くらいのプラスティック製のボールに持ち柄がついていてスムーズに転がり、足の裏とかコロコロするのにとてもいい。同じ文末にしても仕方がないと思うくらいには気に入っている。google:マルチローラーボール 3coins
  • 2/29-3/3 に SOURCENEXT でソフト配布やるみたいなので3/1に Rosetta Stone をもらおうか迷い中。他のは使わないだろうから遠慮しておこう。
  • ELSA speak、発音とか ELSA score は変わってないけれど*2、たまに流暢さが上がってきていたりするのが救い。しかし、80%の壁は厚い。
  • 閏日で免許発行ができないとか報道されているけれど、47都道府県しかないのに免許発行ソフトが数種類あることに驚き。富士通NEC、NTTあたりのどこかが一社で総取りだと思っていた。
  • 人から聞いた話、ELSA speak 50 %オフセールってことで入れてみて、プロフィールのところのアカウントのステータスと思われる所からアップグレードしようとしたら、年間契約がgoogle play で 60 %オフの 5550 円だったとのこと。あと、友人のリンクを踏んだけれどどうやってプロのお試しするの?って聞かれて答えられなかった。
  • クルトガダイブ、未だ定価で目にしない。オレンズはもうちょっと手に入りやすかった気もする。三菱鉛筆は転売屋に利益供与するのが社是なのだと思って諦めようと思う。
  • AI の学習データの著作権云々で日本はゆるいって話、法人税の支払いが日本以外が主だから主体が国外、だから AI から上がった利益の日本納税分以外の割合で著作権侵害とかって話になろうかと思うがどうなのだろうか?
  • 最近の Microsoft は、lightGBM にしても chatGPT にしても、まだよくわからないけれど BitNet B1.58 にしてもパラダイムシフトなところに関わっていて好感持てる。
  • ELSA speak で流暢さが伸びないなら、会話式の練習をするようにと X であった。最近サボり気味でデイリーしかやってなかったから、加えてやってみよう。

  • 最近タウリン入りのドリンクや錠剤が流行っているけれど、単品だと医薬品で薬局で購入できないらしい。ドリンク剤に入れられる程度のものなんだからビタミン剤的にしたほうがいいんじゃないの?
  • 仕事の提出書類で必要な数字を出すのが面倒で少し放置しておいたのを、期限が迫ってきて仕方がないので計算したら予想ぴったり。それだけ埋めたら提出できるので埋めて10分で投函。
  • 本を読んで独学しよう的な本を読むと大体の本に「何冊か本を読んでみよう。書き方の違いで合う合わないがあるから。」とあるのですが、どの本でも巧妙に避けられている点をちょっと突っついてみると結果が出る場合もあるので、本やネットの読み方は大事。
  • 鳥山明氏がお亡くなりになられたとのこと、ご冥福をお祈りします。バイちゃ、バイちゃ。

www.youtube.com

  • ダイソーで売ってるメラミンスポンジ、なんで激落ちくんこんなに安いんやろ?と思っていたらキャラが似ているだけで違うのね。

google:激落ちくん
google:ダイソーメラミンスポンジ

  • chromedp の98%以上が golang 製なのでスクレイピングを並列でできるよなぁと思っていたが、試作してみたら割と簡単にできそうなのでそのうちに細かいところを詰めよう。

>

copilot で作った スマートフォンを使ったAI英会話でうまくならないと苛ついている桃白白
<孫悟空だと洒落にならない位 DRAGON BALL だったので、ちっとも桃白白じゃないところが著作権侵害していなさそうで良い感じ。

*1:単語の発音練習だと色付きの発音記号 文章だと単語単位で再練習するときのページ 発音した後の緑、黄、赤になっている単語単位で下線が引かれている部分を押すと芳しくない部分の発音記号が出てます

*2:むしろ落ちてたりする

【Python】二次元リストの中に append できないの?

あるデータを閾値で区切って処理しているときに、閾値ちょうどの値がそこそこあったりすると、上に入れるか下に入れるかで迷って、ならどっちもやってみてどう考えるのがいいか統計とってやればいいかと手を付けてみる。そのときに、python で 0,1,と閾値 を 2 として、 itertools.product を使ってみると

import itertools

a=[0,2,0,1,2,2]
b=[list(i) for i in itertools.product(*[[i] if i!=2 else [0,1] for i in a])]
print(b)

>>[[0, 0, 0, 1, 0, 0],
>> [0, 0, 0, 1, 0, 1],
>> [0, 0, 0, 1, 1, 0],
>> [0, 0, 0, 1, 1, 1],
>> [0, 1, 0, 1, 0, 0],
>> [0, 1, 0, 1, 0, 1],
>> [0, 1, 0, 1, 1, 0],
>> [0, 1, 0, 1, 1, 1]]

となって戻ってきます。二次元リストにするのが便利なので、「itertools を使わずにできないか?」ということで、二次元リストの中のリストに値を append してやるとこれが予想したとおりにうまく動かない。検索しても二次元リストの中のリストに append するって解説はないみたいで色々やってみたのですが、+ でリストを連結していくのならうまくいきそうだったので、要素が一つのリストを作って、

a=[0,2,0,1,2,2]

def product(a):
    n=a.count(2)
    c=[[]]*2**n
    nn=1
    for cc in a:
        for j in range(2**n):
            jn=j//2**(n-nn)%2
            if cc!=2:
                c[j]=c[j]+[cc]
            else:
                c[j]=c[j]+[jn]
                if j==len(range(2**n))-1:
                    nn+=1
    return c
                
print(product(a))

>>[[0, 0, 0, 1, 0, 0],
>> [0, 0, 0, 1, 0, 1],
>> [0, 0, 0, 1, 1, 0],
>> [0, 0, 0, 1, 1, 1],
>> [0, 1, 0, 1, 0, 0],
>> [0, 1, 0, 1, 0, 1],
>> [0, 1, 0, 1, 1, 0],
>> [0, 1, 0, 1, 1, 1]]

みたいな感じ。

じゃあ、複数データの選択肢が複数なら組み合わせをどうしようかってことで、得意技?の文字列変換でなんとかしてみることとした。

a=["a","b","c"]
b=[0,1,2,3]
c=["d","e","f"]
d=[4,5]

def test(*l):
    def test2(l1,l2):
        r=[]
        for ll1 in l1:
            for ll2 in l2:
                r.append(str(ll1)+","+str(ll2))
        return r
    if len(l)==1:
        return l[0]
    elif len(l)==2:
        return test2(*l)
    elif len(l)>2:
        l1,l2,*l3=l
        return test(test2(l1,l2),*l3)

print(test(a,b,c,d))

>>['a,0,d,4',
>> 'a,0,d,5',
>> 'a,0,e,4',
>> 'a,0,e,5',
>> 'a,0,f,4',
>> 'a,0,f,5',
>> 'a,1,d,4',
>> 'a,1,d,5',
>> 'a,1,e,4',
>> 'a,1,e,5',
>> 'a,1,f,4',
>> 'a,1,f,5',
>> 'a,2,d,4',
>> 'a,2,d,5',
>> 'a,2,e,4',
>> 'a,2,e,5',
>> 'a,2,f,4',
>> 'a,2,f,5',
>> 'a,3,d,4',
>> 'a,3,d,5',
>> 'a,3,e,4',
>> 'a,3,e,5',
>> 'a,3,f,4',
>> 'a,3,f,5',
>> 'b,0,d,4',
>> 'b,0,d,5',
>> 'b,0,e,4',
>> 'b,0,e,5',
>> 'b,0,f,4',
>> 'b,0,f,5',
>> 'b,1,d,4',
>> 'b,1,d,5',
>> 'b,1,e,4',
>> 'b,1,e,5',
>> 'b,1,f,4',
>> 'b,1,f,5',
>> 'b,2,d,4',
>> 'b,2,d,5',
>> 'b,2,e,4',
>> 'b,2,e,5',
>> 'b,2,f,4',
>> 'b,2,f,5',
>> 'b,3,d,4',
>> 'b,3,d,5',
>> 'b,3,e,4',
>> 'b,3,e,5',
>> 'b,3,f,4',
>> 'b,3,f,5',
>> 'c,0,d,4',
>> 'c,0,d,5',
>> 'c,0,e,4',
>> 'c,0,e,5',
>> 'c,0,f,4',
>> 'c,0,f,5',
>> 'c,1,d,4',
>> 'c,1,d,5',
>> 'c,1,e,4',
>> 'c,1,e,5',
>> 'c,1,f,4',
>> 'c,1,f,5',
>> 'c,2,d,4',
>> 'c,2,d,5',
>> 'c,2,e,4',
>> 'c,2,e,5',
>> 'c,2,f,4',
>> 'c,2,f,5',
>> 'c,3,d,4',
>> 'c,3,d,5',
>> 'c,3,e,4',
>> 'c,3,e,5',
>> 'c,3,f,4',
>> 'c,3,f,5']

[[int(u) if u.isdigit() else u for u in t.split(",")] for t in test(a,b,c,d)]

こんな感じの噛ませればいいかと。

ちなみに Version

import sys
print(sys.version)

>> 3.10.11 (tags/v3.10.11:7d4cc5a, Apr  5 2023, 00:38:17) [MSC v.1929 64 bit (AMD64)]
copilotで作った"python で二次元リストの中にappend できないの?というタイトルの画像"

なんかメイドインアビスみたいな画像と怪しい英語での煽り文句になっているみたいな気がするがまぁいいか。

【python】旺文社 Target1900 のダウンロードコンテンツを分割してみた

旺文社 Target1900 のダウンロードコンテンツの音声は数単語ごとにまとまっているが、1単語ごとの音声ファイルが欲しい

Abceed で旺文社 Target1900 の単語単位の音声を復習が必要なものだけ聞けるようにしたかったが、機能がないのかどうもはかばかしくない*1。しかたがないので音声ダウンロードサービスを利用してみる。しかし、ダウンロードコンテンツの音声が数単語ごとにまとまっているので、全部をプレイリストに入れておいてわかるものから消していくという方法が使えない。

しかたがないので、分割しようと思います。

とりあえず、3.見出し語の意味→見出し語 を一括ダウンロードして win11 のデスクトップにおいてある設定です。

流行りに乗って AI を使ってみたいので Whisper を試してみる

最終的に ffmpeg を使うとして分割時間設定をどうするかと、「流行りに乗って AI や!」と Whisper を google colab で試してみる。
2言語混在しているとあまり精度が良くないとのことも、認識言語を日本語にするとそこそこ認識してくれるが、2単語分を一文扱いにしてくれたりする。認識言語を英語とかスペイン語とかにして試してみるも使えそうになかったので30分ぐらいいじって撤退。*2

Audacity で波形を見て、ffmpeg で全面的にやることに

www.audacityteam.org

波形見るのに Audacity を使ってダウンロードしたファイルを見てみると、単語ファイル間は1秒ほど空いている模様。ffmpeg で無音感知があると便利だなぁと検索してみると、silencedetect というオプションがありました。なので全面的にffmpeg 系でいくことに決定。

自動で全部やるのは自分の技術的に無理そうなので、ffplay で聞いて選り分ける

下に書いたコード以外にも silencedetect で stderr に出てきたのを正規表現で抜き出して、無音の最初と最後をリストにして zip 関数使って組み合わせて数数えてとかやっているのですが、大体下のに入っていたので割愛。

jupyter notebook 上で

import subprocess
import re
import os
from IPython.display import clear_output
import pickle

dirs=os.path.join(os.path.expanduser("~"),"Desktop","1900_3_all")
cwd=os.getcwd()
os.chdir(dirs)
s={}
dirlist=sorted([f for f in os.listdir(dirs) if f.endswith(".mp3")])

# cc=76

for n,f in enumerate(dirlist):
    
#     if n<cc:
#         continue
#     if n==cc:
#         s=pickle.load(open("./test.pkl","rb"))
    
    file=os.path.join(dirs,f)
    a=subprocess.run(f"ffmpeg -i {file} -af silencedetect=n=0.001:d=1:m=0 -vn -f null -", capture_output=True, text=True,encoding="utf-8")

    file_end=[float(f) for f in re.findall('silence_start: (\d+.\d+)\n',a.stderr)]
    file_start=[0]+[float(f) for f in re.findall('silence_end: (\d+.\d+)',a.stderr)]
    
    a,b,c=os.path.splitext(f)[0].rsplit("_",2)
        
    idealfiles = 1+int(c)-int(b)
    
    file_time=[(s,t) for s,t in zip(file_start,file_end)]
    if idealfiles==len(file_time):
        continue 
        
    checked=0

    for i,t in enumerate(file_time):
        print(f"{n} {i+1}/{checked}/{1+int(c)-int(b)}/{len(file_time)},{n}/{len(dirlist)}")
        if checked+(1+int(c)-int(b))==len(file_time):
            continue

        subprocess.run(f"ffplay -i {f} -ss {t[0]} -t {t[1]-t[0]+0.5}")
        aa=input("OKならEnter、問題ありなら文字入力後Enter")
        clear_output()
        if aa:
            s.setdefault(n,[]).append(i)
            checked+=1
    pickle.dump(s,open("./test.pkl","wb"))
    
    
os.chdir(cwd)
s

途中で休む対策に pickle 使って途中から始められるようにして*3、後から再度起動した場合に dirlist が pickle.dump 前と変わらないように内包表記使って endswith でなんとかしてます。
ファイルのはじめから音声部分まで1秒無音状態のファイルはなさそうなので file_start のリストの最初の0足して合わせてます。

表示をクリアしてわかりやすくしたり、ファイル名からそのファイル内の単語数を計算して、分割後のファイル数との差をチェックしたら後は飛ばすようにして、手間を惜しんでます。*4

そうして作った削除するファイルの Dictionary がこちら。

s={0: [0, 1, 2],
 2: [11],
 5: [2, 24],
 6: [0, 1],
 8: [12],
 11: [2, 24],
 12: [0, 1],
 14: [10],
 17: [2, 24],
 18: [0, 1],
 20: [10],
 23: [1, 22],
 24: [0, 1],
 26: [10],
 29: [2, 24],
 30: [0, 1],
 32: [11],
 34: [17],
 36: [0, 1],
 38: [11],
 41: [3, 25],
 42: [0, 1],
 44: [10],
 47: [0, 22],
 48: [0, 1, 2],
 50: [6],
 52: [10],
 53: [14],
 54: [0, 1],
 56: [7],
 58: [11],
 59: [15],
 60: [0, 1],
 62: [7],
 64: [11],
 65: [15],
 66: [0, 1],
 68: [8],
 70: [11],
 72: [0, 1],
 74: [9],
 76: [13],
 78: [0, 1],
 80: [9],
 82: [12],
 84: [0, 1],
 86: [8],
 88: [12],
 90: [0, 1, 2],
 92: [5],
 94: [5],
 95: [0, 1],
 97: [5],
 99: [5],
 100: [0, 1],
 102: [4],
 104: [5],
 105: [0, 1],
 107: [5],
 109: [5]}

なんで s なのかは今の僕にもわかりません。その場ののりです。
1989ファイルに分割されていたものを89減らすので数は合いました。

ここまでは前置きなので、次行きましょう。*5

外見だけは公式っぽく行きたいのでカバーアートとかタグとか

カバーアートは手持ちのファイルから抜いておきます。

#1900_3_allディレクトリ内で
ffmpeg -i TG1900_3_Sec01_0001_0014.mp3 cover.png

で、ffprobe も使ってこんなふうに。

s={0: [0, 1, 2],
 2: [11],
 5: [2, 24],
 6: [0, 1],
 8: [12],
 11: [2, 24],
 12: [0, 1],
 14: [10],
 17: [2, 24],
 18: [0, 1],
 20: [10],
 23: [1, 22],
 24: [0, 1],
 26: [10],
 29: [2, 24],
 30: [0, 1],
 32: [11],
 34: [17],
 36: [0, 1],
 38: [11],
 41: [3, 25],
 42: [0, 1],
 44: [10],
 47: [0, 22],
 48: [0, 1, 2],
 50: [6],
 52: [10],
 53: [14],
 54: [0, 1],
 56: [7],
 58: [11],
 59: [15],
 60: [0, 1],
 62: [7],
 64: [11],
 65: [15],
 66: [0, 1],
 68: [8],
 70: [11],
 72: [0, 1],
 74: [9],
 76: [13],
 78: [0, 1],
 80: [9],
 82: [12],
 84: [0, 1],
 86: [8],
 88: [12],
 90: [0, 1, 2],
 92: [5],
 94: [5],
 95: [0, 1],
 97: [5],
 99: [5],
 100: [0, 1],
 102: [4],
 104: [5],
 105: [0, 1],
 107: [5],
 109: [5]}

import subprocess
import re
import os
import time

dirs=os.path.join(os.path.expanduser("~"),"Desktop","1900_3_all")
pic=os.path.join(dirs,"cover.png")
out_dir_name="Target1900JE"
out_dir=os.path.join(dirs,out_dir_name)
cwd=os.getcwd()
os.chdir(dirs)
if not os.path.isdir(out_dir):
    os.mkdir(out_dir)
dirlist=sorted([f for f in os.listdir(dirs) if f.endswith(".mp3")])
j=1
for n,f in enumerate(dirlist):

    file=os.path.join(dirs,f)
    a=subprocess.run(f"ffmpeg -i {file} -af silencedetect=n=0.001:d=1:m=0 -vn -f null -", capture_output=True, text=True,encoding="utf-8")

    file_end=[float(f) for f in re.findall('silence_start: (\d+.\d+)\n',a.stderr)]
    file_start=[0]+[float(f) for f in re.findall('silence_end: (\d+.\d+)',a.stderr)]
    
    a,b,c=os.path.splitext(f)[0].rsplit("_",2)
        
    idealfiles = 1+int(c)-int(b)
    
    file_time=[(s,t) for s,t in zip(file_start,file_end)]
    
    gg=subprocess.run(f"ffprobe -i {file}", capture_output=True, text=True,encoding="utf-8")
    title0=re.findall("(Section \d+\D\d+\D+)", gg.stderr)[0]

    for i,t in enumerate(file_time):

        if n in s.keys():
            if i in s.get(n) :
                continue
        nums="_".join([a,str(j).zfill(4)])
        out_put_file=os.path.join(out_dir,nums+".mp3")
        
        title=title0+str(j)
        command=f'ffmpeg.exe -ss {t[0]} -t {t[1]-t[0]+0.5} -i {file} -i {pic} -map 0:a -map 1:v -c copy -disposition:1 attached_pic -id3v2_version 3 -map_metadata 0 -metadata title="{title}" -metadata track={j}  {out_put_file}'

        subprocess.run(command)
        j+=1
        time.sleep(0.1)
os.chdir(cwd)
print("終了")

後は勉強するだけ

おまけ

コチラで Target1900 の語彙リストを上げていただいています。

ukaru-eigo.com

ここの表をお借りしてきて、こんな感じで

import pandas as pd

url='https://ukaru-eigo.com/target-1900-word-list/'
df = pd.read_html(url)
words=df[0]["単語"].tolist()
jwords=[w.split(",")[0].split(";")[0].split("(⇔")[0].split("(≒")[0] for w in df[0]["意味"].tolist()]

tagtitle=[w0+" "+w1 for w0,w1 in zip(jwords,words)]
tagtitle

>>['を創り出す create',
>> '増加する increase',
>> 'を向上させる improve',
>> 以下略

ffmpeg の metadata の {title} を {tagtitle[j-1]} とでもしてやるとプレイヤータイトル表示が 'を創り出す create' などとなります。

copilot で作った"【python】旺文社ターゲット1900のダウンロードコンテンツを分割してみた。"の画像

*1:元々、本を買ったけれど覚えている、いないの管理が面倒なのでアプリを買おうと思い、セールに目を引かれてこちらを購入。公式だと1冊分だけだけれど買い切りで値段が安いみたい。そちらだとこの機能、あるのかも

*2:ここらへんは検索したら出てくるそのままなのでコードは省略。

*3:コメントアウトしている cc に再開したい n を入れる

*4:全部聞くつもりだったのですが、画面は流れる、数は多いで、途中で何やっているのかわからなくなって来たので追加した

*5: 上記の設定で調べるとダウンロードコンテンツの1,2,3は1989、4は1991で、全部聞いたわけではないのですが、減らす方のファイルは1,2,3ともこの Dictionary の中身で抽出できていたので、できているはず。

【golang】LINE Notify で1000文字を超えるものを分割送信するようにしてみた。

あまり長いものを送信していなかったので気にしていなかったのですが、どうも一送信の制限が1000文字らしいので対応してみました。*1

func line(message string) {
	accessToken := "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
	message_limit := 1_000

	URL := "https://notify-api.line.me/api/notify"
	u, _ := url.ParseRequestURI(URL)
	c := &http.Client{}

	rune_message := []rune(message)
	repeat := len(rune_message)/message_limit + 1

	for i := 0; i < repeat; i++ {
		form := url.Values{}

		end := len(rune_message)
		if i+1 != repeat {
			end = message_limit * (i + 1)
		}
		rune_sliced_message := rune_message[message_limit*i : end]
		form.Add("message", string(rune_sliced_message))

		body := strings.NewReader(form.Encode())
		req, _ := http.NewRequest("POST", u.String(), body)
		req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
		req.Header.Set("Authorization", "Bearer "+accessToken)

		c.Do(req)
	}

}
windows copilot で作った"【golang】LINE Notify で1000文字を超えるものを分割送信するようにしてみた。"の画像

*1:golang でアプリ化しているものに組み込んでいるので golang で。