蛇ノ目の記

技術のことも。そうでないことも。

2017年を振り返る

年の瀬なので今年を振り返ってみる。

雑に言ってしまえば2017年はビープラウド以前・以後に分かれると思う。今回は主に以前に焦点を当てていきたい。

以後については先日の記事が詳しい。お陰さまであの記事を書いた12/27は約300のアクセスがありました。普段の15倍くらいの数です。すごい。読んでくださった方・拡散してくださった方、ありがとうございました。

前職の記憶

新卒で入社して今年の9月半ばまでの約1年と5ヶ月半、某大手SIer子会社の金融系部署で働いていた。何をしていたのかあまり記憶に残ってない。最後の方は災害時にDBを切り替えるためバッチ処理シェルスクリプトで書いていたことと、システムのリリース当日に24時間以上会社にいたことは覚えている。ちなみに転職を考え始めたのは2016年の秋頃から。早い。

Pythonコミュニティ・勉強会への参加

もくもく会に参加し始めた。connpassの参加イベントを遡ると2/26の第14回 Pythonもくもく会 が初参加のようだ。SQUEEZEさんの旧オフィスが会場で、雨が降っていて寒い日だった記憶がある。DjangoでIT勉強会検索サイトを作るための調べごとをしていた。このとき作り始めたのがco[m|nn]pass。コードがすごいことになっているので更新された知識を活用して書き直したい。

同じく2月にPython3エンジニア認定基礎ベータ試験を受験してみた。結果は無事、合格。

3月にはキカガクさんの機械学習・人工知能『脱ブラックボックス』少人数セミナーを受けた。わかりやすい解説のお陰で数学が苦手だけども付いていくことができた。

4月に入って、4/15のPython mini Hack-a-thonに初参加。飲み会で中神さんとSIerつらみあるあるについて話した記憶がある。

PyCon JP 2017のスタッフになったのも4月。みんなのPython勉強会でデザインチームの新村さんがスタッフを募集していたのがきっかけ。初作業日は4/19で、このときはブログの更新をしていた。そのときの記事 PyCon JP Blog: PyCon JP 2017 スタッフ作業日#1(2017.04)を開催しました!

6月は、たかのりさんに誘われてOSSユーザーのための勉強会 #19 PythonでLTをした。みんなのPython勉強会以外の場で話すのは初めてだったので地味に緊張していた。

www.slideshare.net

そして9月のPyCon JP 2017。初参加・初スタッフにも関わらずオープニング・クロージングで司会をするという経験をした。Jonasがいてくれなかったら乗り切れなかった。ありがとう。PyCon JPスタッフ参加がきっかけで、それまで顔見知り程度だったPythonista達や知らなかったPythonista達と距離を縮めることができた。

10月にはPython入門者向けハンズオン #6でTAをやった。教える立場になるのはこれが初めて。2回目以降はまだだ。

今月はみんなのPython勉強会の特別編 Pythonオープンサイエンスシンポジウム in つくば で科学技術分野で使われているPythonの話を聞いてきた。

また、石上さん(@ishigamipro)が主催しているメイドカフェでノマド会に初参加した。メイドさんかわいかった。さらにメイドカフェで勉強会にも参加して、Discord botについてLTをしてきた。

写真

flickrはこちら。 Nao Y. | Flickr

5月13日から15日に掛けて、台湾旅行に行った。初台湾。

現地に知り合いがいたお陰で言葉で困ることはなかった。ありがとう、現地の知り合い。

5月だというのに既にえらく暑かった。雨がちな気候と聞いていたけど幸い、降られることはなかった。

1日目は主に九份、2日目は台北市内(台北101とか市林夜市)、3日目は台北市内で昼を食べてから帰国。という流れだった。5月15日が誕生日なので台湾にいる間に年齢がインクリメントされるという体験もあった。

Taiwan

PyCon JP 2017の直後に九州の友人のところに遊びに行ってきて、もちろん写真を撮ったのだけれどFlickrにアップするのを忘れていた…。

今月にはフィルムカメラ Advent Calendar 201712/19分を担当。夜の新宿・代々木周辺の風景を撮った写真を記事にした。

Shinjuku Night Scene

ここらで今年の振り返りを終えたい。

