らじる★らじる聴き逃しサービスをダウンロードするための手始め:JSONファイルのありか(その2)

おことわり

ここに記載した情報は、私的利用に限定した使用に限ります。著作権侵害は禁じます。

JSON ファイルのありかが変わりました

2017年に以下の記事を書きました。

その後(昨年(2023年)8月末?)にどうやら JSON ファイルのありかが変わったようなので更新記事を書く事にします。今更かよ、とツッコミありそうですね。そろそろ年度末なので対策しておかねばということですよw

まず 聴き逃しサービス のトップページでの JSON は、以前の

から

へと変更されました。いま(2/27 14時過ぎ)の JSON の内容を Ruby に読ませて JSON.parse するとこんな状況。

{"corners"=>
  [{"id"=>3190,
    "title"=>"Asian View",
    "radio_broadcast"=>"R2",
    "corner_name"=>"",
    "onair_date"=>"2024年2月27日(火)放送",
    "thumbnail_url"=>"https://www.nhk.or.jp/prog/img/7880/g7880.jpg",
    "series_site_id"=>"7880",
    "corner_site_id"=>"01"},
   {"id"=>1316,
    "title"=>"歌謡スクランブル",
    "radio_broadcast"=>"FM",
    "corner_name"=>"",
    "onair_date"=>"2024年2月27日(火)放送",
    "thumbnail_url"=>"https://www.nhk.or.jp/prog/img/444/g444.jpg",
    "series_site_id"=>"0444",
    "corner_site_id"=>"01"},
   {"id"=>116,
    "title"=>"ごごカフェ",
    "radio_broadcast"=>"R1",
    "corner_name"=>"火曜放送分を聴く",
    "onair_date"=>"2024年2月27日(火)放送",
    "thumbnail_url"=>"https://www.nhk.or.jp/prog/img/3854/g3854.jpg",
    "series_site_id"=>"3854",
    "corner_site_id"=>"02"},
   {"id"=>97,
    "title"=>"ひるのいこい",
    "radio_broadcast"=>"R1,FM",
    "corner_name"=>"",
    "onair_date"=>"2024年2月27日(火)放送",
    "thumbnail_url"=>"https://www.nhk.or.jp/prog/img/456/g456.jpg",
    "series_site_id"=>"0456",
    "corner_site_id"=>"01"},
   {"id"=>31,
    "title"=>"マイあさ!",
    "radio_broadcast"=>"R1",
    "corner_name"=>"ワールドリポート",
    "onair_date"=>"2024年2月27日(火)放送",
    "thumbnail_url"=>"https://www.nhk.or.jp/radioondemand/json/5642/img/corner/box_31_thumbnail.jpg",
    "series_site_id"=>"5642",
    "corner_site_id"=>"20"},
   {"id"=>32,
    "title"=>"マイあさ!",
    "radio_broadcast"=>"R1",
    "corner_name"=>"健康ライフ",
    "onair_date"=>"2024年2月27日(火)放送",
    "thumbnail_url"=>"https://www.nhk.or.jp/radioondemand/json/5642/img/corner/box_32_thumbnail.jpg",
    "series_site_id"=>"5642",
    "corner_site_id"=>"13"},
      :
   {"id"=>79,
    "title"=>"らじる文庫 by らじるラボ",
    "radio_broadcast"=>"R1",
    "corner_name"=>"徳冨蘆花「草とり」",
    "onair_date"=>"2020年8月25日(火)放送",
    "thumbnail_url"=>"https://www.nhk.or.jp/radioondemand/json/F295/img/corner/box_79_thumbnail.jpg",
    "series_site_id"=>"F295",
    "corner_site_id"=>"18"}]}

以前だと各番組の JSON ファイルへのリンクが書かれていたのですが、今回は含まれていません。残念。なので手動で各番組のページへ飛んでみましょう。

ラジオ英会話の JSON

