使用Instagram API

这里描述的是官方公布的API,而不是通过hack得到的api。
有兴趣可以去这里注册 http://instagr.am/developer/ 。可能会有一个审核激活的过程,大约要几天的时间。
完成后会收到一封邮件,然后就能登录查看文档了。但这个文档似乎暂时并不是很丰富,因为我没找到用于上传的api。

这里主要写了一些使用上的注意事项。

1、外链图片
GET http://instagr.am/p/{shortcode}/media
它接受一个参数size,t表示缩略图 m表示中等, l表示最大,默认为m。
假设有链接 http://instagr.am/p/BSJRn/ 则可以:
<img src="http://instagr.am/p/BSJRn/media?size=m" />
效果:


2、shortcode 转换成 media-id
shortcode 就是 http://instagr.am/p/BSJRn/ 里边的 'BSJRn'
截止目前,我在那个文档中并没有找到有关如何将shortcode 转化成 media-id 的描述。但是通过一些猜测和验证,很容易就会得出正确的算法。
很简单,实际上shortcode就是一个64进制数字,解码的过程就是64进制转化成10进制的过程。
代码:

#A代表0,B代表1,..., _代表63
_chars='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'
_chars_num=len(_chars)

def deshortcode(t):
    t=str(t)
    n,i=0,len(t)-1
    for c in t:
        if c not in _chars: return 0
        n+=(_chars.index(c)*pow(_chars_num, i))
        i-=1
    return n

print deshortcode('BSJRn')
# 21533799

3、参数中的 redirect_uri 必须与注册中的 CALLBACK URL 相同
这一点是跟Twitter API 不同的地方,在注册Twitter客户端时,那个CALLBACK URL可以随便写;而Instagram 则必须跟注册时的相同,否则会硬邦邦的告诉你这样不行。
你可以点击 这里 可以注册一个 Instagram 客户端,不过你必须首先通过之前所说的那个验证才行。

4、验证过程比Twitter API简单得多
不用什么OAuth库,也不用什么参数签名(大概这就是OAuth2.0吧?)。直接通过https获得access_token,然后用这个access_token就可以做任何事情了。特别简单。文档中都有描述这里不多说了。 More

使用W3C Geolocation API

一、浏览器支持状况
IE 9.0+
Firefox 3.5+
Safari 5.0+
Chrome 5.0+
Opera 10.6+
iPhone 3.0+
Android 2.0+

二、geolocation.getCurrentPosition()的用法

/* 获取信息成功时的处理函数 */
function successHandler(pos){
	/*
	* pos是这样的一个对象:
	* {
	* 	coords:{
	*		latitude:纬度
	* 		longitude:经度
	* 		altitude:海拔
	* 		accuracy:精度
	* 		altitudeAccuracy:海拔精度
	* 		heading:设备的移动方向(0°≤heading<360°)
	* 		speed:设备的移动速度
	*	},
	* 	timestamp:1298477342721500
	* }
	*/
}

/* 错误处理函数 */
function errorHandler(e){
	switch(e.code){
		case e.PERMISSION_DENIED:
			//用户或浏览器拒绝提供地理位置时
			break;
		case e.POSITION_UNAVAILABLE:
			//获取位置失败时
			break;
		case e.TIMEOUT:
			//获取位置超时
			break;
	}
	// e.message 是出错信息文字。
}

/* 选项 */
var options = {
	//超时时间(毫秒)
	timeout:5000,
	//位置信息的最长缓存时间(毫秒)
	maximumAge:9999,
	//是否启用高精度定位
	enableHighAccuracy:true
}

//判断浏览器是否支持Geolocation API
if(navigator.geolocation){
	//getCurrentPosition接受3个参数,其中后边两个是可选的
	navigator.geolocation.getCurrentPosition(
		successHandler,
		errorHandler,
		options
	);
}

三、geolocation.watchPosition()与clearWatch()的用法
watchPosition(),它一般用在移动设备上,用于连续追踪位置信息,每次位置更新时都会调用一次successHandler。

