あぼぼーぼ・ぼーぼぼ

のんびり生きたい

『プロダクトマネジメント―ビルドトラップを避け顧客に価値を届ける』を読んだ

プロダクトとは価値を顧客に運ぶことで、プロダクトマネージャーはプロダクト戦略をたてる。なぜこのプロダクトをつくっているのか、どんなアウトカムを生み出すのかをチームが忘れないようにする。

アウトプットではなくアウトカム志向で、というのはなるほど〜と思った。

組織やプロダクトマネージャーのアンチパターン(というかプロダクト主導じゃないパターン)が書かれてあってあの会社はどれに当てはまるかな、あの人はどうかな、とか考えながら読める。

プロダクトマネージャー、プロジェクトマネージャー、プロダクトオーナーの違いがわかる。

後半はもっと面白い。価値を顧客に運ぶためにチームメンバーやステークホルダーがどう思考してどう実験していくかのストーリーが面白い。プロダクトにとっての顧客に価値を運ぶことに直接関係のないことが一切出てこないのが最高だった。スクラムでイテレーション〜やレトロスペクティブ〜みたいな話が出てこずひたすらアウトカム志向でどう顧客の課題を知りどう実験していくかに集中できる本。

www.amazon.co.jp

『エンジニアの知的生産術』を読んだ

社会人にとってやる気は貴重なリソース、というのはマジでそうだなと思った。

3,4章を読んで、自分には本をあまり時間をかけずに一度読んで、その後他の本を読むときとか何かしらのアウトプットをするときに何となくキーワードをもとに引っ張ってこれるくらいでいいかなと思った。何度も読む前提の読み方。情報は必要なタイミングで必要になるだろうから。輪読会でいくらメモしたり議論しながら読んでも1ヶ月後にはほとんど覚えてないので。とにかくたくさん自分の頭の中の棚に置いておく必要があるなと。

5,6章あたりはKJ法よさそう。付箋にとにかく書き出してボトムアップで分類していくのはなるほどと思った。

最後に学ぶ戦略というかT型人材とかπ型人材に触れてておぉ〜ってなった。あと未来に向かって点は繋げられない、過去を振り返って点を繋ぐしかできないから今やってるのを信じろみたいな話もおぉ〜てなった。

https://www.amazon.co.jp/dp/4774198765www.amazon.co.jp

複数のspiderで異なるpipelineを通す

scrapyは使用するpipelineを全て定義する必要があり、普通に書くとどのspiderでも定義した全てのpipelineを通るようになっている。これをそれぞれのspiderで、指定したpipelineだけを通るようにする実装のメモ。

例として、slack_bottweet_botの2つのspiderを定義する。

import scrapy


class SlackBotSpider(scrapy.Spider):
    name = 'slack_bot' # この値を判定につかう

    def parse(self, response):
import scrapy


class TweetBotSpider(scrapy.Spider):
    name = 'tweet_bot' # この値を判定につかう

    def parse(self, response):

slack_bottweet_botそれぞれ専用のpipelineを1つ、共通のpipelineを1つ定義したとすると、以下のように専用のpipelineのprocess_item内で、spider.nameの値をチェックすれば良い。

class SlackPipeline:
    def process_item(self, item, spider):
        if spider.name not in ['slack_bot']:
            return item

        print('SlackPipeline')
        yield item

~~~省略~~~

class TweetPipeline:
    def process_item(self, item, spider):
        if spider.name not in ['twitter_bot']:
            return item

        print('TweetPipeline')
        yield item

~~~省略~~~

class SaveFilePipeline:
    def process_item(self, item, spider):
        print('SaveFilePipeline')
        yield item

settings.pyには全てのpipelineを列挙する必要がある。

ITEM_PIPELINES = {
    'my_crawler.pipelines.SlackPipeline': 300,
    'my_crawler.pipelines.TweetPipeline': 400,
    'my_crawler.pipelines.SaveFilePipeline': 500,
}

これで、slack_bot実行時はSlackPipeline->SaveFilePipeline、tweet_bot実行時はTweetPipeline->SaveFilePipelineを通すことができる(厳密には全てのpipelineのprocess_item()は呼ばれるので、処理をスキップすると言った方が正しい)。

$ scrapy crawl slack_bot
SlackPipeline
SaveFilePipeline

$ scrapy crawl tweet_bot
TweetPipeline
SaveFilePipeline

