d.sunnyone.org
sunnyone.org

ページ

2015-12-20

PowerShellでスクレイピングする (AngleSharp編)

これは、PowerShell Advent Calendar 2015の12/20分の記事です。Invoke-WebRequestとIEエンジンによるDOM処理とは違うやり方について書きます。

---
PowerShellを使うと、Invoke-WebRequestを使うことで、特にライブラリ等を準備せずともWebページをスクレイピングすることができる(参考:PowerShellでスクレイピング 後編 HTMLをパースする) 。

これはこれで便利なのだが、少し複雑なHTMLになると、Where-ObjectやSelect-Objectがだんだん増えてきてしまう。こんな感じである。



やっぱりDOMツリーをたどるなら使い慣れたCSSセレクタの記法が使いたいよね?ということで、今回はCSSセレクタが使える.NET用HTMLパーサライブラリ「AngleSharp」をPowerShellから使ってみるお話。本当はC#で馴染みのあったCsQueryを使おうと思ったのだけど「Not Actively Maintained」ということでAngleSharpにしてみた。

インストール

例によって適当なフォルダに移動してnuget.exeを使ってダウンロードする。
> nuget install AngleSharp

使い方

初期化

まず、AngleSharp.dllをロードし、loaderが設定されたconfigを使って、BrowsingContextを作っておく。

このBrowsingContextを使っていく。

GETリクエスト

シンプルにGETリクエストを出すには、文字列のURLを使ってOpenAsyncを呼び出す。そして返ってきたものに対して、最初に一致したものだけでよければQuerySelector()を、全て欲しい場合はQuerySelectorAll()を呼び出す。

例として、このブログの右側の「ブログ アーカイブ」の一番上のタイトル(画像参照)を取得するにはこんな感じである。



~Asyncは、流儀通りTask<T>が返ってくる。PowerShell世界では扱いに困るのでResultでひたすらブロックしていく(ひどい)

ほぼ同じであるが、もう一つの例として@mutaguchiさんのブログPowerShell Scripting Weblogの右側にあるMVPのタイトルを取得するにはこんな感じである。


各ブラウザの開発者ツール(F12)のコンソールで、document.querySelector('#ArchiveList')などとして、先に試しておくとよい。

POSTリクエスト

POSTにするには、OpenAsync()の第2引数にリクエストオブジェクトを生成する。POSTリクエストを作る便利メソッドはいくつかあり、例えばPostAsUrlEncodedを使うとこんな感じである。

$docができたら、あとはGETと同様に、QuerySelector()やQuerySelectorAll()を呼び出せばOKである。

感想

これで一応、AngleSharpをPowerShellから呼ぶことはできた。Where-Objectを連発するよりは見通しのよいものが書けると思う。ただし、AngleSharpの呼び出す部分は拡張メソッドだらけになっており、PowerShellから大変に呼びにくいので、うーんという感じ。やはりInvoke-WebRequestが、querySelector / querySelectorAllできれば万事OKだったのに、という感じであった。

2015-12-13

Loqui 0.6.4 リリース

今回のリリースは主に、昨晩の「Ubuntu 15.10 リリース記念オフラインミーティング 15.12」のあとの飲み会で、@ut_maito氏が初めて使っているところを見て、使いにくそうに見えた部分のうち、さらっと改善できそうな部分についての対応です。バグを見つけたので併せて直しました。

主な変更点はこんなかんじ。

* Shift+Enterで送信する機能の追加
→複数行モードのときキーボードで送信する手段がなかったので送信できるようにした。すでにCtrl+Enterは1行モード時にNOTICEとして送信にしていたので、Shift+EnterでPRIVMSGで送信。1行の側もShift+Enterで送信できるようにして、どちらもCtrl+EnterでNOTICE / Shift+EnterでPRIVMSGになるよう統一した。

* 初回起動時にアカウント設定ダイアログを表示
→最初に何していいかわからない感じが強く出ていたので、手っ取り早くできそうなところで、最初に使うことになるアカウント設定ダイアログを出すことにした。

* アカウント削除時にクラッシュするバグの修正
→アカウント設定の検証をしていたら、アカウント削除で落ちることがあるバグを見つけたので修正。

