RubyでWebブラウザを操作するツール(知識更新のため

はじめに

過去の人 @riocampos ですw

Ruby で動的なサイトの Web スクレイピングをするときに、Selenuim をラップした Watir を使って動的なHTMLを取得していました。Selenium だと低水準なところに気を遣わないといけないのですが、Watir はうまく Rubyish に(オブジェクト指向っぽく)ラップしてくれていて、とても使いやすいライブラリです。(なお Capybara は自動化テスト向けなので私にはややこしくて重い。)で、目的の動的 HTML を取っちゃえば、あとは使い慣れた nokogiri でギコギコ切ってやれば良いわけですw

過去に書いた記事(もう2015年かよw):

Puppeteer とか Playwright とか

最近 Python の勉強をしていますが、やりたいのはやはり動的サイトの Web スクレイピングなのです*1。そこで Python でもどうせ Selenium を使わなきゃいけないのだろうなあと思いつつ調べていたら、pyppeteer とか playwright-python とかいうのがあると。

んで大元はそれぞれ PuppeteerPlaywright。ザザッと調べてみると、Selenium 一辺倒のところから Puppeteer (Google 支援)が出てきて、その開発チームが Microsoft に移って Playwright を作った、他方で puppeteer も別チームが開発継続してる、とか…。「元祖」「本家」的なw

脱線から戻ります。Ruby でも Puppeteer だの Playwright が使えるライブラリがあるんじゃないかなと思って探してみると、同じ人が puppeteer-rubyplaywright-ruby-client を作ってます。

実際にRubyクライアント書いてみた

ブラウザ自動操作のPlaywrightはRubyからでも使える? - YusukeIwakiのブログ

そのひとが blog で

とか書いてるので、まあ Playwright を Ruby でも Python でも使えばいいのかな、と思いました。

なお Puppeteer と Playwright 両者の違いとして

Puppeteerは単純にCDPを使うことに特化したツールキットなのに対し、Playwrightはブラウザオートメーションのオールインワンツールキットという感じだ。

第13回フクオカRuby大賞で入賞!その振り返り。 - YusukeIwakiのブログ

また

なので、playwright-ruby-clientを作っているから、puppeteer-rubyはもうオワコン?とはならない。用途が全然違う。

playwright-ruby-clientはどちらかというと、Ferrumに近い位置づけで、今後はCupriteのようなCapybaraドライバを作ったり、Vesselのようなクローラー書く仕組みを作ったり、みたいな方向性で成長させていく必要がある。

puppeteer-rubyはあくまでCapybaraとの「共存」ができることを強みに、(本家Puppeteerがなくならない限りはw)育てていくだろう。

いよいよPuppeteerを使う理由がなくなってきた。これからはPlaywrightの時代だ (2021/04/19記事)

とも。Capybara は使わないので、やはり私は Playwright を扱えるようにしよう。

そして。

JavaScript界隈ではCypressをはじめとして、優秀なE2Eテストフレームワークやブラウザ自動操作ライブラリがあるが、Rubyはというと現実的にはSeleniumしかないみたいな状況がずっと続いている。

ただ、IEがほぼ滅んだ今となってはRubyでもPlaywrightはSeleniumと同様に使っていけるはず。

いろいろOSS開発はしてきたが、そろそろSeleniumが唯一無二の選択肢みたいな状況には終止符うちに行きたいなと思っている。

なんだかんだで1年半くらいRuby向けにブラウザの自動操作ライブラリを作っている - YusukeIwakiのブログ

ありがたい。

Ferrum

で。そのライブラリ開発者の方のブログのなかで

RubyからCDP(Chrome DevTools)を使ってブラウザを自動操作できるライブラリはFerrumなどがあるが、それらはみなChromeしか自動操作できない。Firefoxも自動操作できるのは世界でpuppeteer-rubyだけ

第13回フクオカRuby大賞で入賞!その振り返り。 - YusukeIwakiのブログ

と書いていたのを見つけて「 Selenium 以外を使って Web ブラウザを使えるライブラリが他にもあるのか」と少し驚きました。ので、今後ちょっと動かしてみたいと思っています。作者は CDP(Chrome DevTools Protocol)に基づいた gem をいろいろ作ってるようですね。なお上に書いた Puppeteer と Playwright も CDP を使ってるようです。

Ferrum 参考記事:

*1:というかそこにしかモチベがないというか

Python学習めも:0. とりあえず箱

ちょっとずつ Python を勉強している Rubyist の @riocampos です。

Python学習めも:1. Python環境構築」の次が「0. とりあえず箱」というのは数の順序的におかしいのですが、Rubyist として疑問に思った事や気付いた事などを雑多に入れていく箱が欲しかったのでこんな名前になりました。この内容はそのうち別記事になっていく…はずです。

.oO(これ、文句箱になっている気がする…。)

気付き

Google Colab って便利

ゼロからのPython入門講座 - python.jp では Python 実行環境として Google Colab を使ってます。

Colab とは

Colab(正式名称「Colaboratory」)では、ブラウザ上で Python を記述、実行できます。以下の機能を使用できます。

  • 環境構築が不要
  • GPU に料金なしでアクセス
  • 簡単に共有

Colab は、学生からデータ サイエンティスト、AI リサーチャーまで、皆さんの作業を効率化します。

Google Colab

コンソールとは違って、Web から取ってきた画像は出せるし、Matplotlib を使ったグラフも表示出来る。これ便利ですねえ。

しかしバージョンは

いま最新版が3.10.6で、そろそろ3.11が出るのに3.7.3かあ…

>>> import sys
>>> sys.version_info
sys.version_info(major=3, minor=7, micro=13, releaselevel='final', serial=0)

>>> import platform
>>> platform.python_version_tuple()
('3', '7', '13')

参考:

Python での関数とメソッドの違い

Pythonが提供する機能には、abs() のように 関数 として提供されているものと、 文字列.upper() のように メソッド として提供されているものがあります。特定のデータに強く結びつき、利用頻度が高い処理はメソッドとして使えるようになっており、それ以外の処理は関数として使えるようになっていることが多いようです。

メソッド: ゼロからのPython入門講座 - python.jp

つまりメソッドは

  • 特定のデータに強く結びつく
  • 利用頻度が高い

で関数はそれ以外だと。今のところの印象だと、関数は様々な クラス データ型にまたがる処理を引き受けてる感じがしてる(打ち消し線入れたけど、クラスとデータ型の区別はあるのか無いのかよくわからん)。勘違いかもしれんけど。

リスト list であって配列 Array ではない

名称だけの問題かも知れないけど、Ruby の配列は Python ではリスト。

そして Python の array はリストとはまた別のデータ型。使い方が書いてないので理解できてないが、入れられるデータ型を指定してるようなのでメモリ効率が良いのでしょう。メモリのことを気にする PythonRuby よりも低級言語*1なんだなと感じる。

array --- 効率のよい数値アレイ

このモジュールでは、基本的な値 (文字、整数、浮動小数点数) のアレイ (array、配列) をコンパクトに表現できるオブジェクト型を定義しています。アレイはシーケンス (sequence) 型であり、中に入れるオブジェクトの型に制限があることを除けば、リストとまったく同じように振る舞います。オブジェクト生成時に一文字の 型コード を用いて型を指定します。

array --- 効率のよい数値アレイ — Python 3.10.6 ドキュメント

スライス

Ruby で配列の一部分を抜き出すときには範囲演算子b..e(b <= i <= e)/b...e(b <= i < e)を使うことが多い(個人の見解)。Python では「スライス」なる表記 [b:e:s]で範囲(b <= i < e)と刻み(ステップ s)を表現する。スライスの上限値は 常に含まれない ので(ステップを無視すれば) Ruby の範囲演算子のうちの ... に相当する。 まず Ruby で表現。

> # Pythonに合わせて 10 を使う表現(10.times.to_a)にした。いつもは [*0..9] と書く。
> ten = 10.times.to_a 
=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
> ten.size
=> 10
> ten[0..3] # インデックス i が 0 <= i <= 3 を満足する(3を含む)
=> [0, 1, 2, 3]
> ten[0...3] # インデックス i が 0 <= i < 3 を満足する(3を_含まない_)
=> [0, 1, 2]
> ten[5...-2] # インデックス i が 5 <= i < (ten.size -2) を満足する(8を_含まない_)
=> [5, 6, 7]

続いて Python

>>> ten = list(range(10))
>>> print(ten)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> len(ten)
10
>>> ten[0:3] # インデックス i が 0 <= i < 3 を満足する(3を_含まない_)
[0, 1, 2]
>>> ten[5:-2] # インデックス i が 5 <= i < (len(ten) -2) を満足する(8を_含まない_)
[5, 6, 7]

Ruby で普段使っている範囲演算子.. なので Python のスライスは違和感があったのだが、そういえば ... 演算子もあったなあ、ということで納得しました(なんだそりゃ)。

参考:

始値は常に含まれ、終了値は常に含まれない

スライスの使い方をおぼえる良い方法は、インデックスが文字と文字の あいだ (between) を指しており、最初の文字の左端が 0 になっていると考えることです。そうすると、 n 文字からなる文字列中の最後の文字の右端はインデックス n となります。例えばこうです:


>>> word = 'Python'

 +---+---+---+---+---+---+
 | P | y | t | h | o | n |
 +---+---+---+---+---+---+
 0   1   2   3   4   5   6
-6  -5  -4  -3  -2  -1

1行目の数字は文字列の 0 から 6 までのインデックスの位置を示しています; 2行目は対応する負のインデックスを示しています。i から j までのスライスは、それぞれ i と付いた境界から j と付いた境界までの全ての文字から成っています。

3. 形式ばらない Python の紹介 — Python 3.10.6 ドキュメント

スクレイピング

lxml 単体で URL を扱えるのは http だけ

前置き

Pythonスクレイピングするときに一般的に使われるのは Beautiful Soup 4 。ただ、 Beautiful Soup 4 は CSS のみで XPath が使えないらしい(ちゃんと調べてない)。私は以前から XPath を使って HTML を切り刻んでるので Python でも XPath を使いたい。 PythonXML パーサには標準ライブラリとして xml.etree.ElementTree というのがあるようなのだが、遅いのと脆弱性とであまりよろしくないらしい。他方、高速な XML パーサとして lxml がある。「高速」というか C ライブラリの libxml2 のラッパーらしい。

Requests ライブラリ を使わず lxml だけで HTML を取得出来るとの記事があったのだが…

parse()にはURLを直接渡せる

urllibなどを使ってレスポンスをロードしてからlxmlに渡している例が多くありますが、parse()にURLを渡すとそのURLにアクセスして解析してくれます。

 import lxml.html
 tree = lxml.html.parse('http://example.com/')

lxmlでスクレイピングするときのコツ - Regen Techlog (2014-08-06 (最終更新: 2017-07-02))

記事も古いから仕方ないとはいえ…結果は以下。

…だが lxml は https を扱えない orz

というオチなようです。

他の参考リンク:

なので Requests ライブラリか何かで HTML を取ってくる

ということで Requests ライブラリか何かで https の先の HTML を取ってきて、lxml.html.fromstring(html_str) で取得出来る lxml.html.HtmlElement インスタンスxpath メソッドを使ってやればいいようです。

が、文字化けしてるとやはりダメで、下の対処が必要でしたorz ←追記訂正:これは間違った文字コードを前提にした requests.get(url).text で文字化けテキストになったものを lxml.html.fromstring() に渡すからだめなので、バイトコードのまま requests.get(url).contentlxml.html.fromstring() に渡せば問題ない!

Beautiful Soup は「バイト文字列を読み込んで文字コードを推定する機能がある」のと同様に、lxml でもバイトコードであれば文字コードを適切に解釈してくれるようです。ありがたい。

Requests ライブラリで取得した HTML の文字化け

Google Colab で NHK ニュースの新着ニュース一覧の HTML を取得したら日本語部分が文字化けしました(右へスクロールしていってください)。

>>> import requests
>>> url = "https://www3.nhk.or.jp/news/catnew.html"
>>> nhk_news_latest = requests.get(url)
>>> nhk_news_latest.text
<!DOCTYPE HTML>\r\n<!--[if lt IE 7]>      <html class="no-js lt-ie9 lt-ie8 lt-ie7 eq-ie6"> <![endif]-->\r\n<!--[if IE 7]>         <html class="no-js lt-ie9 lt-ie8 eq-ie7"> <![endif]-->\r\n<!--[if IE 8]>         <html class="no-js lt-ie9 eq-ie8"> <![endif]-->\r\n<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->\r\n<head prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb# website: http://ogp.me/ns/website#">\r\n<meta charset="utf-8" />\r\n<meta http-equiv="X-UA-Compatible" content="IE=edge" />\r\n<meta http-equiv="X-UA-Compatible" content="requiresActiveX=true" />\r\n<meta name="fragment" content="!" />\r\n\r\n<title>é\x80\x9få\xa0±ã\x83»æ\x96°ç\x9d\x80ä¸\x80覧ï½\x9cNHK NEWS WEB</title>\r\n<meta name="robots" content="noodp,noarchive">\r\n<meta name="keywords" content="é\x80\x9få\xa0±,æ\x96°ç\x9d\x80,ä¸\x80覧,NHK,ã\x83\x8bã\x83¥ã\x83¼ã\x82¹,NHK NEWS WEB" />\r\n<meta name="description" content="NHKã\x81®ã\x83\x8bã\x83¥ã\x83¼ã\x82¹ã\x82µã\x82¤ã\x83\x88ã\x80\x81NHK NEWS WEBã…

<title> タグが <title>é\x80\x9få\xa0±ã\x83»æ\x96°ç\x9d\x80ä¸\x80覧ï½\x9cNHK NEWS WEB</title> のように文字化け。

文字化けの原因

レスポンスヘッダに文字コード情報が記述されていない場合は、デフォルト値のISO-8859-1が設定されてしまいます。

対策

requestsモジュールでは、取得したHTMLに含まれるテキスト情報から、文字コードを推定してくれる機能があります。

apparent_encodingencodingに指定します。

Pythonのrequestsモジュールでの文字コード対策 - かんちゃんの備忘録

に従って encoding をセットしたら文字化けが解消されました(これも右へスクロールしていってください)。

>>> nhk_news_latest.encoding = nhk_news_latest.apparent_encoding
>>> nhk_news_latest.text
<!DOCTYPE HTML>\r\n<!--[if lt IE 7]>      <html class="no-js lt-ie9 lt-ie8 lt-ie7 eq-ie6"> <![endif]-->\r\n<!--[if IE 7]>         <html class="no-js lt-ie9 lt-ie8 eq-ie7"> <![endif]-->\r\n<!--[if IE 8]>         <html class="no-js lt-ie9 eq-ie8"> <![endif]-->\r\n<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->\r\n<head prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb# website: http://ogp.me/ns/website#">\r\n<meta charset="utf-8" />\r\n<meta http-equiv="X-UA-Compatible" content="IE=edge" />\r\n<meta http-equiv="X-UA-Compatible" content="requiresActiveX=true" />\r\n<meta name="fragment" content="!" />\r\n\r\n<title>速報・新着一覧|NHK NEWS WEB</title>\r\n<meta name="robots" content="noodp,noarchive">\r\n<meta name="keywords" content="速報,新着,一覧,NHK,ニュース,NHK NEWS WEB" />\r\n<meta name="description" content="NHKのニュースサイト、NHK NEWS WEBの新着ニュースについてのページです。ニュース速報はもちろん、NHK NEWS WEBに掲載されたさまざまなジャンルのニュースを新着順に表示しています。日本と世界の「いま」が分かります。" />\r\n<meta name="copyright" content="NHK(Japan Broadcasting…

<title> タグが <title>速報・新着一覧|NHK NEWS WEB</title> とちゃんと読めるようになりました。

エンコーディングを適切にすると以下のように記事タイトルを取得できました。改めて最初から書いておきます。

>>> import requests
>>> from lxml import html
>>> url = "https://www3.nhk.or.jp/news/catnew.html"
>>> nhk_news_latest = requests.get(url)
>>> nhk_news_latest.encoding = nhk_news_latest.apparent_encoding
>>> nhk_news_latest_doc = html.fromstring(nhk_news_latest.text)
>>> lis = nhk_news_latest_doc.xpath('//ul[@class="content--list grid--col-single"]/li')
>>> [li.xpath('dl/dd/a/em')[0].text for li in lis]
['台風12号 八重山地方の一部が暴風域か 暴風や高波に厳重警戒',
 '神奈川県 新型コロナ 1人死亡 新たに5309人感染確認',
 '千葉県 新型コロナ 11人死亡 新たに3757人感染確認',
 '三重県 新型コロナ 2人死亡 新たに1608人感染確認',
 '茨城県 新型コロナ 5人死亡 新たに1834人感染確認',
 '静岡県 新型コロナ 新たに1923人感染確認',
 '長野県 新型コロナ 新たに1075人感染確認',
 '山梨県 新型コロナ 2人死亡 新たに356人感染確認',
 '富山県 新型コロナ 1人死亡 新たに822人感染確認',
 '愛知県 新型コロナ 2人死亡 新たに5193人感染確認',
 '東京都 新型コロナ 7750人感染確認 前週比1800人余減',
 'コロナ第7波 “死亡者の多くは肺炎以外 容体の傾向が変化”',
 '栃木県 新型コロナ 1人死亡 新たに972人感染確認',
 '北海道 新型コロナ 2人死亡 新たに3295人感染確認',
 '長崎県 新型コロナ 3人死亡 新たに610人感染確認',
 '岐阜県 新型コロナ 2人死亡 新たに1364人感染確認',
 '大分県 新型コロナ 新たに706人感染確認',
 '広島県 新型コロナ 2人死亡 新たに2604人感染確認',
 '山口県 新型コロナ 2人死亡 新たに885人感染確認',
 '沖縄県 新型コロナ 新たに721人感染確認']

なおニュースは2022/9/11 17:20現在の記事。

でもね。上にも書いたけど requests.get(url).content にすれば文字化けせずに済む

もう一度書きます。requests.get(url).text で文字化けテキストになったものを lxml.html.fromstring() に渡すからだめなので、バイトコードのまま requests.get(url).contentlxml.html.fromstring() に渡せば問題ない!

requests.get を使ってデータを含むウェブページを取得し、 html モジュールを使って解析し、結果を tree に保存します:


page = requests.get('http://econpy.pythonanywhere.com/ex/001.html')
tree = html.fromstring(page.content)

page.text ではなく page.content を使用する必要があります。なぜなら、 html.fromstring は入力として bytes を暗黙的に期待しているからです。)

