【PhantomJS】ajaxなページをスクレイピングする方法メモ

やりたかったこと

今回やりたかったのはjavascriptでコンテンツを表示するページの解析。
今までもたまにスクレイピング用のスクリプト作ったことはあったけど、wgetphpのfile_get_contentsで取得したhtmlを解析するようのものしかなかった。

ちょっと調べてみるとPhantomJS使う方法とSeleniumを使う方法がたくさん出てきた。
今回はページを読み込むとjavascriptが動いてhtmlを生成するようなページが対象で、やりたいことも簡単だったのでより導入が簡単そうだったPhantomJSを使ってみた。

実装

まずはPhantomJSのインストール。node.jsは入っている前提で。

$ npm install phantomjs
・・・
$ ./node_modules/phantomjs/bin/phantomjs -v
2.1.1

スクレイピング対象のテストサイトを用意

index.html

<!DOCTYPE html>
<head><title>テスト</title></head>
<body>
  <div id="content">読み込み中...</div>
  <script src="./index.js"></script>
</body>
</html>

index.js

window.addEventListener("load", function() {
    setTimeout(function() {
        document.getElementById("content").innerHTML =
            "1,田中<br/>" +
            "2,高橋<br/>" +
            "3,亀井";
    }, 3000);
});

ページが読み込まれてから3秒後にHTMLを表示するようなページ。
実際はajaxでデータを取得してくるイメージだけど今回はこれで。

次は本題の取得用スクリプト
まずは上手くいかないやつ

get.js

var page = require('webpage').create();
var url = "http://example.com/index.html";

page.open(url, function() {
    console.log(page.content);
    phantom.exit();
});

実行すると取得したいものと違う

$ ./node_modules/phantomjs/bin/phantomjs get.js
<!DOCTYPE html><html><head><title>テスト</title></head>
<body>
  <div id="content">読み込み中...</div>
  <script src="./index.js"></script>
</body></html>

修正版

var page = require('webpage').create();
var url = "http://example.com/index.html";

page.open(url, function() {
    // ★5秒後にページを取得する形に変更
    setTimeout(function() {
        console.log(page.content);
        phantom.exit();
    }, 5000);
});

実行

$ ./node_modules/phantomjs/bin/phantomjs get.js
<!DOCTYPE html><html><head><title>テスト</title></head>
<body>
  <div id="content">1,田中<br>2,高橋<br>3,亀井</div>
  <script src="./test3.js"></script>
</body></html>

取得できた!以上です

【cakephp】updateAllを呼んでもデータが更新されなかった原因メモ

あまり使ったことのないcakephpの改修でハマった。

更新されない

$this->MyModel->updateAll(array(
  "text" => "text",
), array(
  "id" => 1,
));

文字列はクォートで囲むと更新される

$this->MyModel->updateAll(array(
  "text" => "'text'",
), array(
  "id" => 1,
));

orz...

あと、カラム名違うとエラーにならずに条件にヒットしない挙動なんですね。
しかも条件の方はクォートくくらないと更新された。

けっこう古めのバージョンなので今のバージョンだとどうなるかわかりません。。。
以上です

【css】javascriptで挿入した要素のz-indexが効かなかった時の原因と対応メモ

css詳しくないのに見よう見まねで書いててjavascriptで生成した要素のz-indexがどうにも効かなくてハマりました。
そのときの対応をメモ。

ググってるz-index当てる要素はpositionがrelativeかabsoluteじゃないといけないって記事があってコレが原因って線でずっと調べてたけど全然直らなくて途方にくれました。
結論からいうと対象の要素の親要素にz-indexが当たっててそのz-indexの方が大きかったということでした。。。

上手く動かないコード

<div style="z-index:1000">
  <!-- javascriptで動的に挿入した要素 -->
  <div style="z-index:1001">要素B</div>
</div>

<!-- javascriptで動的に挿入した要素 -->
<div style="z-index:1000">要素A</div>

わかりずらいですが要素Aをページ全体に置いて、その上に要素Bを重ねたいというのがやりたいことでした。
要素Bを入れる要素の親にz-indexがセットされていてそれが要素Aのz-indexよりも大きかったので表示されない(というか見えない)というオチでした。

修正したコード

<div style="z-index:1000">
</div>

<!-- javascriptで動的に挿入した要素 -->
<div style="z-index:2000">要素A</div>

<!-- javascriptで動的に挿入した要素 -->
<div style="z-index:2001">要素B</div>
まとめ

何が言いたいかわかりずらくなったけど、今回は不特定多数の人にタグを入れてもらってjavascriptでdomを挿入するような機能でした。
同じようなことでハマっている人がいたら参考になったら幸いです。てかまた忘れた頃に同じ事やりそう。。。以上です

【android】ActionBarの背景色とタイトル文字色を変更する方法メモ

背景色を定義
res/drawable/actionbar_background.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
    <solid android:color="#FFFFFF"></solid>
</shape>

res/values/styles.xml

