無駄と文化

実用的ブログ

【jQueryの基本の"き"】パート3 - 起動スクリプトを囲っているアレをひもとく

さて、前回までjQueryプラグインの基本的な使い方をおさらいして、さらに起動スクリプトについて詳しく解説してみました。

今回は、これまで意図的に触れずにスルーしてきた部分をピックアップします。
起動スクリプトを囲っているよく見るアレ についてです。

さっそく「囲っているよく見るアレ」に登場していただきましょう、はい、

<script>
// ↓よく見る書き方。これが今回の主役です
jQuery(function($) {

  /* ここに起動スクリプトの本体が書かれることが多い */

})
</script>

この書き方、ものすごく重要 なんですがjQueryに馴染まない人はいまいち意味が掴みづらいようです。
「サンプルをそのまま試しているのに何故か動かない」と言うときは、この 囲っているよく見るアレ を使いこなせていない事が多いようです。

さて、


囲っているアレ ファミリー

この 起動スクリプトを囲っているアレ にはいくつかのバリエーションがあります。
微妙に書き方が違うバリエーションがいくつもあるのも初心者が混乱する要因の1つのようです。

起動スクリプトを囲っているアレ の意味を解説する前に、起動スクリプトを囲っているアレ ファミリーを全て並べてみましょう。

<script>
// ========= パターン① ========

// その1
$(document).ready(function() {
  /* ... 起動スクリプト ... */
});

// その2
jQuery(document).ready(function() {
  /* ... 起動スクリプト ... */
});

// その3
$(function() {
  /* ... 起動スクリプト ... */
});

// その4
jQuery(function($) {
  /* ... 起動スクリプト ... */
});

// ========= パターン② ========

// その5
$(window).load(function() {
  /* ... 起動スクリプト ... */
});

// その6
jQuery(window).load(function() {
  /* ... 起動スクリプト ... */
});
</script>

目が回ってきましたか?
大丈夫、なんと1~4は全部同じ意味です。5と6も同じ意味。

なので実際は2種類の書き方だけ覚えればいいんですね。
ここでは仮に、「囲ってるアレ パターン①」「囲ってるアレ パターン②」と呼びましょう。

まずは「書き方のバリエーションはいろいろあるけれど効果は同じなんだ」と理解する事が大切です。


囲っているアレ の効果

お待ちかね、起動スクリプトを囲っているアレ の効果を解説しましょう。

起動スクリプトを囲っているアレ には 起動スクリプトが実行されるのを遅らせる 効果があります。
「ちょっと待って!まだ起動しないで。 (数秒後...)はい、もういいよ。起動スクリプト実行!」とそんな感じに。

「囲ってるアレ パターン①」と「囲ってるアレ パターン②」の違いもそこにあります。

<script>
// ========= パターン① ========

jQuery(function($) {
  /*
   * ここに書かれた起動スクリプトは
   * HTML全体が読み込み終わるまで実行されない。
   **/
});

// ========= パターン② ========

jQuery(window).load(function() {
  /*
   * ここに書かれた起動スクリプトは
   * Windowの読み込みが完了するまで実行されない。
   * HTML全体, 全てのCSS, 全ての画像 の読み込みが終わるまでじっと待つ。
   **/
});
</script>


パターン① と パターン② の違いをお分かりいただけるでしょうか?
それぞれ、HTML の読み込みが終わるまでの間Window の読み込みが終わるまでの間 起動スクリプトが実行されるのを遅らせます。

『なぜ遅らせる?そんな必要ある?』
もっともな疑問ですね。


なぜ HTML の読み込みを待つのか

なぜ 囲ってるアレ を持ち出してまで HTML の読み込みを待たなければいけないんでしょうか。
それは JavaScript は読み込まれた瞬間に実行される からなのですが、ちょっとイメージしづらいと思うので、こんな感じの HTML を書いて読み込ませる例を見てみましょう。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>起動スクリプト サンプルページ</title>

<script src="js/jquery.js"></script>
<script>
$('.target').css({
  color: 'red',
});
</script>

