19日に更新してた

アフィリエイトはないよ

r0709あたりの雑記

  • 盆休み前に仕事を少し片付けたつもりだったが、休みが明けたら仕事が増えてた
  • ここ数週間、たまたま14時前後にマクドナルドに行く機会があって昼食をとっていたのですが、時々セット価格が違うのは何でだろうと思っていたら、昼マックかどうかの違いだったらしい。10年くらい前に話に聞いたことがあるあの割引をまだやっていたとは知らなかったというか、そんなものの存在が頭から抜けていた
  • 善光寺は混んでいるかと思っていたが、外国人観光客が少なくて良かった。ミンミンゼミが鳴いてた
  • 三峡ダムが自転に影響しているのが猛暑の原因だ」と言い出す陰謀論がでてこないか wktk して待っているがまだ見たことない
  • 電工二種合格したので免許申請してみた
  • Amazonで在庫切れの輸入盤で注文できるものを頼んでみて来ないのを楽しんでいるのだけれど、julie は流石に新しいからか送ってきた

https://www.amazon.co.jp/my-anti-aircraft-friend-Julie/dp/B0D96MD5SS/www.amazon.co.jp

  • メルカリで文庫本2冊をできるだけ薄くポスト投函で送るために、ダンボール梱包用サイズ3面分だけの図を作ってA4のPDFでダウンロードするアプリを作りました。計るの面倒で

0kkyit6d3dk10ljbipk6rg.on.drv.tw

  • ゲーミングノートの中古は搭載可能最大メモリサイズとGPUメモリサイズが書いてあるだけで欲しさ加減が違ってくる
  • GTX1060 6G i7-8750H メモリー32G という相変わらずのゲーミングノートで LM studio 0.3.25 をいれてみたら、モデル推奨が openai/gpt-oss-20b。「動くのか?こんなでかいのに?」と思って入れてみたが、デフォルト設定で日本語入力、日本語回答で 5.5 ~ 7 token/secくらいでは動く*1。内容はまともと思える範囲。あまり早くはないが漏洩が問題なら使えなくはない感じ。そろそろ 10 年選手のゲーミングノートでこんな感じで動くなら、今のそこそこのデスクトップ用の GPU なら他所様のページにもあるようにもっといい感じで動くだろうし、個人のパソコンでのローカルLLMも現実化してきていると実感
  • 熊退治用の超小型レールガン作って、ボストン・ダイナミクスが作っているような 4 本足のロボットにでも乗せて陸自の武器として熊退治。有事には対人用。なんかそんなロボットの小説読んだことある気がするな。レーザーだったかもしれないけど
  • ちょっと前に書いたプログラムを gemini にみてもらったら、generics 使って書き直したほうがいいと教えてもらった。そう使うのかと勉強になった
  • 映画「国宝」を見に行った。吉沢亮さんも横浜流星さんもどっちがどっちか知らない位で見に行ったが面白かった。昔の光景は色調を昔の写真のような感じになっていて、タバコもスパスパ吸ってるコンプラ何それ?な動く懐かしさ的なところもあったのと、歌舞伎なんて高尚なものにはご縁がないと思っていたが、琴線に触れた自分を自覚したのが大きな収穫だったのかも

kokuhou-movie.com

  • 生きていくだけで溜まっていく何かを吐き出すために物語は必要だと何かで読んだ気がするが実感した


*1:設定いじってたら14token/secが出たがその後再現できない

【golang】chromedp + goquery にて pandas.read_html っぽいことをやってみた

chatGPT に描いてもらった【golang】chromedp + goquery にて pandas.read_html っぽいことをやってみた の画像

はじめに

golang で pandas.read_html のようなものはないのかと探してみたが見つからなかったので、それっぽいものを書いてみました。

やったこと

datatables.net

  • 上のページのテーブル2枚を読み込んで、スライスで返す形にしております
  • 上記ページ内にテーブル自体は 6 つあるのですが、table.display を用いることで表形式のものだけ抽出しています

code

package main

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

	"github.com/PuerkitoBio/goquery"
	"github.com/chromedp/cdproto/cdp"
	"github.com/chromedp/cdproto/dom"
	"github.com/chromedp/chromedp"
)