参考:https://groups.google.com/d/msg/scrapy-users/msKQ7UaYh_E/ee8WSMPRpq0J

Pythonで25時のような表記をdatetimeに変換する

そのままdatetimeにしようとするとエラーが発生する。

import datetime


dt_str = '2020/06/07 25:05'
d = datetime
    .datetime
  .strptime(dt_str, '%Y/%m/%d %H:%M')

# ValueError: time data '2020/06/07 25:05' does not match format '%Y/%m/%d %H:%M'

なので、日付部分をdatetime、時刻部分をtimedeltaとして生成し、両者を足せばOK。

import datetime


dt_str = '2020/06/07 25:05'
d_str = dt_str[:10]  # 2020/06/07
t_str = dt_str[11:]  # 25:05
hour = float(t_str[:2])  # 25
minute = float(t_str[3:])  # 05

d = datetime
  .datetime
    .strptime(d_str, '%Y/%m/%d')  # 2020-06-07 00:00:00

delta = datetime
    .timedelta(hours=hour, minutes=minute)  # 1 day, 1:05:00

dt = d + delta  # 2020-06-08 01:05:00

Python3.7

PythonでTwitter自動投稿botをつくるときに調べたもの

ウェブサイトを定期的にスクレイピングし、自然言語処理をしてその結果をもとにTwitterに投稿するbotをつくった。その際に必要だった技術、調べたもののメモ。

クローラー

scrapy

Pythonクローラーがつくれるフレームワーク。大変お世話になりました。

scrapy-doc-ja.readthedocs.io

qiita.com

今回でいうと自然言語処理をするパイプラインとTwitterに投稿するパイプラインの2つを実装した感じ。

cssセレクタ一覧

scrapy-doc-ja.readthedocs.io

相対パス絶対パスに変換

import scrapy

class MySpider(scrapy.Spider):
    def parse(self, response):
        relative = response.css('a::attr(href)').extract_first()
        absolute = response.urljoin(relative)

ref https://docs.scrapy.org/en/latest/topics/request-response.html#scrapy.http.Response.urljoin

scrapyのロギング最低レベル変更

settings.pyに追記

LOG_LEVEL = "INFO"

ref https://doc-ja-scrapy.readthedocs.io/ja/latest/topics/settings.html#std:setting-LOG_LEVEL

Python言語系

文字列を○文字まで切り取る

# 'あいうえお'
five_str = 'あいうえおかきくけこ'[:5]

文字列をdatetimeに変換

import datetime

date_str = '2020.06.01 12:00'
date = datetime.datetime.strptime(date_str, '%Y.%m.%d %H:%M')

ref Pythonで文字列 <-> 日付(date, datetime) の変換 - Qiita

JST現在時刻をdatetimeで取得

import datetime

dt_jst = datetime.datetime.now(
    datetime.datetime.timezone(datetime.datetime.timedelta(hours=9))
)

ref Pythonで現在時刻・日付・日時を取得 | note.nkmk.me

datetimeの差を求める

タイムゾーンを考慮したdatetimeと、考慮していないdatetimeは計算できない。

import datetime

dt = datetime.datetime.now()
dt1_jst = datetime.datetime.now(
    datetime.datetime.timezone(datetime.timedelta(hours=9))
)
dt2_jst = datetime.datetime.now(
    datetime.datetime.timezone(datetime.timedelta(hours=9))
)


# TypeError: can't subtract offset-naive and offset-aware datetimes
diff = dt - dt1_jst

# OK
diff = dt1_jst - dt2_jst
# diffはtimedeltaオブジェクト
diff.total_seconds() # 0.0

ref python - Can't subtract offset-naive and offset-aware datetimes - Stack Overflow

ref datetime --- 基本的な日付型および時間型 — Python 3.8.3 ドキュメント

docstring

Googleが公開しているdocstringガイドか、numpyというのが主流らしい。

ref styleguide | Style guides for Google-originated open-source projects

ref numpydoc docstring guide — numpydoc v1.1.dev0 Manual

ref [Python]可読性を上げるための、docstringの書き方を学ぶ(NumPyスタイル) - Qiita

カレントディレクトリの絶対パスを取得

import os

path = os.getcwd()

ref os --- 雑多なオペレーティングシステムインタフェース — Python 3.8.3 ドキュメント

自然言語処理