<resources>
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>

    	  <!-- ActionBarのスタイル設定 -->
        <item name="android:actionBarStyle">@style/CustomActionBar</item>
        <item name="actionBarStyle">@style/CustomActionBar</item>
    </style>

    <!-- 背景色の設定とタイトルカラーのスタイル設定 -->
    <style name="CustomActionBar" parent="@style/Widget.AppCompat.Light.ActionBar.Solid.Inverse">
        <item name="android:background">@drawable/actionbar_background</item>
        <item name="background">@drawable/actionbar_background</item>
        <item name="titleTextStyle">@style/titleTextStyle</item>
    </style>

    <!-- タイトルカラーの設定 -->
    <style name="titleTextStyle" parent="TextAppearance.AppCompat.Widget.ActionBar.Title">
        <item name="android:textColor">#000000</item>
    </style>
</resources>

色変えたいだけなのにandroid難しい。。。以上です

参考URL
https://teratail.com/questions/4916

wgetでページ丸ごと取得するコマンドメモ

たまに調査とかで別のサイトをダウンロードしてサイトが見たいことがある。
毎回調べてるのでコピペ用にコマンドをメモしておく。

# iPhone
$ wget -p -H -E http://example.com/ --user-agent="Mozilla/5.0 (iPhone; CPU iPhone OS 9_3_2 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13F69 Safari/601.1"

# Android
$ wget -p -H -E http://example.com/ --user-agent="Mozilla/5.0 (Linux; Android 6.0.1; 404SC Build/MMB29K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36"

いちおう使ってるオプションもメモ

  • p = 全てのリソース取得
  • H = 別ホストのリソースも取得
  • E = 拡張子はhtmlで保存

【onsenui】無限スクロールを実装する

はじめに

今回やりたかったのはよくある無限スクロール。
リストの1番下までスクロールしたら次のデータを取得しにゆきます。
これをAngular + Onsen UIで実装したのでメモしておく。

f:id:yoppy0066:20170214111121g:plain:w250

データ数の多いリストはパフォーマンス的にons-lazy-repeatを使うとよいらしい。
そちらについてはこちらでも試してみた。

実装

テンプレート

HTMLはこんな感じで実装。ons-lazy-repeatを使った基本的な形。
リストの最下部に読込中アイコンを置いて、状態によって表示・非表示を切り替えるようにする。

<ons-page ng-controller="MainCtrl">
  <ons-toolbar>
    <div class="center">リスト</div>
  </ons-toolbar>
  <ons-list>
    <ons-list-item ons-lazy-repeat="delegate">
      {{ item.name }}
    </ons-list-item>
  </ons-list>
  <ons-row ng-if="progress">
    <ons-col style="text-align:center;">
      <ons-icon size="30px" spin icon="md-spinner"></ons-icon>
    </ons-col>
  </ons-row>
</ons-page>
メイン処理

必要な処理としては以下
APIに接続してデータを取得してくる
・1番下までスクロールしたらAPIに接続して次のデータを取得しにいく

1番下までスクロールしたかの判定はこんな感じ
.page__contentを対象にするのがポイントぽい

angular.module('sampleListApp')
    .controller('MainCtrl', function () {

        $('.page__content').on('scroll', function(e){
            var elm = $(e.currentTarget);
            if (elm[0].scrollHeight <= elm.height() + elm.scrollTop()) {
              // ココに1番下までスクロールした時の処理を記述
            }
        });

次はAPI接続部分
適当だけどAPIからはこんな感じのJSONが返ってくる想定

{
  contents: [
    {id:1, "アイテム1"},
    {id:2, "アイテム2"},
    {id:3, "アイテム3"},
    ・・・
  ]
}

取得部分をfactoryとして外だし

angular.module('SampleApp')
    .factory("ApiManager", function($http) {
        return {
            callApi: function(offset, callback) {
                $http({
                    method: 'GET',
                    url: "http://path/to/api?offset="+offset
                }).then(function successCallback(response) {
                    callback(respons.contents);
                }, function errorCallback(response) { });

            }
        };
    });

で、完成形はこんな感じに

angular.module('sampleApp')
    .controller('MainCtrl', function ($scope, $http, ApiManager) {

        $scope.items = [];
        $scope.progress = true;

        ApiManager.callApi(0, function(contents) {
            $scope.items = contents;
            $scope.progress = false;
        });
        $('.page__content').on('scroll', function(e){
            var elm = $(e.currentTarget);
            if ($scope.progress == false &&
                elm[0].scrollHeight <= elm.height() + elm.scrollTop()) {
                $scope.progress = true;
                ApiManager.callApi($scope.items.length, function(contents) {
                    for (var i = 0; i < contents.length; i++) {
                        $scope.items.push(contents[i]);
                    }
                    $scope.progress = false;
                });
            }
        });

        $scope.delegate = {
            configureItemScope: function(index, itemScope) {
                itemScope.item = $scope.items[index];
            },
            countItems: function() {
                return $scope.items.length;
            }
        };
    });

また、ずらずらかいたけど以上です。