lxml と Requests — HTMLスクレイピング — The Hitchhiker's Guide to Python

バイトコードを返す requests.get(url).content を使えば、encoding をセットしなくても最終的に文字化けしません!ネットでは requests.get(url).textだらけだけど、みんな requests.get(url).content を使おう!

>>> nhk_news_local = requests.get("https://www3.nhk.or.jp/lnews/")
>>> nhk_news_local_doc = html.fromstring(nhk_news_local.content)
>>> local_lis = nhk_news_local_doc.xpath('//ul[@class="content--list grid--row-wide"]/li')
>>> [li.xpath('a/dl/dd/em')[0].text for li in local_lis]
['カピバラの赤ちゃん すくすく成長 栃木 那須町',
 '彦根城 夜間特別公開が開始 中秋の名月眺める 滋賀 彦根',
 '伝統芸能の「備中神楽」 中秋の名月のもと楽しむ 岡山 高梁',
 '「恐竜の着ぐるみレース」200mの特設コースを疾走 福井 勝山',
 '西九州新幹線試乗会 最新鋭車両が佐賀 武雄温泉~長崎を走行',
 '【動画】小学生が捕獲「バナナウナギ」水族館で展示 三重 伊勢',
 '【動画】10頭の母パンダ「良浜」22歳誕生日 和歌山 白浜町']

