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

楽しいエンジニア人生!

tailコマンドで、昨日(2020年6月24日)のスクリプトが止まったのでリカバリーする

止まるときもある

2020年6月24日の記事で10万行のURLリストと格闘しました。
kawahara-ci.hatenablog.com
ただ、インターネットは生もので運良く全て上手く行くわけはなく運が悪ければ通り止まります。
実際に止まりましてtail -f 出力ファイル でも更新されなくなりました。

どうすれば良い?

  1. 止まっている行を探す。
  2. スクリプトを止める。
  3. 元のスクリプトを暫定的に変更する。
  4. 実行し直す。

止まっている行はpsで簡単にわかります。

12605 ttys001    0:00.75 curl -A "{$USER_AGENT}" --head http://にゃ~ん

この該当のURLを読み込みファイルでgrepします。
grep -n 'http://にゃ~ん' ./url.txt

43152:http://にゃ~ん

43152行目だとわかります。

次にCtrl-Cでスクリプトを止めます。

それから元のスクリプトを少々手直します。
ここで活躍するのがtailコマンドです!
tailコマンドはファイルの後ろから表示するコマンドで、デフォルトは最後の10行を表示します。
行数の指定も可能でtail -n 20 ファイル名とすると最後の20行を表示します。(-20 と指定も可能)

今回の件はファイル全体の行数から止まった行数を引いた数だから100000 - 43152 = 56848で、止まった行数も含みたいので、tail -n 56849 ファイル名(確認のためにはtail -n 56849 ファイル名 |headとするのが良い)と思うかもしれませんが、実はtailコマンドには便利な技があります。

tail -n +43152 ファイル名 |headとしてみましょう、先程の計算結果での出力と同じ結果になりましたよね?
tailコマンドでは+数値でその行以降というやり方あります。

これでスクリプトを変更します。

#cat ./url.txt | while read line
tail -n +43152 ./url.txt | while read line

変更したら、最後実行しましょう。 実行したけど実行が進まない場合は、43152行が無反応ってことなので、一行進めて43153行に変更しましょう。
これで再開できました、あとは放置するだけです。
余談ですが、止まったのは寝ているときだったので、5時間ぐらい無駄に過ぎました・・・。
止まったらAlertを投げる手もありますが、今回はリカバリー出来るだけでヨシ!とします。

意外と便利なcurlのwriteoutオプション(http_codeとurl_effectiveは便利過ぎる)

唐突にきた調査依頼

依頼主「ここにあるURLリストで正常にアクセスできると、URLが変わった物をリストアップしてほしい。」
私「はい、すぐに!お?10万件?スクリプトをサクッとやりますが、量が多いので実行に1日ぐらいかかります。」

できた物

#!/bin/bash

USER_AGENT='Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15) AppleWebKit/601.1.39 (KHTML, like Gecko) Version/10.1.2 Safari/601.1.39'

cat ./url.txt | while read old_url
do
  code=`curl -A '"{$USER_AGENT}"' -s -o /dev/null -w '%{http_code}' ${old_url}`
  if [ `echo "${code}" | grep '30'` ]; then
    new_url=`curl -A '"{$USER_AGENT}"' -sL -o /dev/null -w '%{url_effective}' ${old_url}`
    printf '"%s","%s"\n' "${new_url}" "${old_url}"
  elif  [ "${code}" = '200' ]; then
    printf '"%s",""\n' "${old_url}"
 else
    printf '"%s","error url"\n' "${old_url}"
  fi
done

説明

  1. url.txtを読み込んで、whileループで回す。
  2. curlのsilentオプションで、outputオプションは/dev/nullで捨てて、writeoutオプションのhttp_codeでHTTPステータスコードを取得する。
  3. HTTPステータスコード300系ならば、再度curlのsilentオプションで、outputオプションは/dev/nullで捨てて、locationオプションを使って最終的にアクセスしたURLに遷移させ、writeoutオプションのurl_effectiveで遷移したURLを取得し、そのURLと元のURLを表示する。
  4. HTTPステータスコードで200ならば、URLをそのまま表示する。

便利なwriteoutオプション

curlのwriteoutオプションはときどき追加があります、そのためcurltool_writeout.cファイルは時々見ると新たな発見があったりします。
github.com

余談ですが、writeoutオプションには、みんなが大好きなjsonというのもあり、サイトを見るときに指定して意味なく情報を見るのが楽しいです。

実行コマンドです。

curl -s -o /dev/null -w '%{json}' https://www.google.co.jp/ | jq

実行結果です。

{
  "url_effective": "https://www.google.co.jp/",
  "http_code": 200,
  "response_code": 200,
  "http_connect": 0,
  "time_total": 0.220751,
  "time_namelookup": 0.002085,
  "time_connect": 0.012867,
  "time_appconnect": 0.138509,
  "time_pretransfer": 0.138608,
  "time_starttransfer": 0.218738,
  "size_header": 938,
  "size_request": 80,
  "size_download": 12052,
  "size_upload": 0,
  "speed_download": 54781,
  "speed_upload": 0,
  "content_type": "text/html; charset=Shift_JIS",
  "num_connects": 1,
  "time_redirect": 0,
  "num_redirects": 0,
  "ssl_verify_result": 0,
  "proxy_ssl_verify_result": 0,
  "filename_effective": "/dev/null",
  "remote_ip": "2404:6800:4004:80d::2003",
  "remote_port": 443,
  "local_ip": "にゃ~ん",
  "local_port": にゃ~ん,
  "http_version": "1.1",
  "scheme": "HTTPS"
}

あれ?Googleのサイトでcontent_typeが懐かしのShift_JISだ!?
"content_type": "text/html; charset=Shift_JIS"
こんなcontent_typeを返すのは変だな、よし!試しにUser Agentを付加してやってみよう!

curl -A 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15) AppleWebKit/601.1.39 (KHTML, like Gecko) Version/10.1.2 Safari/601.1.3
9' -s -o /dev/null -w '%{json}' https://www.google.co.jp/ | jq

実行結果です。

{
  "url_effective": "https://www.google.co.jp/",
  "http_code": 200,
  "response_code": 200,
  "http_connect": 0,
  "time_total": 0.324704,
  "time_namelookup": 0.004633,
  "time_connect": 0.015616,
  "time_appconnect": 0.168842,
  "time_pretransfer": 0.168961,
  "time_starttransfer": 0.280845,
  "size_header": 1017,
  "size_request": 186,
  "size_download": 188613,
  "size_upload": 0,
  "speed_download": 582138,
  "speed_upload": 0,
  "content_type": "text/html; charset=UTF-8",
  "num_connects": 1,
  "time_redirect": 0,
  "num_redirects": 0,
  "ssl_verify_result": 0,
  "proxy_ssl_verify_result": 0,
  "filename_effective": "/dev/null",
  "remote_ip": "2404:6800:4004:80b::2003",
  "remote_port": 443,
  "local_ip": "にゃ~ん",
  "local_port": にゃ~ん,
  "http_version": "1.1",
  "scheme": "HTTPS"
}

変わった!content_typeUTF-8になりました。
"content_type": "text/html; charset=UTF-8"

これは私の推測ですが、Googleの日本サイトはガラケー全盛期に登場しているので、そのときの名残りかな?と思います。
こういう色々な発見があるのでcurlは楽しいです。

長い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