* アカウント設定からプロトコル選択の削除
* 不要な「ツール」メニューの削除
→拡張用に若干複雑なUIになっていたのだけど、多分追加しないので削除。プロトコル選択画面で選べるIP Messenger機能は若干実装されていたのだけど、使っている人はいないよね。


初回起動時はチャンネルバッファの部分が余っているので、ここにガイド的なことを書いてあげたいなぁと思ったのだけど、だいぶ拡張がいりそうなので見送り。

キー(Ctrl+↑/Ctrl+↓)でチャンネルを選択すると、チャンネルツリーの選択部分がわかりにくいという話もあったのだけど、直そうとするとややこしそうなので見送り。手元のCinnamonだと起きないので、テーマに依ってしまう部分もある。

こんなんだけど、ちょっとでもわかりやすくなってるといいな。

2015-12-06

HDDが故障した話 (QNAP TS-219PにDebian jessieをインストールする)

多分5年以上使っていたNAS, TS-219PについているHDDの2台のうち片方が壊れた。Software RAID1構成なので、交換してリビルドすればいいという話だったはずなのだが、長期戦になってしまった。

今回は時系列に沿って。日数はフィクションです。仕事の話じゃなくてよかった(仕事だったらもっと慎重だったろうから、こんなんにならないと思うけど)。

1日目: 拡張できるはずだったのに

もともと2TB x 2本の構成だった。もちろん2TBを買ってきて取り替えればよい。だが、容量を拡張できる機能があることがわかったので、2015年現在のGB単価的(→サハロフの秋葉原レポート)に4TBにしようと思って4TBを2本買ってきた。

もちろん、時代的に4TB扱えなくてもそういうものかという感じな気がするので、compatibility listを見て4TBに対応していることを確認して買った。

ところが、4TBを差しても認識しない。ファームウェアがだいぶ古いので、アップグレードしてみることにする。

2日目: ファームウェアアップグレード

ファームウェアアップデートすると、今度は生きている2TBのディスクまでWeb管理画面から見えなくなってしまった。sshログインを使って、cat /proc/filesystemsを見ると、ext4がいない。ファームウェアの更新がうまくいかなかったのかなと思って再度更新をかけようとすると、バージョンを確認しろという旨のエラーが出て受け付けてもらえない。

バージョンがとんでもなく変わっていたので、変化に耐えられなかったのであろう。もうだめだ。ブラックボックスな独自OSで戦うのはここで限界。汎用OSで使えるものを探そう。

3日目: Debianインストール チャレンジ

Debianが使えないかなと調べてみると、Martin Michlmayr さんという方がまとめてくれている。手順まで詳しく載っており、これは戦えそうである。
Debian on QNAP TS-21x/TS-22x

インストール手順はInstalling Debian on QNAP TS-21x/TS-22xにある通り、

まずフラッシュ領域をバックアップする(後にわかることだが、これは大変に重要であり、絶対に避けてはいけない。)

vfatもマウントできない状態なので、別のマシンでUSBメモリをext2でフォーマットしておいたものを挿入し、catでコピーする。
mount /dev/sdX1 /tmp/usb
cd /tmp/usb
cat /dev/mtdblock0 > mtd0
cat /dev/mtdblock1 > mtd1
cat /dev/mtdblock2 > mtd2
cat /dev/mtdblock3 > mtd3
cat /dev/mtdblock4 > mtd4
cat /dev/mtdblock5 > mtd5
cd
umount /tmp/usb

そうしたら、カーネルやインストーラの入ったinitrdをダウンロードし、flash-debianスクリプトを起動する。
cd /tmp
busybox wget http://ftp.debian.org/debian/dists/stable/main/installer-armel/current/images/kirkwood/network-console/qnap/ts-219/initrd.gz
busybox wget http://ftp.debian.org/debian/dists/stable/main/installer-armel/current/images/kirkwood/network-console/qnap/ts-219/kernel
busybox wget http://ftp.debian.org/debian/dists/stable/main/installer-armel/current/images/kirkwood/network-console/qnap/ts-219/flash-debian
busybox wget http://ftp.debian.org/debian/dists/stable/main/installer-armel/current/images/kirkwood/network-console/qnap/ts-219/model
sh flash-debian

