人生100年!生涯エンジニア人生!

楽しいエンジニア人生!

長いURLに対して Scrapy するときの覚書

結論

Scrapy で長いURLを対象にするときは、設定ファイルのsettings.pyURLLENGTH_LIMITを書いてURLの最大長を記載する。
自分がやったときはURLの長さが3,800文字だったので、4,000文字に設定した。

# URL LENGTH
URLLENGTH_LIMIT = 4000

ログレベルについて

あるサイトを対象にScrapyしてたとき、次のページを取らないというバグが発生する。
ログを眺めているとDEBUGの文字とともにURLが長いからリンクを無視と出ている。

 [scrapy.spidermiddlewares.urllength] DEBUG: Ignoring link (url length > 2083): 対象URL

いや、気付けたから良いのですが、URLを無視するのはdebugでは無いと思っております。
私の考えですがdebugは開発時に使うもので、本番リリースするときは全て取り除いてほしいので、今回の件はwarning辺りに記載してほしいなと思いました。
warningなんて見ない!という考え方もありますが、その場合はinfoでもerrorでもかまいませんけど、今回の件ではdebugでは無いかなーと思います。

msmtp を使う

概要

過去記事やQiitaの記事で使用したmsmtpは普通に便利なのでご紹介します。

過去記事 kawahara-ci.hatenablog.com

Qiitaの記事 qiita.com

ローカル開発環境でメール送信するという需要は少ないとは思いますが、知っていて損はないので記事を書きました。
これをDockerに展開することも可能です。

インストールして設定する

macOSならbrewでインストールする。

brew install msmtp

Windowsだと・・・ごめん、わからない。

インストールが終わったら設定します。
~/.msmtprc に外部メールサーバーの設定をします。
みんなが大好きな Gmailの設定例を書きます。

host smtp.gmail.com
port 587
user 使用するGmailアカウント
password アプリパスワード
from 使用するGmailアカウント
tls on
tls_starttls on
tls_certcheck off
auth on
logfile ~/.msmtp.log

ここで重要なのはアプリパスワードです。
Gmailを外部メールサーバーとして使うためには、アプリパスワードを設定します。
アプリパスワードを使用するには2段階認証プロセスを有効にしないと使用できません。
まあ、エンジニアなら2段階認証プロセスを有効にしていると思うので、アプリパスワードの説明だけです。

アプリパスワードの設定はマイアカウントのセキュリティから設定します。
アプリパスワードを押下するとパスワード生成画面に遷移します。
f:id:hideaki_kawahara:20200519185841p:plain

アプリを選択、デバイスを選択の項目は、備忘録みたいなものなので、適当で良いです。
入力したら「生成」を押下します。
f:id:hideaki_kawahara:20200519190133p:plain

アプリパスワードが生成されました。(このアプリパスワードは破棄済み)
f:id:hideaki_kawahara:20200519190336p:plain

このアプリパスワードを先程の~/.msmtprcに設定します。

使ってみる

過去記事やQiitaの記事を参考にどうぞ!

過去記事 kawahara-ci.hatenablog.com

Qiitaの記事 qiita.com

せっかくなのでPerlでもやってみましょう!
パイプでやればいけます。
下記サンプルでファイルを作りperl msmtp.plとか実行するとメールが送信されます。

#!/usr/bin/perl

if (! open($mail, "| /usr/local/bin/msmtp -t")) 
{
    print "msmtp が無いです。¥n";
    exit;
}

$mailtext = << "EOM";
From: sample\@sample.com
To: sample\@sample.com
Subject: test

test
EOM

print $mail $mailtext;
close $mail;
exit;

なおPHPでもパイプで送信可能ですが、PHPならsendmail_pathを設定すればOKです。

使い終わったら

アプリパスワードが不要になったら削除しましょう。
ゴミ箱のアイコンを押下すると、確認ダイアログ無しで、速攻で削除されます。
f:id:hideaki_kawahara:20200519192458p:plain

Gmail アプリがダークモードに対応したのでCSSのprefers-color-schemeで試してみるが・・・

結論