func main() {
	url := "https://datatables.net/examples/basic_init/multiple_tables.html"
	// chromedp 環境設定
	allocCtx, cancelAllcCTX := chromedp.NewExecAllocator(context.Background(), []chromedp.ExecAllocatorOption{
		chromedp.NoFirstRun,
		chromedp.NoDefaultBrowserCheck,
		chromedp.WindowSize(1280, 720),
		chromedp.UserAgent(`Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/555.0.0.0 Safari/555.0.0.0`),
		chromedp.Flag("headless", true),
	}...)
	defer cancelAllcCTX()

	ctx, cancelCTX := chromedp.NewContext(allocCtx)
	defer cancelCTX()

	// ページに移動
	var tableNodes []*cdp.Node
	if err := chromedp.Run(ctx,
		chromedp.Navigate(url),
		chromedp.WaitVisible(`table.display tbody tr`, chromedp.ByQuery),
		chromedp.Nodes(`table.display`, &tableNodes, chromedp.ByQueryAll),
	); err != nil {
		log.Fatal(err)
	}

	// すべての<table>のHTMLを抽出
	var allTablesHTML []string
	for i, node := range tableNodes {
		var htmlContent string
		err := chromedp.Run(ctx,
			chromedp.ActionFunc(func(ctx context.Context) error {
				var err error
				htmlContent, err = dom.GetOuterHTML().WithNodeID(node.NodeID).Do(ctx)
				if err != nil {
					return err
				}
				return nil
			}),
		)
		if err != nil {
			log.Printf("table %d の取得失敗: %v\n", i+1, err)
			continue
		}
		allTablesHTML = append(allTablesHTML, htmlContent)
	}

	// HTMLをパースして [][]string に変換
	for i, html := range allTablesHTML {
		fmt.Printf("=== Table #%d ===\n", i+1)
		tables, err := parseTableHTML(html)
		if err != nil {
			log.Println("parse error:", err)
			continue
		}
		for _, row := range tables[0] {
			fmt.Println(row)
		}
		fmt.Println()
	}
}

// HTMLの<table>1つを[][][]stringとして返す
func parseTableHTML(html string) ([][][]string, error) {
	doc, err := goquery.NewDocumentFromReader(strings.NewReader(html))
	if err != nil {
		return nil, err
	}

	var allTables [][][]string
	doc.Find("table").Each(func(_ int, tableSel *goquery.Selection) {
		var table [][]string
		rowIndex := 0

		// thead, tbody, tfoot のすべての tr を選択するように変更
		// tableSel.Find("thead tr, tbody tr, tfoot tr").Each(func(_ int, row *goquery.Selection) {
		tableSel.Find("tbody tr").Each(func(_ int, row *goquery.Selection) {
			var rowData []string

			colIndex := 0
			// th (ヘッダーセル) も td (データセル) も対象とする
			row.Find("td, th").Each(func(_ int, cell *goquery.Selection) {
				rowData = append(rowData, strings.TrimSpace(cell.Text()))
				colIndex++
			})

			if len(rowData) > 0 {
				table = append(table, rowData)
			}
			rowIndex++
		})
		if len(table) > 0 {
			allTables = append(allTables, table)
		}
	})

	return allTables, nil
}

実行結果は

 go run .
=== Table #1 ===
[Cedric Kelly Senior Javascript Developer Edinburgh 22 $433,060]
[Dai Rios Personnel Lead Edinburgh 35 $217,500]
[Gavin Joyce Developer Edinburgh 42 $92,575]
[Jennifer Acosta Junior Javascript Developer Edinburgh 43 $75,650]
[Martena Mccray Post-Sales support Edinburgh 46 $324,050]    
[Quinn Flynn Support Lead Edinburgh 22 $342,000]
[Shad Decker Regional Director Edinburgh 51 $183,000]        
[Sonya Frost Software Engineer Edinburgh 23 $103,600]        
[Tiger Nixon System Architect Edinburgh 61 $320,800]

=== Table #2 ===
[Angelica Ramos Chief Executive Officer (CEO) London 47 $1,200,000]
[Bradley Greer Software Engineer London 41 $132,000]
[Bruno Nash Software Engineer London 38 $163,500]
[Haley Kennedy Senior Marketing Designer London 43 $313,500] 
[Hermione Butler Regional Director London 47 $356,250]       
[Jena Gaines Office Manager London 30 $90,560]
[Lael Greer Systems Administrator London 21 $103,500]        
[Michael Silva Marketing Designer London 66 $198,500]        
[Prescott Bartlett Technical Author London 27 $145,000]      
[Suki Burks Developer London 53 $114,500]

最後に

  • thead、tfoot は tableSel.Find 部のコメントアウトを入れ替えることで読み込むことができます
  • 同じコードで他のページを読み込む場合、セレクタを書き換えないと読み込みは難しいかもしれません、つまり pandas.read_html ほど融通は効かない個別対応が必要な形にしています
  • 自分以外の人が使わねばならないがソースコードを見られたくない、触られたくない & JavaScript で作られた表を読み込まなくてはいけない & Python のバイナリ化アプリを使いたくない*1場合には選択肢の一つとしてどうでしょうか、かなり狭い条件ですが
  • goquery 周りは chatGPT にかなりご修正いただきましたというか、ほぼ書いてもらいました