</head>
<body>

<main>
  <h1>起動スクリプト サンプルページ</h1>
  <p class="target">新しい朝が来た、希望の朝だ</p>
</main>

</body>
</html>

jQueryの .css() という機能を使ってターゲットの CSS を書き替えています。
さて、この HTML をブラウザで読み込ませて JavaScript を実行させてみると何が起こるでしょうか?

実は、何も起こりません。


不思議ですね。
実は <head> タグの中に JavaScript を書いているがために、<body> タグ内のターゲット要素を見つけられないのです。

では、HTML が読み込まれる様子をスーパースローカメラでご覧いただきましょう。

f:id:todays_mitsui:20160903221102p:plain

f:id:todays_mitsui:20160903221117p:plain

f:id:todays_mitsui:20160903221141p:plain

f:id:todays_mitsui:20160903221150p:plain

いかがでしょうか?
このスクリプトが期待通りに動かない理由が分かりましたね。


きょう我々はとても重要なことを学びました。
スクリプトが実行されるとき、ターゲットが既にロードされているとは限らないのです。

「JavaScript は読み込まれた瞬間に実行される」とはそう言う意味です。
ブラウザはとてもせっかちなので、ターゲットのロードが完了していなくてもお構いなしです。スクリプトを見つけた瞬間に実行してしまいます。


そこで 起動スクリプトを囲っているアレ ですよ。


HTML の読み込みを待つ意義

反省会が終わったところで先ほどのコードを改良しましょう。

ターゲットのロードが終わってからjQueryのスクリプトが実行されるように、HTML の読み込み完了までスクリプトの実行を待ってもらえばいいのです。
こんなふうに、

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>起動スクリプト サンプルページ</title>

<script src="js/jquery.js"></script>
<script>
jQuery(function($) {

  // これで HTML の全体が読み込まれるまで
  // このスクリプトは実行されない。
  $('.target').css({
    color: 'red',
  });

});
</script>

</head>
<body>

<main>
  <h1>起動スクリプト サンプルページ</h1>
  <p class="target">新しい朝が来た、希望の朝だ</p>
</main>

</body>
</html>

再び HTML が読み込まれる様子を見てみましょう。


f:id:todays_mitsui:20160903221226p:plain

f:id:todays_mitsui:20160903221233p:plain

先ほどの反省を活かして HTML が全て読み込まれたあとでスクリプトを実行しています。
対象である <p class="target"> タグも今回はちゃんと見つかります。

いい感じですね。


「HTML の読み込み」「Window の読み込み」どう違う?

スクリプトの実行をあえて待つことの意義については分かってもらえたと思います。
では 囲っているアレ のパターン①とパターン②の違いについては大丈夫でしょうか?

  • パターン① - HTML が読み込まれるまで待つ
  • パターン② - Window が読み込まれるまで待つ

という違いでしたね。

「HTML の読み込み完了」と「Window の読み込み完了」はどう違うのでしょうか?


Window の読み込み完了は HTML の読み込み完了よりも時間がかかります

「Window の読み込み完了」は CSS や 画像 など 全てのファイルの読み込みが完了した瞬間を意味しているからです。

例えば、HTML を読み込んでいる最中に <link rel="stylesheet" href="style.css"> というような記述があったら、ブラウザはこの「style.css」を追加で読み込みます。
同じように <img src="cat.jpg" alt="My Kitten"> という記述を見つけたら、「cat.jpg」というファイルを追加でサーバーからもらってきます。

f:id:todays_mitsui:20160903221242p:plain

HTML を最終行まで読み終わっても、その中で見つけた CSS や 画像 のダウンロードが完了していなければ Window の読み込み完了とは見なされません。
全ての CSS と全ての画像が読み込まれて、ブラウザ上でページが完全に出来上がった瞬間が「Window の読み込み完了」です。


先ほどは HTML の読み込み完了まで待てば対象の要素が読み込まれているであろうと考えました。