if(navigator.geolocation){
	//watchPosition()接受3个参数,其中后边两个是可选的,与getCurrentPosition()相同。
	//调用完成后会立即返回一个数字id,与setTimeout()相似。
	var watch_id = navigator.geolocation.watchPosition(
		successHandler,
		errorHandler,
		options
	);

	//解除追踪,watch_id就是watchPosition()所返回的那个数字。
	clearWatch(watch_id);
}

参考:
  1. http://www.w3.org/TR/geolocation-API/
  2. http://diveintohtml5.org/geolocation.html
  3. http://www.jsmix.com/html5/try-out-geolocation.html
More

破解 Twitter Search API 的请求限制

You have been rate limited. Enhance your calm.

在Google App Engine上使用过Twitter Search API 的人可能会经常遇到这个头疼的问题,就是API的请求限额经常被刷爆。

出现这个问题有两个原因:首先要从urlfetch的实现原理来看。因为GAE是个云平台,它将资源整合分配。每次通过urlfetch发出一个请求时,首先GAE会在IP地址池里取得一个IP,然后用这个IP访问网络。而IP地址池中的IP都是给所有应用共享的,你也用,我也用,大家都用。另外一个原因,就是Twitter Search API的请求策略是按照 每IP*小时(基于IP的请求限制) 计算,而不是REST API的按照 每用户*小时(基于认证的请求限制)计算。由于GAE发出请求的IP是所有应用共享的,所以给这些IP的请求限额会必然会被海量的应用连续不断的请求刷爆。

有人提出过给search api加入认证(authenticate)就可以实现 每(用户*小时) 的请求策略。但经过实践证明这样做是不行的。

这个问题困扰了我很久,搜索各种资料都没找到什么有用的结果。后来发现了一个一直没有注意到的现象,那就是twitter官网的新界面搜索功能特别好用,而且只要修改了hosts,就可以用https在墙内使用。可Search API所用的域名search.twitter.com没有https支持,而且我在hosts中也没有设置任何search.twitter.com这个域名的IP。那这个搜索结果是怎么传过来的呢? 如果twitter官网使用的是 search.twitter.com 这个域名作为搜索api的地址,那肯定search.twitter.com也支持https,但事实上是不行的。显然,twitter官网肯定使用了别twitter.com域名来搜索!

接下来就简单了,用Firefox/chrome打开https://twitter.com,观察网络活动。于是抓到了一个名叫 pheonix_search 的隐藏API。大概是这样的:


一、API地址
https://twitter.com/phoenix_search.phoenix


二、参数
q - 必须,要搜索的关键字。
since_id - 可选,返回结果的id应全都大于since_id。
page - 可选,这是一个很奇怪的参数,twitter用它来获取搜索结果的下一页。它本身又包含了很多其他参数。
include_entities - 可选,是否包含推文的“实体”。
contributor_details - 未知,在Httpfox中观察它的值一直是:true
domain - 未知,在Httpfox中观察它的值一直是:https://twitter.com
format - 未知,在Httpfox中观察它的值一直是:phoenix

详解page参数:
page是个很奇怪的参数,它本身又包含了很多其他参数(大致)。
   page - 这个不是上边说的page而是真正的页码。
   max_id - 返回结果应全都小于max_id
   rpp - 每页返回的推文个数
   q - 请求关键字
举例(注意这里page参数必须是urlencode过的):
page=%3Fpage%3D2%26max_id%3D44034426279165952%26rpp%3D20%26q%3Dtwitdao
解码后:
?page=2&max_id=44034426279165952&rpp=20&q=twitdao


三、返回数据
json格式,
{
	"statuses":[...], //推文实体数组
	"next_page":false, //下一页,也就是翻页时的page参数
	"error":null //出错信息
}


四、用法
假设要搜索 "twitdao" ,
第一次请求:
https://twitter.com/phoenix_search.phoenix?q=twitdao&format=phoenix
刷新(最新符合关键字的推文):
https://twitter.com/phoenix_search.phoenix?q=twitdao&since_id=43865302966075392&format=phoenix
翻页(更早符合关键字的推文):
https://twitter.com/phoenix_search.phoenix?q=twitdao&page=%3Fpage%3D2%26max_id%3D44034426279165952%26rpp%3D20%26q%3Dtwitdao&format=phoenix