Gmail アプリがダークモードに対応しました。
HTMLメールを受信するとどうなるか確認してみました。
結論から言うとダークモード判定に使うprefers-color-schemeは無視されます!!

Gmailアプリ iPhone版 バージョン 6.0.200412 で確認しました。
f:id:hideaki_kawahara:20200516173736j:plain

msmtpについてはこちらの記事をごらんください。
kawahara-ci.hatenablog.com

Media Queriesで試す

とりあえずダークモードとは関係ないけど、Media Queriesに反応するかということで、画面サイズで色が変わることを確認する。
HTMLメールをコマンドで送信します。
cat t1.txt | msmtp -t

t1.txt ファイルの中身です。

From: "test1" <sample@sample.com>
To: sample@sample.com
Subject: test1
Content-Type: text/html


<html>
<head>
<meta http-equiv="content-type" charset="utf-8">
<style>
@media (max-width: 768px) {
  .test1 {
    color: #f0f;
  }
}

@media (min-width: 769px) {
  .test1 {
    color: #0ff;
  }
}
</style>
</head>
<body>
<h1 class="test1">テストメール</h1>
</body>
</html>

想定では #f0f で表示されると思います。
メールを開いてからダークモードとライトモードの切り替えをしてみます。

Media Queriesで試すのライトモード

f:id:hideaki_kawahara:20200516160650j:plain:w300

Media Queriesで試すのダークモード

f:id:hideaki_kawahara:20200516160641j:plain:w300

ライトモードもダークモードもmax-width: 768pxで指定された #f0f が表示されました。

ちなみにGmailアプリではダークモードのときメールの文面だけをライトモード(ライトテーマ)に変更して表示するモードがあります。(逆のパターンは無いです)

右メニューに「ライトテーマで表示」があります。
f:id:hideaki_kawahara:20200517141111j:plain:w300

文面だけライトモードで表示されました。
f:id:hideaki_kawahara:20200517141241j:plain:w300

prefers-color-schemで試す1

ダークモードの切り替えはprefers-color-schemを使用します。
prefers-color-schemを使用したHTMLファイルを添付してメールします。
その添付したファイルを読んでダークモードとライトモードの切り替えをしてみます。

t.html ファイルの中身です。

<html>
<head>
<meta http-equiv="content-type" charset="utf-8">
<style>
@media (prefers-color-scheme: light) {
  .test2 {
    color: #f0f;
  }
}

@media (prefers-color-scheme: dark) {
  .test2 {
    color: #0ff;
  }
}
</style>
</head>
<body>
<h1 class="test2">テストメール</h1>
</body>
</html>

想定ではライトモードでは #f0f で表示で、ダークモードでは #0ff で表示だと思います。

prefers-color-schemで試す1のライトモード

f:id:hideaki_kawahara:20200516164217j:plain:w300

prefers-color-schemで試す1のダークモード

f:id:hideaki_kawahara:20200516164224j:plain:w300

想定通りライトモードでは #f0f で表示で、ダークモードでは #0ff で表示されました。

prefers-color-schemで試す2

HTMLメールで色が変わることを確認する。
t.htmlファイルに送信先を追加してt2.txt ファイルを作成します。
そのHTMLメールをコマンドで送信します。
cat t2.txt | msmtp -t

t2.txt ファイルの中身です。

From: "test2" <sample@sample.com>
To: sample@sample.com
Subject: test2
Content-Type: text/html


<html>
<head>
<meta http-equiv="content-type" charset="utf-8">
<style>
@media (prefers-color-scheme: light) {
  .test2 {
    color: #f0f;
  }
}

@media (prefers-color-scheme: dark) {
  .test2 {
    color: #0ff;
  }
}
</style>
</head>
<body>
<h1 class="test2">テストメール</h1>
</body>
</html>

想定ではライトモードでは #f0f で表示で、ダークモードでは #0ff で表示だと思いますが・・・。
メールを開いてからダークモードとライトモードの切り替えをしてみます。

prefers-color-schemで試す2のライトモード

f:id:hideaki_kawahara:20200516160645j:plain:w300

prefers-color-schemで試す2のダークモード

f:id:hideaki_kawahara:20200516160653j:plain:w300