NHKローカルニュースは2022/9/12の記事。

オブジェクトの持つ属性だのメソッドだのを知りたい

関数 dir()

関数 vars()

dir()vars() の違い

(dir()では)オブジェクトが持つ属性のみならず、オブジェクトが属しているクラスが持つ属性をも含んだリストが返ってきます。

Pythonのvars()とdir()の違い - minus9d's diary

inspect モジュールのメソッド inspect.getmembers()

不満

クラス、メソッド、データ属性

クラスとは何ですか?

クラスは、class 文の実行で生成される特殊なオブジェクトです。クラスオブジェクトはインスタンスオブジェクトを生成するためのテンプレートとして使われ、あるデータ型に特有のデータ (attribute/属性) とコード (メソッド) の両方を内蔵しています。

メソッドとは何ですか?

メソッドは、オブジェクト x が持つ関数で、通常 x.name(arguments...) として呼び出されるものです。メソッドはクラス定義の中で関数として定義されます:

オブジェクト — プログラミング FAQ — Python 3.10.6 ドキュメント

以前に JavaScript を学んだときにも感じた違和感。Rubyist はデータ属性(アトリビュート)とメソッドとを区別してない。だってクラス内で内部処理があろうが無かろうが(つまりメソッドだろうが属性値だろうが)関係なく「返値」として取り扱ってる。メソッドには引数があることもあるし無い事もある。そして Ruby だと引数の無いメソッドは属性値とほぼ変わりないので、メソッド名のあとにメソッドである印としての括弧 () が存在しない。Python 同様 JavaScript でもやはりメソッドの後ろには(引数が無くても)括弧を付けなきゃいけない。なんだか面倒。でもこれは文法だから従わないといけない。