chatGPT にイラストを書いてもらうときに Gopher くんを入れてくださいとお願いしました

※ 本記事は、自動化ツールによる検出対策の技術的理解と検証を目的としており、商用サイトの利用規約に反する自動操作や不正アクセスを推奨するものではありません。


*1:びっくりするくらい容量喰いますから

r0708あたりの雑記

  • 夏休み入りとともにきれいに晴れ渡り、いいのか悪いのか
  • 中学受験、流行っているみたいですけれど、見て覚えろ的な能力選抜なのはテストだから仕方がないとしても、流行る時点で結構みんなスポ根系?
  • 日産の工場跡地、データセンターにでもなるのかね。電気来ているし、敷地広いし、水も来ているだろうし悪くないだろうけど、あれだけの雇用は得られないからないかな
  • ChatGPT にちいかわ描いてもらうとなんか違うのだけれど、うさぎがやたらに善良な感じでピンク
  • X (twitter) で石破辞めるなと書いている人と、東日本大震災の時に枝野寝ろと書いていた人の重複を見てみたいところ
  • はじめて go: no such tool "compile" なんてでてきてびっくりした。何やらかした?と思ってリペアしても再インストールしても入らないので、ノートン覗いてみたらノートンに検疫されてた。おまけに chromedp までよくわからないエラーを出すようになって面倒だったがなんとか復旧した
  • blob の対応いろいろと面倒だから作るのやめた
  • 不敬は承知で書くが、昭和天皇の最期が管つないでながらえた形だから、国民の象徴の最期があのような形であれば、国はあのような形の最後が国民の理想の死に方とする方策しか出せないのではないか、とふと思う
  • 中学受験、流行っているみたいだけれど、見て覚えろ的な能力選抜なのはテストだから仕方がないとしても、流行る時点で結構みんなスポ根系?向く向かないはあるよね
  • 日産の工場跡地、データセンターにでもなるのかね。電気来ているし、敷地広いし、水も来ているだろうし悪くないだろうけど、あれだけの雇用は得られないからないかな
  • ChatGPT にちいかわ描いてもらうとなんか違うのだけれど、うさぎがやたらに善良な感じでピンク
  • X (twitter) で石破辞めるなと書いている人と、東日本大震災の時に枝野寝ろと書いていた人の重複を見てみたいところ
  • はじめて go: no such tool "compile" なんてでてきてびっくりした。何やらかした?と思ってリペアしても再インストールしても入らないので、ノートン覗いてみたらノートンに検疫されてた。おまけに chromedp までよくわからないエラーを出すようになって面倒だったがなんとか復旧した
  • blob の対応いろいろと面倒だから作るのやめた
  • 不敬は承知で書くが、昭和天皇の最期が管つないで永らえた形だから、国民の象徴の最期があのような形であれば、国はあのような形の最後が国民の理想の死に方とする方策しか出せないのではないか、まさに護憲。とふと思う
  • 税金下げろとか社会保険料下げろって言う人なら、「いつまでも謝罪賠償言ってんじゃねぇ、税金の使い道は日本ファースト」って言う払い勘定を減らそうとする人に投票するのは仕方がないよね
  • 外出するときはだいたいマスクするようにしているのだけれど、風邪をひきにくくなったし発熱することも殆どなくなった。色々言う人はいるだろうし、人に強要するつもりはないけれど体調いいほうがいいからマスクはする。付けてても不審者扱いされなくなったのは何より
  • わいせつ物を RSA で暗号化して配布したら FLmask ばりに捜査、逮捕されるのかな
  • 令和4年の体力テストの年齢別の表を見たら、成人女性の握力、反復横跳びの平均値が男子中学生の平均くらいなので、成人層はこういうテストを受ける体力に自信のある人だけだろうし、若年層は学校でほぼ全員受けていると考えると体力的な男女差は大きい。その意味をどう捉えるかは別として

www.sports-tokyo-info.metro.tokyo.lg.jp

  • 海外のキリスト教系のフェミ団体はポルノ系のカード決済規制に、日本のフェミ団体は BL 擁護にってどうなるのかね。知らんけど
  • 離婚で親権が母親にって話をよくネットで見るけど、「介護が必要な病気の母親が親権とったために、子どもがヤングケアラーをしなくてはならなくなり、満足な教育が受けられなかったのは裁判官の思想による職権乱用による人権侵害である」なんてその子供から起こされる裁判は未来永劫ない?
  • ネット絡みの話題を聞くなら Grok が使いやすいな、同じスレッドで追加質問しても今までの入力を全部反映した内容が返ってくるからうざいけど