ライトモードでは #000 で表示で、ダークモードでは #fff で表示されました。
残念ながらGmailアプリではprefers-color-schemeは無視されるようです。

無視されるのはGmailアプリでライトテーマ表示があるからと推測しております。
上記のメールをダークモードで起動して、Gmailアプリのライトテーマ表示をすると、こんな感じで表示されます。
f:id:hideaki_kawahara:20200517141443j:plain:w300

アンカータグに入ったJavaScriptスキームのサイトにScrapyで挑む

Scrapyしにくいサイト

ここです。
jinzai.hellowork.mhlw.go.jp
ちょっとアクセスすると判るのですが、hrefJavaScript:に入れまくっている(JavaScriptスキーム)平成初期の臭いが漂うサイトですね。
今回は、Scrapyを使ってスクレイピングしてみます。

Scrapyの下準備

Scrapyの説明も含めて、こちらをごらんください。
qiita.com
非常に解りやすいので、オススメです。

デベロッパーツールはともだち

Scrapyは何でもいけますが、リクエスト内容がわからないと画面遷移ができないので調査します。
サイトにアクセスしてから、デベロッパーツールを開きます。
※POSTMANなどツールもありますが、今回は使わないでいきます。
ご存知だと思いますが [表示] - [開発 / 管理] - [デベロッパーツール]でデベロッパーツールで開きます。(ショートカットキーでもOK)
そしてNetworkに切り替えます。
f:id:hideaki_kawahara:20200511182736p:plain

start_urlsのURLを探索する

調査したいリンクを押下します。
そして、Networkで表示されたリソースの一番上を押下して情報を所得したら、一番下までスクロールします。
f:id:hideaki_kawahara:20200511183452p:plain

Form Dataが必要な情報になります。
Form Dataにトークンらしきものが無ければ、このForm Dataのデータを使ってGet Methodにしてアクセスしてみます。
Get Methodにできればコードが減るので、このURLをとりあえず覚えておきます。
※Post Methodのみだったら、ここからコードを書くことになります。

さらにもっとできないか探索します。
調査を続けます、次に都道府県の東京都をチェックしてから検索を押下します。
同じようにForm Dataを見て、トークンらしきものが無ければForm Dataのデータを使ってGet Methodにしてアクセスをします。
そうすると、アクセスできたので、このURLもとりあえず覚えておきます。

トークンがある

検索結果を全て取得したいので、次ページのページネーションを確認します。
画面の下の2を押下します。
そうすると、ついにトークンらしきものが現れました。
f:id:hideaki_kawahara:20200512060855p:plain

ここからは、Post Methodの方が良さそうなので、さきほど覚えたURLをScrapyのstart_urlsとしてコードを書きます。

コードを書く

会社名を取得するので、items.py はcompany_nameと書く。

# -*- coding: utf-8 -*-

# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html

import scrapy

class Post(scrapy.Item):
    company_name = scrapy.Field()

spiders/hellowork.py はこんな感じです。

# -*- coding: utf-8 -*-
import re
import scrapy
from .. items import Post

class HelloworkSpider(scrapy.Spider):
    name = 'Hellowork'
    allowed_domains = ['jinzai.hellowork.mhlw.go.jp']
    start_urls = ['https://jinzai.hellowork.mhlw.go.jp/JinzaiWeb/GICB102010.do?action=search&screenId=GICB102010&cbTokyo=1']

    def parse(self, response):
        for post in response.css('table#search tr'):
            name = post.css('span#ID_lbJigyoshoName::text').extract_first()
            if name is None:
                continue
            yield Post(
                company_name = name
            )

        # 現在のページ数を取得する
        for current in response.css('table tr td[style="width: 60%;"]'):
            current_page = current.css('span#ID_lbSearchCurrentPage::text').extract_first()
            if current_page is None:
                return

        # 次のページがあるか確認する
        next_page = int(current_page) + 1
        has_next_page = False
        for id_pager_tag in response.css('table tr td[style="padding:5px;"]'):
            id_pager_href = id_pager_tag.css('a::attr(href)').extract_first()
            if id_pager_href is not None:
                find_array = re.findall('[0-9]+', id_pager_href)
                if len(find_array) == 0:
                    continue
                if int(find_array[0]) == next_page:
                    has_next_page = True
                    next_page = str(next_page)
                    break

        # 次ページが無ければ終了する
        if has_next_page is False:
            return

        # 次のページをリクエストして、解析用parseに渡す
        yield scrapy.FormRequest.from_response(
                    response,
                    formdata = dict( screenId = 'GICB102010', action = 'page', cbTokyo = '1', curPage = current_page, params = next_page),
                    callback = self.parse, dont_click = True 
                )
        return