Pythonコミュニティに顔を出して始めてPyCon JP 2017スタッフをやり、その直後に転職。この一年でPythonistaの知り合いがかなり増えた。というよりPythonistaの知り合いほぼ全てが今年知り合った。かなり濃密な一年だった。

2017年はありがとうございました。

2018年も引き続きよろしくおねがいします。よいお年を。

SEからPythonエンジニアにジョブチェンジして早くも3ヶ月経った話

某IT勉強会支援サイトとか某Python学習サービスPythonでやっている会社に入社してから3ヶ月が過ぎた。

そういうわけでビープラウドに入社してからの話。

3時間がんばれたら3日いける。3日いけたら3週間いける。そんな言葉を入社してすぐにもらった覚えがある。気付いたらもう3ヶ月。

まずはざっくりと今、何をやっているか。DjangoでWeb開発してます。入社前にやっていたDjango Girls Tutorialでは触ることのなかった Class-Based View で悩んだりユニットテストの修正で迷ったりしているけれど、私はげんきです。

転職の経緯

  • 新卒で入ったSIerではコードを書く機会がほぼ無く、エンジニアとしてこれでいいのか?と思ってた

    • 就職してから気付いたけど自分で思っていたよりコード書くのが好きだった
  • 学生の頃に少しPythonやってたので、勉強を再開しつつPythonやれる仕事を探そう

  • 転職エージェント使ったりPyCon JPスポンサーの会社を見てみたり

  • ビープラウドは猛者のたくさんいる会社という印象だったけど意を決してメールした

  • 入社できた

入社して変わったこと

  • Pythonで仕事ができる

  • コミュニケーションは全てSlack

    • 情報共有がスムーズ

    • #randomでの雑談も楽しい

  • タスクの管理はRedmine

    • 自分の仕事がチケット単位になっているとわかりやすい
  • コードの管理がGitHub

    • PR出してコードレビューしてもらってMergeという流れ

    • コードレビューからして新鮮な概念だった

  • (週5いつでも)リモートワークができる

    • 気軽に自宅で仕事ができる

    • 宅配の荷物が来ても大丈夫

    • ベランダにアウトドアチェア出して仕事したりした

  • 技術の話ができる

    • 前職は…お察しください
  • 猛者がたくさんいる

    • 困ったときの強い相談相手

    • PythonだけでなくVagrantとかDockerのことを教えてもらう機会もあった

  • スーツとか着なくていい

    • 似合わないので服装自由は嬉しいことこの上ない

    • 生活と仕事がくっきり分かれすぎる感じがあまり好きじゃなかった

  • 毎日、乳酸菌とってる

入社して驚いたこと

環境が激変したけど、今ではだいぶ順応してる。前より格段に働きやすい環境なので嬉しい変化しかなかった。

これからも増々Pythonでがんばっていこう。

フィルムで新宿周辺の夜を撮ってみる

これは フィルムカメラ Advent Calendar 12/19 の記事です。

フィルムで新宿・代々木の夜を撮ってきた。一度目は9月29日、二度目は11月17日。

カメラとフィルムは以下の通り。

  • Nikon FE2 Ai NIKKOR 35mm f2S

  • Natura 1600 (09/29)

  • CineStill 50D (11/17)

街角スナップはNatura 1600、光跡夜景にはCineStillという映画撮影用フィルムから作られたフィルムを使ってみた。CineStillは常温で放置してたせいでおかしな模様が出たコマがあった。やめよう常温放置。

まずはNatura 1600で撮った街角スナップ。9月29日。

Untitled

代々木の踏切手前にあるバーガー屋さん。店から漏れる灯りが暖かげ。

Untitled

Untitled

代々木駅横の高架下のあちらとこちら。

Untitled

新宿駅南口付近。ぼやっと光るネオンサイン。

Untitled

新宿駅西口。ヘッドライトの群れと光を反射する車体の金属感。

CineStill 50Dで撮った光跡夜景。11月17日。

ゴリラポッドをガードレールや歩道橋の欄干に括り付けて撮った。どれも露光時間は30秒くらい。

Untitled

代々木の踏切。電車の霊が通り過ぎたかのよう。

Untitled

新宿駅の南口・新南口のはざま。行く光と来る光。