どの番組でも良いのですが、今回は ラジオ英会話 にします。 URL は https://www.nhk.or.jp/radio/ondemand/detail.html?p=0916_01 です。URL の p 以下の ID 「0916_01」がポイントですね。ちなみに先ほどのトップページ JSON のラジオ英会話の部分は

   {"id"=>450,
    "title"=>"ラジオ英会話",
    "radio_broadcast"=>"R2,FM",
    "corner_name"=>"",
    "onair_date"=>"2024年2月27日(火)放送",
    "thumbnail_url"=>"https://www.nhk.or.jp/radioondemand/json/0916/img/0916_0403.jpg",
    "series_site_id"=>"0916",
    "corner_site_id"=>"01"},

となっています。series_site_id と corner_site_id とをアンダースコアで繋げれば ID そのままです。

で。JSON はどこにあるかというと、先ほどの series_site_id と corner_site_id を用いた

にあります。ちなみに以前だと JSON

にありました。新しい JSON の URL のほうがしっかりデザインされてますよね。

JSON の中身を見てみましょう。繰り返し部分は一部省略しました。

{"id"=>42,
 "title"=>"ラジオ英会話",
 "radio_broadcast"=>"R2,FM",
 "schedule"=>"放送:月~金 午前6:45~7:00(NHK-FMは午前7:30~7:45) 再放送(R2で放送):月~金 午後0:25~0:40、午後9:45
~10:00/日 午後4:30~5:45(5回分)",
 "corner_name"=>"",
 "thumbnail_url"=>"https://www.nhk.or.jp/radioondemand/json/0916/img/0916_0403.jpg",
 "series_description"=>
  "中学校までの基本的な英語の学習を終えた高校生や改めて英語を学んで話せるようになりたいと考えている方々に、英語を話すた
めのポイントをわかりやすくお伝えします。\r\n※NHK-FMでも放送します。\r\n\r\n\r\n",
 "series_url"=>"https://www2.nhk.or.jp/gogaku/english/",
 "share_text_title"=>"ラジオ英会話",
 "share_text_url"=>"https://www.nhk.or.jp/radioondemand/share/42_450.html",
 "share_text_description"=>"#radiru",
 "episodes"=>
  [{"id"=>3927357,
    "program_title"=>"ラジオ英会話~ハートでつかめ!英語の極意~(213)",
    "onair_date"=>"2月21日(水)午前6:45放送",
    "closed_at"=>"2024年2月28日(水)午前7:00配信終了",
    "stream_url"=>
     "https://vod-stream.nhk.jp/radioondemand/r/916/s/stream_916_91702667a70e43178e93d3cca4b995a9/index.m3u8",
    "aa_contents_id"=>
     "[radio]vod;ラジオ英会話~ハートでつかめ!英語の極意~(213);r2,130;2024022167905;2024-02-21T06:45:00+09:00_2024-02-21T07:00:00+09:00",
    "program_sub_title"=>"【講師】東洋学園大学教授…大西泰斗,【出演】デイビット・エバンス,秋乃ろーざ"},
    :
   {"id"=>3929320,
    "program_title"=>"ラジオ英会話~ハートでつかめ!英語の極意~(217)",
    "onair_date"=>"2月27日(火)午前6:45放送",
    "closed_at"=>"2024年3月5日(火)午前7:00配信終了",
    "stream_url"=>
     "https://vod-stream.nhk.jp/radioondemand/r/916/s/stream_916_add6258c4f76356280d7ba887ba4960b/index.m3u8",
    "aa_contents_id"=>
     "[radio]vod;ラジオ英会話~ハートでつかめ!英語の極意~(217);r2,130;2024022769370;2024-02-27T06:45:00+09:00_2024-02-27T07:00:00+09:00",
    "program_sub_title"=>"【講師】東洋学園大学教授…大西泰斗,【出演】デイビット・エバンス,秋乃ろーざ"}],
 "same_tag_series"=>
  [{"id"=>497,
    "title"=>"Asian View",
    "radio_broadcast"=>"R2",
    "corner_name"=>"",
    "onair_date"=>"2024年2月27日(火)放送",
    "thumbnail_url"=>"https://www.nhk.or.jp/prog/img/7880/g7880.jpg",
    "link_url"=>"",
    "series_site_id"=>"7880",
    "corner_site_id"=>"01"},
   {"id"=>115,
    "title"=>"ステップアップ中国語",
    "radio_broadcast"=>"R2",
    "corner_name"=>"",
    "onair_date"=>"2024年2月27日(火)放送",
    "thumbnail_url"=>"https://www.nhk.or.jp/radioondemand/json/6581/img/step_china.jpg",
    "link_url"=>"",
    "series_site_id"=>"6581",
    "corner_site_id"=>"01"},
   {"id"=>410,
    "title"=>"ニュースで学ぶ「現代英語」",
    "radio_broadcast"=>"R2",
    "corner_name"=>"",
    "onair_date"=>"2024年2月27日(火)放送",
    "thumbnail_url"=>"https://www.nhk.or.jp/radioondemand/json/7512/img/7512.jpg",
    "link_url"=>"",
    "series_site_id"=>"7512",
    "corner_site_id"=>"01"},
   {"id"=>43,
    "title"=>"エンジョイ・シンプル・イングリッシュ",
    "radio_broadcast"=>"R2",
    "corner_name"=>"",
    "onair_date"=>"2024年2月27日(火)放送",
    "thumbnail_url"=>"https://www.nhk.or.jp/radioondemand/json/3064/img/3064.jpg",
    "link_url"=>"",
    "series_site_id"=>"3064",
    "corner_site_id"=>"01"}]}