説明

  1. parseのresponse.css('table#search tr'):から会社名を取得します。
    会社名はID名がsearchのtableタグのの中にあるのでresponse.css('table#search tr')というので検索せます。
    会社名自体はspanタグのID名がID_lbJigyoshoNameのテキスト項目なのでspan#ID_lbJigyoshoName::textで引っ張り出します。
    なお、tableタグの中にはtrには何も定義が無いので、余分なtrも検索してしまうので、spanタグがうまく取れなかったらスルーします。

  2. 次ページみたいのがないので、現在ページを取得+1を計算してページネーションと比較する方法を取ります。
    現在ページ + 1 in ページネーション数値を次ページとしたいので、まずは現在ページを取得します。
    会社一覧の上に小さく現在ページが表示されているので、そこのタグspan#ID_lbSearchCurrentPage::text がページ数らしいですので、それを取得します。

  3. 次はページネーションのところを探索します。
    table tr td[style="padding:5px;"] を配列で取得して、a::attr(href)JavaScriptスキームを取得して、そこから正規表現で数値だけを取り出します。
    なんで、こんな面倒なことをしているのかというと、上記のページネーションは世間一般のページネーションとは異なっているからで、次ページ表記っぽい>>が実は「10*次ページ数+1」という謎ルールで、それなら素直にアンカーダグのJavaScriptスキームを読んだほうが早いからです。

  4. 現在ページ + 1 in ページネーション数値と判定できれば次ページが存在する、無ければ存在しないのでプログラムは終了とする。

  5. 次ページに遷移させるところは、トークンらしきものがあるので、ここはFormRequestを使います。
    formdataに色々とデータを押し込んで、callbackparse再帰呼び出しで次ページが無くなるまで繰り返すようにして、dont_clickをTrueにしてJavaScriptスキームを発火させています。

動作結果

scrapy crawl Helloworkで実行すると、以下のように会社名が取れていることが確認できます。
f:id:hideaki_kawahara:20200512073118p:plain

なお、CSVファイルに出力したいときはscrapy crawl Hellowork -o company_name.csvとします。

今回、このサイトをスクレイピングして思ったのは、JavaScriptスキームも面倒でしたが、それ以上に面倒と思ったのは、タグにID名が振っていないと、スクレイピングしにくいわ!と心底思いました。
そのため、自分でサイトを作るときは、なるべくタグにはID名を振ろうと反省するのでありました。

#マイナンバー通知カード が廃止(配布の廃止)されます (発行済の通知カードは引続き使用可能)

マイナンバー通知カードは2020年5月末日で廃止(配布の廃止)されます。
発行済のマイナンバー通知カードは引続き使用可能です。
詳細は各自治体のホームページで確認してください。

注意!既に配布済みのマイナンバー通知カードは廃止や無効になりません、引続き使用可能です。
2020年6月以降、マイナンバー通知カードの配布が廃止になるだけです。
現在所有しているマイナンバー通知カードは、2020年6月以降も各種書類を記載するときに使用し、記載住所が現住所と一致していればマイナンバーを証明する書類として使用できます。(確定申告など)
2020年6月以降、新しくマイナンバーを知る方法(書類みたいな物)が各自治体から発表される予定です。

東京都世田谷区、東京都足立区、埼玉県志木市などは2020年5月25日と明記されています。

www.city.setagaya.lg.jp

https://www.city.adachi.tokyo.jp/koseki/20200403tuutikadohaisi.htmlwww.city.adachi.tokyo.jp

www.city.shiki.lg.jp

