ニュースのスクレイピングでタイピング練習

ひらがな数文字を打ち返すだけのタイピング練習は案の定すぐに飽きてしまったので、何か別のネタを考えなくてはならなくなりました。飽きないためには膨大かまたは頻繁に更新される元データがあればいい、青空文庫かな、でも小学生に向いているものがどれほどあるかしらん、頻繁に更新されるといえばニュース、でもこれまた小学生向きではなさそう……と思ったら実にぴったりのものがありました。NHK NENS WEB EASY です。ひとつの記事で50字ほどの文が10ほど。意味もわかりやすくて量もちょうどいい。かなり手間をかけて作られているようです。

さて、これをなんとか持ってきてタイピング練習の材料にしようと思ったのですが、何しろ本業でも何でもないので情報を集めるところからスタートでした。今回やっていることは実は「スクレイピング」という程のこともないのですが、そのとっかかりということで、せっかくなのでここに記録しておきます。

環境の準備

NHK NENS WEB EASY のページの肝心な部分は JavaScript で生成されているようで、Python で単純に requests.get(url) とやっても、ブラウザで見ている HTML ソースとは別のものしか得ることができません。そこでまず、ブラウザが実際に表示するページを取得できるようにします。

Debian パッケージ chromium-driver をインストールします。

sudo apt-get install chromium-driver

これを Python から使うためにライブラリ Selenium をインストールします。

pip install selenium

取得した HTML から必要な箇所を切り出すのには BeautifulSoup4 を使います[1]

pip install beautifulsoup4

でインストールします。

ニュースサイトの構造

ニュースサイトによくあることですが、各記事の URL は数字の羅列のような名前で、先頭ページではそれが日々更新されます。

ブラウザのデベロッパーツールで NHK NENS WEB EASY の先頭ページ https://www3.nhk.or.jp/news/easy/ の構造を見てみます。

<div class="top-news-list__pickup news-list-item" id="js-news-pickup">
  ...
  <h1 class="news-list-item__title is-pickup">
  <a href="./k10011463631000/k10011463631000.html"><em class="title"><ruby>日本<rt>にっぽん</rt></ruby>の<ruby>二酸化炭素<rt>にさんかたんそ</rt></ruby>の<ruby>濃度<rt>のうど</rt></ruby>が<ruby>今<rt>いま</rt></ruby>まででいちばん<ruby>高<rt>たか</rt></ruby>くなる</em><time class="time">6月5日 11時30分</time></a>
  </h1>
</div>

最初に大きく取り上げられている記事は <div id="js-news-pickup"> で、その中の <h1> の中の <a> から記事個別ページの URL が得られます。

その記事個別ページを同様にブラウザのデベロッパーツールで見てみると、記事本文は <div id="#js-article-body"> にあることがわかります。これを切り出してくればいい訳です。

スクレイピング

参考にした(というか、ほぼそのままコピーさせてもらった)コードは「Python Webスクレイピング テクニック集」の「JavaScriptによる描画に対応する」です。

ソース中のコメント「ブラウザを起動する」の箇所で、ブラウザのパスを指定する必要がありました。Debian のパッケージを使っている場合

driver = webdriver.Chrome(executable_path='/usr/bin/chromedriver', chrome_options=options)

です。

切り出し

1回めの

        # ブラウザでアクセスする
        siteurl = "https://www3.nhk.or.jp/news/easy/"
        driver.get(siteurl)

        ...

        # BeautifulSoupで扱えるようにパースします
        soup = BeautifulSoup(html, "html.parser")

        # id で特定の要素を切り出す
        href = soup.select_one("#js-news-pickup h1 a").get('href')

で記事個別ニュースの URL が得られるので、2回めは

        driver.get(newsurl)
        html = driver.page_source.encode('utf-8')
        soup = BeautifulSoup(html, "html.parser")
        # ルビを削除
        for s in soup(['rt']):
            s.decompose()

        ...

        # ニュースの本文
        text = soup.select_one("#js-article-body").text

で、記事本文を切り出します。