m3u8 ファイルへのリンク先要素は stream_url

ってことで m3u8 ファイルへのリンク先は stream_url 要素に書いてある

(中略)

ということになります。

いちおう以前の JSON の中身も比較のため見てみます。m3u8 ファイルへのリンク先を示す要素は、以前だと file_name 要素でしたね。要素の名称の付け方が改善されていて良いと思います^ ^

{"main"=>
  {"site_id"=>"0916",
   "program_name"=>"ラジオ英会話",
   "mode"=>0,
   "media_type"=>"radio",
   "media_code"=>"06,07",
   "media_name"=>"NHKラジオ第2、NHK-FM",
   "site_detail"=>
    "中学校までの基本的な英語の学習を終えた高校生や改めて英語を学んで話せるようになりたいと考えている方々に、英語を話す
ためのポイントをわかりやすくお伝えします。\r\n※NHK-FMでも放送します。\r\n\r\n\r\n",
   "thumbnail_p"=>"https://www.nhk.or.jp/radioondemand/json/0916/img/0916_0403.jpg",
   "thumbnail_c"=>nil,
   "schedule"=>"放送:月~金 午前6:45~7:00(NHK-FMは午前7:30~7:45) 再放送(R2で放送):月~金 午後0:25~0:40、午後9:45~10:00/日 午後4:30~5:45(5回分)",
   "official_url"=>"https://www2.nhk.or.jp/gogaku/english/",
   "share_url"=>"https://www.nhk.or.jp/radioondemand/share/42_450.html",
   "corner_id"=>"01",
   "corner_name"=>nil,
   "corner_detail"=>nil,
   "program_index"=>false,
   "cast"=>nil,
   "dev"=>"2024-02-27T14:26:15.794+09:00",
   "detail_list"=>
    [{"headline_id"=>"3927357",
      "headline"=>nil,
      "headline_sub"=>nil,
      "headline_image"=>nil,
      "file_list"=>
       [{"seq"=>1,
         "file_id"=>"3927357",
         "file_title"=>"ラジオ英会話~ハートでつかめ!英語の極意~(213)",
         "file_title_sub"=>"【講師】東洋学園大学教授…大西泰斗,【出演】デイビット・エバンス,秋乃ろーざ",
         "file_name"=>
          "https://vod-stream.nhk.jp/radioondemand/r/916/s/stream_916_91702667a70e43178e93d3cca4b995a9/index.m3u8",
         "open_time"=>"2024-02-21T07:00:00+09:00",
         "close_time"=>"2024-02-28T07:00:00+09:00",
         "onair_date"=>"2月21日(水)午前6:45放送",
         "share_url"=>"https://www.nhk.or.jp/radioondemand/share/42_450.html?p=0916_01_3927357",
         "aa_contents_id"=>
          "[radio]vod;ラジオ英会話~ハートでつかめ!英語の極意~(213);r2,130;2024022167905;2024-02-21T06:45:00+09:00_2024-02-21T07:00:00+09:00",
         "aa_measurement_id"=>"vod",
         "aa_vinfo1"=>"ラジオ英会話~ハートでつかめ!英語の極意~(213)",
         "aa_vinfo2"=>"r2,130",
         "aa_vinfo3"=>"2024022167905",
         "aa_vinfo4"=>"2024-02-21T06:45:00+09:00_2024-02-21T07:00:00+09:00"}]},
      :
     {"headline_id"=>"3929320",
      "headline"=>nil,
      "headline_sub"=>nil,
      "headline_image"=>nil,
      "file_list"=>
       [{"seq"=>1,
         "file_id"=>"3929320",
         "file_title"=>"ラジオ英会話~ハートでつかめ!英語の極意~(217)",
         "file_title_sub"=>"【講師】東洋学園大学教授…大西泰斗,【出演】デイビット・エバンス,秋乃ろーざ",
         "file_name"=>
          "https://vod-stream.nhk.jp/radioondemand/r/916/s/stream_916_add6258c4f76356280d7ba887ba4960b/index.m3u8",
         "open_time"=>"2024-02-27T07:00:00+09:00",
         "close_time"=>"2024-03-05T07:00:00+09:00",
         "onair_date"=>"2月27日(火)午前6:45放送",
         "share_url"=>"https://www.nhk.or.jp/radioondemand/share/42_450.html?p=0916_01_3929320",
         "aa_contents_id"=>
          "[radio]vod;ラジオ英会話~ハートでつかめ!英語の極意~(217);r2,130;2024022769370;2024-02-27T06:45:00+09:00_2024-02-27T07:00:00+09:00",
         "aa_measurement_id"=>"vod",
         "aa_vinfo1"=>"ラジオ英会話~ハートでつかめ!英語の極意~(217)",
         "aa_vinfo2"=>"r2,130",
         "aa_vinfo3"=>"2024022769370",
         "aa_vinfo4"=>"2024-02-27T06:45:00+09:00_2024-02-27T07:00:00+09:00"}]}]}}