www.youtube.com

  • 玄関のドアの上でツバメが一夜を凌ぐことここ数日。営巣してないからいいか、糞害は微妙にあるけれど。
  • OMRON の体重計を買った、20年もののタニタの体重計と400g程度差が出ていた。経年変化ってのはあるものだなと
  • chromedp.Emulate を使うと全画面キャプチャがうまくいかないので、chromedp.Device から useragent、width、height だけを引っ張ってきて []chromedp.ExecAllocatorOption に入れたほうがきれいにキャプチャできる。ただそのままだと https://bot.sannysoft.com/ の chr_memory で fail が出る
  • ai に"いつも思っているようなことでも言葉にすると心持ちに変化が出る"、"トランスフォーマーの学習みたいですね" といれると chatGPT、claude、deepseek、grok、qwen はそうですねと返してくるが、gemini だけは "トランスフォーマーの学習について解説しますね" ときたけど、もしかして文脈読むの苦手?
  • ハッピーセットの話を聞いて思い出したが、職場の先輩の子供さん(成人しているらしい)のおこぼれで分けていただいた鬼滅の刃のウエハースチョコは美味しかった
  • 東京科学大学の女子枠で色々あるみたいだけど、科大なんだから、科*1がなくちゃいけないんだろうし、科*2をつくらなきゃいけないんじゃない?うまいこと言っているつもりはなくて、中の人としてはわざわざそんな名前にしたんだから
  • エスペラントみたいな新言語を独自に作っている人が llm に学習させて翻訳サイト作っている例はないか grok に聞いてみたがないらしい

*1:とが

*2:しな

【golang】puppeteer-stealth もどきを Edge 使って chromedp でやってみた

chatGPTに描いてもらった【golang】puppeteer-stealth もどきを Edge 上で chromedp 使ってやってみた の画像

はじめに

owiewowe.hatenablog.com
先の記事でおわかりいただけるかと思うのですが自分の環境には Chrome を入れていますので Chromedp で Chrome を動かしています。
しかし、実行ファイルをコピペするだけでいまどきの一般的な Windows 上でなら動くようにしたいとリクエストもあるだろういうことで、 windows 11 上ならおそらくほぼ必須となっているであろう Chromium 版 Edge を使って chromedp 経由で puppeteer-stealth もどきをやってみました。

先の記事との違い

  • 基本的に読み込むページへの対策は同じ
  • windows だと Edge 使用時は起動に Path が必要、Chrome は不必要
  • Edge の 32bit 版、64bit 版双方の Path が引けるように関数化
  • Edge は Headless モードが基本のようで GUI 表示が必要な場合 chromedp.Flag("headless", false) が必要だが、ユーザーエージェントには Chrome 同様に Headless モードであることががわかるように記載されるため、ユーザーエージェントは変えておく
  • 実験に Edge 138 系を使っていますのでユーザーエージェントは合わせています
  • ユーザーエージェントは antibot のページで表示されたものをそのままコピペしてきたので Edg は Typo ではないです

ソース

package main

import (
	"context"
	"errors"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"time"

	"github.com/chromedp/cdproto/page"
	"github.com/chromedp/chromedp"
)