Untitled

人混みはフィルムに焼き付かない。

Untitled

赤い光が分かれていく場所。

Untitled

半透明の人の群れ。

Flickr Albumを作ったのでどうぞよしなに。記事に載せていない写真も何枚かあります。

Shinjuku Night Scene | Flickr

Shinjuku Night Scene

Amazon Linux 2でPython3を有効にする

Discordのbotを動かす環境が欲しかったのでAWS EC2デビューを果たした。

設定もそこそこに、とりあえずインスタンスを立ててみた。

  • リージョン: 東京
  • AMI: amzn2-ami-hvm-2017.12.0.20171212.2-x86_64-gp2
  • インスタンスタイプ: t2.micro

無料枠を使って動かす。AMIはAmazon Linux 2を選んでみた。初AWS、初EC2なのでわからないことだらけで苦労している。

参考:

dev.classmethod.jp

Python3が動くとのことだけど、ログインした直後は動かなかった。調べてみると、 Amazon-Linux-Extras というライブラリにPython3が含まれていた。

$ amazon-linux-extras list
  0  ansible2   disabled  [ =2.4.2 ]
  1  emacs   disabled  [ =25.3 ]
  2  memcached1.5   disabled  [ =1.5.1 ]
  3  nginx1.12   disabled  [ =1.12.2 ]
  4  postgresql9.6   disabled  [ =9.6.6 ]
  5  python3   disabled  [ =3.6.2 ]
  6  redis4.0   disabled  [ =4.0.5 ]
  7  R3.4   disabled  [ =3.4.3 ]
  8  rust1   disabled  [ =1.22.1 ]
  9  vim   disabled  [ =8.0 ]
 10  golang1.9   disabled  [ =1.9.2 ]
 11  ruby2.4   disabled  [ =2.4.2 ]
 12  nano   disabled  [ =2.9.1 ]
 13  php7.2   disabled  [ =7.2.0 ]

なるほど、確かにある。

ヘルプを見てみる。

$ amazon-linux-extras -h
  help      See list of commands.
  info      See details of a specific package.
  install   Enables specified topics and installs their packages.
  list      Lists topics in the catalog. Some may be enabled.

Amazon Linux Extras software topics give you access to the most-recent
stable software you specifically choose, without the uncertainty of a
wholly new environment.

install を使えばよさそう。というわけで $sudo amazon-linux-extras install python3 でインストール。

$ sudo amazon-linux-extras install python3
  0  ansible2   disabled  [ =2.4.2 ]
  1  emacs   disabled  [ =25.3 ]
  2  memcached1.5   disabled  [ =1.5.1 ]
  3  nginx1.12   disabled  [ =1.12.2 ]
  4  postgresql9.6   disabled  [ =9.6.6 ]
  5  python3=latest  enabled  [ =3.6.2 ]
  6  redis4.0   disabled  [ =4.0.5 ]
  7  R3.4   disabled  [ =3.4.3 ]
  8  rust1   disabled  [ =1.22.1 ]
  9  vim   disabled  [ =8.0 ]
 10  golang1.9   disabled  [ =1.9.2 ]
 11  ruby2.4   disabled  [ =2.4.2 ]
 12  nano   disabled  [ =2.9.1 ]
 13  php7.2   disabled  [ =7.2.0 ]
$ python3 -V
Python 3.6.2

やったぜ。

あとは git を入れて、 GitLabのプライベートリポジトリをクローンしてくれば動かせる、はず。

Bleachってなによ

ジャンプで連載してたやつではなく(念のため)

卍解とかもしない(念のため)

仕事で出会ったHTMLサニタイズライブラリなのだけど、日本語の情報がとても少ないのでちょっとまとめたい。

貴重な日本語の情報

www.ianlewis.org

Bleachとは

Bleach — Bleach 2.1.1 20171002 documentation によると、

Bleach is an allowed-list-based HTML sanitizing library

まずHTML sanitizing とはなにか。ググってみると「HTMLエスケープ処理」と出てくる。

例えば、フォームに<script>なんか悪いスクリプト;</script>XSSを試みる入力があったときに、<, >&lt ,&gtに置き換えることで 無効化する。というような感じだろうか。