ルビを削除

ニュースの本文は

...<p><span class="colorC"><ruby>気象庁<rt>きしょうちょう</rt></ruby></span>は「もっと<a href="javascript:void(0)" class="dicWin" id="id-0000"><ruby><span class="under">二酸化炭素</span><rt>にさんかたんそ</rt></ruby></a>を<ruby>出<rt>だ</rt></ruby>さないようにしなければなりません」と<ruby>話<rt>はな</rt></ruby>しています。</p>...

のようになっています。BeautifulSoup の .text で単純にタグを削除するだけでは

気象庁きしょうちょうは「もっと二酸化炭素にさんかたんそを出ださないようにしなければなりません」と話はなしています。

と、ルビが本文に混じってしまい、まともな文になりません。事前に rt 要素を削除しておく必要があります。

この作業をやってみて、rt がほかとは異質なタグ(要素)であることを実感しました。これについてはまた別の記事に書こうと思います。

1文ずつに分解

記事を「。」で区切り、リストにします。「。」自身も含めたいので split が使えません。NHK のニュース記事で全体の最後に「。」がないことはまさかないだろうと仮定して、

        lines = re.findall(".*?。", text)

とします。あとは Errnot がこれを1文ずつ表示するようにするだけです。これでこの bot を相手に XMPP のチャットでオウム返しにタイピングの練習をすることができるようになりました。

それにしても、いろいろ寄せ集めるだけでこれだけできるのですから、便利な世の中になったものだとつくづく思いました。

  1. Python にはじめからある html.parser でもある程度できます。また Selenium にも同様の機能があるようです。

XMPP チャットと bot でタイピング練習

スウちゃん(仮名、4年生)がパソコンでやりたいことのひとつが「字を打てるようになる」こと。

学校でローマ字も習ったし、パソコンには以前からちょっとは触っているので、人差し指打法でたどたどしくは打てます。せっかくだから「正しい」タイピング技法(タッチタイピング)を習得させてやりたいと思っています。父である私はそれをやらずに我流のまま来てしまいました。ある程度までは速く打てるものの指が定まっていないため間違いが多く、打ち直しのため結局は遅いことになってしまっています。きれいにすらすらと打てる人は本当に間違いも少ないので、スウちゃんには最初からその方向でいってほしいと思っているのです。

子どもでもできそうな練習アプリやサイトを見て回りました。Windows 向けはまず却下。ウェブ版のほとんどは Flash ですが、いまどきこれで大丈夫なのでしょうか。Debian の Firefox に Flash を入れて動かすのが面倒なので、これもできるだけ避けたいところです。

いずれにしても、練習の順番に疑問を感じます。たいていのものはまずホームポジション (f)(j) からです。しかしこの2字とも日本語での出現頻度はかなり小さいはずです。「jjj fff」など日本語でもないから、つまらないことこの上なしです。もっと「日本語のローマ字入力のためのタイピング練習」というのがないものでしょうか。

ちょっと検索してみて、こんな意見を見つけました。「これぞ最速!ブラインドタッチ(タッチタイピング)の効率的な練習法」です。

  1. 指を基本となるホームポジションに置く
  2. パソコンのキーボードを見ながらローマ字のAIUEOを入力する
  3. キーボードを見ずに、パソコン画面を見ながらAIUEOを何度も入力する
  4. 子音を入れつつ練習する
  5. ……

そうだよなあ。この順番のほうがうんと納得がいきます。練習ソフトでこの順番になっているものは意外と見つかりません。

というわけで、既存の練習ソフトに頼らないことにしました。

前回、チャット(XMPP)のクライアント使えるようにしたので、チャットの相手側に私がいて「あお」「いおえ」……などと打ってやり、それをオウム返しに打つことにしました。日本語にない単語になってしまいますが、この際しばらく我慢してもらうことにして。

ついでに、日本語でのキーの出現頻度を検索して見つけた「ローマ字頻度表」によると、母音、n の後は t, k, s, … らしいので、この順に1字づつ(ひらがなでいえば5音づつ)増やしていくことにしましょう。