janome.Tokeniserでユーザー辞書を使う

janomeは簡略辞書というフォーマットを使えたため、比較的簡単にユーザー辞書を追加することができました。

from janome.tokenizer import Tokenizer

# uidc.csvの中身の例↓
# あつ森,カスタム名詞,アツモリ
# とたけけ,カスタム名詞,トタケケ
t = Tokenizer(
    udic='udic.csv',
    udic_type='simpledic'
)
t.tokenize(text)

ref Welcome to janome's documentation! (Japanese) — Janome v0.3 documentation (ja)

特定品詞のみ抽出

from janome.tokenizer import Tokenizer

for token in t.tokenize(text):
    if (token.part_of_speech.startswith('名詞,固有名詞')
        or token.part_of_speech.startswith('名詞,一般')
        or token.part_of_speech.startswith('形容詞,自立')
        or token.part_of_speech.startswith('カスタム名詞')):
            print(token.surface)

Twitter API

Twitter API 利用申請手順

全て英語で書かなければならず、さらに○○文字以上の制約がある項目もあり少々面倒。DeepL翻訳にお世話になりました。

www.deepl.com

ref 2020年度版 Twitter API利用申請の例文からAPIキーの取得まで詳しく解説 | 新宿のホームページ制作会社 ITTI(イッティ)

PythonTwitter APIクライアント

いろいろ見つかったが今回はこれを使用。

ref https://pypi.org/project/twitter/

Heroku連携系

秘匿情報をHerokuのConfig Varsから注入

Twitter APIの認証情報などはソースコードにベタ書きしたくないため、Herokuの機構をつかって環境変数にいれます。Pythonから環境変数を取得する方法は下記のとおり。

import os

os.environ.get('SECRET_VALUE', 'default value')

ref https://devcenter.heroku.com/articles/getting-started-with-python#define-config-vars

Heroku Schedulerで定期実行

Herokuのアドオンで追加できる。Dyno Sizeが無料だと10分ごと、○時間ごと、○日ごと、の3パターンが使える。

ref https://devcenter.heroku.com/articles/scheduler

Flutterで自作パッケージをpub.devに公開する

基本的にDeveloping packages & pluginsを参考に進めればOK。

パッケージを生成する

テンプレートには二種類ある。今回はDart packagesとして作成するので以下のコマンドで生成。

flutter create --template=package {package_name}

生成されたら、lib/{package_name}.dartを編集する。

サンプルプロジェクトを追加する

--template=packageで生成したプロジェクトにはtestしか含まれてないので、必要に応じてサンプルプロジェクトを追加する。

パッケージのカレントディレクトリで以下のコマンドでexampleというプロジェクトを生成する。

# in {package_name}
flutter create example

生成されたら、example/pubspec.yamlのdependenciesに自作パッケージを追加。

dependencies:
  flutter:
    sdk: flutter

  {package_name}:
    path: ../

その後example/main.dartに自作パッケージのサンプル実装を記述する。

README.md, CHANGELOG.md , pubspec.yaml, LICENSEの編集

pub.devに公開するにあたってこれらのファイルを正しく編集する。

基本的にそれぞれデフォルトで例が書かれてあるので参考に。LICENSEは他のパッケージを参考に。

CHANGELOG.md

## [0.0.1] - 2019-11-04

* first release

pub.devに公開

以下のコマンドでpub.devにpublishできる。

flutter packages pub publish

途中認証のためにURLをひらく必要がある。認証に成功すればこんな感じ。

f:id:aboy_perry:20191104165801p:plain
pub.dev認証完了

認証完了後、pub.devにアップロードが成功すると晴れて自作パッケージが公開される。

pub.dev

Flutterで1文字目だけスタイルを変える

Text.richTextSpanを使う。TextSpanで設定したstyleはchildrenに引き継がれるので、childrenのTextSpanで上書きする。

final String text = "Flutter";
Text.rich(
    TextSpan(
        text: text.substring(0, 1),
        style: TextStyle(
            fontSize: 36,
            color: Colors.blue,
            letterSpacing: 1,
        ),
        children: <TextSpan>[
            TextSpan(
                text: text.substring(1),
                style: TextStyle(
                    fontSize: 18,
                    color: Colors.black87,
                ),
            ),
        ],
    ),
),

f:id:aboy_perry:20191103183918p:plain:w300
Text.richを使って1文字目のTextStyleを変える