func main() {
	edgePath, err := findEdgePath()
	if err != nil {
		log.Fatalf("Edgeが見つかりませんでした: %v", err)
	}
	// fmt.Println("Microsoft Edge のパス:", edgePath)

	// 起動オプション
	opts := append(chromedp.DefaultExecAllocatorOptions[:],
		// Edgeはpath必須
		chromedp.ExecPath(edgePath),
		chromedp.NoFirstRun,
		chromedp.NoDefaultBrowserCheck,
		chromedp.Flag("disable-extensions", false),
		chromedp.Flag("enable-automation", false),
		// Edgeはheadless false を明示してGUI表示
		// chromedp.Flag("headless", false),

		// GUI表示時は以下三行コメントアウト
		chromedp.Flag("headless", true),
		chromedp.WindowSize(1280, 800),
		chromedp.UserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0"),
	)

	var buf []byte
	// Chromeコンテキスト初期化
	allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), opts...)
	defer cancel()

	startTime := time.Now()
	ctx, cancelurl := chromedp.NewContext(allocCtx)
	defer cancelurl()

	// ステルススクリプト(全自動化検出対策)
	stealthJS := `(() => {
		// navigator.webdriver を削除
		delete Navigator.prototype.webdriver;


		window.chrome = {
			runtime: {},
		};

		// languages を偽装
		Object.defineProperty(navigator, 'languages', {
			get: () => ['ja-JP', 'ja', "en-US", "en",]
		});

		// plugins を偽装
		const createFakePluginArray = () => {
			const fakeMimeType = {
			type: "application/pdf",
			suffixes: "pdf",
			description: "Portable Document Format"
			};

			function makePlugin(name) {
			const plugin = {
				name,
				filename: "internal-pdf-viewer",
				description: "Portable Document Format",
				0: fakeMimeType,
				length: 1,
				item: i => fakeMimeType,
				namedItem: name => fakeMimeType
			};
			fakeMimeType.enabledPlugin = plugin;
			return plugin;
			}

			const plugins = [
			makePlugin("PDF Viewer"),
			makePlugin("Chrome PDF Viewer"),
			makePlugin("Chromium PDF Viewer"),
			makePlugin("Microsoft Edge PDF Viewer"),
			makePlugin("WebKit built-in PDF")
			];

			const pluginArray = {
			length: plugins.length,
			item: i => plugins[i],
			namedItem: name => plugins.find(p => p.name === name),
			[Symbol.iterator]: function* () {
				yield* plugins;
			}
			};

			plugins.forEach((p, i) => pluginArray[i] = p);
			Object.setPrototypeOf(pluginArray, PluginArray.prototype);
			return pluginArray;
		};

		// defineProperty を使わず直接代入することで検出回避(definePropertyはウイルスソフトの要監視対象)
		navigator.plugins = createFakePluginArray();
		
		// permissions.query の notifications 対応
		const originalQuery = window.navigator.permissions.query;

		window.navigator.permissions.query = function (parameters) {
			if (parameters.name === 'notifications') {
				return Promise.resolve({ state: Notification.permission });
			}
			return originalQuery(parameters);
		};

		 Object.defineProperty(window, 'chrome', {
			writable: true,
			enumerable: true,
			configurable: false,
			value: {
			app: {},
			csi: function () {},
			loadTimes: function () {}
			}
		});

		Object.defineProperty(window.chrome, 'webstore', {
			get: function () {
			throw new TypeError("Cannot read properties of undefined (reading 'constructor')");
			}
		});

		Object.defineProperty(window.chrome, 'runtime', {
			get: function () {
			throw new TypeError("Cannot read properties of undefined (reading 'constructor')");
			}
		});

		Object.defineProperty(window.chrome, 'connect', {
			get: function () {
			throw new TypeError("Cannot read properties of undefined (reading 'connect')");
			}
		});

		Object.defineProperty(window.chrome, 'sendMessage', {
			get: function () {
			throw new TypeError("Cannot read properties of undefined (reading 'sendMessage')");
			}
		});

	})();`

	// スクリプトをすべての新規ページにプリロード注入
	if err := chromedp.Run(ctx,
		chromedp.ActionFunc(func(ctx context.Context) error {
			_, err := page.AddScriptToEvaluateOnNewDocument(stealthJS).Do(ctx)
			return err
		}),
	); err != nil {
		log.Fatal(err)
	}

	url := "https://bot.sannysoft.com/"

	// テストサイトにアクセス
	if err := chromedp.Run(ctx,
		chromedp.Navigate(url),
		chromedp.Sleep(2*time.Second),
		// スクリーンショットを取る 2つ目の引数はJpegの品質(0-100)
		chromedp.FullScreenshot(&buf, 90),
		chromedp.Sleep(1*time.Second),
	); err != nil {
		log.Fatal(err)
	}

	if err := os.WriteFile("screenshot.jpg", buf, 0644); err != nil {
		log.Fatal(err)
	}
	fmt.Println(time.Since(startTime))
}

func findEdgePath() (string, error) {
	candidates := []string{
		`C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe`,
		`C:\Program Files\Microsoft\Edge\Application\msedge.exe`,
		filepath.Join(os.Getenv("LOCALAPPDATA"), `Microsoft\Edge\Application\msedge.exe`),
	}

	for _, path := range candidates {
		if _, err := os.Stat(path); err == nil {
			return path, nil
		}
	}
	return "", errors.New("Microsoft Edge の実行ファイルが見つかりませんでした")
}

おわりに

Chrome をインストールしたくないけれど、ブラウザの自動操作をしたい Windows ユーザーにはおすすめです。多くの人に使って貰う予定でインストールや設定をできるだけ少なくしたい方には悪くない選択肢の一つだと思います。

次は opera ? と思われる方もいらっしゃるかもしれませんが、インストールしていないため手を出しません。公式対応しているのかどうか、いまいちよくわかりませんし。

ページの画像を作るときにGopher君入れてと chatGPT に頼みました。

※ 本記事は、自動化ツールによる検出対策の技術的理解と検証を目的としており、商用サイトの利用規約に反する自動操作や不正アクセスを推奨するものではありません。

