Chrome Extention作成時に個人的に詰まった所と対策
Extentionのアップロード時に「An error occurred: please try again later.」というエラーメッセージが出る
知らないうちに、Googleのログイン状態が解除されたことが原因。また、Googleにログインしてから、アップをやり直す。
Extentionをアップロードすると「Pending」という状態になる。そして、アップロードはできるが、Extentionのページからインストールをしても古いバージョンのままになる
作成したExtentionにローカルファイルへのアクセス権限を与えた(manifest.jsonで、permissionsにfile:/*/*がある)ことが原因。
現状では、ローカルファイルへのアクセスを行うExtentionは、人手で悪質なプログラムでないかを判断した後に配布を許可する模様。どうしても必要というわけでないなら、ローカルファイルへのアクセス権限をExtentionに与えない。
Background PageからExtentionのポップアップを開く方法について
現状、その方法はない。そのうち、用意してくれるっぽい。
chrome.tabs.updateやchrome.tabs.executeScriptを呼ぶとクラッシュする
prototype.jsをソースにインクルードしていることが原因かも。prototype.jsを利用しないで実装する。(クラッシュ時に特にエラーメッセージもでないので、3時間くらいクラッシュ原因が分からず苦労した。)
Content Scriptsでのクロスドメイン通信
Contents Scripsとクロスドメイン通信
ChromeのExtentionの実装方法の一つに、Content Scriptsというものがあります。一言で言えば、FirefoxのGreasemonkeyのChrome版です。Content Scriptsを使用すると、任意のWebページに対して、JavaScriptを実行することができます。これにより、本来Webページにはない機能をWebページに追加することができます。
ChromeのExtentionでは、普通のWebページ内のJavascriptとは違い、クロスドメイン通信が可能です。
BackgroudPagesやポップアップなどからクロスドメイン通信をする場合は、XMLHttpRequestなり、jQueryやprototypeなどで用意されているHttpRequest用のオブジェクトを使用すれば可能です。
一方、Contents Scriptsから直接クロスドメイン通信をすることはできません。しかし、BackgroundPagesを用意し、通信自体はBackgroudPagesに行わせるという回りくどい方法で実現可能です。
具体的には、Content Scriptsからchrome.extention.sendRequestという関数を呼び出し、httpリクエストのパラーメータとレスポンスを処理するコールバックをBackgroundPagesに送ります。その後、BackgroundPagesは
クロスドメイン通信を行い、そのレスポンスをContentScriptsから受け取ったコールバックに渡して呼び出します。この一連の流れを下に図示しておきます。
ContentScriptsによるクロスドメイン通信概要図
以下では、Google検索時にYahooによる検索も同時に行い、Googleの広告が表示される部分にYahooの検索結果を埋め込むという簡単なExtentionを例に、Contents Scriptsからのクロスドメイン通信を説明してみます。
サンプルのソースコード
(ソースコード中のYahoo検索 APIを利用するためのアプリケーションIDを記述している箇所を変更しなければ動きません)
Content Scriptsによるクロスドメイン通信のサンプルExtention
manifest.json
1 { 2 "name": "Double Search", 3 "version": "1.0", 4 "description": "Double Search", 5 "background_page": "background.html", 6 "content_scripts": [ 7 { 8 "matches": ["http://www.google.co.jp/search*", "http://www.google.co.jp/search*"], 9 "js": ["prototype.js", "contentscript.js"], 10 "run_at" : "document_end" 11 } 12 ], 13 "permissions": [ 14 "http://search.yahooapis.jp/WebSearchService/V1/webSearch*" 15 ] 16 }
5行目でクロスドメイン通信を行うBackgroundPagesとして「background.html」を指定しています。
6行目から12行目がContentScriptsを設定する上で重要な箇所です。7行目のmatchesでContentScriptsを動作させるサイトを指定しています。8行目で使用するJavascriptファイルを指定しています。ここで記述した順番にロードされます。contentscript.jsではJavascriptライブラリのprototype.jsを利用するので、contentscript.jsより先にprototype.jsを記述しておきます。9行目では動作タイミングを指定しています。
13行目から15行目でクロスドメイン通信を行うサイトのURL(この例では、Yahoo検索 APIのURL)を指定しています。
contentscript.js(重要部分のみ)
13 function searchByYahoo() { 14 var query = getQuery(); 15 16 chrome.extension.sendRequest(query, function(res) { 17 console.log("get response"); 18 console.log(res); 省略 30 }); 31 }
14行目で、Google検索の検索結果ページからクエリを取得しています。
16行目では、chrome.extention.sendRequestを呼び出すことで、BackgroundPages(この例では「background.html」)へYahoo検索に使用するクエリと、Yahooの検索結果を処理するコールバックを渡しています。
background.html
1 <html> 2 <head> 3 <script type="text/javascript" src="prototype.js"></script> 4 <script> 5 chrome.extension.onRequest.addListener(function(request, sender, sendResponse) { 6 console.log(sender.tab ? "from a content script:" + sender.tab.url : "from the extension"); 7 8 new Ajax.Request("http://search.yahooapis.jp/WebSearchService/V1/webSearch", { 9 method : "get", 10 parameters : {"query" : request, "appid" : "appid"}, 11 onSuccess : function(res) { 12 sendResponse(res.responseText); 13 }, 14 onFailure : function(res) { 15 sendResponse(null); 16 } 17 }); 18 }); 19 </script> 20 </head> 21 <body></body> 22 </html>
ここで一番重要なのは、5行目で呼び出している関数chrome.extention.onRequest.addListenerです。この関数では、ContentScriptsからsendRequestを呼び出したときに呼び出すコールバックが引数となります。このコールバックの第一引数requestは、ContentScriptsから送られてきたメッセージ(chrome.extention.sendRequestの第一引数)、第3引数sendResponseは返信を処理するコールバック(chrome.extention.sendRequestの第三引数)となります。
8から17行目で、実際にクロスドメイン通信を行っています。12行目で、sendResponseを呼び出して、クロスドメイン通信の相手サイトからのレスポンスの内容をContentScriptsへ送っています。ここではレスポンスのオブジェクトそのものではなく、テキストレスポンスを渡しています。これは、sendResponseには、JSONオブジェクトかundefiedしか引数として渡せないためです。もし、XMLレスポンスなどを渡すとsendResponseは動作しません。ここで、私は1時間くらいはまりました)。
最後に
特になし
Google Web履歴からWeb活動履歴をダウンロード(Cookieによる認証版)
はじめに
以前公開した、Web活動履歴ダウンロードプログラムに不具合が発生しました。その原因は、Cookie認証を利用していないことでした。そこで、プログラムをCookie認証するように変更しました。
・注意事項
・12月7日以降の修正
- 12月9日:履歴中にvideo resultなどが含まれるときにエラーになる不具合の修正
発生した問題
以前に記載したWeb活動履歴(以下、単に履歴)のダウンロードプログラムが、気づいたら502エラーを吐いて動作しなくなっていたことに気付いた。
よくよく調べてみたら、Linuxのwgetコマンドを使用した場合でも、下記のように502エラーと401エラーを吐いて、履歴のRSSが取得できなくなっていた。
wgetコマンドによる履歴の取得
$ wget --http-user=username --http-passwd=passwd 'https://www.google.com/history/lookup?hl=ja&output=rss' --2009-12-08 00:57:16-- https://www.google.com/history/lookup?hl=ja&output=rss www.google.com をDNSに問いあわせています... 66.249.89.99, 66.249.89.103, 66.249.89.104, ... www.google.com|66.249.89.99|:443 に接続しています... 接続しました。 HTTP による接続要求を送信しました、応答を待っています... 401 Unauthorized www.google.com:443 への接続を再利用します。 HTTP による接続要求を送信しました、応答を待っています... 502 Bad Gateway 2009-12-08 00:57:17 エラー 502: Bad Gateway。
原因考察
以前のダウンロードプログラムとwgetコマンドともに、履歴のRSSを取得できなくなった。しかし、Webブラウザ上では何事もなく履歴のRSSを閲覧することはできる。この2つの違いは、おそらく認証方法です。前者はCookieを利用せず、Didgest認証をしています。後者はCookieを利用した認証をしています。
以上のことから考えて、今回の不具合の原因は、Googleが主な認証方式として、Cookie認証を利用するためと考えました。
実は当初から、10月30日のダウンロードプログラムは、402エラーを吐くときがしばしば起こっていました。この原因は、基本的にはGoogleは認証方式としてCookie認証を利用しているものの、ごく一部のGoogleのサーバでは、Didgest認証も許可していたためと考えられます。
Cookie認証の利用
履歴をGoogleから取得する前に、Cookie認証を行うように、ダウンロードプログラムを変更しました。
変更の味噌である箇所を解説します。
冒頭部分
import lxml.html import fromstring import cookielib
まずは、htmlやxmlのパーサーのlxmlと、cookieを利用するためにcookielibをインポートします。cookielibはpythonの標準ライブラリですが、lxmlについては、別途導入が必要です。
Cookie認証部
def certifyByCookie(user, passwd): cj = cookielib.CookieJar() opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj)) #プロキスを使用する場合 #proxySupport = urllib2.ProxyHandler({'プロキシサーバのスキーム(httpなど)': 'プロキシサーバのURL'}) #opener = urllib2.build_opener(proxySupport, urllib2.HTTPCookieProcessor(cj)) # #認証時にPOSTするデータとCookieを取得 # url = "https://www.google.com/accounts/ServiceLogin?hl=ja&nui=1&service=hist" req = urllib2.Request(url) req.add_header("User-Agent", "Mozilla/5.0") pagehandle = opener.open(req) doc = fromstring(pagehandle.read()) #認証ページ中のinputタグの値を全て取得 data = dict([(input.name, input.value) for input in doc.xpath('//input')]) #ユーザIDとパスワードを設定 data["Email"] = user data["Passwd"] = passwd # #実際に認証 # url = "https://www.google.com/accounts/ServiceLoginAuth?service=hist" #OSがWindowsであり、ここでエラーが出る場合、コメントアウト #data["signIn"] = u"ログイン".encode("sjis") req = urllib2.Request(url, urllib.urlencode(data)) req.add_header("User-Agent", "Mozilla/5.0") pagehandle = opener.open(req) return opener
ここでは、Webブラウザの行うGoogle Web履歴で認証と同じ処理を行っています。
- 認証時にPOSTするデータ(data)とCookie(openerが内部的に保持)を取得
- 認証ページにアクセス
- 認証ページのhtmlをパースして、inputタグの値を取得
- ユーザIDとパスワードを設定
- 実際に認証
後は、このcertifyByCookieが返すopenerを利用して、以前と同じようにGoogle Web履歴にRSS取得するためのHttpリクエストを発行することにより、不具合は修正なくなりました。
Google ブックマークのChrome用のアドオンを作ってみた2
Google ブックマークのChrome用のアドオンを作ってみた
Google ブックマークのChrome用のアドオンを作成したので、それを公開して見ます。機能は非常にシンプルで以下の2つしかありません。
- ブックマーク登録機能
- ブックマーク参照機能
アドオンの入手は下のページからできます。
Google ブックマークのChrome用のアドオン
Windows環境下でlxml+python(lxmlのバイナリは未使用)
はじめに
最近、pythonでHTMLテキストをパースすることが必要になった。当初、HTMLパーサとして、BeautifulSoupを利用していた。しかし、しばしばある特定のWebページのパース時に、HTMLParseErrorという例外が発生した。そのため、他のpython用のHTMLパーサを調べた所、lxmlというものがあることを知った。そこで、このlxmlをWindows環境下でインストールしようとしたのだが、かなり手間取った。
この記事では、Windows環境下で、lxmlのインストーラを使用しないで、lxmlをpythonから利用可能にする手順を説明する。
lxmlのインストールが完了した時点で気付いたのだが、最新でないバージョンのlxmlにはWindows用のインストーラが配布されている。そちらを利用すれば、この記事で説明するような複雑な手順なしにlxmlをインストールできると思われる。
前提
- OSはwindows vista
- pythonのバージョン:2.6
lxmlのインストール
easy_installを既に導入している場合は、単純にコマンドライン上から「easy_install lxml」と実行します。
easy_installが未導入の場合は、まずは上記のlxmlのページから「lxml2.2.3.tar.gz」をダウンロードします。このファイルを解凍したら、コマンドライン上で、解凍場所まで移動し、「python setup.py install」と実行します。
vcvarsall.batが見つかりませんというエラーメッセージが出た場合
原因はVisual Studio C++2008がを入れていないためです。なので、Visual Studio C++2008を入れます(Visual Studio C++2005では駄目です)。
libxml/xmlversion.hが見つかりませんというエラーメッセージが出た場合
lxmlのインストールに必要なライブラリlibxml2がlibxslt存在しないためです。libxml2は、iconvとzlibというライブラリも使用しています。したがって、libxml2、libxslt、iconv、zlibの4つのライブラリのWindows用バイナリをこちらのサイトからダウンロードします。
各ライブラリは、bin、include、libの3つのフォルダから構成されています。環境変数を設定する手間を考えて、4つのライブラリの中身を1つのフォルダにまとめる。このフォルダのフルパス名を仮に「my」とします。そして、環境変数のLIBに「my/lib」を、INCLUDEに「my/include」を追加します。必須ではないですが、各ライブラリ内のコマンドを手軽に使用したい場合は、環境変数PATHに「my/bin」を追加します。
この設定までを済ませれば、lxmlのインストールは成功するはずです。
lxmlのインストール後の設定
とりあえず、lxmlがインストールできたかを確認するために、pythonインタプリンタ上から、以下のコマンドを実行します。
from lxml import etree
エラーにならなければ、インストール完全成功です。
Google Web履歴のRSSの仕様
リクエスト時のパラメーター
単に履歴を取得する場合と、履歴を検索する場合とでリクエスト先のURLや使用可能なパラメータが異なる。
単に履歴を取得する場合
URL:http://www.google.com/history/lookup?output=rss&lr=lang_ja&hl=ja
パラメータ | 意味 |
---|---|
max | 取得する履歴の日時の上限値(ローカル時間から求めたエポック時間) |
min | 取得する履歴の日時の下限値(ローカル時間から求めたエポック時間) |
num | 取得する履歴数(指定しなければ20?) |
start | 取得する履歴のオフセット |
yr | 年 |
day | 日付 |
month | 月 |
output | 履歴の出力形式。現在指定できるのはrssのみ |
lr | 言語関係。lang_jaに指定してしておくのが無難 |
hl | 言語関係。jaに指定しておくのが無難 |
st | 取得する履歴の種類(web,img,ad,fg,blogs,news,bookds,vid) |
履歴を検索する場合
URL:http://www.google.com/history/find?output=rss&lr=lang_ja&hl=ja
パラメータ | 意味 |
---|---|
q | 検索クエリ。指定されていない場合は、単に履歴を取得 |
sort | 検索結果の表示順。dateを指定すると日付順、指定しなければ関連度順 |
(単に履歴を取得するときのパラメータはほぼ使用可能)
注意点
maxとminを同時に指定しない方がよいかも。
それは、maxとminで指定した時間の範囲内に含まれる履歴数がnum以下の場合、numに足りない分だけ最新の履歴データが出力されるためである。