Bleach can also linkify text safely

とあるので、HTML sanitizingだけでなくテキストを安全にリンクにすることもできる。

http://example.comが入力されたときに、<a href="http://example.com"> とリンクに変換できる、ということか。

applying filters that Django’s urlize filter cannot, and optionally setting rel attributes, even on links already in the text.

Djangoのurlize filterではできないフィルタを掛けたり、既にテキスト内に存在するリンクに属性を設定したりできる。

Bleach is intended for sanitizing text from untrusted sources.

信頼できないソースのサニタイズのためとあるので、ユーザに入力されたテキストが対象と考えてよさそう。

基本的な使い方

https://bleach.readthedocs.io/en/latest/#basic-use

>>> import bleach

>>> bleach.clean('an <script>evil()</script> example')
u'an &lt;script&gt;evil()&lt;/script&gt; example'

>>> bleach.linkify('an http://example.com url')
u'an <a href="http://example.com" rel="nofollow">http://example.com</a> url

上は、なんか悪いスクリプトサニタイズして無害化している例。

下はテキスト内のURLをリンク化している例。テキストフォームに入力された内容を、HTMLにして表示する掲示板のようなものと考えるのがいいのかな。

今回は仕事で扱ったリンク化をメインにする。

bleach.clean()

http://bleach.readthedocs.io/en/latest/clean.html#sanitizing-text-fragments

前述の通り、今回のメインはリンク化なのでざっくりと。

悪意のあるHTMLを綺麗にする

bleach.clean(text, tags=[u'a', u'abbr', u'acronym', u'b', u'blockquote', u'code', u'em', u'i', u'li', u'ol', u'strong', u'ul'], attributes={u'a': [u'href', u'title'], u'acronym': [u'title'], u'abbr': [u'title']}, styles=[], protocols=[u'http', u'https', u'mailto'], strip=False, strip_comments=True)

パラメータ

  • text(str) - 綺麗にするテキスト
  • tags(list) - 許可するタグのリスト。デフォルトはbleach.sanitizer.ALLOWED_TAGS
  • attributes(list) - 許可する属性。リストまたは辞書。デフォルトはbleach.sanitizer.ALLOWED_ATTRIBUTES
  • styles(list) - 許可するCSSスタイルのリスト。デフォルトはbleach.sanitizer.ALLOWED_STYLES
  • protocol(list) - 許可するリンクのプロトコル。デフォルトはbleach.sanitizer.ALLOWED_PROTOCOLS
  • strip(bool) - 許可していない要素を除去するか否か
  • strip_comments(bool) - HTMLコメントを除去するか否か

許可するタグと属性はセットなこともあると思うので、例えばattributes

ALLOWED_ATTRIBUTES = {
        'a': ['href', 'title', 'rel'], 
         . . . 
}

という辞書としたとき、tags

ALLOWED_TAGS = ALLOWED_ATTRIBUTES.keys()

とするのもよさそう。

bleach.linkify()

http://bleach.readthedocs.io/en/latest/linkify.html#linkifying-text-fragments

テキストからURLやメールアドレスを探してリンク化する。

bleach.linkify(text, callbacks=[<function nofollow>], skip_tags=None, parse_email=False)

パラメータ

  • text - リンク化するテキスト
  • callbacks(list) - タグの属性を調節するコールバック関数のリスト。デフォルトではbleach.linkifier.DEFAULT_CALLBACKS
  • skip_tags(list) - リンク化したくないタグのリスト。例えば、<pre>タグ内を飛ばしたい場合は['pre']とする。
  • parse_email(bool) - メールアドレスをリンク化するか否か

戻り値

  • リンク化したテキストをunicodeで返す

bleach.linkify()callbacksでは属性の設定や削除、置き換えができる。

http://bleach.readthedocs.io/en/latest/linkify.html#callbacks-for-adjusting-attributes-callbacks

属性を設定してみる

属性の設定を仕事で使ったので、そのあたりを書いてみる。

title属性とtarget属性を設定してみる

import bleach

def set_title(attrs, new=False):
    attrs[(None, 'title']) = 'my title'
    return attrs

def set_target(attrs, new=False):
    attrs[(None, 'target']) = '_blank'
    return attrs