【golang】puppeteer-stealth もどきを chromedp でやってみた

chatGPT に描いてもらった【golang】puppeteer-stealth もどきを chromedp でやってみた の画像

はじめに

プライバシー対策として、python の puppeteer-stealth もどきを chromedp でやってみようかと思ったのですが何から手を付けていいものかわからないため、chatGPT に聞いてみました。

すると、このサイトで通るようにしましょうとのことだったので、とりあえずアクセスしてスクリーンショットを取るようにして問題点を一つづつ潰していきました。
bot.sannysoft.com

やったこと

  • navigator.webdriver を削除
  • languages を偽装
  • plugins を偽装
  • defineProperty は一部のセキュリティソフト*1により挙動が監視されやすいため、挙動検証を目的として代替手法を試行、直接代入する
  • permissions.query の notifications 対応
  • headless 時にユーザーエージェントの指定

コード中のコメントそのままですが、こんな感じです。
Headless モードで必要なユーザーエージェントは chrome のバージョンが 137 系でテストしたため以下のようにしております。
Headed モードでは opts の後半の段落の記載の必要はありません。

ソース

package main

import (
	"context"
	"fmt"
	"log"
	"os"
	"time"

	"github.com/chromedp/cdproto/page"
	"github.com/chromedp/chromedp"
)

func main() {
	// 起動オプション
	opts := append(chromedp.DefaultExecAllocatorOptions[:],
		chromedp.NoFirstRun,
		chromedp.NoDefaultBrowserCheck,
		chromedp.Flag("disable-extensions", false),
		chromedp.Flag("enable-automation", false),

		chromedp.Flag("headless", true),
		chromedp.WindowSize(1280, 800),
		chromedp.UserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"),
	)

	var buf []byte
	// Chromeコンテキスト初期化
	allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), opts...)
	defer cancel()

	startTime := time.Now()
	ctx, cancelurl := chromedp.NewContext(allocCtx)
	defer cancelurl()

	// ステルススクリプト(全自動化検出対策)
	stealthJS := `(() => {
		// navigator.webdriver を削除
		delete Navigator.prototype.webdriver;


		window.chrome = {
			runtime: {},
		};

		// languages を偽装
		Object.defineProperty(navigator, 'languages', {
			get: () => ['ja-JP', 'ja', "en-US", "en",]
		});

		// plugins を偽装
		const createFakePluginArray = () => {
			const fakeMimeType = {
			type: "application/pdf",
			suffixes: "pdf",
			description: "Portable Document Format"
			};

			function makePlugin(name) {
			const plugin = {
				name,
				filename: "internal-pdf-viewer",
				description: "Portable Document Format",
				0: fakeMimeType,
				length: 1,
				item: i => fakeMimeType,
				namedItem: name => fakeMimeType
			};
			fakeMimeType.enabledPlugin = plugin;
			return plugin;
			}

			const plugins = [
			makePlugin("PDF Viewer"),
			makePlugin("Chrome PDF Viewer"),
			makePlugin("Chromium PDF Viewer"),
			makePlugin("Microsoft Edge PDF Viewer"),
			makePlugin("WebKit built-in PDF")
			];

			const pluginArray = {
			length: plugins.length,
			item: i => plugins[i],
			namedItem: name => plugins.find(p => p.name === name),
			[Symbol.iterator]: function* () {
				yield* plugins;
			}
			};

			plugins.forEach((p, i) => pluginArray[i] = p);
			Object.setPrototypeOf(pluginArray, PluginArray.prototype);
			return pluginArray;
		};

		// defineProperty を使わず直接代入することで検出回避(definePropertyはウイルスソフトの要監視対象)
		navigator.plugins = createFakePluginArray();
		
		// permissions.query の notifications 対応
		const originalQuery = window.navigator.permissions.query;

		window.navigator.permissions.query = function (parameters) {
			if (parameters.name === 'notifications') {
				return Promise.resolve({ state: Notification.permission });
			}
			return originalQuery(parameters);
		};

		 Object.defineProperty(window, 'chrome', {
			writable: true,
			enumerable: true,
			configurable: false,
			value: {
			app: {},
			csi: function () {},
			loadTimes: function () {}
			}
		});

		Object.defineProperty(window.chrome, 'webstore', {
			get: function () {
			throw new TypeError("Cannot read properties of undefined (reading 'constructor')");
			}
		});

		Object.defineProperty(window.chrome, 'runtime', {
			get: function () {
			throw new TypeError("Cannot read properties of undefined (reading 'constructor')");
			}
		});

		Object.defineProperty(window.chrome, 'connect', {
			get: function () {
			throw new TypeError("Cannot read properties of undefined (reading 'connect')");
			}
		});

		Object.defineProperty(window.chrome, 'sendMessage', {
			get: function () {
			throw new TypeError("Cannot read properties of undefined (reading 'sendMessage')");
			}
		});

	})();`

	// スクリプトをすべての新規ページにプリロード注入
	if err := chromedp.Run(ctx,
		chromedp.ActionFunc(func(ctx context.Context) error {
			_, err := page.AddScriptToEvaluateOnNewDocument(stealthJS).Do(ctx)
			return err
		}),
	); err != nil {
		log.Fatal(err)
	}

	url := "https://bot.sannysoft.com/"

	// テストサイトにアクセス
	if err := chromedp.Run(ctx,
		chromedp.Navigate(url),
		chromedp.Sleep(2*time.Second),
		// スクリーンショットを取る 2つ目の引数はJpegの品質(0-100)
		chromedp.FullScreenshot(&buf, 90),
		chromedp.Sleep(1*time.Second),
	); err != nil {
		log.Fatal(err)
	}

	if err := os.WriteFile("screenshot.jpg", buf, 0644); err != nil {
		log.Fatal(err)
	}
	fmt.Println(time.Since(startTime))
}