实例代码:
http://code.google.com/p/twitdao/source/browse/trunk/twitter.py?spec=svn62&r=61#238


五、一些特性
1、可以通过加入认证提升Rate Limit
这也是本文主要的目的,就是破解 Twitter Search API 的请求限制。经过一番测试,在pheonix_search的请求中加入OAuth header后,果然可以从“基于IP的请求限制”转化成“基于认证的请求限制”!
2、返回真正的推文实体列表
pheonix_search API 的另外一个优点就是它返回的推文实体是完整的,与home_timline方法返回的推文实体相同。而普通Search API返回的推文实体并不是完整的,并且有些id数据可能会不正确。
Twitter Search API文档中的警告:
Warning: The user ids in the Search API are different from those in the REST API (about the two APIs). This defect is being tracked by Issue 214. This means that the to_user_id and from_user_id field vary from the actualy user id on Twitter.com. Applications will have to perform a screen name-based lookup with the users/show method to get the correct user id if necessary.


六、测试办法
如何知道一个API是“基于IP的请求限制”还是“基于认证的请求限制”呢?每次请求 twitter api 的时候都会返回几个有关请求限制(Rate Limit)的http首部,主要有:
X-RateLimit-Limit - 每小时限制请求数
X-RateLimit-Remaining - 剩余的请求数
X-RateLimit-Reset - 请求限制重置时刻

假设所有请求都用同一个IP。

1、首先,对api发出请求,假设返回:
X-RateLimit-Limit: 150
X-RateLimit-Remaining: 149
X-RateLimit-Reset: 1299344165

2、然后,换一个用户,再次对该api发出请求,
--若返回:
X-RateLimit-Limit: 150
X-RateLimit-Remaining: 148 (仍然在减少)
X-RateLimit-Reset: 1299344165 (注意这里不能变,如果变了要重新开始)
继续更换用户并发出请求,如果X-RateLimit-Remaining持续减少,并且X-RateLimit-Reset一直未变。
则该api请求限制是“基于IP的请求限制”。
--若返回:
X-RateLimit-Limit: 150
X-RateLimit-Remaining: 149 (重新开始计数)
X-RateLimit-Reset: 1299399168 (变了!)
继续更换用户并发出请求,如果X-RateLimit-Remaining按照的减少是基于用户的,并且每个用户都有自己独有的X-RateLimit-Reset。
则该api请求限制是“基于认证的请求限制”。


参考:
  1. https://dev.twitter.com/pages/rate-limiting
  2. https://groups.google.com/d/msg/google-appengine/okY7XSO3EFQ/37AvPss4K88J
  3. http://codeleaks.net/2010/08/19/%E8%AF%B4%E8%AF%B4-twitter-for-iphone-%E7%9A%84-n-%E5%AE%97-%E2%80%9C%E7%BD%AA%E2%80%9D/
  4. http://dev.twitter.com/pages/tweet_entities
More

在GAE中迁移 django 0.96到1.2时遇到的模板问题

django 0.96 与 django 1.2 之间的细微差别造成很多麻烦事。


一、自动转换(autoescape)的问题

autoescape 主要是为了防止模板变量中含有标签等html元素,它会自动将html标签等元素转换实体。
例如:把
<script>
转换成
&lt;script&gt;

但是 django 0.96 没有autoescape,而django 1.2默认会自动对模板变量进行过滤。从0.96更换至1.2的时候,会发现很多不想被过滤的标签全都被过滤了,可以使用safe标记成安全字符。
如果使用了escape过滤器,则不管它出现在过滤链条的哪个位置,一律只会在链条的最后执行。并且如果变量已经标记为safe,那么escape则不会做任何事情。
例如:
{{ val|safe }}

解决办法有两种,一种是将原来模板中的escape去掉,再把不需要过滤的挨个修改成safe。另一种就是全局关闭autoescape:

{% autoescape off %}
....模板其他部分...
{% endautoescape %}


二、{% spaceless %}标签默认行为差别

在 django 0.96 中,去掉所有标签间的空白符号,仅标签间留一个空格。
在 django 1.2 中,去掉所有标签间的空白符号,标签间什么都不留。

举例:
<a href="#">This</a> <a href="#">is</a> <a href="#">mine</a>.
django 0.96 显示为:
This is mine.
django 1.2 显示成:
Thisismine.

这个解决办法很简单,不用这个标签即可,用与不用一般没什么区别。


三、{% include %}与{% base %}标签的路径问题

django 1.2 的标签中,不能使用上层路径,只能从模板根目录向下找。
在 django 0.96 中,可以这样使用:

{% include "../home.html" %}
而在 django 1.2 你必须这样:

{% include "path/to/home.html" %}

对比一下0.96与1.2的 django.template.loaders.file_system 模块,就会发现 get_template_sources 方法的文件路径结合方式不同。0.96 用的是一般的 os.path.join 方法,而 1.2 用的是 django.utils._os.safe_join 方法,后者限制不允许使用 base path 外的路径。而 include 和 extends 标签会先从base path开始寻找模板(这也许是bug?),如果路径里边有 “../” 可能会直接跳出这个 base path ,于是会触发异常:
>>> from django.utils._os import safe_join 
>>> safe_join('templates','../bbb.html') 
Traceback (most recent call last): 
File "<stdin>", line 1, in <module> 
File "C:\Python26\lib\site-packages\django\utils\_os.py", line 44, in safe_joi 
n 
raise ValueError('the joined path is located outside of the base path' 
ValueError: the joined path is located outside of the base path component 
>>> 

另外一个麻烦事就是在django 1.2中必须使用一个空app进行初始化设置才能正确找到模板。
举例,
目录结构:

project 
|--templates 
| |--outter.html
| |--bbb
| | |--in_b.html 
| |--aaa
| | |--in_a.html 
outter.html :
{% include "bbb/in_b.html"%}
in_a.html :
{% include "bbb/in_b.html"%}
main.py:

from google.appengine.dist import use_library
use_library('django', '1.2')

from django.conf import settings
#必须找个空文件(nothing.py)作为引子才能正确找到模板文件
settings.configure(INSTALLED_APPS=('nothing',))

from google.appengine.ext import webapp
from google.appengine.ext.webapp import util, template
import os

class MainHandler(webapp.RequestHandler):
    def get(self):
        #不论settings是否设置INSTALLED_APPS=('nothing',)
        #在outter.html中总能找到bbb/in_b.html或aaa/in_b.html
        self.render('outter.html', debug=True)

        #在aaa/in_a.html中,只有在settings中设置了
        #INSTALLED_APPS=('nothing',)时
        #才能正确找到 bbb/in_b.html ,反过来也是一样。
        self.render('aaa/in_a.html', debug=True)

    def render(self, tpl, vals={},debug=False):
        directory=os.path.dirname(__file__)
        path=os.path.join(directory, os.path.join('templates',tpl))
        self.response.out.write(template.render(path, vals, debug))

def main():
    application = webapp.WSGIApplication([('/', MainHandler)],
                                         debug=True)
    util.run_wsgi_app(application)


if __name__ == '__main__':
    main()


参考:
1、https://groups.google.com/forum/#!msg/google-appengine-python/YaqfeygoiaI/RtDh9pL6XJQJ

More

django模板自动转义(autoescape)机制

用过php的人都头疼过那个magic quotes(特殊字符的转义)问题。经常需要判断哪个情况下自动了转义,那个情况下没有自动转义。不仅要防止忘记了转义,还要防止那些转义之后又被转义的情况。总之很头疼。django模板也有自动转义(html特殊字符)功能,根据文档和阅读源码,发现这个机制很有趣,它能巧妙的避免那些转义多次的问题。

一、状态机制

django把通过模板过滤器(filter)的数据分为3种状态:

1、原始态(Raw string)
即普通的str或unicode类型。如果开启了自动转义,这种数据会被自动转义;如果关闭了自动转义,那么这种数据则原样输出,不做任何改变。

2、安全态(Safe string)
这是已经做过了所有必要的转义。在它再次被修改前,是安全的,输出时不会再被转义。如果处于这个状态的数据经又过了某个过滤器,状态可能会发生改变。主要是SafeString和SafeUnicode,它们是SafeData的子类型。

3、需转义态(Strings marked as "needing escaping")
被标记为“需转义态”的数据,在输出时都会被转义。主要是EscapeString和EscapeUnicode,它们是EscapeData的子类型。

在开启自动转义的情况下,django模板数据过滤器链的尾端,都会根据数据状态进行转义,凡是处于“安全态”的数据都原样输出,凡是处于“原始态”或“需转义态”的数据都会被转义,并使之转换为“安全态”。最后只有处于“安全态”的数据才会最终将其内容输出至html文档中。在数据通过过滤器时,过滤器根据所提供的数据状态判断是否进行转义或其他操作,并根据转义或操作的结果决定是否改变数据状态。


二、内部处理流程

假设有源数据(data),要通过过滤器。
  1. 如果过滤器的 needs_autoescape 属性为True,则给该过滤器准备一个 autoescape 参数。
  2. 运行过滤器,得到一个新数据(new_data)。如果提供了autoescape参数,过滤器就可以根据autoescape的值(True/False)做必要的处理。
  3. 如果过滤器的is_safe属性为True,并且源数据(data)是SafeData实例,则将新数据(new_data)转换成“安全态”。否则,如果源数据(data)是EscapeData,则将新数据转换为“需转义态”。
  4. 返回处理好的新数据(new_data),即下一个过滤器的源数据。
  5. 进入下一个过滤器,重复步骤1-4 直至过滤器链中最后一个过滤器。
  6. 判断过滤器链条最终输出数据(final_data)的状态,如果final_data为“安全态”,则原样渲染到网页,如果final_data为其他状态,则会对final_data进行转义,并渲染到网页。

三、过滤属性的作用及用法

1、is_safe
仅当过滤器中未引入任何不安全数据时使用。但要注意如果替换或删除某些字符也会导致不安全,例如,将已经处于安全状态的数据中的链接<a>去掉了一个大于号变成<a。因此使用这个属性必须要保证真的不会对已经处于安全态的数据造成破坏。
过滤器定义:
@register.filter
def safe_filter(value):
    '%s <a href="#">x</a>' % value
    return nv
safe_filter.is_safe=True
模板:

<!--
python code:
data='<a href="http://www.google.com">Google</a>'
-->
{% autoescape on %}
<ul>
	<li>{{ data }}</li>
	<li>{{ data|safe_filter}}</li>
	<li>{{ data|safe}}</li>
	<li>{{ data|safe|safe_filter }}</li>
</ul>
{% endautoescape %}
显示为:

  • <a href="http://www.google.com">Google</a>
  • <a href="http://www.google.com">Google</a> <a href="#">x</a>
  • Google
  • Google  x

2、needs_autoescape
凡是带有这个属性的过滤器,必须提供一个名为autoescape的参数。值为True代表开启了自动转义,False代表关闭了自动转义。过滤器中一般会用conditional_escape与mark_safe等方法来辅助处理各类状况。
conditional_escape(html),根据状态转义,如果html状态为“安全态”,就直接返回html,否则就对html进行转义。
mark_safe(s),将s转换成“安全态”。

from django.utils.html import conditional_escape
from django.utils.safestring import mark_safe

def initial_letter_filter(text, autoescape=None):
    first, other = text[0], text[1:]
    
    #如果开启了自动转义,就根据text的状态进行转义
    if autoescape:
        esc = conditional_escape
    else:
        esc = lambda x: x
    result = '<strong>%s</strong>%s' % (esc(first), esc(other))
    return mark_safe(result)
initial_letter_filter.needs_autoescape = True


参考:
  1. http://docs.djangoproject.com/en/1.2/howto/custom-template-tags/#filters-and-auto-escaping
More