callbacks = bleach.DEFAULT_CALLBACKS + [set_title] + [set_target]

html = 'abc http://example.com def'

html_linkify = bleach.linkify(html, callbacks = callbacks)

これによってリンク化したテキスト

abc <a href="http://example.com" rel="nofollow" target="_blank" title="my title">http://example.com</a> def

が得られる。

bleach.DEFAULT_CALLBACKS はデフォルトの属性設定コールバック関数でrel=nofollowを設定する (rel=nofollowを指定するとクローラーがリンク先を認識しなくなる)。

 

公式ドキュメントではbleach.linkify.Linkerを使っているので、それについて。

http://bleach.readthedocs.io/en/latest/linkify.html#setting-attributes

たくさんのテキストに同じリンク化の設定を適用したいときに便利そう。

パラメータ

  • callbacks(list) - タグの属性を調節するコールバック関数のリスト。デフォルトではbleach.linkifier.DEFAULT_CALLBACKS
  • skip_tags(list) - リンク化したくないタグのリスト。例えば、<pre>タグ内を飛ばしたい場合は['pre']とする。
  • parse_email(bool) - メールアドレスをリンク化するか否か
  • url_re(re) - URLの正規表現
  • email_re(re) - メールアドレスの正規表現

url_reとかemail_reにマッチするテキストだけリンク化ができる。

まとめ

ざっくりと。

Bleach

  • HTMLから悪意のある要素を取り除いて綺麗にできる

  • テキスト中のURLやメールアドレスをリンク化できる

正規表現でURLを処理する話 - グループ化と繰り返し-

URLを正規表現で処理するということをやった。そのメモ的なやつ。

処理の概要

処理したいURLは以下のような構成とする。

https://example.com/users/@{username}/article/{yyyy}/{mm}/{article_id}/

ユーザが入力したURLを受け取って、usernamearticle_idを取り出したい。

正規表現の設計

入力されるURLの正規表現の設計として以下の3点を考慮する。

  • https:// が抜けていてもマッチする

  • http:// となっていてもマッチする

  • example.com 以下は完全一致させる

正規表現

以下が概要・設計に基づいて作った正規表現

(?:https?:/{2})?example.com/(?:users/@)(\w+)(?:/article/)(?:\d{4}/\d{2})/(\w+)/
正規表現まとめ

参考:

6.2. re — 正規表現操作 — Python 3.6.3 ドキュメント