リブートするよう促されるので、rebootした。
reboot

DHCPでアドレスが払い出されるので、そのアドレスに対してinstallerユーザでsshログインする。
$ ssh installer@192.168.1.XX

cursorsのUIが起動し、インストーラが開始する。あとは選べばよいだけ……のように見えた。

4日目: インストーラが途中で止まる

普通のDebianインストーラだと安心し進んでいくと、パッケージのインストールのプログレスバーが途中で止まってしまう。最初からやり直しても同じ。

何度かディスクの構成を変えながら試してみても変わらないので、もう一つinstallerユーザでsshし、shellを選び/var/logのログを見ていると「UUID ~ doesn't exist in /dev/disk/by-uuid」なるログが。

そのキーワードを基に検索してみると、こんなバグ報告を発見。
#791794 - INSTALL REPORT (Jessie on QNAP TS-420U)

どうやらmdでミラーを組んでしまうと、/dev/disk/by-uuidのIDが更新されず、flash-kernelという裏で動くmtdデバイスにカーネルイメージを書き込むツールが書き込みに失敗するらしい。このツールがエラー終了するのではなく、Ctrl+C待ちで止まってしまっているので、フロントのインストーラのプログレスが止まったままとなっていたようだ。

書いてある感じに、ディスクの構成が終わったタイミングでもうひとつssh接続し、shellに降りて
udevadm tigger
とすることで、進むようになった。

このバグで触れられていたために、mdで作られたRAIDミラーがdegradedだと、起動時にemergency shellに落ちるという恐怖のバグを発見。通常コンソールのないこのNASでは、sshできない状態に落とされるのは致命的である。

#784070 - mdadm Software RAID1 with GPT on Debian 8.0.0 amd64 - Does not mount/boot on disk removal

おすすめされる行為ではないが、sidには修正済みのパッケージがあったため、バージョンの変化やdependencyなどを見てsidから持ってきて入れた。実際に1本外して試したところ、きちんと立ち上がるようになっていた。

LVMを構成したり、NFSやSambaなどなどNASっぽいデーモンをインストールしたりする。Ansibleで作ったのでやりなおせる。

5日目: rsyncデータコピー待ち

USB接続した元々の2TB HDDからrsyncでデータをコピーする。たいへんに時間がかかるが、待つしかない。

6日目: 再起動したら……

データのコピーも終わり、デーモンの動作もOK, 念のため再起動を試しておく。

……立ち上がらない。コンソールがないので状況もわからない。

11/30: フラッシュ・リカバリ

Martinさんはリカバリの方法も書いてくれていたので、QNAP NASのリカバリの手順でインストーラを起動して中の様子を見てみることにする。

Recovery mode of QNAP TS-21x/TS-22x

TS-219Pのリカバリモードは、ブート時にtftpでリカバリイメージを取得し、フラッシュ領域を書き直す。

debian-installerを動作させるイメージを作るには、まずカーネルイメージにpaddingを加えて所定のサイズにする。
dd if=kernel of=kernel.pad ibs=2097152 conv=sync

あとはinitrd.gzと、もともとのmtdblockイメージを連結して作る。取っててよかったバックアップ!
cat mtdblock0 mtdblock4 mtdblock5 kernel.pad initrd.gz mtdblock3 > F_TS-219

このファイル名は、u-boot パラメータが格納されているmtdblock4に入っているので念のため確認しておく。
strings mtdblock4 | grep bootp_vendor_class

次に、適当なノートPCを使ってDHCPサーバ + TFTPサーバを立てる。Martinさんは普通にdhcpdで立てているようだが、dnsmasqはそのどちらの機能も備えており、さくっと立てることができた。

あとは、NASをdnsmasqを立てたノートPCと直結し、背面にあるLANコネクタの隣あたりにあるピンで押すリセットボタンを押しながら、電源ボタンを押して起動する。すると、ピー、ピーと短く鳴って起動する。

その時のコマンド・ログはこんな感じだった。
$ sudo dnsmasq --no-daemon --port=0 --interface=eth0 --domain=example.com \
  --dhcp-range=192.168.0.3,192.168.0.253,255.255.255.0,1h --dhcp-boot=F_TS-219 \
  --enable-tftp --tftp-root=`pwd`