比較

新しい JSON で m3u8 ファイルのありかを取得するには

  • episodes 要素 > 配列 > stream_url 要素

の順番で探せば良くなり、以前の

  • main 要素 > detail_list 要素 > 配列 > file_list 要素 > 配列 > file_name 要素

よりも格段に浅くなりました。名称も分かりやすくなりました。

また、各回の情報もコンパクトにまとまりましたね。以前だと不要に見える要素がたくさん入っていました。ただ、新しい方には番組サイト末尾に「おすすめ番組」を表示させるための(余分な)same_tag_series 要素が加わりました。

再度おことわり

ここに記載した情報は、私的利用に限定した使用に限ります。念押し。

Misskey.ioにノートするためのbookmarklet

ツイ廃の @riocampos ですw しかしとうとうツイ廃から離れる日が来てしまったようです。

Twitterでは日記にならなくなった

残念な事に TwitterAPI が有料化され、その影響で、私の Twitter ライフにとてもとても重要であった Twilog がサービス停止に追い込まれました。このため、 Twitter から Misskey.io への移行を行っております。 Mastodon でも良かったのですがなんとなく Misskey にしました。行き先は りおかんぽす (@riocampos) | Misskey.io です。Misskey や Mastodon など ActivityPub 対応の SNS からは @riocampos@misskey.io で検索してもらえれば見つかります。

