nuro光が全然開通しない

引っ越しを機に、ネットの回線速度が速いと評判のnuro光に契約をした。 Twitter切り貼り記事だけど、メモっておく。

nuro光の申し込み: 4/17

住宅ローンの手続きが一段落したので、新居用のネットを申し込もうと思って、nuro光に公式Webページから申し込み。

1回目のnuro光の工事: 4/27

引越し日に、1回目の工事予約を入れた。

フレッツ光ネクストの解約が・・・できない!

フレッツ光はなぜかTELでの解約(0120-116-116)しかできない。そして、コールセンターは激混みで、全然つながらない。

奇跡的に1回つながったところ、「お客様はマンションプランなので・・・」と、別のところを案内された。

その番号もまたコールセンターは激混みで、この日は夕方までずっとつながらず、終了を迎えた・・・

翌朝、奇跡的につながった。すると「お客様はマンションプランなのですが、解約は0120-116-116のほうで承ります」とのこと。

正直、この時点で「もう二度と、フレッツ光は使わない」というのを心に決めた。

ただでさえ速度出ないフレッツ光ネクストを前回の引越し時に勧誘してきて騙されたというのに、解約時にここまでされたら、もうどこを褒めたら良いのかわからない。

フレッツ光ネクスト 隼 さらば!!:5/4

なんとか0120-116-116を攻略して、5/4に旧住居のネット解約。

ちなみに、解約の数日前にとったベンチマークがこれ

nuro光の2回めの工事が遅いことを知る: 5/10

so-netの入会証が届かないトラブルがあって、

ついでにnuro光調べたときに、2回めの工事がめちゃくちゃ遅いということを知った。

モバイルルータを借りることにする: 5/10

nuro光の人向けにモバイルルータを980円で借りられるよ!みたいなのがある。

f:id:YusukeIwaki:20200526230333p:plain

https://www.nuro.jp/article/wifionu/

ちなみに、 実際には、送料が上乗せされるので、2000円くらいかかる。 みんな誇大広告が大好きなのね。

モバイルルータが届く: 5/11

誇大広告は許せないが、Wifiが翌日に届いたのはさすが。

在庫の関係で、5GB制限があると書いてあったが、しばらくは使えていた

ついにnuro光のレンタルモバイルルータで帯域制限: 5/15

どうやら12GBを超えると帯域制限がかかるらしい。どこにもそんな記載はないが。

あまりに遅くて仕事ができなくなった。IIJmioのSIMでしばらく頑張ることにした。

個人スマホも帯域制限・・・急いで、IIJmioの大容量オプションを購入: 5/21

手持ちの通信手段すべてが128kbpsになってしまった。これはもはや人権がないに等しい。流石にやばいので、IIJmioの大容量オプションを課金することにした。

nuro光の2回めの工事見込みの連絡がSMSでキタ: 5/22

f:id:YusukeIwaki:20200526225935p:plain

7月・・・?!

・・・もうしばらくネット難民だ

Try WimaxWimaxの実力を見ることに: 5/24

UQ Wimaxのギガ放題は、2年縛りっぽくみえるが解約金が1000円だ。UQ以外のWimaxでも短期契約に向いているプランを出しているところがいくつかある。

ということで、Wimaxの実力をまずは見てみようと思い、Try wimaxに申し込む。

5/23の夜だと

全然在庫はなかった。しかし5/24の朝8:30頃にみてみたところ、Wimax Home 01の在庫が有った。速攻で申し込んだ。

Try Wimaxの端末が届いた。

そうそう、大事なことを書き忘れていたが、新居はdocomoLTEの電波がとても弱い。(nuro光の提携のモバイルルータはソフトバンクLTEだったけど、言わずもがな。)

Wimaxだとどうなんだろ?と思って、数カ所で測定してみた。

LTEに比べると2〜3倍の速度が出た。

久々に安心してネットが使える環境になった。Wimaxは俺を裏切らない。

nuro光?おまえはほんとうにやってくるのか?

7月中旬まで、

  • なんらかのwimax契約をする
  • IIJmioの大容量オプションで頑張る

の二本立てでしのぐ生活は続きそうだ。