おわりに

chromedp はとても融通が利くので、個人利用での自動化にはもってこいです。ダウンローダ*2を作るのにはとても便利ですので、機会があれば使用してみてください。
ページの画像を作るときにGopher君入れてと chatGPT に頼みました。

※ 本記事は、自動化ツールによる検出対策の技術的理解と検証を目的としており、商用サイトの利用規約に反する自動操作や不正アクセスを推奨するものではありません。

*1:僕の環境ではノートン

*2:とかダウンロード用の wrapper

r0707あたりの雑記

  • 医者もそのうちに AI による診断での投薬とか検査受診になるだろうし、今の内科が検査機関化していくような気もする。下手すりゃ調子悪いときにオンラインで健保組合や薬局の AI に相談して処方箋なり、検査の受診券なりが発行されるようになって、医者=手術する人になる日が来るかも。
  • Google Keep で月のルーティンリストを作ったらとても便利、追加もできるし、アラームも簡単にできるし、カレンダーに入れるほどかっちり決まっているものじゃないものをいれるといい感じ。写真やメモも放り込めるし、とてもいい。
  • 廃番になって国産で見かけない系統の工具(中国製)を買ったら、とても良かった。AliExpress でも同じのがあったんだけどあんまり値段が変わらないからアマゾンで。
  • 業務用スーパーでアップルシナモンジャム売っているらしいので、行ったら買う。とか言ってたらたまたま寄ったスーパーで売ってた。
  • コンピュータが使える人材が欲しくて情報Iを高校科目にしたのだろうけれど、AI がこんなに早く人材の供給を埋め合わせるようになってしまって、GAFA でも人員削減しているみたいだしどうなる?word, excel が普及した時は SNS がなかったけれど、こんな感じだったのだろうか?
  • ブラウザから LLM に先月と違うソースコード入れて改善お願いしたら Claude 4.0 Sonnet がいい感じでほかはあんまりだったので得手不得手とかタイミングとか。
  • Gemini に Gopher 君描かせると何かが違うものが上がってくるが、ChatGPT だといい感じで出てくる。
  • ジークアクスの映画、見に行かない?と誘われたが、お断りした。ファースト見てるでしょって、機動戦士ガンダムってロボットアニメを「かっこいいな、あのモビルスーツ」で見てただけだし。自分の人生考えたら、ジムやボールで「お母さん」と言って死に行く兵士や、落とされるコロニーの中の人ですし。ええ歳こいた爺さんが見るもんじゃないでしょうし、未来がある人と見るほうがいいかと思う。
  • AI と将来の仕事について話ししてたら、結局「責任」と「哲学」が求められて、運がない人への配分とか社会の構造や倫理の話って方向に動いて、プロパガンダとかの話になったら、切られた。
  • 参政党、名前が賛成と同音でポジティブなイメージ、SNSで反論があるが評価する人が多そうな内容を訴え、それをたしなめる人に名前を広げてもらったりするやり方もすごく上手いが、なんであんなに SNS に最適化された政党になっているの?と思うのも、僕のエコーチェンバー的に立憲やれいわよりも参政党がでてくるんだろうなぁと思う。でも、党のイメージカラーがオレンジだからか党首の方がジャイアンに見えて、街宣が "俺はジャイアン、参政党~♪" 的に見える。
  • AI 使って壁打ちしてちょっとしたコラム風の文章を作ってみたら、これが読みたかったと思うくらいのものになったので新しいページを作ってあげてみた。でも日本語だと書く気がなくなるからアドバイス以上に使うのやめようかと思う
  • 最近、自民党がマスコミに叩かれていないから、石破政権たぶん仕事してない
  • gemini code assist と gemini cli の組み合わせで無料で cursor みたいなことができるということでやってみたが、非常に便利。当面、これでいいな。現状小規模なものでも無理みたいだけれど、仕様書ぽいものを入れたら作ってくれると非常に楽かとおもうし、途中まで表示して全消しして表示できないは辞めろよ
  • 何かで見たように chatGPT に悩み相談をやってみたら、意外な発見があってピースがハマった感じ。アメリカのカウンセリング文化ってのはこんな感じなのかと
  • 借金で自殺した人の借金の額を統計取った中央値と称する額を見た記憶があるけど、たしか300万ぐらいだったから、1600万詐欺られて警察も相手してくれないなら自力救済考えるわな。しゃーない。