さて、ずっと付き合うのは私もたいへんなので、Errbot が相手をするようにプラグインを書いてみました。超いい加減ですがメインはこんな感じ。

    @re_botcmd(pattern=r"^(.*)$", prefixed=False, flags=re.IGNORECASE)
    def question(self, msg, match):
        """文字列を表示する。それを入力してみてください。"""
        if not self.active:
            return None

        s = [[] for i in range(self.levelmax+1)]
        s[0] = 'あいうえおん' * 15
        s[1] = s[0] + 'かきくけこたちつてと' * 10
        s[2] = s[1] + 'さしすせそやゆよ' * 10
        s[3] = s[2] + 'なにぬねのはひふへほ' * 8
        s[4] = s[3] + 'らりるれろ' * 8
        s[5] = s[4] + 'まみむめもわを' * 6
        s[6] = s[5] + '、。ー' * 5
        s[7] = s[6] + 'がぎぐげござじずぜぞ' * 5
        s[8] = s[7] + 'だでどばびぶべぼ'  * 3
        s[9] = s[8] + 'ぢづ'
        s[10]= s[9] + 'ぱぴぷぺぽ' * 2

        reward1 = ['😀','😃','😉','🙂','❤','💗','💞','🎀','👌','✌','👏']
        reward2 = [' OK! ',' いいね! ',' ばっちり! ',' うまい! ',' じょうず! ']

        if match.group(0) == self.get('word'):
            yield(random.choice(reward1) + random.choice(reward2) + random.choice(reward1))

        self['word'] = ''.join(random.choices(s[self.level], k=self.length))
        yield(self['word'])

UI は XMPP クライアントだし応答は Errbot にお任せなので、書くのは実質ほんのわずかです。文字どおり子供だましで、そう遠からず飽きてしまいそうですが。

Rhythmbox を DLNA クライアントとして使う

何を今さらというような内容かもしれませんが、ちょっと苦労したのでメモしておきます。

やりたいことは以前書いた「リモートに置いてある音楽ファイルをローカルPCのスピーカーで鳴らす」と同じです。あれから時間が経ってまたまた環境がずいぶん変わってしまいました。現在は宅内のリモートマシンで DLNAサーバー(Debian の minidlna パッケージ)を動かしており、それから手元の PC で何らかの手段で音楽を聴きたいのです。

手元の PC (OS は Debian testing です)にはインターネットラジオを聴くのに Rhythmbox がインストールされていて(Debian パッケージで言うと rhythmbox と rhythmbox-plugins)、これで DLNA を使えればいい訳で、検索してみると Ubuntu 情報ですが、パッケージ grilo-plugins-0.3 と libgrilo-0.3-0 を追加インストールすれば左欄に DLNA サーバーが現れるかその一番下の + でサーバーが見つかるようになる……はずなのですが、ぜんぜん反応しません。

なかなか手がかりがなく困り果てていたのですが、ふと

(rhythmbox:16045): Grilo-WARNING **: [dleyna] grl-dleyna-servers-manager.c:138: Unable to fetch the list of available servers: GDBus.Error:org.freedesktop.DBus.Error.ServiceUnknown: The name com.intel.dleyna-server was not provided by any .service files

の警告(エラーではないのであまり気にしていなかった)の中のキーワードから、dleyna-server という Debian のパッケージを追加したら、無事に左欄の「共有」のところに DLNA サーバーが現れました。

結局、必要なのは

  • rhythmbox
  • rhythmbox-plugins

  • grilo-plugins-0.3

のほかに

  • dleyna-server

でした(依存関係でそのほかいくつか)。

しかし、dleyna-server に言及している情報にほとんど行き当たらなかったのはどうしてだろう……。

dvipdfmx の map の置き場所

TeXLive 2016 から 2017 での変化なのか、Debian 固有の問題なのかわからないが、メモ。