dnsmasq: started, version 2.75 DNS disabled
dnsmasq: compile time options: IPv6 GNU-getopt DBus i18n IDN DHCP DHCPv6 no-Lua TFTP conntrack ipset auth DNSSEC loop-detect inotify
dnsmasq-dhcp: DHCP, IP range 192.168.0.3 -- 192.168.0.253, lease time 1h
dnsmasq-tftp: TFTP root is /tmp/ts219
dnsmasq-dhcp: DHCPDISCOVER(eth0) 00:08:9b:XX:XX:XX
dnsmasq-dhcp: DHCPOFFER(eth0) 192.168.0.216 00:08:9b:XX:XX:XX
dnsmasq-dhcp: DHCPREQUEST(eth0) 192.168.0.216 00:08:9b:XX:XX:XX
dnsmasq-dhcp: DHCPACK(eth0) 192.168.0.216 00:08:9b:XX:XX:XX
dnsmasq-tftp: sent /tmp/ts219/F_TS-219 to 192.168.0.216
--no-daemonで起動すると、わざわざtail -fしなくてもログが追え便利であった。

10分程待つと再びピー、ピーと音がなり、再起動される。すると、無事に同じようにinstaller@で接続できるようになった。

インストーラが起動したら、ディスクの設定画面まで進めたあと、別のssh接続を行い、shellに降りる。shellが起動したら、以下のようにchrootを実行して、ディスク上の環境を得る。

mount /dev/md0 /mnt/md0
mount --bind /dev /mnt/md0/dev
mount --bind /proc /mnt/md0/proc
mount --bind /sys /mnt/md0/sys
chroot . bin/bash

fstabがおかしいかなと新しく作ったエントリをコメントアウトしたりした。終わったら、元のカーネルに戻すべくフラッシュする。
flash-kernel

rebootすると、無事起動した。原因についてははっきりわかっていないが、おそらく、fstabうんぬんではなく、USB接続したディスクをmdデバイスとして見せた状態でinitrdを更新してしまったために、変な情報を覚えられてしまったのかなと思っている。ディスクが見えなくなるから起動しなくなるかもよみたいなことを書いてあった記憶。

7日目: USBシリアルからコンソール出せれば便利じゃね?

ブートオプション変更すればUSBシリアルにコンソール出せるんじゃね?このデバイスはU-Boot使ってるからfw_setenvでブートオプション変更できるんじゃね?と思ったらやっぱりあった。

U-Boot tools for Debian ARM Linux in QNAP Server | Black God

ダメでもどうせフラッシュすればいいやーと思って書き換えてみることにする。

まずは設定。
$ cat /proc/mtd
dev:    size   erasesize  name
mtd0: 00080000 00040000 "U-Boot"
mtd1: 00200000 00040000 "Kernel"
mtd2: 00900000 00040000 "RootFS1"
mtd3: 00300000 00040000 "RootFS2"
mtd4: 00040000 00040000 "U-Boot Config"
mtd5: 00140000 00040000 "NAS Config"
# vi /etc/fw_env.config
(書き込み)
# Configuration file for fw_(printenv/saveenv) utility for
# QNAP TS-119, TS-219 and TS-219P.

# MTD device name Device offset Env. size Flash sector size
/dev/mtd4 0x0000 0x1000 0x40000

確認。
# fw_printenv

ttyUSB0を使うように書き換え(コマンドはイメージです:注:このデバイスでのコンソールアクセスがない状態での書き換えはやってはいけません)
# sudo fw_setenv bootargs 'console=ttyS0,115200 root=/dev/ram initrd=0xa00000,0x900000 ramdisk=32768'

再びfw_printenvを実行してみると、書き換わっている。

いざ再起動……立ち上がらない。まぁいいや、リカバリならもう慣れた……リカバリ完了。立ち上がらない!?

8日目: カーネルビルドという選択肢

試しにPCでUSBシリアルを接続し、grubからconsole=ttyUSB0として起動しようとしてみると、起動しない。先にやっておくべきだった!