r0706あたりの雑記

  • PDICunicode 版で学辞郎の SIL、SVL を csv に抽出してみたら、抽出ファイルが python utf8 で読み込めなくて、エディタでファイル開いて確認してみたら utf16 なのね。

pdic.la.coocan.jp

  • 学辞朗の抽出をレベルごとにするのが面倒な時は書き出した CSV 内に level が入っているから後からなんとかするも良しな感じ。
  • 正規表現は ChatGPT に任せるのが楽で早い感じなので、SIL、SVL の例文抽出は例文突っ込んで任せたほうがいいと思っていたけれど、どうしても引っかかる文字があったりして、結局自分で細かい修正入れないといけなかった。
  • 学辞郎の例文抽出は SVL が19816、SIL が6000、計25816となった。あとは勉強するだけ…、いつもながら作るのが楽しいだけなんだよな。
  • puppeteer-stealth もどきを chromedp でやってみようかと思ったら、ノートン君が検疫してくれた… 仕方がないので書き換えて対応。下記サイトでなんとか全部対応。

bot.sannysoft.com

  • headless にするとユーザーエージェントが変わるのか、知らなかった。
  • ffmpeg のエラーログを ChatGPT に突っ込んだら、そういえばそうだった系の答えが帰ってきて自分の記憶力にがっかりする。
  • Headed Chrome でうまく動いていても Headless にすると User-Agent を Headed に合わせてもうまく動かない原因究明は面倒で放置、pupeteer-stealth もどき入れても駄目だった…とか言ってましたが、windowsize を指定したらサクサク動くようになりました。
  • puppeteer-sthealth もどきの chromedp のオプション。disable-gpu をいれると少し遅くなる。
opts := append(chromedp.DefaultExecAllocatorOptions[:],
	chromedp.NoFirstRun,
	chromedp.NoDefaultBrowserCheck,
	chromedp.Flag("disable-extensions", false),
	chromedp.Flag("enable-automation", false),

	chromedp.Flag("headless", true),
	chromedp.WindowSize(1280, 800),
	chromedp.UserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"),
)
  • ノートンの検疫に掛かってイライラしていたけれど、build して実行すると 2 回目からはかからないことに今更気がついた。
  • PC のブラウザから、ChatGPT、Claude 4.0 Sonnet、Gemini 2.5 Pro に golangソースコード与えて最適化してくださいと頼んだら、取りあえず動くように出てきたのが Gemini だけだった。しかもしっかり実行速度がはやくなっていた。golang だから?やっぱ Google
  • うなぎのチェーンが流行っているけど、うなぎって絶滅危惧種から外れてないよね。
  • アクアリウムでうなぎを販売して安価な繁殖方法を愛好家の知識でと思ったがうなぎ養殖は許可がいるから難しいかも。

www.jfa.maff.go.jp

  • 自作キーボードが流行っているけれど、スマホでわけわからん広告をクリックせずに済むようなキーボードないかな。フルのキーボードはいらないけれど矢印とかエンターとかタブとかだけのが。
  • go build や go run 時のノートンの検疫、除外ディレクトリを C:\Users\myid\AppData\Local\Temp\go-build* にしたらなんとかなった。...\Temp\* じゃだめなのね。
  • ChatGPT 、Gemini 、Claude の話ばっかりで NPU うまく使えてる話があまり流れてこなくてええのかな、インテルとか AMD とか。マイクロソフトも OpenAI と揉めてるみたいだし。
  • Gemini 、sync.WaitGroup が苦手なのか?wg.Add(1) 、wg.Done() の位置がよろしくない。それだけ直したらサクサク動く。
  • アオハタのアップルシナモンジャム、買いに行ったらなかったので調べてみたら生産停止…
  • codoc の登録をしてみました。よろしくお願いします