LaTeX から PDF を作るのに dvipdfmx を使っている。いまどきは、はじめから pdfLaTeX とか LuaLaTeX を使うのだろうが、もう10年ほどもこの方法で、スクリプトにしてやっているからそのままだ。

つい最近、Debian Testing(Buster) でパッケージを更新したら TeXLive が 2016 から 2017 になったようで、dvipdfmx でフォントを埋め込む際に参照する map ファイルが見つけられず PDF を作れなくなってしまった。map ファイルは /etc/texmf/dvipdfmx/ 以下にある。

調べてみると、/usr/share/texlive/texmf-dist/web2c/texmf.cnf で、以前も今も

TEXMFSYSCONFIG = /etc/texmf

は変わらないが、

TEXMF = {$TEXMFCONFIG,$TEXMFVAR,$TEXMFHOME,$TEXMFSYSCONFIG,!!$TEXMFSYSVAR,!!$TEXMFLOCAL,!!$TEXMFDEBIAN,!!$TEXMFDIST}

TEXMF = {$TEXMFAUXTREES$TEXMFCONFIG,$TEXMFVAR,$TEXMFHOME,!!$TEXMFLOCAL,!!$TEXMFSYSCONFIG,!!$TEXMFSYSVAR,!!$TEXMFDEBIAN,!!$TEXMFDIST}

になり、

TEXMFDBS = {!!$TEXMFSYSVAR,!!$TEXMFLOCAL,!!$TEXMFDEBIAN,!!$TEXMFDIST}

TEXMFDBS = {!!$TEXMFLOCAL,!!$TEXMFSYSCONFIG,!!$TEXMFSYSVAR,!!$TEXMFDEBIAN,!!$TEXMFDIST}

に変更されていた。要するに $TEXMFSYSCONFIG に !! が付き、それが $TEXMFDBS に含められている。

対処としては texmf.cnf の設定を以前と同じようにすればいいのかもしれないが、/etc/texmf/ls-R を(mktexlsrで)作成すれば、すんなり元のように PDF を作れるようになった。どちらのほうがいいのだろう。

環境変数 CHROMIUM_FLAGS と Chromium のオプション –enable-remote-extensions

自分用メモ。

ほかはどうか知らないが少なくともこれを書いている時点の Debian (Stretch (testing))で、Chromium の拡張機能が無効になってしまった。検索してみると、

バグ報告の最後あたりのコメントにあるように、
export CHROMIUM_FLAGS="$CHROMIUM_FLAGS --enable-remote-extensions"

~/.profile に書き加える。あるいは /usr/share/doc/chromium/NEWS.Debian.gz を参照のこと。

chromium-browser (55.0.2883.75-4) unstable; urgency=medium

  * External extensions are now disabled by default.  Chromium will only load
    extensions that are explicitly specified with the --load-extension command
    line option passed into CHROMIUM_FLAGS.  See the chromium-lwn4chrome
    package for an example of how to do this.
  * You can also use the --enable-remote-extensions command line argument to
    chromium, which will bypass this restriction.

これで拡張機能を有効にできる……はずなのだが、起動時に ~/.profile が読み込まれていない。長いことこれに気がついていなかった。既に不要となった環境変数設定くらいしか書いてなかったからな。

少なくとも Debian の Lightdm を使っている場合、~/.xsessionrc を作ってその中に

. $HOME/.profile

と書いておけば、これが読み込まれる。

プログラミング入門の入門

小学2年生のスウちゃん(仮名)は最近、“プログラミング”づいている。

「ロボットごっこ」

1年生の終わりころ、スウちゃんが「ロボットごっこ」をしかけてきた。たとえば「スウちゃん、ちょっと新聞とってきて」と頼んでも、「動かないよ。ロボットに命令してみて。」ってな感じで、新聞一つとってきてもらうのに「前に5歩、左向け。ドアを開けろ。前に8歩……」と延々と指令しなければならない。ゲラゲラ笑ったあと、ロボットと命令者を交代してまたゲラゲラ。

Scratch