そして日記目的には notestock を使います。ありがたやありがたや。

bookmarkletが欲しい

実は Misskey 投稿*1専用の bookmarklet が無くても、サイトのタイトルと URL を抜き出す別の bookmarklet を使っているので問題は無いのですが、なんとなく作っておくと便利かなと思って調べてみました。

参考:見ているページをFedibirdへシェアするJavascriptブックマークレット – momo+

Misskey じゃなく Mastodon の一種の Fedibird への bookmarklet ですが、これはおそらく Twitter 向けの bookmarklet をチョコっといじっただけですね。見覚えある Javascript です。

きっと Misskey もコレと同じく .../share?... とかでノートできるようになってるんじゃね?

調べたらその通りでしたw

Misskey Webの/shareを開くと、共有用の投稿フォームを開くことができます。この共有フォームを利用すると、外部のWebページから、ページの内容をユーザーにMisskeyで共有してもらいたいときに便利です。

共有フォーム | Misskey Hub

ってことで今まで使っていた Twitter 向け bookmarklet を見てみます。たしか Twitter サイトのどこかで配布してたと思うのですが、いま軽く検索しても見当たらなかったのでまあ問題ないでしょうw

javascript:(function(s,e){open("https://twitter.com/intent/tweet?original_referer=javascript:close&text="+e(document.title)+"&url="+e(location.href),"_blank","width=550,height=420,left="+(s.availLeft+s.availWidth/2-275)+",top="+(s.availTop+s.availHeight/2-210));})(screen,encodeURIComponent)

こいつをいじります。 https://twitter.com/intent/tweet? の部分を https://misskey.io/share? にしただけですw

そのまま改変版(タイトル\nURL):

javascript:(function(s,e){open("https://misskey.io/share?original_referer=javascript:close&text="+e(document.title)+"&url="+e(location.href),"_blank","width=550,height=420,left="+(s.availLeft+s.availWidth/2-275)+",top="+(s.availTop+s.availHeight/2-210));})(screen,encodeURIComponent)

ちゃんと動作します。なのでこれで良しw

と思ったのですが、これだとサイトのタイトルと URL との間が改行されてしまうことに気付きました。個人的には改行せず半角スペースで繋ぐように習慣づけているので、ちょっと改変します。

修正版(タイトル URL):

javascript:(function(s,e){open("https://misskey.io/share?original_referer=javascript:close&text="+e(document.title)+"%20"+e(location.href),"_blank","width=550,height=420,left="+(s.availLeft+s.availWidth/2-275)+",top="+(s.availTop+s.availHeight/2-210));})(screen,encodeURIComponent)

これでなお良し。

なおこの bookmarklet は Misskey.io 用になってますが、他の Misskey で使うときには https://misskey.io/ の部分をそれぞれの Misskey の URL に変更するだけで OK だと思います(未検証ですが大丈夫でしょう)。

おまけ:サイトのタイトルと URL を抜き出す別の bookmarklet

こんなヤツです。 iPhoneSafari ではこちらを使ってます。すっごく単純です。

javascript:window.prompt('',document.title +' '+ location.href);void(0);

もしくは ';+ を URL エンコードしたものでも良いでしょう。全く同じ動作をしますので可読性の面からは↑をオススメしたいです。

javascript:window.prompt(%27%27,document.title%20%2B%27%20%27%2B%20location.href)%3Bvoid(0)%3B

*1:Twitter での「ツイート」、Misskey では「ノート(note)」というそうな。

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暫定版) - 別館 子子子子子子(ねこのここねこ)はてブロ部 からのリンクをご利用ください。