でもU-Bootのコンフィグが入っているのは先に見たようにmtd4。フラッシュイメージにはmtd4が入っている。直るはずだ!

とMartinさんのページをよく見てみると、こんな記述が。
During recovery mode, mtd0 (the boot loader), mtd4 (the boot loader configuration) and on some devices mtd5 (device configuration) are ignored
……なぜここを読み飛ばしてしまっていたのか。mtd4はリカバリでは戻らない。やってしまった……

U-Bootのコンソールを叩きにいけば直るはずなので、シリアルコンソールに接続してコマンドを叩けば直る。でも現状その接続はない。そこでもう一つの選択肢。設定されたbootargsで立ち上がるカーネルを作ればよい。

ということで、活躍していないsqueezeのarmelデバイスがあったので、これを使ってlinux-sourceをダウンロード。中に入っているはずのカーネルパッケージに含まれるconfigを見ると、どうもCONFIG_USB_SERIAL=mであるのが影響していそうだ。

ということで、=yになるようmake menuconfigでちょいちょいといじって、カーネルを作り直す。起動しない。linux-sourceからのカーネルビルドの手順にまったく沿っていないためか、そもそも元のconfigで作り直しても全然違うサイズになる。やり方が雑過ぎたようだ。

9日目: クロスビルド環境でのカーネルコンパイル(失敗)

make menuconfigを眺めていると、ブートオプションを強制するオプションがあった。.config的にはこんな感じである。
CONFIG_CMDLINE="console=ttyS0,115200 root=/dev/ram initrd=0xa00000,0x900000 ramdisk=32768"
CONFIG_CMDLINE_FORCE=y

なおビルドについては、armelデバイスが古すぎるためか、一晩寝てもコンパイルが完了しない。これでは時間がかかりすぎるので、クロスビルドするしかないか、と思って適当なページを見ながら、適当なUbuntu 15.10のマシンを使ってコンパイルするも、コンパイルエラー。まともに環境を作ろうとすると大変そうなのでこのビルド方式はあきらめ。
How do I cross-compile the kernel on a Ubuntu host? - Raspberry Pi Stack Exchange

$ sudo apt-get install git ncurses-dev make gcc-arm-linux-gnueabi
(カーネルとconfigを配置/展開)
make ARCH=arm CROSS_COMPILE=/usr/bin/arm-linux-gnueabi- oldconfig
make ARCH=arm CROSS_COMPILE=/usr/bin/arm-linux-gnueabi- menuconfig
make ARCH=arm CROSS_COMPILE=/usr/bin/arm-linux-gnueabi- zImage

10日目: そもそもarmelをエミュレーションした環境でビルドする(途中)

そういえばx86なマシンでもqemu使えばarmel環境をエミュレーションできるんじゃね?ということで環境を作る。
qemu-debootstrapを使ってユーザモードQEMUで動くDockerイメージを作ってみる - ももいろテクノロジー

$ sudo apt-get install debootstrap qemu-user-static
$ sudo qemu-debootstrap --verbose --arch=armel --variant=buildd \
  --include=vim-tiny,less jessie rootfs-debian-armel http://http.debian.net/debian/
$ sudo chroot rootfs-debian-armel

今度こそjessieになったのでHowToRebuildAnOfficialDebianKernelPackageの手順を使ってビルドしてみる。armel指定の部分はこんな感じ。

# fakeroot make -f debian/rules.gen setup_armel_none_kirkwood
# fakeroot make -f debian/rules.gen binary-arch_armel_none_kirkwood binary-indep \
DEBIAN_KERNEL_JOBS=4

普通に進んでいたので、このままほっとけばできたのかもしれないが、4つコアを使っている感じがなく時間がかかりすぎて作業時間オーバー。とりあえず断念。

11日目: そしてシリアルコンソールへ

カーネルビルド方式では、リカバリまで含めての試行錯誤の1ターンがかかりすぎるので、ついにシリアルコンソールに手を出すことにした。ここで失敗するとハードウェア破壊を意味するので、あまりやりたくなかったのだ。

やりかたはMartinさんがピン配置を書いてくれているので、この通り接続する。
Serial console for QNAP TS-21x/TS-22x

