読者です 読者をやめる 読者になる 読者になる

PowerShell でスクレイピングしてみる

唐突だけど、ちょっと PowerShell で Web サイトをスクレイピングしてみる。

PowerShell で HTTP リクエストを送信するには Invoke-WebRequest コマンドレットを使う。

PS D:\Users\dacci> Invoke-WebRequest 'http://example.org/'


StatusCode        : 200
StatusDescription : OK
Content           : <!doctype html>
                    <html>
                    <head>
                        <title>Example Domain</title>

                        <meta charset="utf-8" />
                        <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
                        <meta name="viewport" conten...
RawContent        : HTTP/1.1 200 OK
                    Vary: Accept-Encoding
                    X-Cache: HIT
                    Accept-Ranges: bytes
                    Content-Length: 1270
                    Cache-Control: max-age=604800
                    Content-Type: text/html
                    Date: Fri, 26 May 2017 14:51:17 GMT
                    Expires: ...
Forms             : {}
Headers           : {[Vary, Accept-Encoding], [X-Cache, HIT], [Accept-Ranges, bytes], [Content-Length, 1270]...}
Images            : {}
InputFields       : {}
Links             : {@{innerHTML=More information...; innerText=More information...; outerHTML=<A href="http://www.iana
                    .org/domains/example">More information...</A>; outerText=More information...; tagName=A; href=http:
                    //www.iana.org/domains/example}}
ParsedHtml        : System.__ComObject
RawContentLength  : 1270

結果は何かのオブジェクトで返ってくるらしい。どんなオブジェクトなのか調べてみる。

PS D:\Users\dacci> Invoke-WebRequest 'http://example.org/' | Get-Member


   TypeName: Microsoft.PowerShell.Commands.HtmlWebResponseObject

Name              MemberType Definition
----              ---------- ----------
Dispose           Method     void Dispose(), void IDisposable.Dispose()
Equals            Method     bool Equals(System.Object obj)
GetHashCode       Method     int GetHashCode()
GetType           Method     type GetType()
ToString          Method     string ToString()
AllElements       Property   Microsoft.PowerShell.Commands.WebCmdletElementCollection AllElements {get;}
BaseResponse      Property   System.Net.WebResponse BaseResponse {get;set;}
Content           Property   string Content {get;}
Forms             Property   Microsoft.PowerShell.Commands.FormObjectCollection Forms {get;}
Headers           Property   System.Collections.Generic.Dictionary[string,string] Headers {get;}
Images            Property   Microsoft.PowerShell.Commands.WebCmdletElementCollection Images {get;}
InputFields       Property   Microsoft.PowerShell.Commands.WebCmdletElementCollection InputFields {get;}
Links             Property   Microsoft.PowerShell.Commands.WebCmdletElementCollection Links {get;}
ParsedHtml        Property   mshtml.IHTMLDocument2 ParsedHtml {get;}
RawContent        Property   string RawContent {get;set;}
RawContentLength  Property   long RawContentLength {get;}
RawContentStream  Property   System.IO.MemoryStream RawContentStream {get;}
Scripts           Property   Microsoft.PowerShell.Commands.WebCmdletElementCollection Scripts {get;}
StatusCode        Property   int StatusCode {get;}
StatusDescription Property   string StatusDescription {get;}

HtmlWebResponseObject というクラスらしい。仕様は MSDN ライブラリーに書いてある。どうやら ParsedHtmlIHTMLDocument2 らしいので、ここから DOM ツリーにアクセスできそうだ。やってみよう。

PS D:\Users\dacci> $Response = Invoke-WebRequest 'http://example.org/'
PS D:\Users\dacci> $Response.ParsedHtml.getElementsByTagName('p') | Select-Object innerText

innerText
---------
This domain is established to be used for illustrative examples in documents. You may use this domain in examples wi...
More information...

ちょっと分かりにくいか?こうしてみよう。

PS D:\Users\dacci> $Response = Invoke-WebRequest 'https://www.google.co.jp/search?q=BKB&tbm=isch'
PS D:\Users\dacci> $Response.ParsedHtml.getElementsByTagName('img') | Select-Object src

src
---
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTADCtmeQE39WiR8PW9lEQIjb0dhBGlyBVWIrvSsSmG76E6TJAOROLgb48
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSNrhqxpmfhS7q2Kfnks44XWBMIFvFMwCa1E37zqjWTfaT1-16hHRTqDNzG
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSVdZ1JsRC__JfXyQGjLlAbVZHJMkfJwStbiWMj17ya0MEFLS3fHk3HIl4
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRly7RQUl2_O-RpqF6WpOa-G2gsFkdtARasNCx6izoD1oIlIHgtWAMdrCo
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQXtostzTjbjlRCpfMaOIYyU9wm2kHBPHaEPZc2v30s9jGIHmv8bnQ_8w8U
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcR7Ea1x3z5BWf0hy0CtX10KCTNZTUwWXf7wygj12PvLmzH4Zk_Ix_C5Qsw
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRpDTbrxtseDJd8Yvr9qfIzhTg8xPg1SdCp2AGIw42CAUWrWAlDyu0Llx0x
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTBk3FCVXf3QF3fj8W9ZzYoF8JFuvkS9ECbmqsEYP9FQ39xL4L3lNXak2QT
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcR910YJ_nt9ClK4vZvemyRJWKsuS5AwUCOymnJmEQRaXb51GAfzOpdGw2xW
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTf7O1MFtTszRljwOXdv6FCgcit_wzvsGzQ4QCdfoIS41TXxAvB9Kxe7o6f
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSfp0Sinu_qg-DEZ5buQ3Rrsf5RiBHG82JnZLE-pNGgjk7NzMTzOxVXLw
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcT39kupqy7s37liS_PIWBGzZOfiLP3a8TnOpVwMYckz7pm80S7NPwoUgXb3
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRFr4ui3muADgW0nTge0L8nmQoE1lV-zgvOimKndW95mVcusXSnM73ZMyAB
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcS1NoxwZLTFdh94brkMcdoQy-xrQBVPwEE9jAbqRcL6_voBU4wy98HORis
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTpKn-WIbpDVS9OKB8I8LieNXypojFXrNhg4hQCuPszzceJ6vrC_p9OCw
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRFNG93iXtF6Vns3YNWF_RWeTekpc1K512jeVF3NOHwCPXQzGrqlQMptg
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRHthT8TIbqet-5ai5Kd3BONFWtcPYX181rH7W9_6WkMVvKfIbpSAFjlTM
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQEObWAyf2Vg0fIj9woFNNBS8YeTuKm0K8UiZoZd0Lrsc6aTyYLLL4IZ7M
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTyO7kalfP-76Ls3bZdVfK9n28hcVEeEUjA3Fqi0huuaO2j9jMofBWmVnk
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTzCczGfshsuKVNRe3PxSM7brxaWh-w0rkmBTzUddBoGyv4llzUbb5364FS

もっとも、ページ内の画像を取り出したいなら Images プロパティを使ったほうが早い。

PS D:\Users\dacci> $Response.Images | Select-Object src

src
---
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTADCtmeQE39WiR8PW9lEQIjb0dhBGlyBVWIrvSsSmG76E6TJAOROLgb48
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSNrhqxpmfhS7q2Kfnks44XWBMIFvFMwCa1E37zqjWTfaT1-16hHRTqDNzG
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSVdZ1JsRC__JfXyQGjLlAbVZHJMkfJwStbiWMj17ya0MEFLS3fHk3HIl4
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRly7RQUl2_O-RpqF6WpOa-G2gsFkdtARasNCx6izoD1oIlIHgtWAMdrCo
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQXtostzTjbjlRCpfMaOIYyU9wm2kHBPHaEPZc2v30s9jGIHmv8bnQ_8w8U
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcR7Ea1x3z5BWf0hy0CtX10KCTNZTUwWXf7wygj12PvLmzH4Zk_Ix_C5Qsw
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRpDTbrxtseDJd8Yvr9qfIzhTg8xPg1SdCp2AGIw42CAUWrWAlDyu0Llx0x
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTBk3FCVXf3QF3fj8W9ZzYoF8JFuvkS9ECbmqsEYP9FQ39xL4L3lNXak2QT
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcR910YJ_nt9ClK4vZvemyRJWKsuS5AwUCOymnJmEQRaXb51GAfzOpdGw2xW
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTf7O1MFtTszRljwOXdv6FCgcit_wzvsGzQ4QCdfoIS41TXxAvB9Kxe7o6f
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSfp0Sinu_qg-DEZ5buQ3Rrsf5RiBHG82JnZLE-pNGgjk7NzMTzOxVXLw
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcT39kupqy7s37liS_PIWBGzZOfiLP3a8TnOpVwMYckz7pm80S7NPwoUgXb3
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRFr4ui3muADgW0nTge0L8nmQoE1lV-zgvOimKndW95mVcusXSnM73ZMyAB
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcS1NoxwZLTFdh94brkMcdoQy-xrQBVPwEE9jAbqRcL6_voBU4wy98HORis
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTpKn-WIbpDVS9OKB8I8LieNXypojFXrNhg4hQCuPszzceJ6vrC_p9OCw
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRFNG93iXtF6Vns3YNWF_RWeTekpc1K512jeVF3NOHwCPXQzGrqlQMptg
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRHthT8TIbqet-5ai5Kd3BONFWtcPYX181rH7W9_6WkMVvKfIbpSAFjlTM
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQEObWAyf2Vg0fIj9woFNNBS8YeTuKm0K8UiZoZd0Lrsc6aTyYLLL4IZ7M
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTyO7kalfP-76Ls3bZdVfK9n28hcVEeEUjA3Fqi0huuaO2j9jMofBWmVnk
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTzCczGfshsuKVNRe3PxSM7brxaWh-w0rkmBTzUddBoGyv4llzUbb5364FS

ちなみに、Invoke-RestMethod というコマンドレットもある。こっちは API 呼び出しに向いていて、レスポンスが JSON だったら PSObjectXML だったら XmlDocument というふうに空気を読んで返すオブジェクトを変えてくる。

Juno Proxy Server 0.11

Juno Proxy Server のバージョン 0.11 をリリースしました。変更点は以下のとおりです。

  • Windows サービスとしてインストール出来るようになりました
  • ICU を 58.2 に更新しました
  • 設定の保存形式を変更しました
    • 起動時に古い形式からアップグレードされます
  • その他、バグフィックスやチューニングを行いました

ダウンロードはこちらから

Juno Proxy Server 0.10

ほぼ 1 年ぶりになってしまいましたが、Juno Proxy Server のバージョン 0.10 をリリースしました。変更点は以下のとおりです。

  • 開発環境を Visual Studio 2015 に移行しました
  • ICU を 57.1 に更新しました
  • 通信関連の処理を整理しました
  • その他のバグを修正しました

ダウンロードはこちらから

横浜市民がマイナンバー カードを受け取ったお話

横浜市民であるところのブログ主がマイナンバー カードの交付を受けたので、顛末をちょっとまとめてみる。ここで書いたのはあくまでも横浜市での話で、他の自治体ではどういうことになっているのかは全く分かりません。

通知カード到着~交付申請

2015年11月、通知カードと個人番号カード交付申請書が書留で届く、けどしばらく放っとく。

2015年12月、リサーチの結果、近所のスーパーに申請機能付き証明写真機があることがわかったので、これで申請する。ちなみに申請に使ったのは、DNP の Ki-Re-i というやつ。 そういえば、この機械で申請するときに電子証明書の発行をオプトアウト出来なかった気がする。

交付通知~受け取り予約

2016年5月、交付通知書が届く。『現在、横浜市では12月中にご申請いただいた方宛てに「交付通知書」を順次、発送しています。』ということだそうな。

通知書によると横浜市での受け取りは予約制にしているようなので、早速予約する。予約サイトにアクセスし、通知書に書いてある予約 ID と生年月日を入力して日時を指定する。

受け取りには本人確認資料 2 点 (うち 1 点は官公署が発行 & 顔写真付き) がいるらしい。免許持ってないし、パスポートも切れてるんだよなぁ。ってことでコールセンターに問い合わせると、どうやら年金手帳とか預金通帳で良いらしい。

受け取り

予約当日、必要な物を持って区役所の臨時交付窓口に行く。別件の用事が早く済んで予約の時間より 1 時間近く早く着いた。「まずは受け付けを」って書いてあったので素直に従う。どうも来た順にやってくれるらしい。

受け付けで本人確認資料のコピーを取られ、番号札と「個人番号カード・電子証明書 暗証番号記入用紙(記載票)」というのを渡される。この記載票というのは、個人番号を使ったサービス提供を受ける際に必要な暗証番号やパスワードを書くようになっている。ボールペンの付いたクリップボードにはさまれて渡されるからついつい記入しちゃったけど、手続きでは一切参照されることはなかった。要するに、パスワードを忘れないようにするためのメモ用紙を渡されていたのだ。まぁ誰も彼もがこういうシステムを扱えるわけではないからしょうがないんだけど、セキュリティーに気を使える人は油断しないようにしましょう。ちなみに、暗証番号は数字 4 桁、パスワードはアルファベット大文字と数字両方使った 6 ~ 16 文字。

しばらく待つと「用意ができた」ということで呼び出され、パーティションで囲まれた受け渡し窓口に通される。窓口にはタッチパネルがつながった PC と、それを操作する係員。説明をうけてタッチパネルから暗証番号とパスワードを設定する。カードへの書き込みが完了したら、電子証明書の有効期限や延長手続きの仕方などの説明を受け、最後に書類にサインをして終了。このとき認印を出そうとしたら「いらないですよ」とか言われたんだが、それで良いんだろうか・・・

個人番号カードには、個人情報部分が隠れるようになった専用ケースが付いている。だけどこれがただのビニールの袋なもんだから、持ち歩くには心もとないんだなぁ。対応のカードケースとか作ったらある程度の需要があるんじゃないだろうか?

最後に

平日の夕方だったから待ち時間込で 20 分ぐらいで終わったけど、土曜日とかだと結構混雑しそうだなぁという感じ。自分みたいに保険証しか身分証がないとかでなければ、あんまりいらないんじゃないかなぁ。ただ受け取り期限が設定されていたから (8月)、申請しちゃったのならそれまでに受け取ったほうが良さそう。期限を過ぎるとどうなるのかまでは調べてない。

CentOS 7 で Let's Encrypt

  • 2016-07-23T10:23 自動更新について追記、パッケージ名やコマンド名を更新しました。

CentOS 7 で動かしてるサーバーがあって、サーバー証明書には StartSSL を使ってたんだけど、それを Let's Encrypt に乗り換えてみた。

インストール

公式には git-clone するやり方が書いてあるけど、CentOSでは EPEL のパッケージが使えるので、こっちを使う。EPEL の導入は省略。

# yum -y install certbot

証明書の発行

パッケージなので certbot-auto ではなくて、certbot を使う。それ以外は同じ。プラグインは webroot と standalone が使える。すでにサービスが動いているので webroot を使うことにする。

# certbot certonly --webroot -w /var/www/html -d example.org -d www.example.org

証明書のインストール

certbot certonly コマンドを実行すると以下のファイルができるので、ソフトウェアに合わせて設定すればいい。

パス
/etc/letsencrypt/live/(ドメイン)
cert.pem
証明書
privkey.pem
証明書の秘密鍵
chain.pem
中間証明書
fullchain.pem
中間証明書とルート証明書
Apache

プラグインがないので手動でインストールしないといけない。

SSLCertificateFile /etc/letsencrypt/live/example.org/cert.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.org/privkey.pem
SSLCertificateChainFile /etc/letsencrypt/live/example.org/chain.pem

再読み込み。

# systemctl reload httpd.service
OpenLDAP

こいつでちょっとハマった。

# ls -ld /etc/letsencrypt/{archive,live}
drwx------. 2 root root 48 Apr 30 22:28 /etc/letsencrypt/archive
drwx------. 2 root root 48 Apr 30 22:28 /etc/letsencrypt/live
# ps aux | grep slapd
ldap      7519  0.0  1.4 817420 14552 ?        Ssl  Apr30   0:00 /usr/sbin/slapd -u ldap -h ldapi:/// ldap:/// ldaps:///
root      7755  0.0  0.0 112644   960 pts/2    R+   01:07   0:00 grep --color=auto slapd

slapd は ldap ユーザーで動いているけど、証明書のあるディレクトリは root でしかアクセス出来ない (live の中身は archive へのリンクになっている)。所有権を直接変えることもできるけど、他のサービスとぶつかると嫌なので ACL を使うことにする。

# setfacl -m u:ldap:rx /etc/letsencrypt/{archive,live}
# ls -ld /etc/letsencrypt/{archive,live}
drwxr-x---+ 4 root root 48 Apr 30 22:28 /etc/letsencrypt/archive
drwxr-x---+ 4 root root 48 Apr 30 22:28 /etc/letsencrypt/live
# getfacl /etc/letsencrypt/{archive,live}
getfacl: Removing leading '/' from absolute path names
# file: etc/letsencrypt/archive
# owner: root
# group: root
user::rwx
user:ldap:r-x
group::---
mask::r-x
other::---

# file: etc/letsencrypt/live
# owner: root
# group: root
user::rwx
user:ldap:r-x
group::---
mask::r-x
other::---

LDIF ファイルを書いて OLC で設定変更する。

dn: cn=config
changeType: modify
replace: olcTLSCertificateFile
olcTLSCertificateFile: /etc/letsencrypt/live/example.org/cert.pem
-
replace: olcTLSCertificateKeyFile
olcTLSCertificateKeyFile: /etc/letsencrypt/live/example.org/privkey.pem
-
add: olcTLSCACertificateFile
olcTLSCACertificateFile: /etc/letsencrypt/live/example.org/chain.pem
# ldapmodify -H ldapi:/// -Y EXTERNAL -f change_tls.ldif

だけじゃだめで、サービスを再起動する。

# systemctl restart slapd.service

証明書の更新

証明書を更新するには、certbot renew を実行して、サービスの再読み込みや再起動する。

# certbot renew
(記録しそこねたけど、メッセージが表示される)
# systemctl reload httpd.service
# systemctl restart slapd.service

CentOS 7 なので、自動更新には cron ではなくて systemd を使う。

  • /etc/systemd/system/certbot.service
[Unit]
Description=Let's Encrypt certificate renewal

[Service]
Type=oneshot
ExecStart=/bin/certbot renew
ExecStartPost=/bin/systemctl reload httpd.service
ExecStartPost=/bin/systemctl restart slapd.service

certbot renew で更新されるのは有効期限まで 30 日未満のものだけなので、毎週実行されるようにする。

  • /etc/systemd/system/certbot.timer
[Unit]
Description=Let's Encrypt weekly certificate renewal

[Timer]
OnCalendar=weekly
Persistent=true

[Install]
WantedBy=timers.target
# systemctl enable certbot.timer
# systemctl --type=timer --all
UNIT                         LOAD   ACTIVE   SUB     DESCRIPTION
certbot.timer                loaded inactive dead    Let's Encrypt monthly certificate renewal
systemd-readahead-done.timer loaded inactive dead    Stop Read-Ahead Data Collection 10s After Completed Startup
systemd-tmpfiles-clean.timer loaded active   waiting Daily Cleanup of Temporary Directories

LOAD   = Reflects whether the unit definition was properly loaded.
ACTIVE = The high-level unit activation state, i.e. generalization of SUB.
SUB    = The low-level unit activation state, values depend on unit type.

3 loaded units listed.
To show all installed unit files use 'systemctl list-unit-files'.

Juno Proxy Server 0.9

Juno Proxy Server のバージョン 0.9 をリリースしました。変更点は以下のとおりです。

  • ICU を 55.1 に更新しました
  • 初期化処理や UI 周りの実装を整理しました
  • 通信関連の処理を整理しました
  • その他のバグを修正しました

ダウンロードはこちらから

Juno Proxy Server 0.8

Juno Proxy Server のバージョン 0.8 をリリースしました。変更点は以下のとおりです。

SSL/TLS による接続の待ち受けに対応

Schannel を使った暗号通信に対応したサーバーを追加しました。世の中的には脆弱性で騒がれてますが、SSL 3.0 ~ TLS 1.2 に対応しています。使用するには Windows の証明書ストアに、秘密鍵を持った証明書を登録する必要があります。

TCPUDP の相互変換

Scissors を使い、TCPUDP を相互変換できるようになりました。UDP サーバーと TCP モードの Scissors (またはその逆) を組み合わせると機能します。データグラムを受信すると、その長さを先頭に付加して TCP セッションに流します。逆に TCP セッションでは、先頭部分をデータ長と解釈してデータグラムとして送信します。この動作は
stone と同じもので、相互運用することもできます。

その他

このバージョンから開発環境が Visual C++ 2013 になりました。必要に応じて再頒布可能パッケージもインストールしてください。