正規表現 HOWTO — Python 3.6.3 ドキュメント

  • (?:...)

    • グループ化。ただしこのグループにマッチする文字列は後で参照されることがないmatch.groups() の戻り値に含まれない。今回は正規表現でマッチさせたいが、後から参照することのない部分に用いた。
  • (...)

    • グループ化このグループにマッチする文字列は後から参照することができる(match.groups()の戻り値に含まれる。今回は username article_idを取り出すための用いた。
  • ?

    • 繰り返し直前の正規表現を0回または1回繰り返したものにマッチする
  • \w

    • [a-zA-Z0-9_]に相当する。
  • \d

    • [0-9]に相当する。

正規表現を書くときは regex101.com をよく使っている。

regex101.com

それに加えて、@shimizukawaさんにDebuggex.comを教えてもらった。正規表現が文字列にどうマッチしているか可視化してくれる便利サイト。

https://www.debuggex.com/

実案件での失敗

(?:https?:/{2})?(?:example.com/)? ...のように必ずマッチさせたいURLのホスト部をグループ化した直後で ?に置いていた。

これでは https://users/@{username}/ariticle/{yyyy}/{mm}/{article_id}のような不正なURLがマッチする。他の関数が使っている既存の正規表現を見よう見真似で書いたことが原因の一つ。その正規表現では ?(?:https?:/{2})? のようにスキームに対して使っていた。ここを拡大解釈して、その他の部分でも使ってしまった。

既存のコードを参考にするときは、その意味を自分で咀嚼してから使うべきだと思い知った次第。

正規表現の先読み・後読みを知った話

実に2週間ぶりの更新。転職してそろそろ2ヶ月になる頃だけど、実案件で苦戦しまくっている。今回の話もそこから出てきたネタ。

正規表現には先読みアサーション 後読みアサーション なるものがあることを知ったので、今回はそれについて書く。

公式ドキュメントはこちら

6.2. re — 正規表現操作 — Python 3.6.3 ドキュメント

正規表現 HOWTO — Python 3.6.3 ドキュメント

先読み

先読みアサーション

  • (?=...)

... が続く文字列にマッチする。例えば以下のようになる。

>>> import re
>>> m = re.search(r'Nikon(?=FE2)', 'NikonFE2')
>>> m.group(0)
'Nikon'

マッチしないものも混ぜてみる。

>>> import re
>>> cameras = ['NikonFE', 'NikonFE2', 'NikonFM2']
>>> for camera in cameras:
...     m = re.search(r'Nikon(?=FE2)', camera)
...     if m is not None:
...             print(m.group(0), camera)
...     else:
...             print('None', camera)
...
None NikonFE
Nikon NikonFE2
None NikonFM2

カメラの機種名でやってみたけど、実践的に考えると特定のsuffixのある文字列を引っ掛けるのに使えそう。もちろん (?=...) には正規表現が使える。

>>> m = re.search(r'Nikon(?=[A-Z0-9]{3})', 'NikonFE2')
>>> m
<_sre.SRE_Match object; span=(0, 5), match='Nikon'>
>>> m.group()
'Nikon'

否定先読みアサーション

  • (?!...)

...が続かない文字列にマッチする。例えば以下のようになる。

>>> cameras = ['NikonFE', 'NikonFE2', 'NikonFM2']
>>> for camera in cameras:
...     m = re.search(r'Nikon(?!FE2)', camera)
...     if m is not None:
...             print(m.group(0), camera)
...     else:
...             print('None', camera)
...
Nikon NikonFE
None NikonFE2
Nikon NikonFM2

機種名がFE2でない機種がマッチしている。

特定のsuffixを持たない文字列を引っ掛けるのによさそう。

仕事で<meta property="og:image:type" ~ />ではなく<meta property="og:image" ~ />正規表現で引っ掛ける方法を探していて今回のネタに行き着いた、という裏事情がある。

BeautifulSoupを使って属性を条件にしてタグを取得する、というのが絡んでいる処理なのだけど、その振る舞いを勘違いしており先読みアサーション氏には出る幕がなかったというオチがつく。無念。この反省もブログのネタとして活かしたい。

後読み

後読みアサーション

  • (?<=...)

文字列内の現在位置の前に、現在位置で終わる ... とのマッチがあれば、マッチします。

とある。

(?<=abc)def は abcdef にマッチを見つけます。

>>> import re
>>> m = re.search('(?<=abc)def', 'abcdef')
>>> m.group(0)
'def'

含まれるパターンは、固定長の文字列にのみマッチしなければなりません。

先読みアサーションとは異なって、パターンに正規表現を使うことはできない。

シャーロック・ホームズの相棒だけを探す。

>>> import re
>>> watsons = ['JamesWatson', 'JohnHWatson', 'IBMWatson']
>>> for watson in watsons:
...     m = re.search(r'(?<=JohnH)Watson', watson)
...     if m is not None:
...             print(m.group(0), watson)
...     else:
...             print('None', watson)
...
None JamesWatson
Watson JohnHWatson
None IBMWatson

同じファミリーネームの人物がなかなか思いつかなくて苦労した。一つだけ人間じゃないのが混ざってるけど気にしない。

否定後読みアサーション

  • (?<!...)

文字列内の現在位置の前に ... とのマッチがない場合に、マッチします。

とある。また後読みアサーションと同様にパターンに正規表現は使えない。

今度はシャーロック・ホームズの相棒じゃない人たちを探す。

>>> for watson in watsons:
...     m = re.search(r'(?<!JohnH)Watson', watson)
...     if m is not None:
...             print(m.group(0), watson)
...     else:
...             print('None', watson)
...
Watson JamesWatson
None JohnHWatson
Watson IBMWatson

DNAの二重螺旋構造を見つけてノーベル賞取った人とIBMのすごいやつがマッチした。

後読みアサーションは特定のprefixを持つ(あるいは持たない)文字列をマッチさせる、というような使い方ができそう。

そういう感じで正規表現の先読み・後読みアサーションについてわかったことを書いてみた。