・・・・つーかさ、、4月17日に申し込んで7月中旬になるなんて思ってなかったよ! nuro光

とはいえ、nuro光がなかなか開通しないのは、自分の調査不足もあるので、我慢するとしよう。

・・・つーかさ。、ふつうに帯域制限のゆるいWimaxを短期契約したほうがいいじゃないか!!モバイルルーターは全然おすすめできないよ!!!

帯域制限は本当にストレスである。

まとめ

  • nuro光を契約するときには、代替インターネット環境が必須だ
  • nuro光が紹介しているモバイルルーターは、980円ではなく、送料を含めると2000円くらいかかる。
  • nuro光が紹介しているモバイルルーターは12GBで帯域制限がかかる。自分のようなビジネス用途でインターネットを使う人には不向き。
    • UQ Wimaxのギガ放題など、帯域制限のない&解約手数料の安いサービスを利用したほうがいい。

糸島の立石山が最高だった

コロナコロナ言われている中、先週末も今週末も登山している。

f:id:YusukeIwaki:20200425164429p:plain

10年前の滑落で肩を脱臼したところが、ここのところの在宅勤務で痛み始めていることもあり、家にじっとしているとどうしても体が弱くなるように思う。

「不要不急の外出だ!」って言われそうだけども、本当にそうか?体を動かして体温を高めて、適度に筋力もキープしてたほうが、いざというときに病気に戦える体になるんじゃないか?

他にも写真をアップしようとしたけど、はてながアップさせてくれなかった・・・ざんねん。

f:id:YusukeIwaki:20200425165108p:plain

やっぱりPuppeteerをRubyから使えないと困るので、puppeteer-rubyを作ることにした

yusukeiwaki.hatenablog.com

から2ヶ月と少し経って、やっぱりpuppeteerはRubyから使えないと仕事で困るので、開発を再開した。

concurrent-ruby は意外とシンプルに使える

JSのasync/await をどうやって攻略するか?というところがポイントなのだけど、concurrent-rubyConcurrent::Promises っていうクラスを使うと解決ができた。

concurrent-ruby自体は非同期処理をするためのごちゃまぜライブラリな感が否めなくて「結局なにを覚えたらええの?」ってなるんだけど、覚えるべくは先に書いた Concurrent::Promises. だいたい以下の5つくらいのイディオムを覚えておけば、async/await, PromiseっぽいものをRubyで素直に書ける。

非同期ジョブとしてのPromiseの生成

Concurrent::Promises.future {
  # 時間のかかる処理
}

コールバックのPromise化

future = Concurrent::Promises.resolvable_future
コールバック do |result|
  future.fulfill(result)
end

Promiseが終わるまで待つ(JSのawait)

begin
  result = future.value! # fulfillされるまでブロッキング
rescue => err
  # rejectされたとき
end

同時並行で走らせ、全部終わるまで待つ(JSのPromise.all)

Concurrent::Promises.zip(
  future1,
  future2,
  ...
)

同時並行で走らせ、どれか1つが終わるまで待つ(JSのPromise.race)

Concurrent::Promises.any(
  future1,
  future2,
  ...
)

Rubyで非同期処理をスムーズに書くための工夫

goto メソッドは同期だけど、 click は非同期で、 type_text は同期で・・・・

のようなライブラリは自分のような凡人だと「どのメソッドが非同期かわかりづらいな〜〜!!」となってしまい使う気がなくなってしまう。

最初は本当にメソッドごとに同期とか非同期とかばらばらだったんだけど、開発している自分でもだんだんわからなくなってきたので、

  • 非同期なメソッドは必ず async_ から始める
  • 非同期メソッドには必ず同期版のメソッドも用意する

のようにしてみた。

実装的には

  # @param timeout [number|nil]
  # @param wait_until [string|nil] 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2'
  private def wait_for_navigation(timeout: nil, wait_until: nil)
    main_frame.wait_for_navigation(timeout: timeout, wait_until: wait_until)
  end

  # @param timeout [number|nil]
  # @param wait_until [string|nil] 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2'
  # @return [Future]
  async def async_wait_for_navigation(timeout: nil, wait_until: nil)
    wait_for_navigation(timeout: timeout, wait_until: wait_until)
  end