神奈川県横浜市は2020年5月31日と明記されています。
www.city.yokohama.lg.jp

埼玉県川越市は2020年5月末日と明記されています。
www.city.kawagoe.saitama.jp

マイナンバー通知カード廃止(配布の廃止)で変わること

注意: 記載住所が現住所と一致していればマイナンバーを証明する書類として今後も使用できます。

  • マイナンバー制度の廃止ではない。
  • マイナンバー通知カード廃止(配布の廃止)は、自治体によって異なるが遅くとも2020年5月31日で廃止(配布の廃止)される。
  • 2020年6月以降で変わることは以下のとおりです。
    • 新生児には、マイナンバー通知カードは送付されません。(個人番号通知書らしく、今のところ未定)
    • 自治体がマイナンバー通知カードに変わる通知方法を行う。(個人番号通知書らしく、今のところ未定)
      • 住民票をマイナンバー付きで出せるけど料金かかるんだよね・・・。
    • マイナンバー通知カードを紛失しても、マイナンバー通知カードの再発行はできなくなります。
      • 現在は再発行手数料500円です。
    • 引越しをするときに、役場にマイナンバー通知カードに住所を記載してもらう必要がなくなります。
    • 引越しをした後、マイナンバーカードを申請する申請書IDは無効になります。(2020年6月以降、申請書IDの新規発行については不明)
    • マイナンバーカードの有効期限を超えたとき(電子証明書ではない)、マイナンバーカードを「新規作成」する方法が公式にアナウンスされているが、上記のとおり引っ越しをしていると申請する申請書IDは無効になります。
    • マイナンバーを知る方法は、各自治体に一任されているので自治体に問い合わせてください。(個人番号通知書らしく、今のところ未定)
  • 現在所有しているマイナンバー通知カードは、2020年6月以降も各種書類を記載するときに使用するので引き続き所有してください。

今のところ細かい情報について未定が多く、2020年6月以降に情報が出ると思います。
マイナンバーカードを申請する申請書IDが失効したときは この書類を使用するらしい。 https://www.kojinbango-card.go.jp/hpsv/wpmng/documents/tegaki-kofu-shinseisho.pdf

マイナンバー通知カード廃止(配布の廃止)の理由

マイナンバー通知カード廃止(配布の廃止)は、2020年5月31日施行されるデジタル手続法に対応した動きです。
www.kantei.go.jp

簡単に言うと デジタル手続法案についての7ページ目に書かれていますが「通知カード」と記載事項変更等の手続を廃止し、負担軽減とマイナンバーカード普及を実現」によることです。

他に総務省の資料は、あんまりないですが・・・デジタル・ガバメント及びマイナンバー制度について(12ページ)石田総務大臣閣議後記者会見の概要 平成31年3月19日マイナンバー通知カードの廃止の話が確認できます。

参考までにデジタル手続法が決定したときの答弁があります。
平成31年3月15日に平井内閣府特命担当大臣閣議後記者会見要旨
www.cao.go.jp

それらの動きに応じて各自治体に通達がきているようで、Googleで「マイナンバー通知カード 廃止」で検索すると、多くの自治体が廃止の話を掲載しています。
www.google.com

デジタル手続法について

デジタル手続法が施行される背景などがあり、各省庁の大臣もオンライ化への動きが活発になっています。

「コロナ発生届けが内科医の一言にてWEB対応できるようになる(結核や麻疹等の他の感染症も対応する方向)」は、行政やるじゃん!と思った方は多いと思います。
togetter.com
当然ながら各部署の努力もありますが、その動きを活性化させたのはデジタル手続法が近々施行されるなどの動きがあったのだと思います。
デジタル手続法があるから各省庁や大臣なども動けたんだろうと思います。

あと、IT担当大臣の竹本直一氏の「しょせんは民・民の話」と言ったのも、官は法整備してますの意味合いだったんだろうと、わたしは推測します。
しかし、言い方が悪かったのが、そのひと言で一気にオンライン化が進んだのは皮肉なものです。
www.itmedia.co.jp

今回のコロナ禍が行政を変えた側面もありますが、デジタル手続法が整備されていたということを多くの人は知っておいてほしいと思います。