では仮に 対象の画像 を操作するようなスクリプトを実行したいときは?
HTML 読み込み完了時点で 対象の画像 がロードされているとは限りませんよね。その場合は Window の読み込みが完了するまで待ってスクリプトを実行するべきかもしれません。


まとめ

さて 起動スクリプトを囲っているアレ について長々と解説してきました。
今回のダイジェストです。

  • 囲っているアレ は HTML や Window の読み込み完了を待つ
  • スクリプト実行時にターゲットの読み込みが終わっているとは限らない
  • Window の読み込みは HTML の読み込みよりも時間がかかる

これさえ分かればもう怖くないですね。

<script>
// ========= パターン① ========

// その1
$(document).ready(function() {
  /*
   * ここに書かれたスクリプトは
   * HTML の読み込み完了を待って実行される
   * */
});

// その2
jQuery(document).ready(function() {
  /*
   * ここに書かれたスクリプトは
   * HTML の読み込み完了を待って実行される
   * */
});

// その3
$(function() {
  /*
   * ここに書かれたスクリプトは
   * HTML の読み込み完了を待って実行される
   * */
});

// その4
jQuery(function($) {
  /*
   * ここに書かれたスクリプトは
   * HTML の読み込み完了を待って実行される
   * */
});

// ========= パターン② ========

// その5
$(window).load(function() {
  /*
   * ここに書かれたスクリプトは
   * Window の読み込み完了を待って実行される
   * */
});

// その6
jQuery(window).load(function() {
  /*
   * ここに書かれたスクリプトは
   * Window の読み込み完了を待って実行される
   * */
});
</script>

たったのこれだけ!

どれを使えばいいか迷います?
ひとまずは、その4 と その6 を使い分ければ大丈夫ですよ。


私からは以上です。



前回と前々回のリンクを貼っておきます。

blog.mudatobunka.org

blog.mudatobunka.org

Scrapy のクローリング中に win32api が無くてコケる問題に対処(Windows10, 64bit, Python2.7)

f:id:todays_mitsui:20160827190511p:plain


昨日は Windows で Scrapy 1.1.2 をインストールするために必要な libxml2 のインストールについて解説しました。

blog.mudatobunka.org

が、どうやら Windows ではクローリングを実行するときにもう一つ win32api というライブラリが必要になるようです。
win32api が無いと、クローリングの実行中にコケます...。


状況再現

ひとまず適当な Spider を書いて走らせてみましょう。
月並みですが CNN.co.jp の記事一覧ページ から記事タイトルと URL を抜き出す Spider を書きます。

example/spiders/test.py

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

import scrapy


class TestSpider(scrapy.Spider):
    name = "test"
    allowed_domains = ["cnn.co.jp"]
    start_urls = (
        "http://www.cnn.co.jp/archives/",
    )

    def parse(self, response):
        articles = response.xpath("//ul[contains(@class,'story-list')]//a")

        for article in articles:
            title = article.xpath("./text()").extract_first()
            title = title and title.strip()

            url = article.xpath("./@href").extract_first()
            url = response.urljoin(url)

            yield {
                "title": title,
                "url": url,
            }

このように。


で、これを scrapy crawl test で走らせると、

f:id:todays_mitsui:20160828140247p:plain

f:id:todays_mitsui:20160828140254p:plain

コケます。


ImportError: No module named win32api」 というエラーが出ているので、まぁ、win32api が無いんでしょう。


この件については Scrapy の公式ドキュメントの FAQ に情報がありました。

Scrapy crashes with: ImportError: No module named win32api
You need to install pywin32 because of this Twisted bug.

はい、と言うわけで Twisted のバグに対処するために win32api が必要になってるみたいですね。
確かに以前のバージョンではこんなエラー出てなかったもんなぁ!

pywin32 をインストールしてくれと書かれているので、しましょう。


pywin32 をダウンロード

pip で簡単にインストールできないかとやってみたのですが、出来ず。
どうやら Windows 用に exe ファイルが配布されているようです。