メンバって何よ

名称の話なのだが「メンバ」という単語がある。上記の「クラスとは何ですか?」での「データ (attribute/属性)」に相当するようである。どうやらC++ 由来の用語らしい。

C++ の用語で言えば、通常のクラスメンバ (データメンバも含む) は (プライベート変数 に書かれている例外を除いて) public であり、メンバ関数はすべて 仮想関数(virtual) です。 Modula-3 にあるような、オブジェクトのメンバをメソッドから参照するための短縮した記法は使えません: メソッド関数の宣言では、オブジェクト自体を表す第一引数を明示しなければなりません。第一引数のオブジェクトはメソッド呼び出しの際に暗黙の引数として渡されます。

9. クラス — Python 3.10.6 ドキュメント

「メンバ」なる用語が気になったのは、オブジェクト(インスタンス)の持ってる属性値やメソッド名を調べるときに、関数 dir() の他に inspect.getmembers() というメソッド*2がある、と知ったとき。

dir()関数以外で、inspectという標準ライブラリを使う方法もあります。

getmembers()を使うと属性が取得できるようだ。

標準ライブラリinspectで調べる - 【dirとinspect】Pythonライブラリの属性、メソッド一覧を調べる方法 - よちよちpython

このメソッドの名称が「ゲットメンバーズ」。んじゃ「メンバ」って何よ?って事になったわけで。

しかも、なんとなく「メンバ」が属性値っぽいよなーと思っていつつも、実際に inspect.getmembers() の返値を見ると属性値のほかにメソッドも含まれているし、なんかよくわからん。

徐々に慣れていくしかないのであろう。

なお inspect.getmembers() に関する情報の役立ちリンク:

関数とメソッド、文の可読性

Ruby の場合は「何もかも全てオブジェクト、関数のように見えてもメソッド」という極端にオブジェクト指向な言語です。そして「返値はどの式にも存在する、メソッドチェーンで続けていくのが快感」という点もやはりクセあります*3

例えばこんな感じでメソッドチェーンを続けていくのが Rubyish(個人の意見です)。

> "Ruby_haS_MeThod_chAin_cOnTinuE_InFinite.".split("_").map { |word| word.downcase }.join(" ").capitalize
=> "Ruby has method chain continue infinite."

そして素人に毛が生えた程度の知識で上と同じことを Python で書いてみた。「内包表記」ってやつを使った。

>>> " ".join([word.lower() for word in "Ruby_haS_MeThod_chAin_cOnTinuE_InFinite.".split("_")]).capitalize()
'Ruby has method chain continue infinite.'

可読性を上げようと思えばいくらでも上げられるのでしょうが、現状では英文を日本語訳するかのような「一文の中で前へ行ったり後ろへ行ったり」感がとても強くて読みづらい。

Ruby に慣れていると、インスタンスの処理を後ろにドットで続けていくことが「見やすい」と感じるため、Python での「関数として引数の前に関数名が来る」事、そしていちいち括弧を付けなきゃいけない(というか括弧が無い事での可読性の低下の激しい)こと、それに加えてメソッド名がインスタンスの後ろに来る事による(上にも書いたけど)あっち行ったりこっち行ったり感に違和感を強く感じています。関数しか使わないのであれば H(G(F(x))) みたいに順々に前へ読んでいくので一方向なのだけど。

他方、括弧の使わなさに関しては Ruby が極端なのですけど。

",".join(["a", "b", "c"])"a,b,c".split(",") の(ある種の)対称性

Ruby だと配列 ["a", "b", "c"], で結びつけるのは Array#join メソッドを使った

["a", "b", "c"].join(",")

であり、逆に文字列 "a,b,c", で区切って行列にするのは String#split メソッドを使った

"a,b,c".split(",")

とするわけです。が、Python では前者が文字列メソッド

",".join(["a", "b", "c"])

になっています。Rubyish な脳では真っ先に拒絶反応が生じたのがこのメソッド。

「なんでコレクションに対するメソッドじゃなくて文字列メソッドなのよ!?」

この疑問を感じるのはどうやら Rubyist だけではないようで、Python ドキュメントの FAQ にも取り上げられています。

…一部のプログラマに不快を感じさせていると思われる…

「文字列リテラル (文字列定数) のメソッドを使うのは醜すぎる」

「私は実際、要素を文字列定数とともに結合させるよう、シーケンスに命じているのだ」

そしてこれらの不満への返答が ↓ です。

join() は、セパレータ文字列に、文字列のシーケンスをイテレートして隣り合う要素の間に自身を挿入するように指示しているので、文字列のメソッドです。このメソッドは、独自に定義された新しいクラスを含め、シーケンスの規則を満たすいかなる引数にも使えます。

納得は出来ない*4のですが、ココだけを我慢すればまだ何とかなるかなあと思っています。

学習元

*1:最近はどうやら「低水準言語」と言うようだ

*2:用語は「メソッド」で良いんだよね??

*3:他の言語はほとんど触ってないので実はよく分かってませんが

*4:美的センスに欠けると感じる

Python学習めも:1. Python環境構築

はじめに

最近はすっかりプログラミングから離れている @riocampos です。とはいえ Ruby はいちおう書けるし、テキトーな自動化はできます。

最近、heroku の無料プランが終了するとの記事がありました。

とても世話になったのですが今は動かさず放置してます。なので heroku のアプリが消え去っても仕方ないでしょう。ただ、サーバで動かしたいことはいろいろあるのです。引越を検討する必要もあるのでしょう。

で。

heroku では Ruby がメインに近い言語として取り扱われていましたが、他の無料 PaaS では動かなさそう…。以前から Python も勉強しなきゃと思っていたので、そろそろ動き出そうかと。

環境構築

基本的にコレに従う事にしました。

なお現在の環境は

  • MacBook Pro (13-inch, 2017, Two Thunderbolt 3 ports)
  • macOS Big Sur 11.6(アップデートしてない…)

前準備:pyenv を削除

個人的に言語のバージョン管理は anyenv を使っています。で、何か似たような名前の venv というのが出てきてるので何だろうと悩んでいましたが、バージョン管理ではなくパッケージ管理なのですね。

で、久しぶりに anyenv を使って pyenv を更新しようとしたのですが…