これを楽しむということは“プログラミング”に興味を持つかも、と思い、まずはタブレットで楽しめる Scratch Jr. を紹介したら大喜び。が、操作性が今ひとつ(なかなかブロックが思うように移動・接続できない)なのと、単純すぎるのか、ほどなく飽きてしまった。

そこで PC で Scratch を教えてみる。ちょうどその頃(2016年3月末) NHK で「Why!? プログラミング」という番組の放送があり、ますます興味が湧いてきて、かなり真剣に取り組んでいる。

ところが今度は Scratch の自由度の高さが仇となった。キャラクター(コスチューム)を自由に描き変えることができるのだが、そこでお絵かきに夢中になり肝心のプログラムのほうはそっちのけ。まあそれはそれでいいのだけれど。

さらには、なんだか複雑なゲームを構想してしまい、とてもじゃないがすぐに結果が出ないので熱気が冷めてしまった。その前に習得しなければならないものがとてもたくさんある。

ハードウェア IchigoJam

一方で、機会があって「IchigoJam 体験」に参加。スウちゃんは初めてのハンダ付けに挑んだ。案外うまくできるものだ。CPUと数点のパーツだが自分で組み立てて、それでテレビに文字が出るというのは感動するようだ。手で触れることのできる形あるものというも実に大事なことなのだなとあらためて思う。

しかし、いろいろとハードルが高い。コンポジット出力なのでPC用モニターにつなげず、下手をするとテレビにもその端子がなかったりする。家のは大丈夫だったが、いざ始めようとするとテレビの真ん前に本体とキーボードをいちいちセットしなければならないのがちょっと面倒だ。それにそのキーボードの端子も PS/2 だ。これは家にもいくつかあることはあったがどれも US 配列で、日本語JIS配列が前提の IchigoJam BASIC だと、多用するダブルクオーテーションや丸括弧がキートップの印字と異なっていて、スウちゃんはひどく苦労している。しかも黒い画面に表示できるのは白い文字のみ。おっさんホイホイであることは間違いないが、現代の子どもにとって快適な環境とは言えなさそうだ。これを楽しめるようになるには、もうしばらく別のところでの修行が必要だ。

Code.org

Scratch は自由度が高くて的が絞れず、BASIC は何かと障壁が高い上に味気なく、どうしたものかと思っていたところに Code.org にたどり着いた。

  • (+) ステージが細かく設定してある。ゴールが設定されているため気が散らない。
  • (+) ゲーム感覚でクリアしていくことで、飽きることなく続けることができる。
  • (-) 日本語がおかしなところが多い。子ども向けだと翻訳も変えなければならないのだと思わされた。

ゲームっぽいところは良し悪しで、ただそのステージをクリアすることのみが目的となってしまうのがちょっとよくないところ。

スウちゃんは「コース2」から始め、現在はそれを終了しようとしている。「コース3」は日本語訳がされておらず英語のままだ。課題のところはちょっとした文章になっているから自分で理解するのは当分のあいだは無理で隣から教えてやるしかなさそうだが、せめてブロックの単語は英語で覚えてもらうことにしようかなと思っているところ。

ともかく小学2年生である現在のスウちゃんには、これがいちばん受け容れられた。

「ルビィのぼうけん」

そうこうしているあいだに、絵本「ルビィのぼうけん」がちょうど出版された(2016年5月)のでさっそく購入。スウちゃんは主人公が自分と似ているなあととても親近感を覚えて、かなり気に入ったようだ。ワークも、もともと手を動かすのが好きなので特に着せ替えなどは大いに楽しんでいる。

ただ、前に Scratch や Code.org などのビジュアルプログラミング言語を体験してしまっているためか、頭の中だけとか紙と鉛筆だけだけだとなんというか、まどろっこしいような感じらしい。もっと早い時期か、あるいは逆に高学年か中学生くらいになって概念だけを抽象化して捉えられるようになってからのほうが楽しめるのかもしれない。

まとめ

親としてもしっかり事前に構えていたわけではなかったので、いきあたりばったり的に「そういえばこれはどうだろう」と思いついたものに触れさせてみたという感じで、ここまでスウちゃんが実際に体験した順に書いてきた。