こちらで配布されています。

なんかいきなり SourceForge が出てくると不安になるんですけど、どうやら公式の配布元みたいです。


README にも書かれているとおり最新のビルド(Build 220)のディレクトリの中からマシンやPythonのバージョンに合った exe ファイルをダウンロードします。
私は「pywin32-220.win32-py2.7.exe」を使いました。


pywin32 をインストール

exe ファイルなので普通にダブルクリックするだけでインストーラーが立ち上がるんですが、その方法だと virtualenv 環境から参照できません。
virtualenv 環境下にインストールするには、virtualenv 環境に入った状態で easy_install を使います。

env\Scripts\activate
easy_install pywin32-220.win32-py2.7.exe

すると、

f:id:todays_mitsui:20160828143045p:plain

f:id:todays_mitsui:20160828143052p:plain

上手く行ってそうですね。


再びクローリングを実行

気を取り直して scrapy crawl します。

scrapy crawl -o result.json test

今度は無事にクローリングが進んで結果が result.json という名前で保存されます。

result.json

[
  {
    "url": "http://www.cnn.co.jp/world/35088116.html",
    "title": "ボリビアの鉱山スト 交渉役の次官が撲殺、5人を逮捕"
  },
  {
    "url": "http://www.cnn.co.jp/usa/35088113.html",
    "title": "車のトランクから現金300万ドル押収 米・メキシコ国境"
  },

  /* ... 中略 ... */

  {
    "url": "http://www.cnn.co.jp/usa/35088101.html",
    "title": "ネオコン代表格、クリントン氏に投票を検討 「トランプ氏は危険」"
  }
]

データ、取れてます。

もう安心、あなたの顔が見えたから(2回目)


私からは以上です。

Scrapy インストール中に libxml2 が無くてコケる問題に対処(Windows10, 64bit, Python2.7)

f:id:todays_mitsui:20160827190511p:plain


Scrapy を最新版の v1.1.2 にしたくて pip install scrapy したらインストール中にコケました。

python -m virtualenv env
env\Scripts\activate
pip install scrapy

とやっても...

f:id:todays_mitsui:20160827175447p:plain

f:id:todays_mitsui:20160827175453p:plain

コケる。


なにやら libxml2 というライブラリが見つからないと言われています。

c:\windows\temp\xmlXPathInitu5z5db.c(1) : fatal error C1083: Cannot open include
file: 'libxml/xpath.h': No such file or directory

*********************************************************************************
Could not find function xmlCheckVersion in library libxml2. Is libxml2 installed?
*********************************************************************************

対処しましょう。


lxml をインストール

こちらのサイトを見ますと...

「pip での lxml のインストールが失敗した時は Windows 用のビルド済みバイナリを使ってくれ。非公式だけどね」と書いてあります。
なのでリンク先に飛んで Wheel ファイルをダウンロードします。

今回は「lxml-3.6.4-cp27-cp27m-win32.whl」を落としてきました。


では、コマンドプロンプトに戻りまして。

pip install lxml-3.6.4-cp27-cp27m-win32.whl

インストール実行!

f:id:todays_mitsui:20160827182446p:plain

はい、上手くいきました。

ここでもインストールに失敗する場合は別の Wheel ファイルを試してみてください。
Windows の 32bit/64bit の違い、Python のバージョンの違いによっても落としてくるべきファイルが違います。


再び Scrapy をインストール

lxml が無事インストールできたら、先ほどコケた Scrapy のインストールを再び行います。

pip install scrapy

今度は滞りなくうまくインストール完了しました。
バージョンは最新の v1.1.2 (2016年8月時点)。

f:id:todays_mitsui:20160827183739p:plain

もう安心、あなたの顔が見えたから


私からは以上です。


20160828 追記

Windows で Scrapy を利用するときにはもう一つ win32api というライブラリが必要になるようです。
こちらは pip install scrapy するだけでは入らないので別途インストール作業が必要です。

別記事を書きました。こちらも参照してください。

blog.mudatobunka.org