$ anyenv update -v pyenv
Updating 'pyenv'...
 |  cd /Users/riocampos/.anyenv/envs/pyenv
 |  fatal: unable to connect to github.com:
 |  github.com[0: 20.27.177.113]: errno=Operation timed out
 |  
 |  Failed to update. Use 'verbose' option for detailed, or 'force' option.
Updating 'pyenv/pyenv-pip-rehash'...
 |  cd /Users/riocampos/.anyenv/envs/pyenv/plugins/pyenv-pip-rehash
 |  Already up to date.
Skipping 'pyenv/python-build'; not git repo
Updating 'anyenv manifest directory'...
 |  cd /Users/riocampos/.config/anyenv/anyenv-install
 |  Already up to date.

なにやらエラーが出てる。 pyenv 自体は今もアクティブなプロジェクトだし、anyenv も生きてる。

とはいえ、もう Python 2 を使う事も無いだろうし、バージョンを変えることもまず無いかなあと。それに以下のようなことも書かれてます。

複数のバージョンのPythonを同時にインストールして利用しても問題ないようになっています。

例えばPython3.7と3.8を同時にインストールしておくと、macOSUnix環境では python3.7 コマンドと python3.8 コマンドで使用するバージョンを切り分けて実行できます。

Pythonのバージョン - Pythonのインストール方針: Python環境構築ガイド - python.jp

さらには

などという記事まで…。

なので pyenv を消しちゃいます。

$ anyenv uninstall pyenv
anyenv: remove /Users/riocampos/.anyenv/envs/pyenv? y
$ ls -l ~/.anyenv/envs
total 0
drwxr-xr-x  18 riocampos  staff  576  7 25  2019 crenv
drwxr-xr-x  30 riocampos  staff  960  9  7 10:05 goenv
drwxrwxr-x  25 riocampos  staff  800  5 14  2021 nodenv
lrwxr-xr-x   1 riocampos  staff   19  6 10  2019 rbenv -> /Users/riocampos/.rbenv/

そして anyenv を使う前から pyenv を使っていた(移行の経緯のメモ→ anyenvをインストール2 - 別館 子子子子子子(ねこのここねこ)はてブロ部)ので、 pyenv 自体も手動で消します。

$ ls -l ~/.pyenv/
total 392
-rw-r--r--   1 riocampos  staff   35414 10  3  2021 CHANGELOG.md
-rw-r--r--   1 riocampos  staff    9411 10  3  2021 COMMANDS.md
-rw-r--r--   1 riocampos  staff    3380  5 14  2021 CONDUCT.md
-rw-r--r--   1 riocampos  staff     680  5 14  2021 Dockerfile
-rw-r--r--   1 riocampos  staff    1092 10 19  2014 LICENSE
-rw-r--r--   1 riocampos  staff     852  5 14  2021 Makefile
-rw-r--r--   1 riocampos  staff   22146 10  3  2021 README.md
drwxr-xr-x   3 riocampos  staff      96  4 13  2016 bin
drwxr-xr-x   5 riocampos  staff     160  2 24  2017 completions
drwxr-xr-x  27 riocampos  staff     864 10  3  2021 libexec
drwxr-xr-x   3 riocampos  staff      96 10  3  2021 man
drwxr-xr-x@  6 riocampos  staff     192 10 19  2014 plugins
drwxr-xr-x   5 riocampos  staff     160  5 14  2021 pyenv.d
drwxr-xr-x@ 34 riocampos  staff    1088  9  7 10:21 shims
drwxr-xr-x   7 riocampos  staff     224  7 19  2018 src
-rw-rw-r--   1 riocampos  staff  104764  7 19  2018 terminal_output.png
drwxr-xr-x  29 riocampos  staff     928 10  3  2021 test
-rw-r--r--@  1 riocampos  staff      13  2  7  2018 version
drwxr-xr-x@  5 riocampos  staff     160  2  7  2018 versions
$ rm -rf ~/.pyenv

消えました。

Homebrew で Python をインストール

先にも書いたけど

これに従っていきます。

Homebrew で Python を入れる手順になってますね。以前から Homebrew は使ってるし、他のコマンドをインストールするときに Python が入ってるんじゃないかなあ、ってことで先ずは確認。

$ brew list | grep python
python@2
python@3.10
python@3.8
python@3.9
$ which python
/usr/bin/python
$ which python3
/usr/local/bin/python3
$ which python3.10
/usr/local/bin/python3.10
$ python --version
Python 2.7.16
$ python3 --version
Python 3.10.6

既にいっぱい入ってた。んで $ python だとシステムのほうの Python 2.7.16 が動いちゃうことも分かった。

python コマンドを実行すると、プレインストールされた Python 2.7を実行してしまいます。間違えて実行しないように気をつけましょう。

Pythonの実行: Python環境構築ガイド - python.jp

pip

pip は、The Python Package Index に公開されているPythonパッケージのインストールなどを行うユーティリティで、Python 3.4以降には、標準で付属しています。

pip: Python環境構築ガイド - python.jp

ってことは改めてインストールする必要無いと。

パッケージのインストールは、pipinstall コマンドで行います。例えば、Pythonの代表的な画像処理パッケージ pillow パッケージをインストールするときは、次のように実行します。

$ python3 -m pip install pillow

Note

python3 コマンドを使わず、pip3 コマンドを使っても実行できますが、初心者の方には python3 -m pip ... の形式をおすすめします。

不要なパッケージは、uninstall コマンドで削除できます。つぎの例は、pillow パッケージを削除します。

$ python3 -m pip uninstall pillow

pip: Python環境構築ガイド - python.jp

どうやら Python の複数のバージョンを入れているときには、パッケージもバージョン毎に入れることになるようですね(参考:【Python】”pip install” と “python -m pip install” の違い | だえうホームページ)。なので python3 -m pip install ... のほうが好ましいと。

脱線:python -m ... とは

今後はたびたび公式ドキュメントを引用していきます。

  • -m モジュール名 として Python モジュールパスにあるモジュールを指定された場合、そのモジュールをスクリプトとして実行します。

  • -m <module-name>

sys.path から指定されたモジュール名のモジュールを探し、その内容を __main__ モジュールとして実行します。

引数は module 名なので、拡張子 (.py) を含めてはいけません。モジュール名は有効な Python の絶対モジュール名 (absolute module name) であるべきですが、実装がそれを強制しているとは限りません (例えば、ハイフンを名前に含める事を許可するかもしれません)。

パッケージ名 (名前空間パッケージも含む) でも構いません。通常のモジュールの代わりにパッケージ名が与えられた場合、インタプリタ<pkg>.__main__main モジュールとして実行します。この挙動はスクリプト引数として渡されたディレクトリや zip ファイルをインタプリタが処理するのと意図的に同じにしています。

このオプションが指定された場合、 sys.argv の最初の要素はモジュールファイルのフルパスになります (モジュールファイルを検索している間、最初の要素は "-m" に設定されます)。 -c オプションと同様に、カレントディレクトリが sys.path の先頭に追加されます。