こんな感じで、非同期で呼ぶ必要があるものは同期版をFutureでラップして返すだけ。(もしくは逆に、同期版は非同期のメソッドをawaitするだけ。)

ダサいけども、こうすることで、

await hoge

のようになっていたら「あ、これは(同期メソッドだから)awaitいらない」って気づけるし、

await_all(
  send_message ..., # バグ
  async_wait_for_navigation,
)

のようになっていたら「あ、これは非同期メッセージにしないといけないところを同期メソッドを使ってしまってる」と気付ける。

ドキュメンテーションでFutureが返るかどうかを示すという方法ももちろんあるにはあるけど、命名で解決できるならそのほうが実装負荷が少なくて済むと思った。

(未解決)なぜかJSとはイベントコールバック順序が変わってたりする...

おそらく非同期処理のタイミングの問題なんだろうけど

  • JSだと必ずattachToTargetの返り値が返ってくるよりも前に Target.attachedToTarget がコールバックされるのに、puppeteer-rubyだとTarget.attachedToTargetのほうが後に呼ばれることが多い
  • JSだと必ずexecutionContextCleared -> executionContextCreated の順でコールバックされるのに、 puppeteer-rubyだとexecutionContextCreated -> executionContextClearedの順で呼ばれることが頻繁にある

などなど。

2つ目は特に意味がわからなくて、ブラウザのJS実行コンテキストが「作られたあとで破棄された」のように解釈されるので、いつまで経っても document を取得できなくて詰む。

とりあえずは

    @context_id_to_context.values.each do |context|
      if context.world
        context.world.context = nil
      end
    end
    @context_id_to_context.clear

のロジックを少し改造して、「1秒以内に作られたExecutionContextはclearで破棄しない」というひどいロジックを入れて

    # create時に記録しておいた時刻から1秒たっていないIDは削除対象外にする
    now = Time.now
    context_ids_to_skip = @context_id_created.select { |k, v| now - v < 1 }.keys

    @context_id_to_context.reject{ |k, v| context_ids_to_skip.include?(k) }.values.each do |context|
      if context.world
        context.world.context = nil
      end
    end
    @context_id_to_context.select!{ |k, v| context_ids_to_skip.include?(k) }

こうやって回避。ただ、これでも時々 document を取得できない問題は起きる。謎・・・

(追記: v0.0.11で解決しました!! puppeteer-ruby 0.0.11で完走率が劇的に改善。ブラウザの自動操作が快適に。 - YusukeIwakiのブログ)

(2021.01.29 追記: 問題の原因はおそらくこういうことです→ concurrent-rubyのConcurrent::PromisesはJavaScriptのPromiseと結構違う - Qiita

ともあれ、動いた・・・!

require 'puppeteer'

Puppeteer.launch(headless: false, slow_mo: 50, args: ['--guest', '--window-size=1280,800']) do |browser|
  page = browser.pages.first || browser.new_page
  page.viewport = Puppeteer::Viewport.new(width: 1280, height: 800)
  page.goto("https://github.com/", wait_until: 'domcontentloaded')

  form = page.S("form.js-site-search-form")
  searchInput = form.S("input.header-search-input")
  searchInput.type_text("puppeteer")
  await_all(
    page.async_wait_for_navigation,
    searchInput.async_press("Enter"),
  )

  list = page.S("ul.repo-list")
  items = list.SS("div.f4")
  items.each do |item|
    title = item.Seval("a", "a => a.innerText")
    puts("==> #{title}")
  end
end

こんなコードを書けば

image

こんなかんじでグリグリ動くものはできた。(まだまだ未実装機能が山のようにあるけど・・・w)

とりあえずgem pushしとく

RailsのSystemTestCaseに結合できれば最高だなって思っているので、しばらくは空き時間で開発するとおもうけど、いつモチベーションが切れるかはわからない...。

そんな自分への戒めもこめて、とりあえずgem pushだけしといた。

rubygems.org

ちなみに完全に蛇足だが、 puppeteer って名前のGemは8年くらい前に取られていて、登録できなかった・・・!

puppeteer | RubyGems.org | your community gem host

 

コントリビューション、issueお待ちしております。(対応できるとは言っていない)

github.com