いまになって振り返ってみて、ここまでに挙げたものを「小学生が“プログラミング”入門するのに適した順番」に並べ替えてみると、

  1. 「ルビィのぼうけん」
  2. Code.org
  3. Scratch
  4. ハードウェア (IchigoJam や Arduino?)

になるだろうか。最後の項目に前後してテキスト型プログラミング言語(Python だろうか)が入るかなあ。

「子どものプログラミング」というのは流行のようで、習い事としても人気になりつつあるらしい。ちょっと調べてみただけでもいろいろな教材があって、正直言って驚いた。それでもまだ未成熟という感じもして、もう数年経てばきっと多くの事例がフィードバックされて、より洗練された言語、教材、教授法が出てくるのだろうと思った。

「プログラミング学ぶ」ではなく「プログラミング学ぶ」

さて結局のところ、小学校低学年というこの時期だと“プログラミング”といっても、言語はどれかとか具体的なコーディングとかではなく、プログラミングに通じる思考法みたいなもの、つまり

  • 論理的に考える
  • 手順をこまかく分割
  • 類型化してまとめる
  • 条件分岐を考える

というようなことを習得する、ということに尽きる。そしてそれは日頃の遊びやお手伝い(たとえば工作、お料理の手伝い……)にすぐに活かされるものだ。

そう考えると将来プログラムを組めるようになる、とかとはまったく無関係に単に「日常生活にとって大事なことを学ぶ」という、何と言うことはない普段の学校や家庭での学びと何も変わらないのだ。

だから、小学生低学年程度の子どもが“プログラミング”に接するというのは、「プログラミング学ぶ」ではなく「プログラミング学ぶ」ということ、“プログラミング”そのものが目的ではなく、ひとつの手段・道具に過ぎないのだと思う。

Debian 8 の Mailman 2.1.18 に更新したらトラブルが起きた

メーリングリスト(ML)を運用しているサーバー の OS を Debian 7 (Wheezy) から Debian 8 (Jessie) に更新しました。これにより、ML 管理ソフト Mailman のバージョンが 2.1.15 から 2.1.18 になりました。

すると ML の配信に失敗する(ML に宛ててメールを出してもどこにも配信されない。エラーも返ってこない)状態になってしまいました。OS のバージョンアップだったので、関係するソフトウェアの多くが更新されており(たとえば Bind9, Postfix, Python,…)、しばらくどこが原因かわからず途方に暮れていましたが、ようやくエラーログを見つけ出し、それを検索して調べることで解決できました。

結局のところ、この人と同じで、MLの説明などを日本語(EUC-JP)で記述していた箇所を Web の管理画面から書き直すことで、何のことはなく問題は解消しました。

こういうのはだいたい解決してしまった後に見つけるものですが、/usr/share/doc/mailman/NEWS.Debian.gz に、

mailman (1:2.1.16-1exp1) experimental; urgency=low

  This version has changed the encoding of most strings, templates
  and pages to UTF-8 to meet the Debian release goal of full UTF-8
  support in all packages. It also no longer automatically converts
  mails to ISO-8859-1.

  If you have been using any nōn-ASCII strings in places such as
  the mailing list description, these were be stored wrongly in the
  list configuration file (config.pck), so you will need to change
  those (e.g. via the webinterface) again in order to have them be
  displayed correctly.

と書いてありました。

さてメールは配信されるようになったものの、ヘッダーに DKIM-Signature: が残っていて、設定ファイル /etc/mailman/mm_cfg.py に記述していた REMOVE_DKIM_HEADERS が効いていないようです。以前はちゃんと機能していました。

これまた検索してみると、オプション from_is_list と組み合わせないと効かないようで、それと無関係に効かせようとしてもバグがあるようです。上流の新しい版では修正されているので、Mailman/Handlers/CleanseDKIM.py を差し替えました。

その上で、設定ファイルで REMOVE_DKIM_HEADERS = 2 としました。