1.1.1. インターフェイスオプション - 1. コマンドラインと環境 — Python 3.10.6 ドキュメント

正直なところ、この段落の半分も分からない。が、これらのドキュメントを読めるようになることが学習目的でもあるので頑張る。

現状で分かるのは、pip ユーティリティもモジュールであり、単体実行(pip install ...)できるけど、Python インタプリタから実行する(python3 -m pip install ...)こともできる、ということ。そして後者だと、どのバージョンの Python を使って pip を実行するか、すなわちどのバージョンの Python にパッケージをインストールするのかを明示して作業できる、ってこと。

いまのところは Python 3.10 しか使わないので問題ないけど、パッケージも Python のバージョン依存があるようだし、不確定要因があるとデバッグも面倒になるので python3 -m pip install ... で pip を使うように癖付けておきたい。

venv

とうとう来たね venv 。

import requests
print(requests.get("https://www.python.jp").text)

で。これだと requests モジュール*1が入ってないよ、ってエラーが出るんだけど、リンク先だと

ImportError: No module named requests

とある。でも Python 3.10 だと

ModuleNotFoundError: No module named 'requests'

になってる。調べると3.6から変わったようだ。

exception ModuleNotFoundError

ImportError のサブクラスで、import 文でモジュールが見つからない場合に送出されます。また、 sys.modulesNone が含まれる場合にも送出されます。

バージョン 3.6 で追加.

組み込み例外 — Python 3.10.6 ドキュメント

続いて仮想環境を作る。.venv ディレクトリ以下に仮想環境のモジュールなどをぶち込むようですな。公式でも .venv が「よく使われるディレクトリ名」だと書いてます。

$ python3 -m venv .venv
$ ls -l
total 8
-rw-r--r--@ 1 riocampos  staff  65  9  7 11:37 sample.py
$ ls -al
total 8
drwxr-xr-x  4 riocampos  staff  128  9  7 11:49 .
drwxrwxr-x@ 5 riocampos  staff  160  9  7 11:36 ..
drwxr-xr-x  6 riocampos  staff  192  9  7 11:49 .venv
-rw-r--r--@ 1 riocampos  staff   65  9  7 11:37 sample.py
$ ls -al .venv/
total 8
drwxr-xr-x   6 riocampos  staff  192  9  7 11:49 .
drwxr-xr-x   4 riocampos  staff  128  9  7 11:49 ..
drwxr-xr-x  12 riocampos  staff  384  9  7 11:49 bin
drwxr-xr-x   2 riocampos  staff   64  9  7 11:49 include
drwxr-xr-x   3 riocampos  staff   96  9  7 11:49 lib
-rw-r--r--   1 riocampos  staff   92  9  7 11:49 pyvenv.cfg

あっという間にファイルがたくさん作られた。

続いて仮想環境をアクティブにする*2と、プロンプト($)の前に仮想環境のディレクトリ名が追加される。

$ source .venv/bin/activate
(.venv) $ 

仮想環境下で requests モジュールのインストールを実行。上記したように python3 -m pip install ... の手法で。