コネクタはPHR-4というもので千石電商の2Fにあったので買ってケーブルとピンヘッダに差すコネクタをハンダ付けしてケーブルを作った。

RS-232Cレベルではなく、3.3Vなので昔買ったFT232RLの変換アダプタにつなげばよさそうだなーと思ったが、Raspberry Piのシリアルピン使えるんじゃね?とRasPiの存在を思いだし、RasPiが登場。TS-219P - RasPiをTx - Rx, Rx - Tx, GND - GNDで接続した。

RasPi側ピン
(https://www.raspberrypi.org/documentation/usage/gpio/)より

TS-219P側ピン

黄: Tx
黒: Vcc (接続しない)
赤: Rx
青: GND


シリアルポートの場所はここ。ケースは固いので開けたくなかったが、開けないと手が届かない。

RasPiは、ブートオプションconsole=に当該ポートが指定されていて使えないので、/boot/cmdline.txtを編集してconsole=ttyAMAの記述をトル。
http://elinux.org/RPi_Serial_Connection
console=パラメータの変更はまさに悩んでいる部分なので、こうさらっと変更できると複雑な気分。

あとは起動して、
$ cu -s 11200 -l /dev/ttyAMA0
として待つ。あとはTS-219Pをブートすればコンソールが現れるのでauto-bootを止める。あとはsetenv, saveしてブート。
         __  __                      _ _
        |  \/  | __ _ _ ____   _____| | |
        | |\/| |/ _` | '__\ \ / / _ \ | |
        | |  | | (_| | |   \ V /  __/ | |
        |_|  |_|\__,_|_|    \_/ \___|_|_|
 _   _     ____              _
| | | |   | __ )  ___   ___ | |_ 
| | | |___|  _ \ / _ \ / _ \| __| 
| |_| |___| |_) | (_) | (_) | |_ 
 \___/    |____/ \___/ \___/ \__|  ** LOADER **
 ** MARVELL BOARD: DB-88F6281A-BP LE 

U-Boot 1.1.4 (Jan  5 2009 - 12:58:51) Marvell version: 3.4.4

U-Boot code: 00600000 -> 0067FFF0  BSS: -> 00690DCC

Soc: MV88F6281 Rev 3 (DDR2)
CPU running @ 1200Mhz L2 running @ 400Mhz
SysClock = 400Mhz , TClock = 200Mhz 
(略)
Hit any key to stop autoboot:  0 
QNAP: Recovery Button pressed: 0
Marvell>> printenv
(略)
Marvell>> setenv bootargs console=ttyS0,115200 root=/dev/ram initrd=0xa00000,0x900000 ramdisk=32768
Marvell>> saveenv
Saving Environment to Flash...
................................................................
.
Un-Protected 1 sectors
Erasing Flash...
.
Erased 1 sectors
Writing to Flash... done
................................................................
.
Protected 1 sectors
Marvell>> printenv
baudrate=115200
(略)
bootargs=console=ttyS0,115200 root=/dev/ram initrd=0xa00000,0x900000 ramdisk=32768

Environment size: 1339/4092 bytes
Marvell>> boot
Unknown command 'uart1' - try 'help'
## Booting image at 00800000 ...
   Image Name:   
   Created:      2015-12-02  15:12:54 UTC
   Image Type:   ARM Linux Kernel Image (uncompressed)
   Data Size:    1968432 Bytes =  1.9 MB
   Load Address: 00008000
   Entry Point:  00008000
   Verifying Checksum ... OK
OK

Starting kernel ...

Uncompressing Linux... done, booting the kernel.

無事起動。これでちゃんと起動するDebian環境が得られた。

おわりに

USBシリアルでコンソールを見ようというのは本当に余計だった。

自分の環境ではこんな目にあってしまったが、今まで見てきたARMデバイスの中ではDebian officialで済む範囲が多いデバイスなので、Debianを入れてみるというのはアリだと思う。カーネルイメージは公式のものを使えなかったり、ファンコントロールは外部だったりといったことが多いが、これはどちらもofficialに含まれている。

もちろんメーカーの力は得られなくなるので、いわゆる自己責任で。新しく買ってきてというよりは、昔の保証切れてそうな箱でやってみるという感じがよいかとは思う。