(.venv) $ python3 -m pip install requests
Collecting requests
  Downloading requests-2.28.1-py3-none-any.whl (62 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 62.8/62.8 kB 669.4 kB/s eta 0:00:00
Collecting certifi>=2017.4.17
  Downloading certifi-2022.6.15-py3-none-any.whl (160 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 160.2/160.2 kB 1.2 MB/s eta 0:00:00
Collecting idna<4,>=2.5
  Downloading idna-3.3-py3-none-any.whl (61 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 61.2/61.2 kB 1.6 MB/s eta 0:00:00
Collecting charset-normalizer<3,>=2
  Downloading charset_normalizer-2.1.1-py3-none-any.whl (39 kB)
Collecting urllib3<1.27,>=1.21.1
  Downloading urllib3-1.26.12-py2.py3-none-any.whl (140 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 140.4/140.4 kB 1.4 MB/s eta 0:00:00
Installing collected packages: urllib3, idna, charset-normalizer, certifi, requests
Successfully installed certifi-2022.6.15 charset-normalizer-2.1.1 idna-3.3 requests-2.28.1 urllib3-1.26.12

sample1/.venv/lib/python3.10/site-packages 以下に5つのモジュールがインストールされた。

ちなみにこの時点で sample1 ディレクトリの容量は 20.3MB(requests モジュールのインストール前の sample1 ディレクトリの容量は 18.2MB。そのうち pip が 11.6MB、setuptools が 4.8MB、その他が1.8MB。requests モジュールのインストールで 2MB ほど追加されたわけか)。仮想環境を1つ作ると、わりと容量喰いますね。

では再度スクリプト実行。

(.venv) $ python3 sample.py
<!DOCTYPE html>
<html lang="ja">
<head>
 :
</body>
</html>

https://www.python.jp からの HTML のダウンロードに成功。

仮想環境から抜け出すには deactivate。通常のプロンプトに戻る。

(.venv) $ deactivate
$

仮想環境から抜け出すコマンドは簡単なのに、仮想環境に入るときのコマンドは何だか面倒に感じる。

ということで環境構築は出来た。あとは「Python[完全]入門」オススメのVisual Studio Code – コード エディター | Microsoft Azureをインストールしておこう。

とりあえずここまでで区切り。

良さげだと感じた本たち(まだ買ってない

*1:Ruby の癖で「requests ライブラリ」とか書いてしまいそう→どうやら「ライブラリ」の中に「モジュール」と「パッケージ」が含まれるらしい→Python のコーディング規約 PEP 8 ってなに? | 民主主義に乾杯

*2:もちろん '. .venv/bin/activate' のほうが短くて楽ではあるのだけど、'.' だと何なのか分からないので好きじゃない。ので 'source' と明記。

radirudegogaku0.rb更新(2022/05)

ごあいさつ

こんにちは。gogakuondemand.rb および radirudegogaku.rb 作者の @riocampos です。またまたご無沙汰しております。

更新内容

今回の更新はNHK側仕様変更(?)に対する更新です。

NHK語学講座のらじる★らじる聴き逃しサービスを取得するRubyスクリプトradirudegogaku.rb(2022/05) - 別館 子子子子子子(ねこのここねこ)はてブロ部コメント欄より

  • DK

当方では2021/10更新版で5/13(金)頃から、ダウンロードが途中で終了する現象が発生しています。
ffmpeg
5.0 :NG
4.4 :NG
4.3 :NG
4.2 :OK

 :

オプション「-http_seekable 0」を追加変更することで、ffmpegのバージョン5.0.1でもエラーなく最後までダウンロードできました。

との情報に基づいて修正しました。当方で確認したところ FFmpeghttp_seekable オプションは FFmpeg バージョン 4.3 以降で有効 であるようです*1。ですので今回の2022/05更新版は FFmpeg バージョン 4.3 以降の限定になります(http_seekable オプション非対応の FFmpeg を使っているとダウンロードのプログレスバーが出ず、ダウンロードも行われません)。

スクリプトの使い方・ダウンロード

NHK語学講座のらじる★らじる聴き逃しサービスを取得するRubyスクリプトradirudegogaku.rb(2022/05) - 別館 子子子子子子(ねこのここねこ)はてブロ部 からのリンクをご利用ください。

感謝

報告頂いた tama さん、解決策をご提供頂いた DK さん

参考ツイート

*1:詳細未検証です違ってたらゴメンナサイ

radirudegogaku0.rb更新(2021/10)

ごあいさつ

こんにちは。gogakuondemand.rb および radirudegogaku.rb 作者の @riocampos です。またまたご無沙汰しております。

更新内容1

今回の更新は NHK語学講座のらじる★らじる聴き逃しサービスを取得するRubyスクリプトradirudegogaku.rb(2020/4/2暫定版) - 別館 子子子子子子(ねこのここねこ)はてブロ部 でコメントして頂いていたダウンロード時のトラブル対応です。

  • あひる

Linux Mint 20.2 (最新版) です。

   :

最近たまにですが、番組の途中でダウンロードが切れてしまう時があるようです。NHK側の問題のような気がしていますが、ご参考までに報告いたします。

  • riocampos

   :

実を言いますと当方でもダウンロードが切れてしまうことがあります。

基本的にはNHK側の問題だと私も推測しています。

ファイルサーバに保存させている当方の特殊事情なのかなあとも思っていたのですが、他の環境でも生じるのであれば、すこし調査しないといけませんね。

時間かかると思いますが、お待ち頂ければ幸いです。

結論としてはやはり NHK サーバ側の問題だと思われます。スクリプト側でダウンロードを正常にすることは難しいので、ダウンロード異常があればメッセージを出すようにしました。

メッセージは二種類。ダウンロード開始時に問題が生じた場合と、ダウンロード途中に問題が生じた場合です。後者の場合には手動でダウンロードファイルを削除する必要があります。いずれにせよ再度スクリプトを実行しなきゃいけません。

更新内容2

そしてダウンロードを再実行すると、作成されたストリーミングファイルの日時が(当然ながら)再実行した日時になってしまうので、ファイルを日時順に並べていると順序が乱れてしまいます。対策として、ストリーミングファイルの日時をNHKラジオ第2で放送された時刻に設定するようにしました。

このため、radirudegogaku.rb を更新した直後だと、新しくダウンロードしたファイルの日時が以前にダウンロードしたファイルよりも古い日時になってしまうかもしれません。申し訳ないのですが、利用者側でダウンロード先を変更するなどの対策を行ってください。

更新内容3

そして(お待たせしました)ダウンロードしたファイルを mp3 にするオプションを追加しました。

更新内容4

Ruby 2.7 以降および 3.0 以降でも

warning: calling URI.open via Kernel#open is deprecated, call URI.open directly or use URI#open.

の注意メッセージが出ないように変更しました。またスクリプトに若干の修正を加えました

スクリプトの使い方・ダウンロード

NHK語学講座のらじる★らじる聴き逃しサービスを取得するRubyスクリプトradirudegogaku.rb(2020/4/2暫定版) - 別館 子子子子子子(ねこのここねこ)はてブロ部 からのリンクをご利用ください。

macOS Big Surへのアップグレードめも

作業直後メモ

MacBook Pro (13-inch, 2017, Two Thunderbolt 3 ports)での作業。macOS 10.13 High Sierraからのアップグレード。1時間ぐらいで完了。現在のバージョンはmacOS Big Sur 11.3.1。 起動時に

  • RSSCSIPeripheralDeviceType00_10_4
  • RSSCSIPeripheralDeviceType00_10_5

についての警告が出た。「システム情報>使用停止されたソフトウェア」から確認するとRatocのドライバのことのようだ。サーバのmacにはRatocの外付けHDDを付けているが、何かの時にこのMacBook Proに繋いだのだろう。

見た感じがいろいろ変わっていて違和感あるような、iOS的で馴染みあるような。

あと terminal のシェルが zsh になったらしい。

The default interactive shell is now zsh.
To update your account to use zsh, please run `chsh -s /bin/zsh`.
For more details, please visit https://support.apple.com/kb/HT208050.

Big Surにした一番の目的

これ↓

au契約iPhone12でIIJmioギガプランeSIMに申込&テザリングまで設定

前置き

以前はiPhone 6sで「auフラットプラン7プラスN」に加入していました(月額税別5480円に家族割プラス2人で-500円)。普段の月利用量は7GB弱だしTwitter通信量はカウントされない、という私にピッタリのプランでした。
しかしiPhone 6sの画面を割ってしまいました。そのためiPhone 12 Pro Maxへ機種変更。そのときに契約は5Gプラン必須となり「ピタットプラン 5G」に変更せざるを得なくなりました(4GB〜7GB利用で月額税別5980円に家族割プラス2人で-500円)。私の行動範囲では5Gエリアはほぼ無く、5Gプランを選ぶメリットはありません。わりと悲しかったです。
povoに乗り換えしたほうが安くあがるけど、20GBも要らないし、ezwebのメールアドレスが消えるのは少し辛い旧人類。どうしようか悩んでいました。

eSIMでデータ通信をau以外にして安くあげよう!

先日、この記事を読みました。
ASCII.jp:新料金プランに注目が集まる今、あえて3大キャリアのメインプランに残って低コストで利用する方法を考える
データ通信を安いMVNOにしつつ、段階的料金設定のメインプラン(auだとピタットプラン)は最安を維持する、というやり方です。
この4月からIIJmioに新しいプラン「ギガプラン」が出来ました。eSIMだとかなり安いです。


「ピタットプラン 5G」の1GB以下利用だと本体部分は税別2980円、4GB〜7GB利用で月額税別5980円、その差額は3000円。4GBまでの差額だと1500円。
一方、ギガプランならば8GBで税別1000円、20GBでも税別1500円。
明らかに「ピタットプラン1GB以下利用+ギガプラン」のほうが安くあがります。しかも15GBとか20GBとかにすればかなりゆとりも持てる。
さらにはIIJmio eSIMのMVNOdocomo回線なので、au回線よりもエリアが広い場合が多い*1
ということで、この記事の案に乗ることにしました。
なお(この記事の末尾にも書きましたが)eSIMを使ってデュアルSIMにすると5Gが使えなくなります。5Gを重視する方はご注意ください。

まずはSIMロック解除の申込

今回は即金で購入したので100日制限はかかりませんでした。
auサイトの脅し文に負けなければ大丈夫です。
申し込んだあと、すこし通信の挙動が変になる(設定変更の影響?)のですが、しばらく待つと大丈夫です。

IIJmioにギガプラン申込

これはギガプランのサイトから申し込んでいけば特に問題なし。作業が深夜だったため、開通は翌朝になりました。

iPhone 12 Pro Maxへの設定手順

ようやくこの記事の目的箇所にきましたw
なお、以下の作業は無線LANMacのある環境で行いました。
なおApple公式での設定など解説サイトはこちら:eSIM でデュアル SIM を活用する - Apple サポート

IIJmioからのアクティベーションコードをスキャン

開通連絡メールにアクティベーションコードへのリンクがあります。まずMacでそのサイトを開いて、サービスコードを選択すると、2次元バーコードが表示されます。
iPhone 12の「設定アプリ>モバイル通信>モバイル通信プランを追加」で出てくるカメラでその2次元バーコードをスキャンします。
f:id:riocampos:20210407200613j:plain:w300f:id:riocampos:20210407200610j:plain:w300
すると、しばらく通信した後に「モバイル通信プランを追加」の画面になります。
f:id:riocampos:20210407201308p:plain:w300
「モバイル通信プランの名称」「デフォルト回線」「iMessageとFaceTime」などはデフォルトのままで良いでしょう。
f:id:riocampos:20210407201625j:plain:w300f:id:riocampos:20210407201614p:plain:w300f:id:riocampos:20210407201619j:plain:w300
「モバイルデータ通信」は、もちろん副回線(IIJmio)に変更します。
f:id:riocampos:20210407201959p:plain:w300
これで完了です。プロファイルなどは自動で設定されるらしいです。
コントロールセンターを出すと「副回線 IIJ」「主回線 au」と表示されていますので、もうIIJmioにもつながっていることが分かります*2
f:id:riocampos:20210407202447p:plain:w300
なお「副回線 IIJ」の右横の「LTE」はIIJmioの接続状態です。4GじゃなくLTE表示なんですよね…。
また、今までのキャプチャ画面でも気付いた方も居るでしょうが、アンテナのピクト表示が「!!!!」みたいな感じになっています。どうやらこれが副回線と主回線とそれぞれの電波強度を二段表示しているようなのです。私はこのビックリマークのようなピクト表示を見て「設定に問題があるんだよ!」と怒られているのかと思いましたよw

テザリング設定

(ってか、ここを一番書きたかったんだけど、前置きが長い…w)
やはりテザリングは必要ですよねー、家のネットワークがトラブったときとかの検証や、無線LANのない出先でMacを使いたいときとか、いろいろと。
ってことでテザリング。こちらは若干の設定が必要でした。
まずは設定ページへ。
iPhoneの「設定アプリ>モバイル通信>副回線>モバイルデータ通信ネットワーク」を選択します。
f:id:riocampos:20210408003946j:plain:w300f:id:riocampos:20210408004051j:plain:w300f:id:riocampos:20210408004121j:plain:w300
このページの最上段の「モバイルデータ通信」及び最下段の項目「インターネット共有」にそれぞれ、APN欄に「iijmio.jp」、ユーザ名に「mio@iij」、パスワードには「iij」を入力します(パスワード部分は「・・・」表示になるのでスクリーンキャプチャできてないですね)。
f:id:riocampos:20210408004355j:plain:w300
すると「設定アプリ>モバイル通信」の最上段に、先程は消えていた「インターネット共有」つまりテザリングの項目が現れます。これでテザリングできるようになりました!
f:id:riocampos:20210408004620j:plain:w300

テザリング参考サイト

IIJmio公式のヘルプサイトはこちら。
eSIMはテザリングに対応していますか?

モバイルデータ通信APN・テザリングAPNの両方に下記を設定ください。

APN iijmio.jp
ユーザー名 mio@iij
パスワード iij

なお、次の記事(2019年時点)では「インターネット共有」の箇所への設定だけでもテザリングが可能になるとのことですが、この設定ではうまくいかないようです。「モバイルデータ通信」の箇所にも設定してください。
キャリア+格安SIMの合わせ技が簡単に! IIJmioの「eSIM」で通信費を節約しよう(2/2 ページ) - ITmedia Mobile

補足:IIJmioの設定確認アプリ

利用した通信量など確認できます。iPhoneアイコンでの名称は「みおぽん」になってます。入れておいたほうが便利だと思います。
スクリーンキャプチャ向けに「プレゼンテーションモード」の設定があるのが面白いです。
f:id:riocampos:20210408012136p:plain:w300f:id:riocampos:20210408012132p:plain:w300
IIJmioクーポンスイッチ https://apps.apple.com/jp/app/iijmio%E3%82%AF%E3%83%BC%E3%83%9D%E3%83%B3%E3%82%B9%E3%82%A4%E3%83%83%E3%83%81/id629809928

補足2:eSIMを使ってデュアルSIMにすると5Gが使えなくなります

「設定アプリ>モバイル通信>主回線>音声通話とデータ」画面を確認すると、5Gオン・5Gオート共にグレーアウトして選択出来なくなっています*3。その下に「デュアルSIMモードのときは、5Gは使用できません。」との表記もあります。
f:id:riocampos:20210408014700p:plain:w300
IIJmioのギガプランサイトの「5G無料」のイラストの下にも小さく「eSIMは非対応です」とあります。
f:id:riocampos:20210408015414p:plain:w300
いつの日かeSIMなどデュアルSIMでも5Gが使えるようになるのかどうなのか、よく知りません。繰り返しますがご注意下さい。

追記:昨年10月の記事にも、デュアルSIMだと5Gが使えないと書いてありました。
iPhone 12 Proに楽天モバイルのeSIMを設定してみた 5G SIMの併用には注意点も(2/2 ページ) - ITmedia Mobile

追記2:Apple公式にも書いてました。

  • 5G およびデュアル SIM について

iPhone 12、iPhone 12 mini、iPhone 12 Pro、iPhone 12 Pro Max で両方の回線を実際に使っている場合、5G は利用できません。これらのモデルの iPhone で 5G を使うには、いずれかの電話回線をオフにし、デュアル SIM モードを無効にしてください。
eSIM でデュアル SIM を活用する - Apple サポート



2021/8/22未明追記:
かけめぐるさま、コメントありがとうございます。

iPhone 12 は iOS 14.5 へのアップデートで eSIM でも 5G 回線が使えるようになりました。

5G およびデュアル SIM について
iPhone 12、iPhone 12 mini、iPhone 12 Pro、iPhone 12 Pro Max でデュアルモードで 5G を使いたい場合は、必ず iOS 14.5 以降をインストールしてください。
eSIM でデュアル SIM を活用する - Apple サポート (日本)

が、残念ながら IIJmio のギガプラン側が eSIM の 5G に対応していないのです。つまり回線側問題。

IIJmio ギガプランが開始された時(今年5/25)に質問したところ


との返答でした。

また上にも書いてましたが、IIJmio のギガプランサイトの「5G無料」のところでは現状もまだ「※タイプD:SMS機能とeSIMは非対応です。」とあります。

繰り返しになりますが、IIJmio 側の回線設定が eSIM 5G に対応しない限りは使えない、との結論になります。なお、まだまだ 5G エリアは狭いので私自身の行動エリアではほぼ圏外です。なので 5G 対応されても使う予定は当分無いです。遠い未来には改善されるかもしれませんね。

*1:妻実家の屋内では、auだとほぼ圏外だがdocomoだとOK

*2:auは頭にスペースでも入ってるのか、IIJの表示とズレちゃうのが美しくない…

*3:iOS 14.5以降ではグレーアウトではなくなり「5Gオート」になりました