☑移动数据业务×6年 ☑语义聚合×4年 ☑O2O×5年的一个老兵。
20120917 @郑昀汇总 外界流传的JAVA/PHP服务器端获取客户端IP都是这么取的: 伪代码: 1)ip = request.getHeader("X-FORWARDED-FOR") 可伪造,参考附录A 2)如果该值为空或数组长度为0或等于"unknown",那么: ip = request.getHeader("Proxy-Client-IP") 3)如果该值为空或数组长度为0或等于"unknown",那么: ip = request.getHeader("WL-Proxy-Client-IP") 4)如果该值为空或数组长度为0或等于"unknown",那么: ip = request.getHeader("HTTP_CLIENT_IP") 可伪造 5)如果该值为空或数组长度为0或等于"unknown",那么: ip = request.getRemoteAddr() 可对于匿名代理服务器,可隐匿原始ip,参考附录B 之所以搞这么麻烦,是因为存在很多种网络结构,如 Nginx+Resin、Apache+WebLogic、Squid+Nginx。下面挨个儿讲一下。 郑昀 :△ 首先,明确一下,Nginx 配置一般如下所示: location / { proxy_pass http://yourdomain.com; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } 注意看红色字体,这些配置与下面的闯关拿IP有关。 ——————————————————————————————— ——第一关|X-Forwarded-For :背景—— 这是一个 Squid 开发的字段,并非 RFC 标准。 简称 XFF 头,只有在通过了 HTTP 代理或者负载均衡服务器时才会添加该项。在 Squid 开发文档中可以找到该项的详细介绍。 XFF 格式如下: X-Forwarded-For: client1, proxy1, proxy2 可以看出,XFF 头信息可以有多个,中间用逗号分隔,第一项为真实的客户端ip,剩下的就是曾经经过的代理或负载均衡服务器的ip地址。 ——第一关|X-Forwarded-For :场景=客户端--CDN--Nginx—— 当用户请求经过 CDN 后到达 Nginx 负载均衡服务器时,其 XFF 头信息应该为 “客户端IP,CDN的IP”。 一般情况下CDN服务商出于自身安全考虑会将屏蔽CDN的ip,只保留客户端ip。 那么请求头到达 Nginx 时: 在默认情况下,Nginx 并不会对 XFF 头做任何处理此时 Nginx 后面的 Resin/Apache/Tomcat 通过 request.getHeader("X-FORWARDED-FOR") 获得的ip仍然是原始ip。 当 Nginx 设置 X-Forwarded-For 等于 $proxy_add_x_forwarded_for 时: 如果从CDN过来的请求没有设置 XFF 头(通常这种事情不会发生),XFF 头为 CDN 的ip此时相对于 Nginx 来说,客户端就是 CDN 如果 CDN 设置了 XFF 头,我们这里又设置了一次,且值为$proxy_add_x_forwarded_for 的话: XFF 头为“客户端IP,Nginx负载均衡服务器IP”,这样取第一个值即可 这也就是大家所常见的场景! 综上所述,XFF 头在上图的场景,Resin 通过 request.getHeader("X-FORWARDED-FOR") 获得的ip字符串,做一个split,第一个元素就是原始ip。 那么,XFF 头可以伪造吗? ——第一关|X-Forwarded-For :伪造—— 可以伪造。 XFF 头仅仅是 HTTP Headers 中的一分子,自然是可以随意增删改的。如附录A所示。 很多投票系统都有此漏洞,它们简单地取 XFF 头中定义的ip地址设置为来源地址,因此第三方可以伪造任何ip投票。 ——————————————————————————————— ——第二和第三关|Proxy-Client-IP/WL-Proxy-Client-IP :背景—— Proxy-Client-IP 字段和 WL-Proxy-Client-IP 字段只在 Apache(Weblogic Plug-In Enable)+WebLogic 搭配下出现,其中“WL” 就是 WebLogic 的缩写。 即访问路径是: Client -> Apache WebServer + Weblogic http plugin -> Weblogic Instances 所以这两关对于我们来说仅仅是兼容而已,怕你突然把 Nginx+Resin 换成 Apache+WebLogic 。 也可以直接忽略这两个字段。 ——————————————————————————————— ——第四关|HTTP-Client-IP :背景—— HTTP_CLIENT_IP 是代理服务器发送的HTTP头。 很多时候 Nginx 配置中也并没有下面这项: proxy_set_header HTTP_CLIENT_IP $remote_addr; 所以本关也可以忽略。 郑昀 :△ ——————————————————————————————— ——第五关| request.getRemoteAddr() :背景—— 从 request.getRemoteAddr() 函数的定义看: Returns the Internet Protocol (IP) address of the client or last proxy that sent the request. 实际上,REMOTE_ADDR 是客户端跟服务器“握手”时的IP,但如果使用了“匿名代理”,REMOTE_ADDR 将显示代理服务器的ip,或者最后一个代理服务器的ip。请参考附录B。 综上, java/php 里拿到的ip地址可能是伪造的或代理服务器的ip。 郑昀 :△ +++附录A XFF 与 Nginx 配置的测试用例+++ 测试环境: nginx+resin 内网IP:172.16.100.10 客户端IP:123.123.123.123 测试页面: test.jsp <% out.println("x-forwarded-for: " + request.getHeader("x-forwarded-for")); out.println("remote hosts: " + request.getRemoteAddr()); %> nginx 配置一 proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; wget测试 wget -O aa --header="X-Forwarded-For:192.168.0.1" "http://test.com/test.jsp" 页面返回结果: x-forwarded-for: 192.168.0.1, 123.123.123.123 remote hosts: 172.16.100.10 curl测试 curl -H "X-Forwarded-For:192.168.0.1" "http://test.com/test.jsp" x-forwarded-for: 192.168.0.1, 123.123.123.123 remote hosts: 172.16.100.10 nginx 配置二 proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; wget测试: wget -O aa --header="X-Forwarded-For:192.168.0.1" "http://test.com/test.jsp" 页面返回结果: x-forwarded-for: 123.123.123.123 remote hosts: 172.16.100.10 curl测试 curl -H "X-Forwarded-For:192.168.0.1" "http://test.com/test.jsp" x-forwarded-for: 123.123.123.123 remote hosts: 172.16.100.10 测试结果: 1、配置 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 增加了一个真实ip X-Forwarded-For,并且顺序是增加到了“后面”。 2、配置 proxy_set_header X-Forwarded-For $remote_addr; 清空了客户端伪造传入的X-Forwarded-For, 保证了使用 request.getHeader("x-forwarded-for") 获取的ip为真实ip, 或者用“,”分隔,截取 X-Forwarded-For 最后的值。 +++附录B 搜狗浏览器高速模式的测试用例+++ 访问路径: 搜狗浏览器“高速”模式(即使用代理)-->LVS-->Apache 获得的值为: x-forwarded-for:180.70.92.43 (即真实ip) Proxy-Client-IP:null WL-Proxy-Client-IP:null getRemoteAddr:123.126.50.185 (即搜狗代理ip)
ActiveMQ版本:5.5.1 MQ 所使用的 MySQL 是 InnoDB存储引擎 记录人:@郑昀 现象: 业务表面现象:无。系统现象:无。 日志信息:业务系统发送 MQ 消息时,下面这种错误日志断断续续地一直都有: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure The last packet successfully received from the server was 60,001 milliseconds ago. The last packet sent successfully to the server was 0 milliseconds ago. 原因: ActiveMQ 持久化方案我们选的是 MySQL 。 MySQL 的全局变量 wait_timeout 默认是28800,单位是秒,即8小时。 运维部早先在测试 ActiveMQ 5.5 主从方案时,发现当 Master 宕机后,锁(数据库里的一个排他锁)有可能没有释放,导致 Slave 无法成为 Master(这是 ActiveMQ 5.5 的 BUG,已在 5.6.0和5.7.0 修复)。 所以运维部将 wait_timeout 参数调短为了60秒,希望能加速锁释放。 当业务不繁忙时,也许会有60秒既不生产也不消费,因此 MySQL 主动断开了 Connection 。此时,如果作为 MySQL Client 身份的 Broker Service,它的数据库连接池对 连接断开检测和自动重连 做得不好的话,那么 Broker 首次访问 DB 时 jdbc 就会报错。 短期解决办法: 将 ActiveMQ 所使用的 MySQL 的全局变量 wait_timeout 恢复为默认值 28800 。(已完成) 中期解决方法: 将 ActiveMQ 升级到 5.6.0或5.7.0 稳定版本。 相关的BUG: ActiveMQ 的缺陷单 AMQ-1958 报告: JDBC master/slave deadlock when connection is lost 影响版本: 4.1.2, 5.0.0, 5.1.0, 5.2.0 Fix版本:5.6.0 状态:2012年4月17日已修复 描述: 在一个纯 JDBC Failover 场景下(1 Master+1 Slave):如果 Master 失去了数据库网络连接,数据库里的锁将不会释放。这样 Slave 不知道 Master 已不能执行任务,Slave 仍尝试不断获取锁;当 Master 重启后,它自己也无法获得锁,于是变身为 Slave;结果就是 0 Master+2 Slave 。 环境:Oracle 10,MySQL 5 (这也就是运维部在 ActiveMQ 5.5.1 测试场景所遇到的) AMQ-3654 也报告类似问题: JDBC Master/Slave : Slave cannot acquire lock when the master loose database connection 影响版本:5.5.0 Fix版本:5.7.0 状态:2012年6月13日已修复 描述:与 AMQ-3654 描述一样。只不过报告的版本号不一样。 参考资源: 1)火丁老王,MySQL里的wait_timeout 『wait_timeout 过大有弊端,其体现就是 MySQL 里大量的 SLEEP 进程无法及时释放,拖累系统性能,不过也不能把这个指设置的过小,否则你可能会遭遇到“MySQL has gone away”之类的问题。』 『在MySQL命令行里通过SET来设置即可,避免服务重启: mysql> set global wait_timeout=60;』 『查有无生效,用 show global variables 查全局变量: mysql> show global variables like 'wait_timeout'; 单纯使用 show variables 的话就等同于使用的是 show session variables,查询的是会话变量,你会误以为设置没有生效。 』 2)iteye,关于 MySQL 的 wait_timeout 连接超时报错解决方案 3)AMQ-1958 4)以前有人提议在数据库连接字符串中增加autoReconnect=true&failOverReadOnly=false,但这只对 MySQL 4.0 以前的版本有效,对 MySQL 5.0 以后无效。 5)ActiveMQ JDBC Master Slave帮助 『 Master failure If the master looses connection to the database or looses the exclusive lock then it immediately shuts down. If a master shuts down or fails, one of the other slaves will grab the lock and so the topology switches to the following diagram 』 6)wait_timeout 参数说明 『参数含义:服务器关闭非交互连接之前等待活动的秒数。 在线程启动时,根据全局wait_timeout值或全局interactive_timeout值初始化会话wait_timeout值,取决于客户端类型(由mysql_real_connect()的连接选项CLIENT_INTERACTIVE定义)。 参数默认值:28800秒(8小时)』
郑昀 201102 早先说过线上Resin的配置文件中要增加线程池大小、各种timeout参数(resin 4.0.15的默认配置文件肯定没有这些参数,需要另行增加)。 在resin 4.0.10里,有这么一个bug,thread-max的数量设置没有起作用:http://bugs.caucho.com/view.php?id=4251 ,但后面到了resin 4.0.15应该就修复了。 下面内容会给出背景介绍以及建议配置。 1、背景: 郑昀认为,要综合考虑resin线程池大小、“-Xmx :JVM最大可用内存”、“-Xms:初始堆大小”、“-Xmn:Young Generation的heap size”参数互相匹配。(JVM有2个GC线程。第一个线程负责回收Heap的Young区。第二个线程在Heap不足时,遍历Heap,将Young 区升级为Older区。Older区的大小等于-Xmx减去-Xmn,不能将-Xms的值设的过大,因为第二个线程被迫运行会降低JVM的性能。) 来避免线上resin服务反复出现以下异常: l OutOfMemoryError: Java heap space l OutOfMemoryError: PermGen space 2、Resin.xml就可以设置JDK参数: 郑昀注意到,Resin 4.0已支持把JDK参数加入resin配置文件resin.xml里。 参考resin的帮助文档: 『JDK arguments Resin 4.0 has moved all JDK arguments into the resin.xml file, in the <jvm-arg> tag. Because the Resin 4.0 watchdog starts each Resin server instance, it can pass the arguments defined in the configuration file to the JVM. By moving the Java arguments to the configuration file, server configuration is easier and more maintainable. 』 3、建议规则: 1、 Server端JVM最好将-Xms和-Xmx设为相同值。为了优化GC,最好让-Xmn值约等于-Xmx的1/4。 2、 通过增大 “-XX:PermSize”和“-XX:MaxPermSize”这两个参数来避免出现JVM内存永久保存区域溢出引发Resin的500错误。(郑昀认为,因为线上用了spring+struts,这些框架用到大量动态class,ClassLoader是把这部分内存放在PermGen space里的。而JVM的GC是不会清理PermGen space的。这样容易导致线上应用报告PermGen space内存溢出。) 4、建议resin配置: 所以,郑昀建议线上部署的Resin 4.0.15的resin.xml中增加如下配置节点: <server-default> <jvm-arg>-Xms1024m</jvm-arg> <jvm-arg>-Xmx1024m</jvm-arg> <jvm-arg>-Xmn256m</jvm-arg> <jvm-arg>-XX:PermSize=128m</jvm-arg> <jvm-arg>-XX:MaxPermSize=256m</jvm-arg> <thread-max>1024</thread-max> <socket-timeout>30s</socket-timeout> <keepalive-max>512</keepalive-max> <keepalive-timeout>60s</keepalive-timeout> </server-default> 请提反馈意见。谢谢。
郑昀 201102 现象: 下面是偶然出现的Resin错误日志,郑昀的结论是不必过于担心下面这个错误,但最好能修改配置: 『 [2011/02/24 22:11:20.593] {} HmtpServlet[WebApp[production/webapp/admin.resin/ROOT]] requires an active com.caucho.security.Authenticator because HMTP messaging requires authenticated login for security. In the resin.xml, add an <sec:AdminAuthenticator> [2011/02/24 22:12:29.198] {main} Unable to find native library 'resin_os' for com.caucho.bootjni.JniProcess. Resin expects to find this library in: (Unix) /application/webserver/resin-4.0.15/libexec64/libresin_os.so On Unix, run ./configure; make; make install. The JVM exception was: java.lang.UnsatisfiedLinkError: no resin_os in java.library.path 』 背景描述: 如果重启了resin,不管是人为的还是自动的,那么resin的引导程序就用上“com.caucho.bootjni”这个包,它是resin的引导类(bootstrap package)。 但由于resin版本升级到4.0之后,重启时,如果会根据自身resin.xml的如下配置: <server-default> <resin:if test ="${resin.userName == 'root'}" > <user-name>www-data</user-name> <group-name>www-data</group-name> </resin:if> </server-default> 做判断,如果当前启动Resin的用户是root,那么就会使用user-name节点中指定的www-data用户身份启动Resin(也因此有些工程师自己测试时启动不了Resin,就是因为www-data用户不存在)。 线上情景: 当然,郑昀说我们线上的resin.xml配置文件中已经把这段话注释了: <server-default> <!-- - If starting Resin as root on Unix, specify the user name - and group name for the web server user. <user-name>www-data</user-name> <group-name>www-data</group-name> --> </server-default> 所以,如果不是root帐号登录操作resin重启,比如假设是用一个webmaster帐号(没有root privileges权限)操作,那么 Resin启动时必须绑定80端口,而Unix仅仅允许root帐号绑定1024以下的端口号(所以8080端口就不存在此问题), 此时它会采用root身份,一旦绑定所有端口,就立刻丢弃root特权(privileges), 那么Resin接下来可能因为身份的问题无法加载libresin_os.so, 于是很有可能因此就报告了郑昀上面贴的错误:“Unable to find native library 'resin_os' for com.caucho.bootjni.JniProcess.”。 郑昀的建议: 1、 可以忽略此类错误。 2、 打开resin.xml里的开关,改用户名为resin,然后增加这么一个用户useradd resin: <!-- - If starting Resin as root on Unix, specify the user name - and group name for the web server user. --> <resin:if test="${resin.userName == 'root'}"> <user-name>resin</user-name> <group-name>resin</group-name> </resin:if> 3、或者直接复制一个libresin_os.so文件到 /usr/lib64/ 下,大家都能加载这个so。 参考资料: 1、 官方文档《Migrating from Resin 3.0 to Resin 4.0》: Unix allows only root to bind to ports below 1024. If you use Resin as your webserver (recommended) and bind to port 80, you'll need to start Resin as root. In Resin 4.0, the Resin process can drop privileges as soon as it's bound to all its ports. You can configure the user that Resin uses in the <server> or <server-default> sections: <server-default> ... <resin:if test="${resin.userName == 'root'}"> <user-name>www-data</user-name> <group-name>www-data</group-name> </resin:if> 2、 Resin 4.0.14曾经有一个bug,就是你虽然设置了user-name字段,但是resin仍然在root身份下运行,而不是你写的用户名。 但在我们线上这个版本resin 4.0.15已经修复了这个BUG: 01-25-11 16:03 ferg Fixed in Version => 4.0.15
郑昀 201102 1、文件句柄数问题 现象1:访问页面出现500错误,错误描述为:java.lang.NoClassDefFoundError,后面跟的类名各式各样不一一列举了。 现象2:Resin被Wathcdog自动重启,日志中表明这是因为:Resin shutdown from out of file descriptors。 分析:由于Linux默认文件句柄限制为1024(通过命令ulimit –n来查看),所以当Web Server应对高负载起了大量线程,incoming socket connections和outgoing socket connections都很多,而且后台代码也可能涉及大量句柄打开,尤其是struts+spring组合。 因此当句柄数迅速到达1024的限制后,无法打开新句柄,于是乎文件打不开、类定义找不到,诸如此类的错误就出现了。 解决: 短期策略是调大Linux下对文件句柄数限制。操作如附录1所示。对于这个数字,一般建议是32667,也可以设置的更大,我们直接调为51200。 2、PermSize问题 现象:Resin被持续访问一段时间后,比如一天,就会报告如下500错误,导致所有页面不能访问: OutOfMemoryError:PermGen space 此时,就只能重启机器了,重启resin都没用。 分析:因为线上用了spring+struts,这些框架用到大量动态class,ClassLoader是把这部分内存放在PermGen space里的。而JVM的GC是不会清理PermGen space的。这样很容易导致线上应用报告PermGen space内存溢出。 解决:通过在resin.xml中增加jvm-arg一系列参数,调大“PermSize”和“MaxPermSize”这两个参数来尽量避免出现JVM内存永久保存区域溢出错误。PermSize默认值参考附录2。修改情况参见附录3。 3、Xmx和Xms问题 现象:Resin报告如下500错误,导致所有页面不能访问: OutOfMemoryError:Java heap space 此时重启resin还可以恢复。 分析:JVM默认的最大可用内存(-Xmx参数)和初始堆大小(-Xms参数)都偏小,很容易限制住服务器的能力。当Java架构的能力还没有发挥出来时,就已经被系统默认的各种参数限制住了,导致各种各样的异常。 解决:通过在resin.xml中增加jvm-arg一系列参数,调大“Xms”和“Xmx”以及“Xmn”这三个参数。修改情况参见附录3。Xmn附录4。 其中Xmx的值可以设置得很大,比如4096MB,跟你的物理内存大小差不太多都行,64位Linux支持得住。Xmn的值是Xmx数值的1/4。 Xms的值可以与Xmx一样,省得当压力上来后,初始堆大小发现不够,JVM又得花时间去把内存区大小扩到最大可用内存的数值,就在这个过程,持续不断的高负载可能已经将服务器冲垮。这个机理就类似于我们熟知的SQL Server要根据业务,仔细思考指定数据库空间初始值和最大值以及自增长模式。默认SQL Server数据库空间自动增长是按10%比例增加的:如果你最开始建库时分配了1GB空间,但当压力上来后,数据很快到达1GB,那么数据库就会先锁住所有请求,自动在磁盘上增长出100MB的空间,在这个自动增长过程,所有Web请求就会被挂住,最终可能全部超时。 附录 附录1: 编辑/etc/profile,加入 ulimit -n 51200 有人认为设置为4096就可以,但在网络服务器上此数字最好调成几万,不然流量冲上来太容易冲破。 还有设置命令是:ulimit –SHn 51200,-S、-H这两个参数的说明如下: -H 设置硬件资源限制。 -S 设置软件资源限制。 -n size:设置内核可以同时打开的文件描述符的最大值。 还有另一种操作修改三个地方的设置,参见《修改 Ubuntu ulimit 限制》。 附录2: 没有给resin.xml加PermSize的情况下,默认计算规则是: “JVM使用-XX:PermSize设置非堆内存初始值,默认是物理内存的1/64;由XX:MaxPermSize设置最大非堆内存的大小,默认是物理内存的1/4。” 那么,如果是物理内存4GB,那么64分之一就是64MB,这就是PermSize默认值,也就是永生代内存初始大小; 四分之一是1024MB,这就是MaxPermSize默认大小。 附录3: 优化规则:Server端的JVM最好将-Xms和-Xmx设为相同值。为了优化GC,最好让-Xmn值约等于-Xmx的1/4。 线上resin 4.0.15的resin.xml中增加如下配置节点: <server-default> <jvm-arg>-Xms4096m</jvm-arg> <jvm-arg>-Xmx4096m</jvm-arg> <jvm-arg>-Xmn1024m</jvm-arg> <jvm-arg>-XX:PermSize=128m</jvm-arg> <jvm-arg>-XX:MaxPermSize=256m</jvm-arg> <thread-max>1024</thread-max> <socket-timeout>30s</socket-timeout> <keepalive-max>512</keepalive-max> <keepalive-timeout>60s</keepalive-timeout> </server-default> 附录4: JVM的-Xmn参数含义是Young Generation的heap size。 JVM有2个GC线程。第一个线程负责回收Heap的Young区。第二个线程在Heap不足时,遍历Heap,将Young 区升级为Older区。Older区的大小等于-Xmx减去-Xmn,不能将-Xms的值设的过大,因为第二个线程被迫运行会降低JVM的性能。
郑昀 20110306 集中回答一下网友对互联网信息监测的提问。 对于社区化信息挖掘、互联网海量信息挖掘,抽样是被迫的,但它仍然是一个好方法。 1.为什么被迫抽样? 即使是针对Twitter,做消息监控也是抽样。 也就是说,但凡是没有权限调用FireHose API(即Streaming API,参考郑昀的文章:http://www.cnblogs.com/zhengyun_ustc/archive/2010/06/22/streaming.html ),拿不到全部数据,一定是抽样。 从统计学角度,抽样到一定量级,是可以涵盖全部热点的。 互联网热点追踪,本身就不可能做到全面覆盖,毕竟你公司又不是Google,即使是Google,它也监控不了Facebook。 而且做互联网数据挖掘,也不需要抓取到所有数据。参考郑昀的文章:http://www.cnblogs.com/zhengyun_ustc/archive/2009/08/31/1556966.html 。 2.少量数据上也可以做特征提取 关于数据抽样这方面,可以参考郑昀的文章:http://www.cnblogs.com/zhengyun_ustc/archive/2009/10/27/1590805.html 其中有段话: 在语义的世界里,可以近似地说:万事万物都是特征提取。 你只要找到特征,事情就好办。 如果你找不到明确的特征,那么什么样的机器智能也无法准确地帮助你。 多数时候,唯一的麻烦在于,你所认为的特征,实际上不是特征。。。 如果你没有成为新浪微博的官方合作伙伴(不仅仅是应用获得审批的开发者),那么搜索接口你是调用受限的,但至少1、2分钟调用一次是可以的,所以只要你不是大公司的人,一定拿不到微博转发行为的90%数据,你只能在10%数据上做文章。 根据传播学原理,热点追踪只需要在传播节点上做拦截即可。这也就是玩聚SR的设计原理,参考郑昀的文章:http://www.cnblogs.com/zhengyun_ustc/archive/2011/02/05/aboutidea.html ,不需要全网抓取论坛、博客、微博的帖子,只需要在收藏、网摘、RSS阅读器、Twitter等传播节点上追踪大家分享、推荐、收藏、转载的链接和文字即可。 抓新浪微博或国内微博的人,基本都是几条腿走路: 一条腿,调用官方API,保证抓取频率不超过对方限制; 第二条腿,通过模拟登录,对搜索微博的网页进行翻页,也保证抓取频率不要过高。 为什么是两条腿走路呢? 第一,因为国内微博的微博搜索html样式变化过好多次,那么调用API就可以保证随时都有数据在抓,不会有遗漏; 第二,双向保障,由于新浪微博对登录用户搜索次数也有限制(主要是针对用户名的,而不是封你IP地址),所以新浪微博模拟登录通过搜索页抓取微博消息,频率不能太快,那么两条腿走路,就可以尽量多地抓取到数据。 关于特征抽取,你可以搜索以下关键词配搭: 二元组+语义 三元组+语义 3.处理数据的套路 套路一: 数据抓取-->信息抽取-->数据清洗-->元数据提取(分词、提取标签、提取实体、信息指纹、分类等)-->元数据入库(如MySQL)(原始数据可以抛弃)-->统计(包括层次聚类、针对实体的情感趋势分析等)-->展现。 套路二: 数据抓取-->信息抽取-->数据清洗-->信息指纹提取-->数据存入NoSQL DB中(如MongoDB)->做map/reduce-->NLP后续处理-->统计-->展现。 4.是语义还是统计学? 由于我们玩聚网的创建人之一是统计学科班出身,所以我们基本都是从统计角度出发思考特征提取。包括情感趋势分析(Setiment Analysis,简称SA),也都是走统计路,虽然我们也会计算否定句、否定之否定、疑问句等常见句式,但后来我慢慢认为我们做的不是语义应用,只能说是自然语言处理应用或数据挖掘应用。我们常说的所谓“机器智能”,哪怕是“机器学习”,也只是在词频啦、权重啦、TF/IDF啦、重复次数啦、各种影响因子啦等上面做做文章,距离机器理解文章内容还远的很哪远的很。 郑昀 北京报道 赠图一枚: 我的最新推特: 1、 历史杂志上讲周润发同学在从无线艺人培训班毕业后,每天收拾干净利落就守在公司电梯处,见人就问早安,很快有些监制就开始打听这个小伙子是谁了。周润发有天赋又有巧劲,从龙套演员到剧集主演只用了两年。 2、 才知道知乎( http://www.zhihu.com/ )是用 Python 开发的。实时的消息提醒应该是用friendfeed出品的Tornado。但前台到底是用Pylons还是Django开发的呢? 3、 meme是常用描述流行基因的词。在互联网上用它多半是指挖掘流行趋势监测大规模传播。所以较早的techmeme、rssmeme,中期的tweetmeme、srmeme、rtmeme,都属于memeTracker应用。我2006年写文章介绍过:http://is.gd/nuGAdC
Kuber 的 SocialBadge 能够根据给出的 Email地址、Twitter用户主页地址、Google User Profile地址等得到: 此人的 Google Reader Shared Items URL(如果有的话); 此人关注哪些人(Twitter、Google Reader等里面的Followings)。 测试连接: 1、我的google profile 链接:http://kuber.appspot.com/social/search?q=www.google.com%2Fprofiles%2Fzhengyun 2、我的twitter链接:http://kuber.appspot.com/social/search?q=twitter.com%2Fzhengyun 在此基础之上,我希望: 1、给定若干高权重的、社会化媒体重度使用者的 Twitter帐号或Google Profile地址;此用户集合我们称之为:TargetUsers。 2、分别获取每个人的关注列表(此人都关注哪些节点(Node)),此用户集合称为:FollowingNodes。合并重复后,得到一个总的节点集合,称之为:WatchList。 3、针对 WatchList 的每一个Node,遍历之: 检查它对应的Web服务我们是否支持。我们目前暂定支持 Google Reader Shared Items、Twitter、delicious(这些都有链接或短消息)。确定该数据是否有权访问。如果对方未开放权限(访问会得到403状态码),就忽略。 异步收集该Node的数据。如果是GoogleReader就收集它分享的每一篇文章。如果是Twitter,就收集它发布的RT消息和带HTTP链接的消息。如果是delicious,就收集它的收藏链接。 4、针对TargetUsers的每一个用户,逐一计算他关注的世界(FollowingNodes)中: 大家分享最多的文章或链接,仿照SR的算法; 大家转发最多的Twitter消息,仿照锐推榜的算法。 目的是: 观察在现有中国社会化媒体使用情况下,个性化计算是否能满足阅读需求。 观察对于收集到的社会化媒体重度使用者,他们所关注的世界的热文和热推是否有价值,对于一般用户来说。
03-获取 TargetUser 的 Followings 列表 郑昀 201005 隶属于《02.技术预研》小节 【注:去年的旧文。上一篇是《02-在 Kuber SocialBadge 基础上再前进一步》和《01-学习 Kuber 的 SocialBadge 好榜样 | 02.技术预研 | Social》,下一篇是《04-WebFinger的利用 | 02.技术预研 | Social》】 从 www.google.com/profiles/jason5ng32 的链接开始吧。 一、通过 TargetUser 的输入连接获取他的其他链接 测试代码: import socialgraph q="www.google.com/profiles/jason5ng32" its_me = u'me' types = u'types' instance = socialgraph.Api() results = instance.lookup(q) attributes = [k for k in results['nodes'].iteritems()][0][1]['attributes'] nodes_referenced = [k for k in results['nodes'].iteritems()][0][1]['nodes_referenced'] # 此人姓名: myname = attributes['fn']) # 此人其他链接: links_of_me = ['http://'+q,]#把起源链接也加进去 for link in nodes_referenced.iterkeys(): if(its_me in nodes_referenced[link][types]): #print link links_of_me.append(link) all_nodes_of_me = ','.join(links_of_me) 不过,为了确保我们主要精力放在那些常用的社会化媒体上,还要过滤一下这些链接。 二、把 TargetUser 的所有链接提交给 SocialGraph 获取 Followings 在提交前,为了确保我们主要精力放在那些常用的社会化媒体上,还要过滤一下这些链接。只保留 Twitter 、Google Reader Shared Items、Google Profile、Delicious 、豆瓣这几种链接。 获取 followings 的 http 请求类似于: http://socialgraph.apis.google.com/lookup?q=http%3A%2F%2Fwww.google.com%2Fprofiles%2Fzhengyun%2Chttp%3A%2F%2Fwww.google.com%2Freader%2Fshared%2F15221435823542888940&fme=1&pretty=1&sgn=0&edi=1&edo=1&jme=1&pretty=1 测试代码: import re """ import httplib2 import socks h = httplib2.Http(proxy_info = httplib2.ProxyInfo(socks.PROXY_TYPE_HTTP, 'localhost', 1984)) """ import socialgraph #q="www.google.com/profiles/jason5ng32" #q="twitter.com/fenng" #q="www.google.com/profiles/electronixtar" q="www.google.com/profiles/zhengyun" its_me = u'me' its_contact = u'contact' types = u'types' patternSupportServices = re.compile(u'(douban\.|twitter\.|google\.com\/reader\/shared|delicious\.|google\.com\/profile\/)',re.IGNORECASE) instance = socialgraph.Api() #instance = socialgraph.Api(httplib2_inst=h) results = instance.lookup(q) from print_r import print_r attributes = [k for k in results['nodes'].iteritems()][0][1]['attributes'] nodes_referenced = [k for k in results['nodes'].iteritems()][0][1]['nodes_referenced'] # 此人姓名: myname = attributes['fn'] # 此人其他链接: support_links_of_me = ['http://'+q,] for link in nodes_referenced.iterkeys(): if(its_me in nodes_referenced[link][types]): if(len(patternSupportServices.findall(link))>0): support_links_of_me.append(link) # 过滤后的此人的链接,以逗号分隔 support_nodes_of_me = ','.join(support_links_of_me) print support_nodes_of_me # 请求 Followings : results = instance.lookup(support_nodes_of_me,edo=1,edi=0,fme=1,jme=0) myFollowings = [] for node in results['nodes'].iteritems(): nodes_referenced = node[1]['nodes_referenced'] for fo in nodes_referenced.iterkeys(): if(its_contact in nodes_referenced[fo][types]): if(len(patternSupportServices.findall(fo))>0): myFollowings.append(fo) print myFollowings print_r(instance._last_request['res']['content-location']) 注: 这里有一个问题: 当想获取 Google Reader 里关注的人时,有一个选项可能阻碍获取。 估计必须该人在 google profile 里专门为“在我的个人资料上显示我正在关注的人和正在关注我的人的名单” 打上勾,才能够使得 google social graph 显示该人的关注列表。待确认。 默认“在我的个人资料上显示我正在关注的人和正在关注我的人的名单” 是打开的,当初 google buzz 出世时大家还争论过这个默认选项侵犯隐私。 附录A: http://pypi.python.org/pypi/socialgraph/ 是Python wrapper for Google's Social Graph API。 注意1:它使用了 httplib2 库,需要预先安装。 注意2:它使用了 cjson 库,需要用 easy_install python-cjson 安装。但在 Windows 上安装之前,需要先确保你安装了 MingGW,然后阅读 http://python.cx.hu/python-cjson/#win32 ,就是试图用 MingGW 作为编译器。 如果你安装 cjson 失败,那么就需要修改 socialgraph.py 的源代码了,把 import cjson 替换为: # We require a JSON parsing library. These seem to be the most popular. try: import simplejson parse_json_func = lambda s: simplejson.loads(s.decode("utf-8")) except ImportError: try: import cjson parse_json_func = lambda s: cjson.decode(s.decode("utf-8"), True) except ImportError: import json parse_json_func = lambda s: _unicodify(json.read(s)) 也就是优先采用 simplejson 解析 json。 然后把两处 cjson.decode(content) 都替换为 parse_json_func(content) 。 注意3: 为了更好地控制 HTTP 请求,我修改了 socialgraph.py 的代码:传入了 httplib2 的实例。这样可以用代理;lookup 方法增加了一个 jme 的参数。
郑昀 20110330 背景知识: 什么是MX记录? 用于电子邮件系统发邮件时根据收信人的地址后缀来定位邮件服务器。例如,当收件人为“user@mydomain.com”时, 系统将对“mydomain.com”进行DNS中的MX记录解析。如果MX记录存在,系统就根据MX记录的优先级, 将邮件转发到与该MX相应的邮件服务器上。 什么是正向解析? 比如你在万网注册的域名, www.55tuan.com ,在万网登录后域名管理界面上增加了一个MX记录,指向邮件服务器121.11.24.146。这就叫正向解析。 什么是A记录? A (Address) 记录是用来指定主机名(或域名)对应的IP地址记录。用户可以将该域名下的网站服务器指向到自己的web server上。同时也可以设置域名的子域名。 什么是反向解析? DNS服务器里有两个区域,“正向查找区域”和“反向查找区域”,反向查找区域即IP反向解析,它的作用就是通过查询IP地址的PTR记录来得到该IP地址指向的域名。 举例,用 info@news.gaopeng.com 作为发件人给用户邮箱 a@163.com 发邮件。网易的邮件服务器接到这封邮件,就会查看邮件头。邮件头里会显示这封邮件是由哪个IP地址发出来的,如: Received: from mta406.us.news.gaopeng.com (mta406.us.news.gaopeng.com [208.50.56.68]) 那么208.50.56.68就是发送邮件的SMTP服务器IP地址。然后根据这个IP地址进行反向解析,如果反向解析到这个IP所对应的域名确实是 mta406.us.news.gaopeng.com ,那么就接受这封邮件。如果反向解析发现这个IP没有对应到 news.gaopeng.com ,那么就拒绝这封邮件。 注1:反向解析的域名的A记录一定要指向该IP。 注2:反向解析跟域名注册商无关,是给你分配IP地址的IDC机房做的。所以有时候会存在费用问题。 反向解析的过程在Windows下可以模拟为: 命令行:nslookup -qt=ptr 输入你要查的IP地址,如下所示: > 208.50.56.68 非权威应答: 68.56.50.208.in-addr.arpa name = mta406.us.news.gaopeng.com 什么是SPF记录: 全称是Sender Policy Framework,即发信者策略架构,通常直接称为SPF。 SPF是为了防范垃圾邮件而提出来的一种DNS记录类型,它是一种TXT类型的记录,它用于登记某个域名拥有的用来外发邮件的所有IP地址。 举例,还是用nslookup查看美团的SPF记录: C:\>nslookup > set type=txt > meituan.com 非权威应答: meituan.com text = "v=spf1 ip4:58.83.134.224/27 ip4:59.151.43.32/27 ip4:211.151.229.32/27 i p4:211.151.229.64/26 ip4:211.151.229.128/28 ip4:173.45.234.162 ip4:173.45.238.15 5 ip4:72.14.188.19 include:aspmx.googlemail.com -all"
个性化阅读的过去、现在和未来(一)·概述 郑昀 20110414 以前曾经撰文讲过Topic Engine的过去、现在和未来。Topic Engine是一个生生不息的应用方向,因为从News Group、邮件列表、聊天室、论坛、Google News、博客圈子、群组。。。,人们一直因话题(有人也叫主题,英文为Topic)而聚集而交友,话题一直在生生不息层出不穷,组织形式在不断变异。 现在再讲讲个性化阅读的过去、现在和未来,也算是这个话题的延续。 一、概念定义 泛泛地说,只要是根据用户的历史行为(发言、标签等数据,点击流、分享、收藏、推荐、跳过等动作),动态决定哪些资讯内容(论坛帖子、新闻资讯、博客、微博、等)呈现给用户,都叫个性化阅读。 二、历史阶段 2005年~2007年: 这个阶段还没有Social数据,所以: 首先需要用户选定对哪些分类频道感兴趣,比如历史、人文、明星、体育等。稍微聪明一点的做法,不让用户选分类,而是问用户几个问题,然后就大致匹配出用户的兴趣点。 其次,系统决定给用户展现哪些分类的资讯。 随着用户点击,资讯实时不断变化,点击越多,系统越了解用户的阅读喜好。 这阶段的问题是: 1、利用成熟的协同过滤算法,但由于都在追求实时计算,运算量较大,有一定技术门槛; 2、对用户背景还是不够了解,仅仅通过用户点击流终究太浅。 3、普遍存在冷启动问题。 2008年~2010年: 有了Twitter,有了Facebook,有了Social Graph,个性化阅读器纷纷利用Twitter/Facebook帐号登录,展现的资讯是用户自己好友的Timeline聚合,主要是合并那些被诸多好友推荐的热点链接、图片和视频。不过,这波潮过去之后,像http://thoora.com/ 、http://twittertim.es/等都没有找到足够的用户群,还没有像2005年杀出来的TechMeme那么成功。 这阶段的问题是: 1、依赖于Twitter/Facebook的Social Graph,依赖于好友推送,可供阅读的数据过少,可供计算的数据过少,限制了自身应用的发展; 2、除非与Twitter保持良好的关系,能拿到 Streaming Firehose 接口,提前积累用户数据,否则用户Timeline信息需要积累一段时间,造成大量用户登录后没有可阅读的数据。 2010年: FlipBoard杀出重围,自动排版技术独步天下。 2011年: 随着国内新浪微博、豆瓣等拥有Interest Graph(兴趣图谱)+Social Graph(社交图谱)海量数据的网站崛起,成为主流数据源,如何把2005年到2010年这些探索择其优点都整合起来,成为一个大课题。 Zite的横空出世,被众人热捧为“Flipboard Killer”,强调的是基于社会化关系的个性化推荐阅读方式。而Flipboard目前的战略重点主要还是集成各种社会化应用及内容源,并以其创造性的阅读体验方式展现出来。国内已经有几家也在Zite的方向上,尤其是iPad应用上,动了起来。 三、热门?还是个性化? 在2009年SXSW大会上,SheGeeks 直言不讳:『 热门内容(Popularity)已经过时了,某种程度上令人讨厌。 我不想知道什么是最流行的,Techmeme已经帮我做到了。我想知道什么东西和我相关。我们需要更多“相关性过滤服务”。』 此时,会有几种做法: 1、以热点资讯为主(先有蛋),以社交图谱为辅(后引入鸡):将社交图谱引入热点资讯阅读中,像Quora(或中国的知乎)一样按人来隔离不同话题(不同热点)的讨论。Zite的方式类似于此。 2、以社交图谱为主:组织一度好友和二度好友的数据,做好数据挖掘。曾经有人在很久远的年代说过,“建立一个Social Network,每一个用户都推荐出自己喜欢的内容,那么被推荐得最多的,就一定是大多数人最受欢迎的内容。如果把这些推荐内容的用户区分成不同的群体, 就会得到特定群体欢迎的内容。Digg的想法就源于此。不过,这需要用户有足够的动力去推荐自己喜欢的内容,否则,Network也无法形成”。 3、以人为阅读中心:有人很多年前说过“许多人的blog阅读体验和阅读闲谈专栏是相似的,他们选择读什么不读什么的判断依据不是话题,而是作者,因为只有这样才能保证阅读到的内容的质量”。 4、以Topic为中心:用户定义或发掘用户感兴趣的Topic,只要是一篇文章谈及了用户关注的某一个主题,那么就推送给他。或者来自于不同人的文章集中地探讨某个话题,那么把这些文章自动聚合为一个Dialogue(虚拟对话),推送给用户。 除了第一种做法之外,我曾经尝试过其他三种做法。在中国的大环境下,要么数据过少,要么数据质量不高,都不能很好地做到有“发现、探索”、“新鲜、有趣”的冲击力。 当Social能完整地提供三重元素时:1、 你的身份标识(Indentity):Who you are; 2、 你的联系人或圈子(Contacts):Who you know; 3、 你的网际行为(Activities):What you do 。 那么,Social Graph,Interrest Graph,再联合热点资讯,揉入2005年以来的协同过滤算法,至少能做到make something people want吧。 四、Interest Graph的变化 以前,郑昀针对不同人群做的信息聚合,单纯从内容分类(也就是靠自然语言处理的自动分类算法)做,属于从信息本身下手。这种方式有一个问题: 某一类人群,虽然有一些集中的阅读点,但还有边缘的共同兴趣。举例,如IT人群,虽然共享和推荐的大多数是IT科技文章,但也涌现出很多受欢迎的兴趣点,如韩寒的文章,如冷笑话,如创意趣味产品。 这也就是为何基于 Tag 方式的阅读模式,以及基于指定主题的追踪模式,都不容易持久耐用的原因。一个人群的阅读兴趣点是比较模糊的。对于一个人来说,如果一个信息过滤器供应点科技,供应点娱乐,适当补充些人文历史,就能保证一定的粘度。 所以,郑昀后来觉得从内容分类,由于不引入人工,只靠比较大条的自然语言处理分类,对于博文、微博、论坛帖子等文字质量不稳定的信息会分得很粗糙,所以改变思路,从人群分类开始做。 也就是,划分出目标人群,依靠人群来挑拣信息,NLP算法为辅。这样有一个额外的好处,人群的兴趣点在动态变,短期地变,长期地变,但由于锁定人群,所以筛选出来的信息也在变。而相比之下,自动分类做出的信息,隔几个月或半年后,就要重新训练机器,因为往往信息包含的语言特征变了。 这也是信息聚合中的一个实际考虑点。 现在,中国也有了自己的Interest Graph,比如新浪微博,它的数据天然就表明一个人的兴趣喜好,以及连续波动,都可以跟踪和挖掘出来。以前依靠遍历Twitter、Google Reader、FriendFeed的好友所得到的社群分离,现在通过新浪微博等Social Graph都可以得到类似的。 五、人员配比 一般我对这个领域(Topic Engine啦、个性化阅读啦、Meme Tracker啦),研发人员配比是这么建议的: 爬虫2人, 文本挖掘4人(新词发现+分词+分类一个人,实体识别与发现+情感趋势分析一个人,事件识别与发现一个人,User Interest Profile一个人), 数据挖掘和分析2人, Web前端展现(包括手持设备)3人, 产品经理1人, 12人是一个比较不错的开局。 待续。敬请期待。 郑昀 于北京报道
MapReduce with MongoDB and Python 从 Artificial Intelligence in Motion 作者:Marcel Pinheiro Caraciolo (由于Artificial Intelligence in Motion发布的图在墙外,所以将图换到cnblogs) Hi all, In this post, I'll present a demonstration of a map-reduce example with MongoDB and server side JavaScript. Based on the fact that I've been working with this technology recently, I thought it would be useful to present here a simple example of how it works and how to integrate with Python. But What is MongoDb ? For you, who doesn't know what is and the basics of how to use MongoDB, it is important to explain a little bit about the No-SQL movement. Currently, there are several databases that break with the requirements present in the traditional relational database systems. I present as follows the main keypoints shown at several No-SQL databases: SQL commands are not used as query API (Examples of APIs used include JSON, BSON, etc.) Doesn't guarantee atomic operations. Distributed and horizontally scalable. It doesn't have to predefine schemas. (Non-Schema) Non-tabular data storing (eg; key-value, object, graphs, etc). Although it is not so obvious, No-SQL is an abbreviation to Not Only SQL. The effort and development of this new approach have been doing a lot of noise since 2009. You can find more information about it here and here. It is important to notice that the non-relational databases does not represent a complete replacement for relational databases. It is necessary to know the pros and cons of each approach and decide the most appropriate for your needs in the scenario that you're facing. MongoDB is one of the most popular No-SQL today and what this article will focus on. It is a schemaless, document oriented, high performance, scalable database that uses the key-values concepts to store documents as JSON structured documents. It also includes some relational database features such as indexing models and dynamic queries. It is used today in production in over than 40 websites, including web services such as SourceForge, GitHub, Eletronic Arts and The New York Times.. One of the best functionalities that I like in MongoDb is the Map-Reduce. In the next section I will explain how it works illustrated with a simple example using MongoDb and Python. If you want to install MongoDb or get more information, you can download it here and read a nice tutorial here. Map- Reduce MapReduce is a programming model for processing and generating large data sets. It is a framework introduced by Google for support parallel computations large data sets spread over clusters of computers. Now MapReduce is considered a popular model in distributed computing, inspired by the functions map and reduce commonly used in functional programming. It can be considered 'Data-Oriented' which process data in two primary steps: Map and Reduce. On top of that, the query is now executed on simultaneous data sources. The process of mapping the request of the input reader to the data set is called 'Map', and the process of aggregation of the intermediate results from the mapping function in a consolidated result is called 'Reduce'. The paper about the MapReduce with more details it can be read here. Today there are several implementations of MapReduce such as Hadoop, Disco, Skynet, etc. The most famous isHadoop and is implemented in Java as an open-source project. In MongoDB there is also a similar implementation in spirit like Hadoop with all input coming from a collection and output going to a collection. For a practical definition, Map-Reduce in MongoDB is useful for batch manipulation of data and aggregation operations. In real case scenarios, in a situation where you would have used GROUP BY in SQL, map/reduce is the equivalent tool in MongoDB. Now thtat we have introduced Map-Reduce, let's see how access the MongoDB by Python. PyMongo PyMongo is a Python distribution containing tools for working with MongoDB, and is the recommended way to work with MongoDB from Python. It's easy to install and to use. See here how to install and use it. Map-Reduce in ActionNow let's see Map-Reduce in action. For demonstrate the map-reduce I've decided to used of the classical problems solved using it: Word Frequency count across a series of documents. It's a simple problem and is suited to being solved by a map-reduce query. I've decided to use two samples for this task. The first one is a list of simple sentences to illustrate how the map reduce works. The second one is the 2009 Obama's Speech at his election for president. It will be used to show a real example illustrated by the code. Let's consider the diagram below in order to help demonstrate how the map-reduce could be distributed. It shows four sentences that are split in words and grouped by the function map and after reduced independently (aggregation) by the function reduce. This is interesting as it means our query can be distributed into separate nodes (computers), resulting in faster processing in word count frequency runtime. It's also important to notice the example below shows a balanced tree, but it could be unbalanced or even show some redundancy. Map-Reduce Distribution Some notes you need to know before developing your map and reduce functions: The MapReduce engine may invoke reduce functions iteratively; thus; these functions must be idempotent. That is, the following must hold for your reduce function: for all k,vals : reduce( k, [reduce(k,vals)] ) == reduce(k,vals) Currently, the return value from a reduce function cannot be an array (it's typically an object or a number) If you need to perform an operation only once, use a finalize function. Let's go now to the code. For this task, I'll use the Pymongo framework, which has support for Map/Reduce. As I said earlier, the input text will be the Obama's speech, which has by the way many repeated words. Take a look at the tags cloud (cloud of words which each word fontsize is evaluated based on its frequency) of Obama's Speech. Obama's Speech in 2009 For writing our map and reduce functions, MongoDB allows clients to send JavaScript map and reduce implementations that will get evaluated and run on the server. Here is our map function. wordMap.js As you can see the 'this' variable refers to the context from which the function is called. That is, MongoDB will call the map function on each document in the collection we are querying, and it will be pointing to document where it will have the access the key of a document such as 'text', by callingthis.text. The map function doesn't return a list, instead it calls an emit function which it expects to be defined. This parameters of this function (key, value) will be grouped with others intermediate results from another map evaluations that have the same key (key, [value1, value2]) and passed to the function reduce that we will define now. wordReduce.js The reduce function must reduce a list of a chosen type to a single value of that same type; it must be transitive so it doesn't matter how the mapped items are grouped. Now let's code our word count example using the Pymongo client and passing the map/reduce functions to the server. mapReduce.py Let's see the result now: And it works! :D With Map-Reduce function the word frequency count is extremely efficient and even performs better in a distributed environment. With this brief experiment we can see the potential of map-reduce model for distributed computing, specially on large data sets.All code used in this article can be download here.My next posts will be about performance evaluation on machine learning techniques. Wait for news!Marcel CaracioloReferences http://nosql.mypopescu.com/post/394779847/mongodb-tutorial-mapreduce http://fredzvt.wordpress.com/2010/04/24/no-sql-mongodb-from-introduction-to-high-level-usage-in-csharp-with-norm/
郑昀 20101011 一个微博用户的关注者数量(在Twitter中称为Followers),有几种用途: 一、对于Google来说,由于一个用户关注另一个用户,相当于一个页面指向另一个页面,所以PageRank的算法大致可照搬。 "One user following another in social media is analogous to one page linking to another on the Web. Both are a form of recommendation," Singhal tells Technology Review. "As high-quality pages link to another page on the Web, the quality of the linked-to page goes up. Likewise, in social media, as established users follow another user, the quality of the followed user goes up as well." 当决定哪一条微博消息(Tweet)要显示在搜索结果中前列时,Google不单单关注followers的数量,还关注这些followers的价值。 二、在热门消息榜类型的应用里,却要反其道而行之,要削减followers多的用户权重。 对于微博客来说,如果要做一个热门消息实时榜单,有一个问题绕不过去,那就是对人气特别旺的帐号如何处理?在做Twitter锐推榜时,很多人提出这个问题,能不能让那些followers数量巨大的人少上榜,更有人建议让上榜阈值与该用户的followers数挂钩,比如成反比,followers越多,上榜越困难。 由于followers数与微博消息的价值之间没有明确的关联,所以不适合简单粗暴地成反比。 在针对国内微博网站,如新浪微博,制作热门转发消息实时榜单(t.rtmeme.com)时,我采用如下简单的规则,来减少名人上榜几率。 加入关注者数量的考量 一条消息是否能够上榜,当然取决于它的转发数和评论数,但是名人关注者多(粉丝多),理所当然地有更大几率被转发,然而名人的消息未必有什么价值。t.rtmeme.com虽然一直在阻止明星推上榜,但名人或者说人气比较旺的用户,却很难遏制。 所以需要在t.rtmeme.com上榜公式中引入关注者数量这个参数。 一般来说,在新浪微博中,1K个关注者意味着转发数可能是个位数的,10K个关注者时转发数可能平均达到两位数,所以可以近似一个转发比率1:1K。 那么一个关注者数为88万的名人@冯小刚 ,他的转发因子是880(即期望平均转发数是880次);此时,如果他有一条消息被转发了1000次,那么1000/880=1.14,这个数字代表转发数是否超过预期。 下面再多举些例子: 关注者数为31352的@作业本 ,转发因子是31;他的某消息转发数是544,那么544/31=17.5,就说明该消息价值明显优于冯小刚的那条。 关注者数为1,894,927的@李开复 ,转发因子是1894;某消息转发数是5351,那么5351/1984=2.69,也还在水准之上。 关注者数为1,981,311的@任志强 ,他某条被转发了85次的消息转发水准度就是85/1981=0.04,就很不值得上榜(如果不考虑这个因子,那么凭借转发数多评论数多,它肯定可以上榜)。 关注者数为917,734的@头条新闻 ,他某条转发了320次的消息,水准度是320/917=0.34,相当一般的消息,可入可不入榜。 关注者数为434,135的@新浪娱乐 ,某条转发了99次的消息,水准度是99/434=0.22,所以说很多新浪自己维护的帐号,所发的消息大多不值得上榜。 因此设置一个转发水准度的阈值,比如要求每条上榜消息的转发水准度大于0.5,就能避免大明星、名人、人气王们随随便便发条消息就能上榜。 [完]
01.MySql连接错误:Cannot get hostname for your address 郑昀 2010 隶属于《05.数据入库》小节 某Web应用的数据库部署在 100.ZZZ.YYY.XXX 的 MySql 5.5.5 实例(新装机器)上, 但是当用PHP或者Python从远端机器上试图访问该数据库时,会得到如下错误提示: “Can't get hostname for your address”。 需要琢磨一下才能明白,这句提示是 MySql Server 返回给客户端的。 也就是说,MySql Server在试图对客户端IP地址进行反向域名解析,试图得到主机名,然而我们发起访问的客户端要么是自己 的机器,要么是机房的服务器,所以无法得到主机名,MySql Server 遂报错,拒绝客户端连接。 错误截屏 譬如,Python会得到这样的异常: 事件日志的报告 此时 MySql Server 所在服务器上,Windows 事件日志出现了如下错误: 事件类型: 警告 事件来源: MySQL 事件种类: 无 事件 ID: 100 日期: 2010-01-01 事件: 11:29:13 用户: N/A 计算机: SERVERII 描述: IP address '100.ZZZ.YYY.XXX' could not be resolved: getnameinfo() returned error (code: 11004). For more information, see Help and Support Center at http://www.mysql.com. 简单解释 MySQL server received a request from you to allow you to connect to the database. So next thing it tried to do is to check what name is bound to your IP address (name resolution) and it failed to do so. So it just denied you access. 可以这么理解mysql处理客户端解析的过程: 1,当 mysql client 发起连接请求时,MySql Server 会主动去查 client 的主机名。 2,首先查找Windows系统目录下 /etc/hosts 文件,搜索域名和IP的对应关系。 3,如果hosts文件没有,则查找DNS设置,如果没有设置DNS服务器,会立刻返回失败;如果设置了DNS服务器,就进行反向解析,直到timeout。 解决办法 第一种方法 修改Hosts 在 MySql Server 所在服务器上,修改 Windows 的 hosts 文件,增加一行记录,如: 100.ZZZ.YYY.XXX dummy.ju690.cn 然后在 100.ZZZ.YYY.XXX 机器上用 Python 发起连接请求,经测试,可以正常连接,说明 MySql Server 这下可以通过 getnameinfo() 解析出100.ZZZ.YYY.XXX 的主机名了。 但这种方法很机械,所以一般采用下面这种方法。 第二种 修改MySql 的配置文件 my.ini The solution: Just add skip-name-resolve option to your MySQL configuration file (my.ini). 在 MySql Server 的配置文件 My.ini 中,增加如下两行: [mysqld] skip-name-resolve 它将禁止 MySql Server 对外部连接进行 DNS 解析,使用这一选项可以消除 MySql 进行 DNS 解析的时间。 但需要注意,如果开启该选项,则所有远程主机连接授权都要使用IP地址方式,否则MySQL将无法正常处理连接请求。 参考:http://www.ixdba.net/article/89/2127.htmlhttp://blog.chinaunix.net/u/25264/showart_1936561.html 可能的后果 如果开启 skip-name-resolve 选项,要确认 MySql 是否采用过主机名的授权, 在 mysql 中运行如下命令: mysql> select user,host from mysql.user where host <> 'localhost' ; 一般会得到以“%”授权(也就是任何地址)的记录: +------------------+-------------+ | user | host | +------------------+-------------+ | root | % | | user_sync | 192.168.0.113 | 如果有host名是什么“DB1”“DB2”的,那么删除授权表中有 hostanme 的记录,然后重启mysqld。
05.php_pdo引用不恰当libmysql.dll导致Apache崩溃 郑昀 2010 隶属于《07.杂项》小节 现象 在测试环境Windows XP/2003+PHP v5.2.14+Apache v2.2下,使用php_pdo对象试图连接 MySql 数据库服务时,apache的error.log中什么也没报告,httpd.exe 二话不说径直崩溃。 在 php.ini 中注释掉 extension=php_pdo.dll 后,Apache则不再崩溃。 解决方法 该环境在 %SYSTEM32% 目录下,曾放置过一个 libmysql.dll 。 经测试,Apache加载的是此路径下的 libmysql.dll 。 由于 libmysql.dll 文件并没有列出版本号,所以只能通过文件大小来判断是哪一个版本的。 一般,PHP v5.2.14自带的 libmysql.dll 文件大小是2,076KB。 MySql v5.0 自带的则是1,531KB。 MySql v5.5 Cluster自带的是2,703KB。 MySql v5.5 自带的是4,168KB,并且已经有了版本号:5.5.5.0。 怀疑该环境 %SYSTEM32% 目录下的 libmysql.dll 文件与Apache v2.2+PHP v5.2.14不兼容,所以用PHP自带的 libmysql.dll 文件替换,遂成。 参考说法 http://bugs.php.net/bug.php?id=46289 BUG报告道: [2009-06-22 01:53 UTC] ramin dot farmani at gmail dot com Hi similarly my httpd.exe crashed when i runnig a site wroten by Yii framework it's seem we have a big problem in php_pdo_mysql library I using php 5.2.10 [2009-09-02 01:07 UTC] Parad0X dot UA at gmail dot com I was able to fix this by removing MYSQL from Windows' PATH env setting. Looks like when PHP is looking for libmysql.dll it uses the first one it finds in mysql\bin and it's not quite compatible. Or you can try to add php to the path before mysql. I hope that helps. 【注:此处建议,在系统变量PATH中,把PHP的目录放在MySql\bin目录前。】 [2009-09-02 07:45 UTC] pajoye@php.net Ok, it was then the classic case where MySql's libmysql DLL was used instead of the version bundled with PHP. We already have many bogus reports about this issue and your solution is the right one (as explain in the other reports). 【注:此处认为这是一个经典的场景,把MySql自带的libmysql替换PHP自带的,导致不兼容悲剧。】
04.微博消息的语言检测 郑昀 201010 隶属于《02.数据解析》小节 大意是,封装Google语言检测ajax web service的接口,输入一段话,输出语言种类。这个方法是从RssMeme.com看来的,经测试效果还不错,可用于检测微博客消息的语言,如中文、日文、韩文等。但由于Google对过于频繁的请求会重置链接,所以提请注意,这个Web Service不适合大量密集请求提交。 一、简单示范 访问http://ajax.googleapis.com/ajax/services/language/detect?v=1.0&q=hello+world 链接,你可以看到返回结果是一个json字符串: {"responseData": {"language":"en","isReliable":false,"confidence":0.114892714}, "responseDetails": null, "responseStatus": 200} 记得加版本号参数:v=1.0,否则返回如下json: {"responseData": null, "responseDetails": "invalid version", "responseStatus": 400} 二、如果是日文微博客消息呢? 举例,送去检测的微博客消息是: RT @ufotable: 本日22時より星海社ウェブサイト「最前線」にて『坂本真綾の満月朗読館』第二夜『山月記』が 配信されます。第二夜の映像演出も弊社デジタル部が担当い…http://goo.gl/brJE 经过urlencode变换后,提交到Google,返回的结果是: {"responseData": {"language":"ja","isReliable":true,"confidence":0.88555187}, "responseDetails": null, "responseStatus": 200} 这样用result['responseData']['language']就获得了语言的代号。 只要检查这个代号不是“zh-CN”,那么就不是中文语言了。 四、封装Google Language Detect Ajax Web Service 示范: import urllib import httplib2 try: from base import easyjson except: pass class Detect(): google_api_prefix = 'http://ajax.googleapis.com/ajax/services/language/detect' def __init__(self, httplib2_inst=None): """从外可以传入httplib实例,便于在外部加设代理软件穿墙""" self.http = httplib2_inst or httplib2.Http() def post_sentence(self, q): return self._fetch( self.google_api_prefix, {'v':"1.0",'q':q} ) def _fetch(self, url, params): request = url +"?"+ urllib.urlencode(params) resp, content = self.http.request(request, "GET") return easyjson.parse_json_func(content) def detectZHCN(self, text): """输入文字如果检测到是zh-CN,返回True,否则返回False""" data = self.post_sentence(text)['responseData'] if(data): language = data['language'] if(language=='zh-CN'): return True return False
郑昀 20101119 [杜宪] 才知道陈道明老婆是那个著名的杜宪,(人民网的报道也很不淡定)。 [教养] 才知道对一个公民的劳动教养决定是由一个本地劳动教养管理委员会和公安机关联合作出的,不需要走任何法律程序(有点像精神病院强制收治你一样容易)。公民可以对劳教决定不服提请行政复议,然后不知再怎么地(很模糊,貌似依照行政诉讼法)才能向人民法院起诉。 [555] 昨天听电台才知道,美国电影里常出现的555开头号码是行规惯例,因为为测试链路中所有交换机的基本功能,全部由5组成的号码作为特别的测试号码被保留,时至今日只剩下555-01xx开头的可供虚构,不会骚扰到真实用户。 [钟楼枪击案] 才知道德州大学1966年的惠特曼钟楼枪击案(15死31伤)据称是美国“杀戮年代”的开始标志,成为校园枪击案系列事件的开端。 [GroupOn] 才知道团购鼻祖GroupOn居然都2600名雇员了,同时向29个国家提供本地团购服务。市场经济发达国家真是好,只要确立一种顺畅的商业模式,2008年底才建立的GroupOn迅速扩张,而且是以国家计的。 反观中国大陆,美团网创建也差不多两年了,也才刚刚覆盖10个城市。千橡互动也算是耕耘很久的大公司了,糯米网也就勉强12个城市。
郑昀 20101124 @williamlong 说,『我发现国内的SNS和微博,没有一个能实现将好友以通讯录的形式导出的功能,而国外的linkedin、plaxo等都提供好友通讯录导出功能。』 据称Google也曾阻止Orkut用户导出用户数据。而据一名Facebook工程师认为,『Facebook的政策一向连贯。Facebook最重要的原则就是每个人能拥有并控制自己的信息。每个人都拥有自己的好友列表,但其好友的信息不属于用户。用户无权批量导出他们的私人邮件地址。』除非对方网站是Facebook合作伙伴,如微软和雅虎,也正因为此,Facebook还是能找到导出自己好友Email的方法。 所以,Social Graph如果被公司认为是最重要财产之一,而不是只依赖于页面(关联)广告盈利,那么它就不会让Social Graph自由流通。 当然SNS或Miniblog的Social Graph也是有起源问题的,通常的做法是自动导入Gmail联系人、MSN好友,但未必能因为海纳几川而让海也对等开放。你可以这样认为,Facebook类型的SNS里,你光是靠导入第三方联系人列表是没用的,必须要对方同意你的好友请求,所以“海”就是“海”,虽然“川”汇流而入,但你已经很难区分哪部分数据是网站自己的,哪部分数据是第三方的了,你就很难确认用户隐私策略该如何应用了。 譬如说,我在新浪微博上关注了闾丘露薇,她也follow了我,那么我是否可以获取她的邮件地址并导出呢?这还真是一个问题。 @王翌 曾经说,『新浪微博的产品,需要在“关系中心”这里,放80%的精力,“重构用户群的人际关系链”,是打破腾讯在中国互联网社会化垄断中最重要的一环。』我相信新浪微博的Social Graph是新浪最宝贵财富之一。 目前通过 微博的 User.Show API 和 Twitter的 User.Show API 可正常获取一个用户的关注和粉丝列表,你可以导出好友的昵称、地理位置、经纬度、描述等信息,但都不会给出对方的邮件地址,从这一点来说,你作为第三方开发者确实可以探索它们的Social Graph,但你无法把它们的Social Graph导入到另一个微博或SNS网站。 另外还要追问另一个情景: 假设微博网站允许导出(双向关注的)好友email列表,那么招致非议的MSN式病毒式推广又将卷土重来。我之所以极少确认MSN或Gtalk的好友请求,就是因为,我无法保证对方以及他所使用的第三方服务不滥用这个好友列表,我不想看到邮箱中充斥着SNS拉客邮件,不想看到因为某些人在一个垃圾网站上把我的邮件地址导入后对我标记了一个Tag就发系统邮件给我,去死吧,这种让人厌恶的病毒式推广。所以,虽然我可能在某个miniblog上关注了你,但我希望这个关系仅限于此地,不要随便扩散。 从这个角度说,Social Graph将你的好友的email地址作为好友的隐私,而不是你的权利主张,是有道理的。
郑昀 20101124 当用 Scrapy(一个开源爬虫框架) 访问 http://www.cjis.cn/info/zjzx.jsp 页面时,由于该页面html中指定了 <meta http-equiv="refresh" content="30; url=http://www.cjis.cn/info/zjzx.jsp"> ,所以 Scrapy 会自己循环请求该页面,直至到达最大跳转限制后退出,并打印: DEBUG: Discarding <GET http://www.cjis.cn/info/zjzx.jsp>: max redirections reached 。 所以我们必须禁用 RedirectMiddleware ,操作如下: 修改一个scrapy project的settings.py,增加下面这段话: DOWNLOADER_MIDDLEWARES_BASE = { 'scrapy.contrib.downloadermiddleware.robotstxt.RobotsTxtMiddleware': 100, 'scrapy.contrib.downloadermiddleware.httpauth.HttpAuthMiddleware': 300, 'scrapy.contrib.downloadermiddleware.useragent.UserAgentMiddleware': 400, 'scrapy.contrib.downloadermiddleware.retry.RetryMiddleware': 500, 'scrapy.contrib.downloadermiddleware.defaultheaders.DefaultHeadersMiddleware': 550, #'scrapy.contrib.downloadermiddleware.redirect.RedirectMiddleware': 600, 'scrapy.contrib.downloadermiddleware.cookies.CookiesMiddleware': 700, 'scrapy.contrib.downloadermiddleware.httpproxy.HttpProxyMiddleware': 750, 'scrapy.contrib.downloadermiddleware.httpcompression.HttpCompressionMiddleware': 800, 'scrapy.contrib.downloadermiddleware.stats.DownloaderStats': 850, 'scrapy.contrib.downloadermiddleware.httpcache.HttpCacheMiddleware': 900, } 注意,把 RedirectMiddleware 给注释了。
Hacker News与Reddit的算法比较 郑昀 20101213 Hacker News是Y Combinator旗下的一个新闻频道,属于digg类产品, SEOmoz曾经在2008年7月隆重推出Reddit、Stumbleupon、Del.icio.us和Hacker News算法全揭秘。由此,这些知名Web2.0网站的算法浮出水面。谷文栋曾在2009年时如下讲述了Hacker News的Ranking算法: (p – 1) / (t + 2)^1.5 其中, 1)p 表示文章得到的投票数,之所以要使用 (p – 1),应该是想去掉文章提交者的那一票。 2)(t + 2)^1.5, 这个是时间因子。t 表示当前时间与文章提交时间间隔的小时数。但为什么要加 2 之后再取 1.5 的幂,似乎就没什么道理可言了,也许是个 trial-and-error 的结果吧。 他对“1.5”这个参数也没有做出解释。 Amir Salihefendic(他是Plurk、Todoist的Co-Founder)在今年10月份撰文《How Hacker News ranking algorithm works 》完整地解释了Hacker News的Ranking算法,从中我们才得以知道那个神秘的“1.5”是什么。稍后我们还会拿这个排序规则与Reddit的排序规则做对比。 一、Hacker News的ranking算法 Hacker News是用Arc编写的(Arc是Lisp的一个“方言”)开源项目,源代码可以从http://ycombinator.com/arc/arc3.tar 获取。Ranking算法就写在这个包的news.arc文件里。鉴于这种语言是未来的语言,不容易看懂,所以代码就不贴了,光把注释贴出来: ; Ranking ; Votes divided by the age in hours to the gravityth power. ; Would be interesting to scale gravity in a slider. 努力领会这段神谕的同时,Amir说其实本质上执行的ranking规则是: Score = (P-1) / (T+2)^G where, P = points of an item (and -1 is to negate submitters vote) T = time since submission (in hours) G = Gravity, defaults to 1.8 in news.arc 也就是说,前文所谓的莫名其妙的“1.5”,其实就是“Gravity(万有引力)”参数,默认是1.8。p之所以减去1,就是要去掉文章提交者的那一票。 1.1.Gravity(G)和Time(T)的作用 Gravity和time对于一篇文章(即一个item)的得分有着重要影响。一般来说: 随着T的增加(即时间的流逝),得分将下降,意味着更早被提交进来的文章将会得到越来越低的分数; 对于更早的文章,如果gravity增加,得分将会下降得更快。 (在这里要说明一下,著名的搜索引擎网站wolframalpha的plot语法能帮你根据公式(代入参数)画图,方便编纂文档随时举例或者调整算法,超赞。) ·得分是如何取决于time的 Amir利用wolframalpha绘制了公式代入参数后的趋势变化图,如下所示(下图访问链接http://goo.gl/ddYe): How score is behaving over time 上图中,定参Gravity为默认值1.8,随着时间的流逝,文章得分都会迅速下降。你还能看出来,24小时后(即t=24时)无论你的文章得到多少投票,它依然会得到一个非常低的得分。 ·得分是如何取决于gravity的 当固定投票数,改变gravity又会如何呢?(下图访问链接http://is.gd/iJSbT): How gravity parameter behaves 可以看到gravity越大,得分下降趋势就越快。 ·Ranking规则的Python实现 这个得分函数就是这么简单: def calculate_score(votes, item_hour_age, gravity=1.8): return (votes - 1) / pow((item_hour_age+2), gravity) Amir最后说道:“最关键的是你要理解这个算法如何起作用,以及你如何为自己的应用定制这个算法。” 相对于下面要讲述的Reddit排序算法,Hacker News的Ranking算法主要区别在于引入了time since submission这个参数,这个参数是不断变化的,从而导致一篇文章的得分不断变化。而Reddit下的一篇文章得分(Rank)是不变的,越新的文章得分越高,所以排序自然可能排在最前面。 二、Reddit的ranking算法 Reddit 是Digg类型网站,曾经在Digg 4改版引发用户众怒时被用户刻意追捧,独立访问数暴涨50%,并刻意将reddit链接分享至digg,使得digg主页充斥着对手reddit的链接。 谷文栋也曾于2008年12月发表过《Social Media Algorithm: Reddit》详细地解释了Reddit的公式及其参数构成。2010年11月,Amir也对此作了图文并茂的阐述:《How Reddit ranking algorithms work》。 它的公式如SEOmoz所描述的: reddit formula 具体参数定义我就不再赘述了,可参考谷文栋或SEOmoz的文章。 Reddit也是开源的,其Python代码可从 http://code.reddit.com/ 寻到。他们的排序算法则因为性能问题改由Pyrex实现(Pyrex 是一种专门设计用来编写 Python 扩展模块的语言。根据 Pyrex Web 站点的介绍,“它被设计用来在友好易用的高级 Python 世界和凌乱的低级 C 世界之间搭建一个桥梁。”据说,“使用 Python like 的语法来编写 Python 的 C Module ,自动翻译成C语言代码,进而编译获取C代码的高效率。而且配合 Python 的 Distutils ,使得构建过程简单到了只需要 setup.py 的程度”)。 你可以点击链接 http://code.reddit.com/browser/r2/r2/lib/db/sorts.py?rev=4778b17e939e119417cc5ec25b82c4e9a65621b2 查看这个“hot ranking”算法被翻译为Python后的代码。 reddit很强调submission time(文章提交时间)。 2.1.提交时间的作用 提交时间对于文章评级有如下作用: 提交时间对ranking有很大影响,新文章肯定比老文章rank值高; 这个得分将不会随着时间流逝而递减,但新文章会得到更高的得分。这与Hacker News的算法有着显著区别。 下图显示了,固定Up和Down投票数的情况下,不同的提交时间,rank分值的变化: 从上图可以看出,同样的,随着时间的推移,新文章的得分会逐渐超越高同样投票数的老文章。 2.2.log函数的作用 这个“hot ranking”算法用了logarithm函数来确保最前面的投票权重大于接下来的投票。它的弯弯绕说法是这样的: The first 10 upvotes have the same weight as the next 100 upvotes which have the same weight as the next 1000 etc... 谷文栋是这么解释的: 前 10 票获得的权重,与 11 到 101 票所获得的权重是一样的。 图形化的阐释是: 如果不用logarithm函数,那么情况就会是这样: 还是图解看上去直观吧? 2.3.反对票的作用 Reddit是少数几个允许有反对票的Digg类型新闻聚合网站,Hacker News就没有投反对票一说。 它的公式中有这么一个因子: up_votes - down_votes 它的作用可以图形化为: 也就是说,那些有争议性话题的文章,得分将会比只得到赞同票的文章更低。 2.4.总结 Amir总结了一下Reddit的算法: Submission time(提交时间)是一个非常重要的参数,玩聚SR和RT就都是参照Reddit重视文章或热门消息的发布时间的。 前10票的得分,和接下来的100票,是一样的。这样,得了10票的文章,就可能和得了50票的文章得分相似。 争议性话题(得了很多反对票的)得分会很低。 (Amir还讲了Reddit的comment ranking,有兴趣可以自己去看看) 三、小结 就我在玩聚SR上使用Reddit排序算法的经验: Hacker News的算法能够让老文章消失得更快,比如24小时之前的文章,基本就不会在榜单上存留。 Reddit的算法则能够让众望所归的那些优秀文章能够停留相当长时间,然后随着时间流逝,一点一点地被新的、好的文章挤下去。 Reddit的算法还有一个好处就是,当投票数变化了,才需要去重新计算一次文章的Rank,不用管时间的流逝。
03-网页内容的编码检测 郑昀 201005 隶属于《02.数据解析》小节 我们需要确定网页的内容/标题等文字的编码格式,比如 utf-8 、gb2312 等。 通用检测顺序 一般来说,国内外类似服务的检测顺序是(参见参考资源A): charset parameter in HTTP Content-type header. <meta http-equiv="content-type"> element in the <head> of a web page for HTML documents. encoding attribute in the XML prolog for XML documents. Auto-detect the character encoding as a last resort. 也就是,HTTP Response 的 header 里 content-type 如果指定 charset 的话,优先级是高于 HTML 里的 content-type 的。 由于我国网络服务商不一定保持 HTTP Content-type header 与 meta charset 一致,比如新浪新闻、和讯、网易新闻的 html 里都会写明 meta charset 是 gb2312,但新浪新闻的 HTTP Content-type header 里却只输出:Content-Type: text/html ,并没有给出 charset 参数。网易新闻则 HTTP Header 中指定 GBK ,而 HTML 里却指定 GB2312 。 国外的一些服务探测我国网站时,容易因此得到乱码,如我的文章《Yahoo! Pipe的charset问题之解决方法》所说的。 这样带来的一个问题就是: 当 HTTP Content-type header 与 meta charset 不一致时,到底采信谁的声明? 当然也可以用 chardet 来检测内容,但 chardet 非常消耗资源,在网络爬虫中频繁调用 chardet 吞吐大量 html 字符串,会降低抓取效率。 BeautifulSoup 自动探测机制 BeautifulSoup 会自动判断页面编码,如果判断不出来就调用 chardet 探测。它的探测顺序是: Beautiful Soup tries the following encodings, in order of priority, to turn your document into Unicode: An encoding you pass in as the fromEncoding argument to the soup constructor. An encoding discovered in the document itself: for instance, in an XML declaration or (for HTML documents) an http-equiv META tag. If Beautiful Soup finds this kind of encoding within the document, it parses the document again from the beginning and gives the new encoding a try. The only exception is if you explicitly specified an encoding, and that encoding actually worked: then it will ignore any encoding it finds in the document. An encoding sniffed by looking at the first few bytes of the file. If an encoding is detected at this stage, it will be one of the UTF-* encodings, EBCDIC, or ASCII. An encoding sniffed by the chardet library, if you have it installed. UTF-8 Windows-1252 BeautifulSoup 优先用 meta charset 指示的编码进行探测,但未必对。 举一个异常的例子,http://www.miniclip.com/games/cn/ ,它的 HTTP Content-type header 是 utf-8,而 meta charset 却是 iso-8859-1,实际上它的编码是 utf-8 。 对于这种情况,怎么办? 可以让 BeautifulSoup 强制按照 HTTP Content-type 声明的编码做转换: from BeautifulSoup import BeautifulSoup from urllib import urlopen response=urlopen('http://www.miniclip.com/games/cn/') charset=BeautifulSoup.CHARSET_RE.search(response.headers['content-type']) charset=charset and charset.group(3) or None page=BeautifulSoup(response.read(),fromEncoding=charset)
05-访问超时设置 郑昀 201005 隶属于《01.数据抓取》小节 设置 HTTP 或 Socket 访问超时,来防止爬虫抓取某个页面时间过长。 pycurl 库的调用中,可以设置超时时间: c.setopt(pycurl.CONNECTTIMEOUT, 60) 在 Python 2.6 版本下,httplib 库由于有如下构造函数: class HTTPConnection: def __init__(self, host, port=None, strict=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT): self.timeout = timeout 所以可以设置: >>> h3 = httplib.HTTPConnection('www.cwi.nl', 80, timeout=10) 参见文档 #2452: timeout is used for all blocking operations : 如果通过 HTTPConnection 或 HTTPSConnection 的构造函数给定超时时间,那么阻塞操作(如试图建立连接)将会超时。如果没有给或者赋值 None ,那么它将使用全局的超时时间设置。 Python 2.5 下,因为 HTTPConnection 类的 __init__ 函数没有 timeout 参数,所以通过一个隐藏很深的函数: httplib.socket.setdefaulttimeout(3)#输入参数单位貌似是分钟 来设置超时。 设置全局超时 最后,抓取时如果实在找不到什么函数能设置超时时间,那么可以设置全局的 socket 超时,虽然这样做不大合适: >>> import socket >>> socket.setdefaulttimeout(90) setdefaulttimeout() was a hack to allow to set the timeout when nothing else is available. 如何捕获超时异常? 举例: from urllib2 import urlopen import socket slowurl =”http://www.wenxuecity.com/” socket.setdefaulttimeout(1) try: data = urlopen(slowurl) data.read() except socket.error: errno, errstr = sys.exc_info()[:2] if errno == socket.timeout: print "There was a timeout" else: print "There was some other socket error"
07-爬虫的多线程调度 郑昀 201005 隶属于《01.数据抓取》小节 一般让爬虫在一个进程内多线程并发,有几种方法: Stackless :Stackless Python是Python的一个增强版本。Stackless Python修改了Python的代码,提供了对微线程的支持。微线程是轻量级的线程,与前边所讲的线程相比,微线程在多个线程间切换所需的时间更多,占用资源也更少。 Twisted :主要利用 Twisted 中的异步编程能力。如 addCallback , callLater , defer.succeed,deferToThread , callFromThread 和 callInThread 等等。 threading 和 Queue :这都是 Python 原生库。从这个库可以衍生出很多线程池的第三方实现。如2003年的一个实现。比如 Christopher Arndt 的一个版本。比如2010年的一个实现。 greenlet 和 eventlet :greenlet 不是一种真正的并发机制,而是在同一线程内,在不同函数的执行代码块之间切换,实施“你运行一会、我运行一会”,并且在进行切换时必须指定何时切换以及切换到哪。粗糙来讲,greenlet是“阻塞了我就先干点儿别的,但是程序员得明确告诉greenlet能先干点儿啥以及什么时候回来”。 (注:关于 Python Threading 代码片段,参考:http://code.activestate.com/recipes/langs/python/tags/meta:requires=threading/ ,有很多例子) Twisted 的 callInThread 和 callFromThread 区别 这两个函数的定义在 IReactorThreads 的文档里。 Method callInThread : Run the callable object in a separate thread. Method callFromThread : Cause a function to be executed by the reactor thread. Use this method when you want to run a function in the reactor's thread from another thread. Calling callFromThread should wake up the main thread (where reactor.run() is executing) and run the given callable in that thread. If you're writing a multi-threaded application the callable may need to be thread safe, but this method doesn't require it as such. If you want to call a function in the next mainloop iteration, but you're in the same thread, use callLater with a delay of 0. 也就是说,reactor.callFromThread 是在由 reactor.run() 激发的主消息循环(main event loop)中执行,所以也就能被 reactor.stop() 终止执行。甚至可以通过: reactor.callFromThread(reactor.stop) 来主动要求主消息循环关闭 reactor 的主线程运行。 callFromThread 有时候比较危险,如果压的任务太多,会阻塞主消息循环,造成其他事件无法得到及时的处理。 参考 callInThread 的代码,可以看出它是在 reactor 的一个私有线程池里工作的: def callInThread(self, _callable, *args, **kwargs): if self.threadpool is None: self._initThreadPool() self.threadpool.callInThread(_callable, *args, **kwargs) 所以,我们可以通过 from twisted.internet import reactor reactor.suggestThreadPoolSize(15) 来设置该线程池的大小。默认最小是5个线程,最大10个(the default reactor uses a minimum size of 5 and a maximum size of 10)。 这里有两个问题: 1、如何通知 callInThread 执行任务的线程退出呢,如何确保线程池内的工作线程安全退出呢? 2、如果让工作线程去某网站抓取页面,由于 TCP/IP 的不确定性,可能该工作线程挂起,长时间不返回。如果线程池内的每一个线程被这样耗尽,没有空闲线程,就相当于抓取全部停止了。某个线程或许会因请求超时而退出,但这也未必可靠。一般通过代码: import timeoutsocket timeoutsocket.setDefaultSocketTimeout(120) 设置 socket 超时时间,但有时候就是会莫名其妙地挂住线程。 threads.deferToThread和reactor.callInThread的区别 twisted.internet.threads.deferToThread 与 callInThread 一样,默认用 reactor.getThreadPool() 所开辟的线程池。它调用这个线程池的 threadpool.callInThreadWithCallback 方法,实际效果和 reactor.callInThread 一样。区别只是 deferToThread 可以返回一个deferred对象,从而允许你设定回调函数。 示范代码: def finish_success(request): pass threads.deferToThread(parseData, body).addCallback(lambda x: finish_success(request)) twisted 的 defer.succeed twisted还提供了一个简易办法 twisted. internet.defer.succeed(result) Return a Deferred that has already had '.callback(result)' called. This is useful when you're writing synchronous code to an asynchronous interface: i.e., some code is calling you expecting a Deferred result, but you don't actually need to do anything asynchronous. Just return defer.succeed(theResult). 代码示范参考 howto 文档的 Returning Deferreds from synchronous functions 。 还可以参考 《Twisted 服务器开发技巧(1) - 将性能优化到底》中的示范。 defer.succeed 说白了就是为了让某函数 A 返回一个 Deferred 对象,从而让 A.addCallback(…) 异步触发成为现实。
10-穿墙代理的设置 郑昀 201005 隶属于《01.数据抓取》小节 我们访问 Twitter 等被封掉的网站时,需要设置 Proxy 。 1.使用HTTP Proxy 下面是普通HTTP Proxy的设置方式: 1.1.pycurl 的设置 _proxy_connect = "http://127.0.0.1:1984" c = pycurl.Curl() … c.setopt(pycurl.PROXY, _proxy_connect) 1.2.urllib2 的设置 req = urllib2.Request(link) proxy = urllib2.ProxyHandler({'http':'http://127.0.0.1:1984'}) opener = urllib2.build_opener(proxy,urllib2.HTTPHandler) urllib2.install_opener(opener) req.add_header('User-Agent', URLLIB2_USER_AGENT) urllib2.urlopen(link) … opener.close() 1.3.httplib 的设置 conn = httplib.HTTPConnection("127.0.0.1",1984) conn.request("GET", "http://www.python.org/index.html") r = conn.getresponse() print r.status, r.reason 1.4.httplib2 的设置 import httplib2 import socks httplib2.debuglevel=4 h = httplib2.Http(proxy_info = httplib2.ProxyInfo(socks.PROXY_TYPE_HTTP, 'proxy_host', proxy_port)) r,c = h.request(http://bitworking.org/news/) 参见 httplib2 的 Wiki 说明。 socks 库需要安装,主页地址:http://socksipy.sourceforge.net/ ,它不需要安装,下载源代码包后直接把 socks.py 复制到 Python's lib/site-packages 目录即可。 httplib2 则用 easy_install httplib2 安装即可。 1.5.socket代理 更底层的socket代理如下所示: import socks, socket socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, "proxy_host", proxy_port) socket.socket = socks.socksocket 需要 socks 库。 1.6.twisted.web.client 的设置 from twisted.internet import reactor from twisted.web import client class ProxyClientFactory(client.HTTPClientFactory): def setURL(self, url): client.HTTPClientFactory.setURL(self, url) self.path = url factory = ProxyClientFactory('http://url_you_want') reactor.connectTCP('http://proxy_host', proxy_port, factory) reactor.run() 如果代理需要Basic身份验证,如代理地址是:http://user:pwd@host:port ,那么就用到proxy-authenticate header了: import base64 # Proxy credentials proxyAuth = base64.encodestring('%s:%s' % (proxy_username,proxy_password)) proxy_authHeader = "Basic " + proxyAuth.strip() factory = ProxyClientFactory('http://url_you_want')factory.headers={'Proxy-Authenticate': proxy_authHeader} reactor.connectTCP('http://proxy_host', proxy_port, factory) 只要把Proxy-Authenticate header字段设置好,前面的httplib/urllib2等如法炮制,可参考 Python2.6的 test_urllib2 示范代码中的 test_proxy_basic_auth 函数。 2.使用HTTPS Proxy 如果代理是HTTPS的,并需要证书,那么请参考 python-httpclient 的写法。 2.1.urllib2 的设置 Python 2.X可参考 Recipe 456195 的代码。或参见 Python2.6的 test_urllib2 示范代码中的test_proxy_https 函数。 Python 3k的参见 revision 74204。
01-学习 Kuber 的 SocialBadge 好榜样 郑昀 20100510 隶属于《02.技术预研》小节 Kuber 是我们做社会化推荐个性化计算的同路人。我和他一直在讨论相关应用和技术。 最近 Kuber 推出了 SocialBadge ,是利用 google social graph api 做的。 SocialBadge 算是社会化推荐封闭社区的预研 demo 了。有了这个雏形,面向全球 Social 重度用户的个性化推荐计算有了可能。 它的页面简单解释 页面展示的数据,基本都是通过页面的 Javascript 解析各种API(json格式的)而来,没有什么后台。 1、 Reading Stream 异步显示的 google reader shared items/delicious 数据是 javascript 临时从json提取的。比如,通过你输入的邮件地址、Twitter链接、Google帐号等字符串,让Social Graph查询到你的 Google Reader Shared 公开链接,然后由Yahoo! Pipe把它的RSS烧制成json格式。 2、 如果你什么都没查到,可能是因为GFW屏蔽了Google,也可能是因为你的输入在Social Graph里查不到数据。 它的工作原理 首先,它是把 twitter.com/kuber 的字符串提交给 google social graph 的 lookup 方法,如下面的链接: http://socialgraph.apis.google.com/lookup?q=twitter.com/kuber&fme=1&jme=1&edi=1&edo=1&pretty=1 得到了一部分 claimed nodes ,然后继续把这些 nodes 构造进 lookup 方法的 URL里,发起第二次请求(这是因为第一次取得的 link 太少,再发一次取得更多链接): http://socialgraph.apis.google.com/lookup?q= http://twitter.com/kuber, http://www.cnblogs.com/kuber, http://www.feedzshare.com/, http://friendfeed.com/kuber, http://www.google.com/profiles/113557210616935738114 &fme=1&jme=1&edi=0&edo=1&pretty=1 (真实链接请点击:http://is.gd/c239A ,有时会被 GFW 重置)(多个 link 可以用逗号分隔提交给Google) 像页面上 Kuber 得到了他豆瓣的链接:http://www.douban.com/people/kuber/miniblogs ,这种链接不一定是google profile 里面列的, 但是google profile 肯定是重要的数据源。比如说你的blog里面列出了豆瓣, 也会在Social Graph里面。关键是XFN(XFN,XHTML Friends Network,XHTML社交网络,http://gmpg.org/xfn/ ,是一个通过XHTML标记语言在网页上表示人与人之间的社交关系的方法。)。 “Followings”列表里也不仅仅是Twitter的好友,blog 里面列出来的友情链接也可能。但遗憾的是,Social Graph API尚无法给出Google Profile里呈现的你的关注列表,或者Google Reader里你关注的对象。 Google Social Graph 还会错判某个节点隶属于你,比如搜 twitter.com/zhengyun ,列出的一个 friendfeed 帐号就是错误的、不存在的。我不能说这种误判属于偶然,但如果让你做,你未必能比Google做得更好。 如果一度好友少的话 Social Graph还会考虑二度好友。比如说一个 Flickr 用户,在 Flickr 上有朋友,由此连接到这个朋友的twitter好友,即A->B->C。还可以再加入二度好友的 GReader 和 Delicious 。二度好友就是权重低些。二度好友可以根据交流活跃度来算权重。比如说我只有你一个好友, 你经常RT、Reply的人才会算进去。 社会化推荐个性化社区的终极目的 我觉得最终目的是提供一个普世的个性化阅读社区。手段可以丰富多彩甚至为各个国家的二度开发者开放接口。比如对于中国大陆,可以加入豆瓣、天涯社区等常用服务的支持。否则如果只是面向 Social 重度使用者提供服务,人群太窄。
02-Twisted 构建 Web Server 的 Socket 长链接问题 郑昀 201005 隶属于《07.杂项》 背景 利用几句简单代码可以构建一个 Web Server: from twisted.internet import reactor from twisted.web.server import Site from my_webhandler import * reactor.listenTCP(8080, Site(MyWebResource.setup())) 更复杂的运用参见IBM文档库:第 1 部分讲述异步服务器编程; 第 2 部分介绍编写Web服务的高级技术; 第 3 部分用 Woven 模板实现动态Web服务器;第 4 部分讲述如何利用 SSH。或者Configuring and Using the Twisted.Web Server。 有时易造成 Socket 连接打开过多 当用此 Web Server 接收 PubSubHubbub Hub Server 发送过来的各种请求时,遇到了一个大问题: 随着时间推移,处于 ESTABLISHED 状态的 Socket 连接越来越多,慢慢抵达500多个, TCP X.X.X.X:8080 72.14.192.68:55693 ESTABLISHED TCP X.X.X.X:8080 74.125.126.80:59064 ESTABLISHED 最终导致服务爆出异常“too many file descriptors in select”,当此异常发生时,已无法挽救,只能重启服务。 这里的知识点是 Windows 下 select module 文件描述符(file descriptor)最多是512个,而在 Linux 下这个限制为 32767 ,如果超过这个限制值,就会出现类似上面的异常。 我们暂且不管这个问题牵涉到 PubSubHubbub Hub 喜欢保持长链接(Hubs MAY leave their TCP connections open and reuse them to make HTTP requests for freshly published events)并maybe重用连接的习惯。 既然 Server 端(指我们的 WebServer)无法保证总能断开闲置连接,那么可以通过设置闲置超时来解决这个问题。 twisted.web.server.Site 的 timeout 设置 twisted.web.server.Site 类的初始化函数有一个可选参数 timeout ,它的默认值是 60*60*12 ,应该就是12小时。 这个 timeout 值经由 twisted.web.http.HTTPFactory 的初始化函数赋值给 twisted.internet.protocol 的 timeOut 属性,从而能够在底层 HTTP 协议发现连接闲置超时后交由 TimeoutMixin 处理。 12小时太长。所以才会有许多处于 ESTABLISHED 状态的 Socket Connections 积累。所以我们缩短为 15分钟。So,我们只需要在开始执行: reactor.listenTCP(8080, Site(MyWebResource.setup(),timeout=60*15)) 即可。 这样,当 Socket 接收完数据后(此时调用self.resetTimeout()重置)连接闲置时间超时,将默认调用twisted.protocols.policies.TimeoutMixin.timeoutConnection 函数,它的定义是: def timeoutConnection(self): """Called when the connection times out. Override to define behavior other than dropping the connection. """ self.transport.loseConnection() 也就是关闭连接。此时,会有输出提示的: Timing out client: IPv4Address(TCP, '72.14.192.68', 43949) 经过实践,确实可以让Web Server 占用的 Socket 连接大为减少。 何为 TimeoutMixin 有人和我一样抱怨 Twisted 的这个问题: 『Client will not close the connect unit it disconnects itself. But the server can't control the connect. In some critical environment, the server has to maintain a lot of connect which maybe is idle.』 有人回复说, twisted.protocols.policies.TimeoutMixin 可以让 Server 主动断开连接: class TimeoutTester(protocol.Protocol, policies.TimeoutMixin): timeOut = 3 timedOut = 0 def connectionMade(self): self.setTimeout(self.timeOut) def dataReceived(self, data): self.resetTimeout() protocol.Protocol.dataReceived(self, data) def connectionLost(self, reason=None): self.setTimeout(None) def timeoutConnection(self): self.timedOut = 1 另一个样例参见: http://code.google.com/p/proxy65/source/browse/trunk/proxy65/socks5.py 参考资源: 1、How to set client timeout with reactor.listenTCP? 2、参考 twisted 的文档 《Knowing When We're Not Wanted》: 『 Sometimes it is useful to know when the other side has broken the connection. Here is an example which does that: from twisted.web.resource import Resource from twisted.web import server from twisted.internet import reactor from twisted.python.util import println class ExampleResource(Resource): def render_GET(self, request): request.write("hello world") d = request.notifyFinish() d.addCallback(lambda _: println("finished normally")) d.addErrback(println, "error") reactor.callLater(10, request.finish) return server.NOT_DONE_YET resource = ExampleResource() This will allow us to run statistics on the log-file to see how many users are frustrated after merely 10 seconds. 』 3、关于Windows频繁打开关闭端口时出现的问题 ; 4、Choosing a TCP Port for a Network Service ; 5、02-Twisted 构建 Web Server 的 Socket 长链接问题 | 07.杂项 | Python ; 6、Windows频繁打开和关闭端口可能引发的问题 | 07.杂项 。
04-WebFinger的利用 郑昀 201005 隶属于《02.技术预研》小节 Kuber 的 SocialBadge 还利用了 WebFinger ,从而可以根据用户输入的 Email 地址获取它的关联信息。当然 Kuber 还是走 Social Graph 来得到关联信息,因为 WebFinger 的数据能被 Social Graph 调用。 什么是 WebFinger ? Finger 是个UNIX指令,在Unix系统下输入 finger email@domainname.com 就可以知道和该Email账户相关的信息。可以理解为Finger 指令是在网络发展初期,用Email帐号来作为每个网民的网上身份证的一种尝试。 WebFinger 的目的是什么呢?那就是通过使用户能将元数据信息附加其中,从而让现存的Email地址更具价值。元数据包括如下内容: * 想要公开的个人资料 * 指向ID提供商的链接(比如 OpenID server) * 公共密匙 * 以此Email地址为ID的其他服务 (比如 Flickr, Picasa, Smugmug, Twitter, Facebook的所有服务的用户名) * 指向一个网络身份的URL地址 * 个人数据资料 (昵称,姓名等) * 甚或是一个不包含公共元数据的,但仅仅包含一个指向包含元数据终端的链接。 Google推出WebFinger的目的,就是要让你的email更有用,使之成为一种以email为中心的OpenID。 WebFinger 现已融入 Google 各种 Social API 可以在Buzz API Doc里发现 WebFinger 的踪影。Buzz API FAQ里说道:计划引入『distributed profile and contact information with WebFinger』。Google的人也说过:『 We've now enabled WebFinger for all gmail/google profiles with public profiles. 』 Google Webfinger 的查询地址是: http://www.google.com/s2/webfinger/?q=acct%3Azhengyun%40gmail.com 即传入关键词为:“acct:foo@example.com”。acct 代表 account 。 上面这个查询的返回是: <?xml version='1.0'?> <XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'> <Subject>acct:zhengyun@gmail.com</Subject> <Alias>http://www.google.com/profiles/zhengyun</Alias> <Link rel='http://portablecontacts.net/spec/1.0' href='http://www-opensocial.googleusercontent.com/api/people/'/> <Link rel='http://portablecontacts.net/spec/1.0#me' href='http://www-opensocial.googleusercontent.com/api/people/110589389080388532670/'/> <Link rel='http://webfinger.net/rel/profile-page' href='http://www.google.com/profiles/zhengyun'type='text/html'/> <Link rel='http://microformats.org/profile/hcard'href='http://www.google.com/profiles/zhengyun' type='text/html'/> <Link rel='http://gmpg.org/xfn/11' href='http://www.google.com/profiles/zhengyun'type='text/html'/> <Link rel='http://specs.openid.net/auth/2.0/provider'href='http://www.google.com/profiles/zhengyun'/> <Link rel='describedby' href='http://www.google.com/profiles/zhengyun' type='text/html'/> <Link rel='describedby' href='http://s2.googleusercontent.com/webfinger/?q=acct%3Azhengyun%40gmail.com&amp;fmt=foaf' type='application/rdf+xml'/> <Link rel='http://schemas.google.com/g/2010#updates-from'href='https://www.googleapis.com/buzz/v1/activities/110589389080388532670/@public' type='application/atom+xml'/> </XRD> 其中,它返回的 https://www.googleapis.com/buzz/v1/activities/110589389080388532670/@public 就是我的 Google Buzz 公开更新。 Alias节点则是:『The <Alias> element indicates an alternative URI the account might be known as. This would typically be an HTTP profile page, email address (prefixed with a mailto: URI scheme), or another account URI. The first link provides the location of the user’s Portable Contacts service.』所以一般都是对应于用户的Profile地址。 Google 的这个查询入口给了我们一种可能:根据输入的 Email 地址(尤其是Gmail地址),获取他的Profile地址。
01-如何获取 Twitter User Profile 郑昀 201005 隶属于《07.杂项》小节 主要通过三种方式。当然,算上各种公开的 twitter 第三方Proxy API ,会更多。 由于每一种方式都有请求频率限制,所以建议最终程序混合这三种方法,要么随机选择其一,要么按优先级逐次访问,如果访问不通,立刻切换到下一种。 1.YQL 方式 YQL 和 Yahoo! Pipe 都是最优秀的 mashup 工具之一,身为 Geek 至少要熟悉 Yahoo! Pipe 。 1.0.背景 雅虎发布的YQL,背后的机理是每个人都可以将他的数据以一种特殊的方式供其他人使用,谁想要读取这些数据,只需要使用一种类似SQL语法的语言即可。 它是一个具有基于REST接口,可以让开发者从各类资源中查询数据的在线查询处理器。 它是一个严格的查询处理托管服务。这也意味着YQL不受单独的数据资源限制,甚至不限制应用于雅虎的自身产品。YQL可以操作任何第三方数据源,只要对方是一种常见的格式,如RSS, ATOM, JSON, XML,等等。 以上文字参见互动百科。 1.1.Twitter API 都可适用于 YQL 上面说了,只要对方是一种常见的数据格式,如JSON、RSS,都可以被 YQL 作为数据源。所以,我们访问 Twitter 受访问频率限制时,可以走这条路。 1.2.YQL 的 Python 封装库 Python yql 库主页地址:http://python-yql.org/ 安装简单:easy_install yql 。使用也非常简单: >>> import yql >>> y = yql.Public() >>> query = 'select * from flickr.photos.search where text="panda" limit 3'; >>> result = y.execute(query) >>> result.rows 但要小心,yql 库实际上是组装了一个查询YQL的 HTTP URL ,用自定义或外部传入的 httplib2 实例发起请求,那么它势必受到网络环境的影响,比如 GFW 的干扰。 1.3.请求限制 YQL 在 Usage Information and Limits 中说: 『 Per application limit (identified by your Access Key): 100,000 calls per day. Per IP limits: /v1/public/*: 1,000 calls per hour; /v1/yql/*: 10,000 calls per hour. All rates are subject to change. YQL rate limits are subject to the rate limits of other Yahoo! and 3rd-party Web services. 』 1.4.YQL 的 Console 网页测试 进入 http://developer.yahoo.com/yql/console/ 页面, 在 Your YQL Statement 输入框中输入如下 SQL 命令: select * from json where url='http://api.twitter.com/1/users/show/fenng.json' 然后点击“TEST”按钮,稍后,FORMATTED VIEW就会以 XML 方式显示出 Twitter 用户 fenng 的 profile 了。 SQL 命令 select * from json where url="http://search.twitter.com/search.json?q=rt&rpp=20&lang=zh" 可透过 Twitter Search 接口得到最新的 RT 消息。 SQL 命令 select * from json where url='https://twitter.com/statuses/user_timeline/fenng.json' 可以得到 fenng 最近发布的若干条 Tweets 。 1.5.Python YQL 的调用方式 对于其他数据源的请求,比如 flickr ,在我们的网络环境下,yql 库都不会有什么问题。但对于 Twitter 数据源,貌似就容易被重置链接(但浏览器访问 YQL Console 做查询却没事儿。所以也许是User-Agent的事儿。),于是乎我们就要设置一个代理穿墙。而代理选择 Puff 貌似容易得到 401 HTTP状态,所以选择 Mr.Zhang 了。 代码如下: import yql import httplib2 import socks h = httplib2.Http(proxy_info = httplib2.ProxyInfo(socks.PROXY_TYPE_HTTP, 'localhost', 2010)) y=yql.Public(httplib2_inst=h) results=y.execute("select * from json where url='http://api.twitter.com/1/users/show/fenng.json'") print results.rows print results.uri (注:socks 库需要安装,主页地址:http://socksipy.sourceforge.net/ ,它没有安装程序,下载源代码包把 socks.py 复制到 Python's lib/site-packages directory 即可。httplib2 则用 easy_install httplib2 安装即可。) 你也可以把 YQL 认为是一个代理,能把任意数据格式转换为指定格式的代理。 2.SocialGraph 方式 Google Social Graph 的 lookup 方法可以获取一个 twitter 用户的关联信息。 我们可以通过组装这样的 URL : http://socialgraph.apis.google.com/lookup?q=twitter.com%2Fzhengyun&pretty=1 来得到 zhengyun 的 twitter profile ,结果如下所示: { "canonical_mapping": { "twitter.com/zhengyun": "http://twitter.com/zhengyun" }, "nodes": { "http://twitter.com/zhengyun": { "attributes": { "fn": "zhengyun", "adr": "\u5317\u4eac", "photo": "http://a1.twimg.com/profile_images/27832922/zhengyun_ustc_bigger.jpg", "exists": "1", "rss": "http://twitter.com/statuses/user_timeline/zhengyun.rss", "atom": "http://twitter.com/statuses/user_timeline/zhengyun.atom", "url": "http://twitter.com/zhengyun", "profile": "http://twitter.com/zhengyun" } } } } 当然,这没有人家 Twitter 自己的API给的数据全,但也算是能得到用户的 Location(就是 adr 字段)和 头像(photo字段)等最基本字段了。 2.1.请求限制 Google 文档写道:『Use of the Social Graph API is subject to a query limit of 50,000 queries per user per day. If you go over this 24-hour limit, the Social Graph API may stop working for you temporarily.If you continue to exceed this limit, your access to the Social Graph API may be blocked』,看来需要知道检测到“stop working for you temporarily”时触发哪种异常,这样才能避免被block。 3.Twitter 方式 最后可以直接访问 Twitter 的 user/show 方法: http://api.twitter.com/1/users/show.xml?id=noradio ,而不需要请求 statuses/user_timeline 方法。 举例: http://api.twitter.com/1/users/show.json?id=zhengyun 3.1.请求限制 如果我们的出口IP在 twitter 的白名单上,那么访问限制是:20,000 requests per hour。否则是每小时150~350次。 附录A: Python YQL 库对查询命令 select * from json where url="http://api.twitter.com/1/users/show/fenng.json" 实际是组装了一个 HTTP 请求: http://query.yahooapis.com/v1/yql?q=select%20*%20from%20json%20where%0A%20url%3D%22http%3A%2F%2Fapi.twitter.com%2F1%2Fusers%2Fshow%2Ffenng.json%22&diagnostics=true
01-Twitter Streaming API的调用 郑昀 201006 隶属于《实时分析搜索引擎/02.数据获取》小节 修改历史: 1 2010年10月修正,因为Twitter要求必须走OAuth接口。 Twitter 提供了两种 Streaming 接口,让第三方可以省却轮询,由 Twitter 主动把合适的数据推送过来,近乎实时。 1、chirpstream api 接口地址是:http://chirpstream.twitter.com/2b/user.json [需FQ] 这个接口只是把你和你的好友以及各种与你有关的消息都推送过来。 参考此文《Tutorial: consuming Twitter's real-time stream API in Python 》,也可以从http://pastebin.com/U4m1Zsvs 这里复制此文源代码。 你需要维持一个 Socket 长连接。 你还需要通过 Twitter 的 OAuth 验证。 你将会得到一个 Twitter 用户登录后所应该看到的各种消息流。 我们检测单次数据包的最后是否以 \r\n 结尾: if data.endswith("\r\n") and self.buffer.strip(): 以此来判断检测一个完整的 JSON object 。 程序需要处理的几种常用消息: friends : 登陆用户都有哪些好友,即 my followings;这是一个id数组,每一个 id 代表一个用户,此 id 并非用户的 screenname ,而是 Twitter 用户的唯一数字标识。你可以通过持续积累 tweets 消息中的 user id ,从而获知自己的 friends id 数组中每一个 id 对应是谁。 event 之 retweet : 其他用户使用官方格式的 retweet 转发的消息; event 之 favorite : 其他用户使用官方格式的 favorite 按钮收藏的消息; event 之 follow : 其他用户关注了本登陆用户; tweet : 普通的消息。从 in_reply_to_user_id 字段可获知是回复给谁的消息。 friends 消息数据示范如下: {u'friends': [80831118, 750713, 1580781,]} event 共有以下类型: follow direct_message retweet favorite unfavorite delete event 之 消息数据示范如下: {u'source': {u'id': 1490631, u'location': u'30.209214,120.198216',u'screen_name': u'Fenng'}, u'created_at': u'Sun Jun 20 08:46:09 +0000 2010', u'target': {u'id': 31347758, u'screen_name': u'minzhou',u'name': u'Min Zhou'u'following': True}, u'event': u'follow' }(已略去大量无关字段)即 Json 中用 u'event': u'follow' 来指明本消息是什么类型的 event 消息。 tweet 消息数据示范如下: {u'text': u'@cnkang \u5176\u5b9e\u6211\u5bf9\u627e\u5927\u8d28\u6570\u66f4\u6709\u5174\u8da3\u2 026\u2026', u'created_at': u'Sun Jun 20 08:43:36 +0000 2010', u'coordinates': None, u'source': u'<a href="http://echofon.com/" rel="nofollow">Echofon</a>', u'in_reply_to_status_id': None, u'in_reply_to_screen_name': u'cnkang', u'in_reply_to _user_id': 35457544, u'place': None, u'geo': None, u'id': 16604579153L, u'user': {u'id': 6132042, u'screen_name': u'delphij', u'following': None}}(已略去大量无关字段)这是一个 Reply 消息。一般 Twitter 客户端软件都是根据此消息体内的“in_reply_to_user_id ”字段判断被回复者的id 是不是自己的id,或者在不在自己的 friends id 数组里,如果都不是,就可以忽略此 Reply 消息。 2、streaming api Twitter 的这个接口能够让第三方近乎实时地获取公开数据的各种子集: 『The Twitter Streaming API allows near-realtime access to various subsets of Twitter public statuses.』 接口地址:http://dev.twitter.com/pages/streaming_api ,需FQ。 2.1.权限问题 以 Streaming 方式连接 stream.twitter.com ,需要先通过 Twitter OAuth验证。 目前默认访问者只能使用 filter 这一个方法。 其他的 firehose 、retweet 、links 等方法则必须拥有访问权限,否则会得到如下提示: Error 403 User not in required role 。 如果想申请特别权限,可以写信给 api@twitter.com ,说明原因。 2.2.判断 Json Object 结尾 由于是长连接,所以判断一个完整的 Json Object ,需要某个特殊标志。这还需要请求参数的配合。 Streaming API 的 filter 方法有一个输入参数:delimited。 它的含义『Indicates that statuses should be delimited in the stream. Statuses are represented by a length, in bytes, a newline, and the status text that is exactly length bytes. Note that "keep-alive" newlines may be inserted before each length.』 所以我们指定它的值是 newline ,这样 Json Object 之间就以 \r\n 作为分隔符。 文档上称 『 every object is returned on its own line, and ends with a carriage return. Newline characters (\n) may occur in object elements (the text element of a status object, for example), but carriage returns (\r) should not.』也就是说,\n 会出现在 Json Object 包体内,但 \r 则不会。 所以,我们检测单次数据包的最后是否以 \r\n 结尾: if data.endswith("\r\n") and self.buffer.strip(): 以此来判断检测 JSON object 的结尾。 2.3.Streaming API无法处理『Non-space delimited languages』 Twitter 的 John Kalucki 曾经这样回答: We break the status text into tokens by whitespace and punctuation, then apply the tokens to a hashmap of tracked terms. If the language doesn't have whitespace, the only thing that will match is the entire Tweet. 即 Streaming API 目前还不能支持阿拉伯语、汉语、日语等『Non-space delimited languages』,原因是他们只能根据空格和标点把tweet打散成一个一个token。 http://apiwiki.twitter.com/User-Stream-Implementation-Suggestions 也有一句话提示: 『Non-space delimited languages are currently unsupported by track. For example: Arabic, Chinese, and Japanese language queries will not return results.』 2.4.track 参数的输入 先不理会他们能不能处理亚洲文字的追踪。 我们用 track = ["的","我","了","#duanzi","@rtmeme","@zhengyun"] 这么一个数组定义所要追踪的 keywords ,词与词是 Or(或) 的关系。注意,参考我的“玩聚网技术Wiki\01.数据抓取”之《06-HTTP请求中的编码问题》,必须对track里的中文字符的 UTF-8 编码字符串做转义: trackKeywords = map(lambda s: unicode(s, 'gbk', 'ignore').encode('UTF-8', 'ignore'),track) 一旦一条新 tweet 发布,正文提及了 track 数组中的某一个词,这条消息就会被主动推送过来。它的 Json 格式与 chirpstream api 之 tweet 格式一样。 2.5.locations 参数的输入 filter 方法支持用发布者的地理位置过滤,比如 locations 参数为 [-74,40,-73,41] 就可以把从 New York 发布的 tweets 都推送过来。这也要求用户的 tweet 用 Geotagging API 发布,否则无法确定发布者的地理信息。默认用户设置中“Add a location to your tweets”这个选项是选中的。 -74,40的含义是,经度-74,维度40. 第一对经纬度应该是目标区域的SW(西南角)的位置信息。 -74,40和-73,41选中的区域叫做 bounding box ,这个区域很小,经度维度之间不能相差一度。如果区域过大,会得到如下提示: Location track must be less than 1 degrees on a side 。 你选中的目标区域,它的经纬度信息可从:http://www.getlatlon.com/ 得到。 注:中国大陆用户很多都是从代理上的 Twitter ,所以它的 geo 信息可能是代理的。 2.6.Twitter Annotations的追踪 Twitter Streaming API将支持Twitter Annotations:你可以track一个注释类型,比如你可以近乎实时地得到所有标注了“movie”注释类型的 tweets ;你还可以track一个注释类型和值。 背景知识:什么是Twitter Annotations. 背景PPT:[slideshare]Twitter API Annotations .
郑昀 201007 原标题:Working on Sentiment Analysis on Twitter with Portuguese Language 原作者:Artificial Intelligence in Motion 原作发表日期:2010年7月20日 原文地址:http://aimotion.blogspot.com/2010/07/working-on-sentiment-analysis-on.html 虽然是讲葡萄牙语下的情感分析,但作为一个入门指导也有可看之处。 摘要翻译如下: 情感分析(Sentiment Analysis,缩写SA)是基于互联网上发布的内容,辨识出人们对某事物的感情或者感觉,如某个产品、公司、地点、人。这种分析方式最终可能会得到一份完整的报告,描述人们对于一个事物的看法,而不需要你寻找并阅读相关的所有意见和新闻。 在机器学习领域,情感分析属于文本分类问题,它需要检测关于某一个特定主题(topic)的正负面意见。挑战主要是,识别文本中各种情感是如何表达的,它们是否是正面或负面意见。 下面列出一些情感分析应用: 公司股票领域的SA:通过对分析人士的意见进行处理和汇总,尝试预测股价的趋势; 产品的SA:一个公司可能会对他们的顾客如何评价某一个产品感兴趣。比如,Google会使用情感挖掘(Sentiment Mining)技术来获知人们如何谈论Android手机Nexus One,它们会被用来促进产品的改进甚至新销售政策。 地点(Places)的SA:一个要出去旅游的人可能想知道最好的游玩地点或者最好的餐馆。意见挖掘(Opinion Mining)可能会帮这些人推荐好的places。 竞选(Elections)的SA:投票者可以用SA来统计其他投票者对某一个候选人的态度。 游戏和电影的SA。 另一个SA应用是分析社会化网络中的状态信息,如Twitter或Facebook。 在这个领域有不少工具,但多数只能处理英文tweets,对于其他语言的往往会分错正负面。 这是一个新领域,特别是处理葡萄牙语时,所以我决定开发一个简单的SA工具来处理tweets。我将使用一个常见的简单的机器学习技术“朴素贝叶斯 Naive Bayes”,来分类tweets影评。 SA如何工作?都需要哪些步骤呢? 1、数据收集和预处理:重要的是,要删除那些仅仅表述事实却没有意见表达的条目。要把注意力集中在用户的意见上。 2、分类(Classification):一般是三个分类:正面(positive)、负面(negative)、中性(neutral)。 3、结果的展现:在这一步里,几种意见的分类必须被归纳好,以便展示。目的是便于理解人们是怎么谈论一个事物的,可以用图形或文字展现。 第一步,数据收集和预处理 需要先提取出文本中的关键词,这些词能让你正确地分类,把它们储存为特征向量(Feature Vector)的形式,F=(f1,f2,…fn)。一个特征向量的每一个坐标代表一个词(word),也被叫做(原文的)特征。 每一个特征的值可能是一个二进制数值,表明这个特征有还是没有,一个整型数可以进一步表示原文中该特征的强度(intensity)。 一定要选择一组好的特征,因为它将影响到随后的机器学习过程。 选择好的特征,需要靠着我们的直觉,垂直领域的知识以及大量的实验。 我们的处理方法包含词袋(Bag of words)的使用。(译注:在信息检索中,Bag of words model假定对于一个文本,忽略其词序和语法,句法,将其仅仅看做是一个词集合,或者说是词的一个组合,文本中每个词的出现都是独立的(unigrams),不依赖于其他词是否出现,或者说当这篇文章的作者在任意一个位置选择一个词汇都不受前面句子的影响而独立选择的。这种假定有时候是不合理的。)这个方法的挑战在于,如何选择适合成为特征的词。 例如,就这个模型而言,“今天我看了电影《暮光之城》,很美丽”,那么可以表达为特征向量: F={'今天':1,'我':1,'看':1,'电影':1,'暮光之城:1,'很':1,'美丽':1,} 这里我把特征向量表述为一个python的字典。 显然,在实际应用中,一个特征向量将会有大量的词,这种模型将会非常低效。 一个做法是手动选择最重要的关键词,比如“美丽的”(形容词)很好地指明了作者的意见。最重要的词如“优秀”“可怕”都可以被选为特征。但Pang et al.的论文表明手动选词已经被统计模型(statistical models)胜过,该模型是根据在已有的训练语料(corpus)词的出现几率来选择一组词作为特征。这样,选择的质量就依赖于语料的多少,以及某垂直领域训练和测试数据的相似度。 当语料中混杂了不同垂直领域的文本,而它们又和我们要分类的领域没有什么共同特性,那么就会导致不正确的结果。 还需要建立一个停止词表,保存一些没什么用的词,如代词、介词等。 这个模型有局限性,它不能了解词之间的句法关系以及一个词的不同含义。 第二步,分类 分类算法对于SA任务来说是成熟的。 在分类一个新tweet前,需要有一个被标记过的训练集(training set)。训练集的数据需要标记出句子的主观性(subjectivity,即判断这个句子是一个事实的陈述,还是一个意见的表达)和倾向性(polarity)。本文用的是最简单但非常高效的一种机器学习技术:朴素贝叶斯(Naive Bayes,缩写NB)。 朴素贝叶斯模型或者朴素贝叶斯分类器是一个简单的概率分类器。它假定一个特征值对给定分类的影响独立于其他特征值。比如一辆小轿车可以被认为是一个交通工具,如果它有四个轮子,一个引擎,至少两个门。即使这些特征互相依赖,但朴素贝叶斯分类器就是认为这些特征独立地贡献概率。 在我们这儿,tweet中的每一个词都被当成一个独特变量,目的是发现词属于某一个特定类(正面还是负面)的概率。尽管有朴素的设计和简化的假定,但朴素贝叶斯分类器在很多复杂的真实环境里工作得非常好。最常用的就是垃圾过滤。实际上,最流行的包之一SpamAssassin就是用的NB算法。 我用的是Naive Bayesian Classifier的一个简单Python实现。 我们先要训练分类器,所以要创建一个把tweets(或words)已经分好类(分为正负面)的训练集。 第三步,Summarization 最后一步,结果展现。 Summarization by TwitterSentiment Analyzer - http://twittersentiment.appspot.com/ 我们研究领域:Twitter上的影评 我将提供工程的所有源代码。目前WebService可以像这样请求,返回的格式是JSON: http://mobnip.appspot.com/api/sentiment/classify?text=Encontro+Explosivo+filme+ruim+Danado&query=encontro+explosivo (注意,它只支持葡萄牙语。查询的电影是危情谍战Knight and Day。) 返回的结果是: {"results": {"polarity": "negativo", "text": "Encontro Explosivo filme ruim Danado", "query": "encontro explosivo"}} 也就是负面的表达。 小结: 我们可以注意到情感分析是互联网的一个趋势,你可以从微博客或Social networks获得大量包含了意见的数据。 我觉得主观性识别(subjectivity identification,即判断一个文本是一个意见还是事实陈述),和讽刺的识别,是很难做的。拼写错误的词和缩写词,常见于博客和社会化网络,在搜索和分类时,也是大麻烦。
关于Cutt.com关于Topic Engine 郑昀 20100726 上个月参加谷文栋效力的简网的内部产品调研会,提前了解了Cutt.com的功能。Cutt的Slogan是“关注你的关注”,原本以为它会是一个以个性化推荐为主打的应用,但从Slogan上看,更接近于信息过滤器。当你搜索时,它像一个Topic Engine,当你点击某一篇文章阅读时,它的界面又像Google Reader或鲜果阅读器。 Cutt.com在组织资讯时,我把它划分到Topic Engine一类,暂且不谈它的阅读模式和群体智慧。 Topic Engine的过去、现在和未来 先定义什么是Topic Engine。这是一个应用层面的概念,与算法层面的Topic Model概念不大一样。 我们所说的Topic,指可以用一个或一组关键词描述的一个主题,它对应于一个真实存在的、可被人理解的话题,比如用“小S Or 徐熙娣”关键词描述的主题“徐熙娣”。之所以叫“Engine”,指的是在有限(如新闻、论坛、博客、微博客、图片、视频等)的网络数据中,按照某种聚合逻辑,自动把与本主题有关联的资讯做有机整理和展现。 举一个例子,一个Topic Engine通常要展现的有: Title——Jessica Alba Topic Logo Topic 隶属的 Categories(可以用作面包屑导航) Topic Wiki Item Related Top Stories(Story和Article的区别在于,一个Story可以包含若干内容大致相似、来自于不同新闻源的Articles。也就是Story做了去重和相似性计算工作。) Related Status Stream(即关联微博消息) Related Images Related Videos Related Topics/Topics Relations Tag Cloud(标签云) Entities Cloud(实体云,主要是人名) 有点儿像搜索引擎的搜索结果。 只不过多做了很多细化工作,而且也不是随便一个关键词就是一个Topic,需要事先算好。 Topic下的每一类数据都要事先做好排序,大胆地抛弃,大胆地去重,根据合并重复次数、普通网页反向链接次数或社会化网络引用和转发次数来做优化。 自动发现Topic并归类 Topics不是人工定义的,虽然有人工干预的成分。它包含实体发现与识别(Entity Detection and Recognition)、关系发现与识别(Relation Detection and Characterization)甚至事件发现与识别(Event Detection and Characterization)等技术点。 比较简单的做法是,利用自然语言处理中的实体识别技术,利用书名号,来确定人名、地名、组织名、书名、网络游戏名和电影名,这些显而易见都肯定是Topics。然后利用自然语言处理中的自动分类技术确定这些Topics都隶属于哪些类别。比如“徐熙娣”属于“明星娱乐”类。当然,对于娱乐明星,可以直接找百度明星列表(百度贴吧明星列表、百度新闻明星档案列表),一下子就提取到大量正确的明星名。 这里会有很多dirty work。如,语料中包含“《最终幻想》”,那么根据书名号知道“最终幻想”应该是一个Topic,接下来截取这个词出现位置前后N段/N句话作为一个字符串,送给自动分类引擎,判断属于哪一个分类,是电影还是书籍还是游戏,有时会有歧义的。也可以反过来做,先整理出某一个分类的语料,然后在这个语料集内做Topic Detection and Recognition。这个分类的语料一般由人工整理,保证语料的纯净;也可以是机器整理的。 比如说DIPRE(Dual Iterative Pattern Relation Expansion)算法,就是在输入极少的情况下,先根据已经定义好的一个可靠的数据模式,比如“Google”和“Android手机”,叫做“seed items”。拿这个seed items去搜索到同时出现这两个词的文档,然后依靠机器自动发现一些模式,再把新模式继续在对应网站里深度广度地寻找匹配的文档,反复迭代,最终能够从一两个seeds找到一大批可靠的实体关系和模式。机器自动准备分类所需语料,也有点像这个原理,但得先手工拟定一批分类。 这样,一个分类找几个核心关键字;在大量语料中找出现核心关键字的文章,再算这些文章的一些特征;然后统计一些特征权重;设一定阈值可以找到该分类文章的一些特征,同时也能找到一些出现频次较高的标签等,这些标签就可以作为Topics。 Topics之间的连接点 在Topic Page上,Related Topics列出的Topic,是跟本主题最相关的主题。 一个直截了当的做法是,首先匹配到最近包含“徐熙娣”一词的热文(这个热文集合确定是相对可靠的语料,而不是随随便便的一篇文章)中,统计一同出现过的频率最高的实体(Entity)名称,取前几个;认定这些实体名称跟“徐熙娣”有明显的联系,我们称之为Connections。能够对每一个Topic做Connections计算的应用,就叫做Connections Engine。 Connections Engine还可以计算Topics之间的关系远近,绘制为图形,用可视化的方式表示。比如,酷我的明星连连看: 查看Cutt的“吴尊”频道,会发现文章中的“罗志祥”“高晓松”“崔始源”都没有作为标签提取出来,但Cutt里却又有罗志祥和高晓松频道,说明Cutt的标签提取并非我们自然语言处理里的传统“实体识别”,有点怪。 如何确定一个Topic映射的关键词Keywords 举个例子,“徐熙娣”这个Topic,映射的Keywords为:“小S” Or “徐熙娣”。文章中包含“小S”,也会被归类到这个Topic下。 再比如,你在Cutt.com搜索“陈莎莎”,会提示你: 简网为您找到了如下和 陈莎莎 相关的频道:陈紫函频道。 这说明,Cutt知道陈莎莎是陈紫函的别名(但Cutt不知道王静雯是王菲的曾用名(Updated:我拼写错误,应该是“王靖雯”,这样就可以找到王菲))。这两个例子属于“实体别名识别”范畴。解决这个问题,不知道Cutt实作采用什么办法,有人是利用维基百科的数据做。 Topic Engine的过去 国外做Topic Engine的,起起伏伏有很多家,比如: daylife:参考主题Lindsay Lohan领略什么是新闻门户级的Topic Page; 曾经的boxxet; 曾经的ellerdale,现被flipboard收购; 曾经的奇虎聚客; Evri,现在它收购了Twine.com:参考主题Orlando Bloom即可领略Topic Page的构建相当与时俱进,Connections图很快就反映出他结婚的关系; 在Twitter的HashTags数据上构建的twubs; 当然还包括曾经的玩聚,2008年前有一个版本是Topic Engine+Connection Engine,于北京奥运前夕因过于敏感而自废武功。 Topic Engine的现在 现在还活着的此类应用,有着强大的开放API,做着结构化互联网数据的基础工作,与强者结盟。 你可以用daylife的API构建出一样精彩的新闻聚合和分析小门户。比如,给定一个Topic,API返回: Related Story、Images、Videos、Quotes、Timeline、Topics。它还可以给定一篇文章,输出相关文章。Cutt.com目前也在考虑开放像Daylife一样的API,它至少能让你搭出一个自动更新的Topic Page。 Evri的API则在语义方面更进一步,深挖洞广积粮,如下图所示: 著重于实体识别、实体关系的挖掘、关联推荐、情感分析等常规语义任务。这些语义任务要想做好,真是任重道远。 国外很多公司,都是在基础应用数据的构建上下大力气,再比如FreeBase(隶属于Google新近收购的Metaweb),国内很少见到这样做事的。 Topic Engine的未来 从过去Topic Engine的外在形式来说,不容易运营成功。有几个原因: 1、通常是只读的,用户难以围绕Topic Page做互动。 2、排列结果通常是站方算法固化的,与具体用户的行为无关。 3、实时性存在问题,当然在微博客出现之后,这个问题会比较好解决,否则就只能设定信息源是有限的A-List(指经过挑选的优质信息源),便于做快速扫描。 4、同一个Topic下堆积太多同质文章,容易让陌生用户产生焦虑感,当然这取决于下一步如何引导用户消除这种焦虑感,让用户觉得这就像RSS阅读器一样,订阅了就走,老数据无所谓,等有了新数据再回头来看。 5、对数据的分析太少,基本是资讯链接的堆砌。 6、中国大陆优质原创文章太少,同一批文章反复出现在不同频道里。 7、优质信息源太少,更新频率太低,用户关注几个Topic之后,数据更新太慢。 8、用户兴趣变化很快。比如在Google Reader里,人们常会订阅/退订一些Feed,当Feed很多时略显不便。而新一代的flipboard/TwitterTimes则根据用户自己的Twitter/Facebook生成资讯阅读界面,用户同样需要Follow/Unfollow一些id,来体现自己的兴趣变迁;Follow正确的人,也是很麻烦的。同样,在Topic Engine中你也不得不需要做一些维护工作。 未来Topic Engine是否能克服过去的这些问题,把以下几个东西结合好,也许有很好的前景: Topic Engine+Connections Engine+Recommendation Engine+Collaborative Filtering+Real-Time Streaming 当然,Cutt.com 不仅仅是Topic Engine+Connections Engine,据谷文栋介绍,它还要承载这样的使命:『根本需要,是兴趣。我不感兴趣的,就不要来纠缠我。我们希望有这样一种方式:你只需要表明自己的兴趣,然后通过群体智慧与机器运算,让信息找到你。』它要用简单的界面,隐藏复杂的技术细节。
郑昀 20100806 话说惠普有一个研究社会化网络数据挖掘的 Social Computing Lab(SCL) 实验室,领头的是 Bernardo Huberman 博士。 他们最近发布了一个研究报告《Influence and Passivity in Social Media》,基于250万用户的2200万 Tweets 数据。它的一个结论是:the correlation between popularity and influence is weaker than it might be expected,名气和影响力没什么关联,比人们期望的弱得多,High numbers of followers does not equal influence because those followers do not re-Tweet,名气和影响力是两码事,关注者多不等于有影响力,重要的是有多少人愿意转发你的消息。 最简单测量你的名气和影响力之间关系的是,发布一个能统计点击次数的短域名网址,看看到底有多少人从你的这条 Tweet 点击,别管你有多少万关注者,你是否有足够的影响力让人点击一个链接。 做PR或Ad的公司要注意这一点,想让微博客上的人帮你营销,不要只看他的 Followers/关注者/粉丝 数量,这数字没啥用,要精确测量他的真正影响力。 惠普的这个研究由于并不特定针对 Twitter ,所以它的结论也适用其他社会化网络。 加入了 Passivity 维度 大多数人都只是信息的消极接收者,他们并不会把东西转发到自己的网络里。为了让一个人有影响力,不应该仅仅停留在吸引别人的注意(眼球)从而变得有名上,还需要让用户克服他们的消极性(passivity)。 SCL 利用人在 Social Network 里的消极性,设计了一个影响力通用模型。它还开发了一个算法来量化网络中所有人的影响力,有点类似于HITS算法,综合考虑了网络的结构属性和用户之间的传播行为。 一个用户的影响力不仅依赖于他影响到的听众的多少,而且依赖于他们的消极性。 以前的影响力测量方法则主要基于一些个体的统计属性,比如关注者的数量,比如锐推(retweets)的数量。 SCL的这个算法有很好的预测能力,比如预测一个发布链接的点击次数上限会是多少。 SCL还发现那些高消极性的节点(node),大多数都是垃圾制造者(spammers)或者机器人账户(robot users)。(注:不知道SCL如何评估@rtmeme这种机器人。) 实作 SCL 像锐推榜一样也是从Twitter Search API进去,查询包含 http 关键词的 Tweets ,试图收集齐提到链接的 Tweets(下称“链接推”)。历经300小时,获得了2200万条相关消息,其中1500万条的链接经过检查是有效格式。据他们评估,这2200万只是那个时间段内 Twitter 全部消息的十五分之一。 然后对这个集合中的用户,逐一通过 Twitter API 查询该用户的元数据,尤其是 followers/followings 的数量。 这样就得到了一个带着时间戳的 URL 集合,一个对应用户的完整的 Social Graph 。 user retweeting rate=用户A决定转发的URLs数量 / 用户A从他的 followings(他所关注的人)接收到的 URL 数量 。 audience retweeting rate=用户A发布的URLs中被 Followers(关注者)所转发的数量 / 用户A的一个 follower(关注者)从A这里所接收到的 URL 数量 。 很容易计算用户之间的 pairwise influence 关系,比如在 Twitter 里,要计算用户A对用户B的影响力,只需要统计B锐推A的次数即可。但你很难利用这种 pairwise influence 信息去计算一个用户(如@zhengyun)对整个网络的影响力。 SCL设计了 IP(Influence-Passivity) 算法,每一个用户都有一个 influence score 和 passivity score 。一个用户的 passivity 得分用来评估其他人想要影响到他有多难。 该算法有以下假设: 1、一个用户的 influence score 依赖于她所能影响的人数以及这些人的 passivity 。 2、一个用户的 influence score 还要考虑到,她所能影响的人专注程度(how dedicated)。 3、一个用户的 passivity score 依赖于这样一些人的影响力:她能接收到这些人的消息但却没有被影响到。 4、一个用户的 passivity score 还要考虑到,how much she rejects other user's influence compared to everyone else. 算法迭代计算 passivity 和 influence 得分,有点像 HITS算法 的寻找 Authority 页面以及指向它们的 Hub 页面。 给定一个加权有向图(weighted directed graph,也叫带权有向图) G = (N,E,W),N是所有节点的集合(nodes),E是弧的集合,W是权值。某条弧 e = (i,j) 的权值 Wij ,代表一个比率:i 对 j 的影响力 / i 试图施加在 j 上的全部影响力。 IP算法将这个 graph 作为输入。SCL 是这样构图的: nodes是那些曾经发布过三条以上链接推的人。 如果用户 j 曾经至少锐推过用户 i 的链接推一条,那么弧arc (i,j)就存在。这条弧的权值计算公式为:Wij=Sij/Qi,其中,Sij是用户 i 发布的链接推中被用户 j 锐推的数量,Qi 是用户 i 发布的链接推数量。 最终计算时,SCL 的这个 graph 的节点数是 45万个,1百万条弧,平均权值是0.07。 在这个 graph 数据基础上,SCL 计算了 PageRank、Influence和Passivity 得分、Hirsch Index. (注:H-index ,是一种评价学术成就的方法。一名科研人员的h指数是指他至多有h篇论文分别被引用了至少h次。h指数能够比较准确地反映一个人的学术成就。一个人的h指数越高,则表明他的论文影响力越大。例如,某人的h指数是20,这表示他已发表的论文中,每篇被引用了至少20次的论文总共有20篇。) 在 Twitter 里,一个用户的 H-index 是 h ,代表他的被人锐推了至少 h 次的链接推总共有 h 条。 (待续)
03-PubSubHubbub 和 twisted 的 Persistent connections 能力 郑昀 201005 隶属于《07.杂项》 关于上节《02-Twisted 构建 Web Server 的 Socket 长链接问题》,还可以继续探讨为何会保持 Socket 长链接。 该关闭的连接没关闭? 有人在twisted邮件列表中也反映: 『We close the render_POST with a request.write('data') & a request.finish() but the connection stays open』调用了request.finish(),socket连接依然保持着,这现象和我们遇到的一样。 在 twisted.web.server 里,Request 的 finish 函数是这么定义的: def finish(self): http.Request.finish(self) for d in self.notifications: d.callback(None) self.notifications = [] 它调用了 twisted.web.http 的 Request 类之 finish 方法: def finish(self): """We are finished writing data.""" if self.finished: warnings.warn("Warning! request.finish called twice.", stacklevel=2) return if not self.startedWriting: # write headers self.write('') if self.chunked: # write last chunk and closing CRLF self.transport.write("0\r\n\r\n") # log request if hasattr(self.channel, "factory"): self.channel.factory.log(self) self.finished = 1 if not self.queued: self._cleanup() 可以看出 request.finish() 只是说要结束写数据了,把缓冲区内的数据都发送给对方,并没有去断开连接,否则就应该主动执行 self.transport.loseConnection() 。所以不主动断开socket连接也是设计使然。 PubSubHubbub 的持久连接 本来 PubSubHubbub 的 Hub 本来就是要保持长连接,从而重用连接。它的文档 PublisherEfficiency 上称: 『HTTP persistent connections and pipelining By default in HTTP 1.1, TCP connections will be reused between requests. For a publisher serving many separate Atom feeds, this allows Hubs to get around the expense of creating a new TCP connection every time an event happens. Instead, Hubs MAY leave their TCP connections open and reuse them to make HTTP requests for freshly published events. 』 twisted.web.server 也支持持久连接 twisted.web.server 是支持 support persistent connections and pipelining 的。 文档指出: 『Alternatively, they can return a special constant,twisted.web.server.NOT_DONE_YET, which tells the web server not to close the connection』即通过返回一个NOT_DONE_YET来告知Web Server不要关闭socket连接。 所以会看到 Google Code 上不少代码都是在 render 函数内返回 server.NOT_DONE_YET 。 twisted.web.server.py 中, Request.render 函数定义中有这么一句判断: if body == NOT_DONE_YET: return ... self.finish() 也就是说如果你返回 NOT_DONE_YET ,就不会再调用 request.finish() 了。 这个 NOT_DONE_YET 标志有几种解释: http://www.olivepeak.com/blog/posts/read/simple-http-pubsub-server-with-twisted 说: 『returns server.NOT_DONE_YET. This tells Twisted to not return anything to the client, but leave the connection open for later processing.』 http://www.ibm.com/developerworks/library/l-twist2.html?S_TACT=105AGX52&S_CMP=cn-a-l 则认为: 『The odd-looking return value server.NOT_DONE_YET is a flag to the Twisted server to flush the page content out of the request object.』 http://blog.csdn.net/gashero/archive/2007/03/02/1519045.aspx 说: 『你可以使用魔术值 twisted.web.server.NOT_DONE_YET ,可以告知Resource有些事情是异步的而且尚未完成,直到你调用了request.finish()。然后调用request.finish()直到写完了所有的响应数据。』 总之,不管如何解释,return server.NOT_DONE_YET from the render_GET/render_POST method, so the connection keeps open是没错的。 所以身为 PubSubHubbub Subscriber 角色的 Web Server ,我们的 render_POST 方法可以这么写: # 处理 PubSubHubbub Hub 推送过来的数据 def render_POST(self, request): try: body = request.content.read() def finish_success(request): if(request._disconnected or request.finished): print('***>This request is already finished.') pass else: print('***>This request wiil be finished.') request.setResponseCode(http.NO_CONTENT) request.write("") request.finish() def finish_failed(request): print('-=->fail in parseData?') threads.deferToThread(self.parseData, body).addCallbacks(lambda x: finish_success(request), lambda x: finish_failed(request)) """deferToThread 方法默认用 reactor.getThreadPool() 开辟的线程池。 它调用这个线程池的 threadpool.callInThreadWithCallback 方法,实际效果和 reactor.callInThread 一样。区别只是 deferToThread 可以返回一个 deferred ,能够 addCallback。 """ except: traceback.print_exc() request.setResponseCode(http.NO_CONTENT)#即204 return NOT_DONE_YET 也就是,接收到 POST 过来的数据后,异步扔给另一个方法解析和处理,这厢立刻 return NOT_DONE_YET , 等处理成功了,回调里再 finish 掉当前的 request 。
04-Windows频繁打开和关闭端口可能引发的问题 郑昀 20100810 隶属于《07.杂项》小节 老赵写了一篇《关于Windows频繁打开关闭端口时出现的问题》,论述了他从 Windows Web Server 2008 R2 通过 TCP 连接对 Cent OS 下的 MongoDB 数据库服务做压力测试,遇到了 Socket 连接资源耗尽,导致程序报告“由于系统缓冲区空间不足或队列已满,不能执行套接字上的操作。”错误的情况。 Socket 连接资源耗尽,在 Windows Server 下很常见,如果使用者程序写得没问题的话,一般都是微软(或其他软件厂商)设置的一些默认参数不合时宜导致的。 我以前写过一篇《02-Twisted 构建 Web Server 的 Socket 长链接问题 | 07.杂项 | Python》,记述了另外一种 Windows Server 常见问题:在 Python 开发语言下,用 twisted 框架编写一个 Web Server ,接收 Google PubSubHubbub 的 Hub Server 推送过来的 Google Reader 文章共享信息时,遇到了文件描述符到达上限的现象。这个模式的特点是,用 twisted.web.server 对接 PubSubHubbub Hub Server ,双方都支持重用连接,照理说,既然保持长连接并且重用 socket 连接,不应该出现这种“too many file descriptors in select”错误。这里的知识点是 Windows 下 select module 文件描述符(file descriptor)最多是512个,而在 Linux 下这个限制为 32767 ,如果超过这个限制值,就会出现类似上面的异常。 老赵的文章指出了一个有意思的知识点:临时端口号可分配范围, 临时端口号范围定义 当一个客户端程序(比如说一个浏览器)初始化一个 connection 连接远端服务(比如一个网站)时,客户端会打开一个“ephemeral(临时)”端口,端口号一般是随机分配的。你可以用微软工具 TCPView 来查看。 那么这个进程为出站连接(outbound connection)分配端口号时,端口号的可分配范围与操作系统有关。其中一个主要影响因素是 MaxUserPort ,位于注册表的 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters 下。 下面这张表列出了不同 Windows 操作系统所能分配的端口范围: 操作系统 MaxUserPort 数值的 含义 端口范围,如果MaxUserPort 没有定义 端口范围,如果MaxUserPort 已定义 最小值 最大值 Windows NT/2000/XP/2003 Ending Port 1025 to 5000 1025 to [MaxUserPort] 5000 65535 Windows 2000/XP/2003 withKB951748/KB951746 Ending Port 49152 to 65535 1025 to [MaxUserPort] 5000 65535 Windows Vista/2008 Number of Ports 49152 to 65535 49152 to [49152 + MaxUserPort − 1] 255 16384† 表1 端口范围 当然,你如果在服务器上安装了微软的一些服务,还是会自动修改这个 MaxUserPort 参数的,比如Small Business Server 2000/2003 会修改为 60000, ISA Server 会修改为 65535, Exchange Server 2003 会改为 60000 。 看了这张表后,你会知道: 1、不要使用小于1025的端口号; 2、尽量使用系统临时端口范围(ephemeral port range)之外的端口,比如你定义自己应用程序要打开的端口号为8000,而不是5000。 3、重用连接。 查看了我的笔记本电脑(Win XP)和服务器(Windows 2003),注册表里都没有设置过这个MaxUserPort参数,所以端口范围就是默认的,在服务器上,可分配的临时端口是从1025到5000。 回到我的文件描述符打开过多的问题上, 重用端口失败+保持长连接生效 如果监听的 Google Reader User 足够多,这些用户又在一个时段集中分享文章,那么 Google PubSubHubub Hub Server 就会以极快的速度把数据推送过来,此时如果重用 socket 端口失败,而保持长连接策略又起作用,于是很快在 twisted web server 监听的端口上打开的文件描述符又爆了。 处理办法: 1、参考《02-Twisted 构建 Web Server 的 Socket 长链接问题 | 07.杂项 | Python》, twisted.web.server.Site 类的初始化函数有一个可选参数 timeout ,它的默认值是 60*60*12 ,也就是12小时,它的目的是闲置端口超时自动关闭。在我们的情景下,超时时间太长,所以才会有许多处于 ESTABLISHED 状态的 Socket Connections 积累。 所以我们缩短为 15 分钟(也可以更短),让没有得到重用的 Connections 尽快自动关闭。只需要在开始执行: reactor.listenTCP(8080, Site(MyWebResource.setup(),timeout=60*15)) 即可。 2、参考《03-PubSubHubbub 和 twisted 的 Persistent connections 能力 | 07.杂项 | Python》,接收到对方 Server 推送过来的数据后,异步扔给另一个方法解析和处理,render_POST 方法立刻 return NOT_DONE_YET 标志, 等异步处理数据成功了,回调里再 finish 掉当前的 request 。
郑昀@玩聚SR 20091127 Scrapy里面用到了Parsley。Parsley是一个挺有意思的小东西,它综合运用了CSS、XPath、正则表达式和JSON,是描述如何从网页里提取结构化数据的简单语言。估计做爬虫(Crawler/Spider)的人都会定义一套类似的模板。只不过Parsley还帮你把具体实现做了,用各种开发语言。 基本事实 Parselets就是用Parsley语言写成的片段(snippets)。 你可以近似认为一个Parselet定义了一套动作,描述如何从html代码中精确抽取数据,比如标题在哪儿,标题的链接怎么拿,评论数在哪儿如何提取。 Parsley有各种语言实现包,Ruby、Python、C/C++等。pyparsley是对应的Python库。 Code和Result示例 具体例子参见:http://parselets.com/parselets/yc/15 , 左侧的Code就是我们通常说的模板,右边的Result就是提取的结构化数据。 那么它是如何变为现实的呢? 实现 安装Parsley,再安装http://github.com/fizx/pyparsley,然后运行如下Python代码,就可以从给定网页链接,通过Parselet的描述,获得json格式的结构化数据。 zhengyun 20091127 beijing
郑昀@玩聚RT 20091210 1、 请打开链接: http://martin.atkins.me.uk/peoplesearch/#riku http://martin.atkins.me.uk/peoplesearch/#hecaitou http://martin.atkins.me.uk/peoplesearch/#kunshou 尝试People Search。 2、 请注意上面这个页面的底端写着: This service is powered by Google AJAX Search API and Google Social Graph API, but it is not a product of nor is it endorsed by Google. 也就是说此第三方应用主要是用Googe Social Graph API拼接出来的。 不是说它这个应用做得多么曲折,而是程序员可以看看它是怎么运用Google API的。 3、 其实Google Blog在4月份曾以『Google People Search』为题报道过,但实际上它的搜索结果是来自于Google Profile Search,也就是你在 http://www.google.com/profiles/ 中所填写的信息。你不填Google Profile,它就是废柴。 4、 如果你在Google里搜索『Google People Search』, 你只会得到一大堆讲述如何在Google.com里用搜索技巧找人的文章。 zhengyun 北京报道 5、 如果你真的对前面那个People Search感兴趣,不妨看看他们的开源代码: itswhoyouknow[Social Network Browser] 至少你可以观摩一下它的javascript是如何对Google Social Graph API再次封装的,如何调用Google AJAX Search API 的。
郑昀@玩聚SR 20100106 泛Digg式的热文系统需要有重复内容检测机制。 一、Digg的做法 比如Digg在09年6月30日发表了一篇《Dupe Detection Updates Are Here》,指出几点: 通常的重复内容是同一个站点下的同一个Story,只不过链接不同罢了(这在国内论坛很常见,帖子的链接有好多种变换,实际上指向的都是同一个帖子)。此时用文本相似性计算(document similarity algorithm)即可解决。 另一种常见重复是不同网站的同一个(或相似)文章。估计Digg利用自己的search配搭一些参数做搜索,从而快速精确地识别相似的标题和内容。 当你提交给Digg一个链接时,它就已经开始了重复检测,在你填写标题和描述之前。如果它怀疑你可能提交了一个重复文章,那么它会立刻提示你,如下所示: 但旋即有人撰文《Digg Duplicate Detection Fail 》指出 Digg宣布重复内容检测的两篇文章就是重复上榜: 可见,人主观上认为的内容重复,和机器模拟判定(复杂的算法也可以很好逼近,比如论文抄袭检测系统,但对于Digg来说,肯定要求算得快且准),还是有些距离的。 二、国内的一些问题 国内总是有特殊情况。 比如转贴满天飞。而且多半是转载到大站或名博的文章被推荐被分享的几率远远大于原创站点。 比如国人架设的WordPress的RSS Feed,总是先输出一个中文编码(就是把博文标题URLEncode了)的链接,然后同一篇文章隔一会儿又输出一个英文链接。于是经常两个链接都有人推荐和分享。 比如新浪博客,很多人(包括名博们)经常反复编辑同一篇文章,导致编辑器把同一篇文章保存为N多份,导致同一篇博文一发就是三五篇,链接还都不同,崩溃啊。 比如cnBeta,转贴第三方的文章后,输出的RSS是摘要输出。此时,大家往往分享的是它。这带来两个问题:一,摘要很短,不足以与原文判定是否重复;二,cnBeta投递者往往会修改原文标题,要么加几个字,要么加助词或符号(估计是SEO的需要)。 比如Solidot,以点评夹带原文摘录的方式发布。也是同样的问题:文字内容很短,还加了编辑的文字,更加影响相似性计算;标题往往与原文不同。 比如一些名博转载别人文章时,总会在最前面加上自己的点评,这样就略微影响文本相似性计算。 三、SR的做法 SR的热文一开始并没有加入去重功能,一个是认为问题不大,又不是人为提交,不存在Spammer问题;一个是不好确定原创者。(SR不是Digg的人工Digg方式,而是主动收集Google Reader/Twitter/Delicious/等用户的推荐、分享和收藏行为,进行统计,再加入一些简单逻辑,从而决定哪些文章或链接可以上热榜。) 后来确实有很多人反映这个问题,同一篇文章很有可能重复上榜三、四次,比如谷奥发布一次,cnBeta转载一次,Solidot点评一次,keso’s view一次,煎蛋转载一次等等。 现在的重复内容检测逻辑是: 首先对文章内容较长的,是基于Shingle的重复检测办法; 其次对文章很短的,比如cnBeta摘要输出的RSS内容,比如Solidot,比如南方报业旗下的RSS内容,先提取标签,然后计算文章的标签相似度。 这两种办法算起来很快,但未必总能检测出来重复,继续积累吧。 郑昀 北京报道
郑昀 20090916 有好几个不同时代的MP3音乐播放器,包括iPod Nano,但过了新鲜劲儿,利用率就不高了。 随机又智能 我对音乐并不太挑,只要旋律好,歌手音色好。所以,互联网的那种随机播放音乐服务(加上一点点小智能),比如last.fm的电台,比如Last.fm Scrobbler(需要设定一个歌手名字作为启动),比如亦歌,我都很喜欢(唯一遗憾是亦歌自动播放以中文歌曲为主)。 像前面提到的这两个服务一样,记住我曾经手动标记过的“喜爱”和“厌恶(黑名单)”很重要,以此可展开推荐,但随机的权重也要大,以便发现“新”音乐(几十年前的都行)。 困扰 对我来说,让手持设备们能投入实用会存在困扰。原因很简单: 音乐必须时常更新,什么好音乐也架不住翻来覆去地听; 我没时间下载音乐到硬盘上; 我没时间同步和管理这些音乐到手持设备上(比如删除听腻了的歌); 我只想按下“随机播放”那个选项,接下来的都应该是自动,包括从互联网下载音乐和同步。 考虑到歌曲的版权问题,国内能解决这个同步方案的可能只有谷歌音乐了。 试着解决 我是这么设想的: 计算机上需安装一个应用程序,名字假设是“glu”。 首次接入计算机场景 早上上班时,郑昀将手持设备(比如iPod Nano)插入计算机USB口,自动激活glu程序界面。 郑昀点击glu界面上的“同步”按钮。 保持设备不拔出,glu将自动从谷歌音乐下载足够听6个小时的音乐文件(这个下载过程很容易,市面上已经出现了一些脚本),直接存储入设备的介质上。曲目是类似于亦歌那样随机编制的,充分混合各种音乐流派,以近年音乐为主,中外歌曲皆有,间或混入年代久远的歌曲。 每隔三首歌曲(一般会是15~18分钟的播放时间),glu将强行插入一个为时0.5~1分钟的广告(类似于CRI广播电台的播放习惯),从程序设定角度,可以让用户无法在播放器中跳过广告。广告是应用服务商的固定数据接口下载的语音广告,由专人维护,定期更新。以此解决盈利问题。 下班时,点击glu的停止同步按钮,或直接将设备从USB口拔出。 用户收听场景 播放器外表只有两个可控开关,用iPod Nano打比方吧: 就用iPod的转轮来控制用户的心情,从喜悦到悲伤,默认是“无所谓”。之所以这么设定,是因为音乐的流派,如谷歌音乐挑歌的流派(摇滚|民谣|校园等等),还是太单调,我会无法忍受一直听某一个流派的。Last.fm Scrobbler的确定歌手从而持续播放与此歌手相似曲风的模式,稍微好一些,稍微加大了发散性,但还不够发散。有时我听着听着,就得重新切换一个歌手。 iPod的中心按钮是停止/播放按钮。 比如我下班时心情不错,于是调整转轮,于是前面glu上传的这些歌曲中,播放器会优先随机播放那些喜气的;当我滚动转轮时,音乐会随时切换到不同心情的。 由于glu上传了足够播放时间的音乐,所以应该这一晚上都够听了。重复放几遍也无所谓,因为够发散。 为了降低播放器的复杂度,不支持像Last.fm一样可以从播放器上标记一首歌曲为“喜爱”和“禁止”。我希望这个功能尽可能在计算机的客户端上实现。 再次接入计算机场景 第三天,听得差不多了,郑昀打算换下播放器的音乐。 将播放器插入USB口。 glu启动。用户点击glu的“同步”按钮。 glu清空播放器存储介质上的所有歌曲,然后继续上传新选定的随机歌曲。 glu可能需要避免新上传的歌曲是上一次传过的,尽量不重复。 glu有自己的随机选歌算法。 okay,大致如此,用户只需要点击“同步”按钮,保持播放器接入计算机时间足够长,选择播放心情(可选),播放即可。用户不需要绞尽脑汁寻找最近想听的歌曲想听的歌手,寻觅可下载的音乐资源,不用先下载mp3文件包然后上传到手持设备中事后还要删除,费劲。 让机器智能管理音乐,用户只需要到时叫一声“先生,您倒是喷哪!”就行了,接下来就口吐莲花了。 如果这个播放器以及配套应用,能够自动读取用户的以下社会化音乐服务用户行为记录就更好了,从而能够根据用户行为作出智能选择: last.fm的用户行为:标记过“喜爱”和“禁止”的歌曲清单;最近启动过的电台(主要是歌手名称); 亦歌的用户行为:收藏的歌曲清单;歌手黑名单; 酷狗和酷我等客户端软件的用户行为。 郑昀 20090916 北京报道
郑昀 20090918 1、背景知识 Google Reader 用户可以再次分享他订阅的文章,只要获知某一个用户的User ID,即可通过以下格式访问他的: shared items feed: http://www.google.com/reader/public/atom/user%2F$USERID$%2Fstate%2Fcom.google%2Fbroadcast shared items 主页: https://www.google.com/reader/shared/$USERID$ Profile 主页: http://www.google.com/profiles/$PROFILE_ID$?hl=en Profile Widget: http://www.google.com/s2/widgets/ProfileCard?uid=$PROFILE_ID$ (0919注:UserID并不是ProfileID,你可以从shared items 主页的html代码里找到Profile ID。) 用户还可以对某篇文章表示Like。 通过对Shared和Like行为的数据收集,并构造一个简单的公式,可以大致测量出Google Reader用户的分享活跃度,简称GRUserRank。 GRUserRank 的用途: 是一个参考指标,当估计用户对某一篇热门文章的推荐贡献时; 能有效地区别对待用户,把活跃用户、分享质量低的用户、休眠用户分开,有利于优化程序; 也是一种社会化的参考指标。 2、如何遍历 xlvector 在《Google Reader的数据收集》中提及,因为每一个Shared Items Feed给出了like它的用户id(具体逻辑请参考我的文章《Google Reader的Likes操作数据如何获取?》),所以只要从某一批用户的Shared Item Feed出发,就可以通过广度优先搜索将整个Google Reader的用户数据抓下来。这个数据集可以说内容非常丰富,包含了时间和内容信息,相信在它的基础上可以做出不少工作。 3、分享活跃度的计算公式 这个思路是可以用来计算 GRUserRank 的。 第一步, 我们从 玩聚SR 的已收集GR用户ID集合(基本能保证都是中文用户)开始,扫描每一个用户的Shared Items Feed,根据正则表达式: <gr:likingUser>([0-9a-z_!~*'()-]+)</gr:likingUser> 从中获取所有 likingUser 的ID,存入全局字典,统计每一个User最近作出Like操作的次数(变量简称Likes),保证唯一性。 第二步, 这样,我们拿到了一个很大的GReader User ID集合(简称LikingUsers),由于主要是对中文文章做like操作的用户,所以也基本是中文用户。当然这个集合里: 并不能遍历到所有的GReader中文用户; 并不是所有人都公开了自己的Shared Items; 只有少部分人创建了Google Profile,拥有自己的Logo。 接下来,我们遍历 LikingUsers 集合,按照 Likes 的顺序,即经常标记Like的用户优先遍历。 对于每一个用户,要拿到以下数值: 三十天内是否分享过文章:没有的话,说明此用户已经是休眠用户; 四天内分享过多少文章:变量简称Shares; 最近分享的三篇文章的发表时间新鲜度:变量简称FreshMeats。用每一篇文章发表时间减去一个基准时间值(我取前四天的日期作为基准,如今日是9月18日,那么基准时间是2009-09-15),然后取平均值。 最近分享的三篇文章的标题是否都不包含中文:如果都不包含中文,说明该用户也许不是中文用户,可以disable了。 第三步, 按照我的文章《Social Media排序算法的四种模式》,我们还要指定一个时间基数: BaseSeconds :12.5 小时周期内的总秒数,45000秒。 那么公式就是: GRUserRank = Log10(Likes*因子A+Shares*因子B)+FreshMeats/BaseSeconds 因子A、B自己调整,我取2和3。
郑昀 20090919 有很多利用了群众性智慧的机器智能系统是回答 What’s popular 这个问题的,比如: 阅读(Reading)领域: techmeme tweetmeme stumbleupon iGoogle What's Popular gadget Delicious Popular Bookmarks 玩聚SR 事件(Event)领域: ? 趋势(Trend)领域: 搜索引擎搜索词热榜 Google Trends:Hot Trends(以天为单位) Twitter自己的Trending Topics侧边栏以及众多第三方的应用,如twopular 玩聚RT 和 玩聚PP 头条(Headline)领域: popurls alltop 1、单一测量维度 郑昀认为,多数机器智能是以单一系统的内部数据做测量标准的。 我的文章《Social Media排序算法的四种模式》中提及 Digg 排序算法,拥有华丽无比的众多规则,但无论是投票的速度、投票用户的级别、评论和评分的数量、Bury的数量、用户的Popular Ratio等参数,都还是 Digg 系统内部的数据。 Tweetmeme 拥有高效的追踪Twitter世界热门链接的能力,但它的评价体系还是在围绕着 Twitter 的数据转,虽然体贴的链接分类(内容分类为科技、娱乐、游戏等;链接分类为News、图片和视频)是外部数据的计算结果。 Rssmeme 仅是依据 Google Reader Shared Items 的分享次数。stumbleupon 和 Delicious Popular Bookmarks 都是依据自身系统的收藏次数。 这些足够庞大的系统在互联网成熟期的国度,是完全可以反映 What’s popular 的。 但在中国,由于各个 Social Media 应用用户都不足够多,比如微博客,比如RSS阅读器。即使人数足够多,也只是往往趋同于某一特定气质人群。比如,猫扑的用户气质,天涯社区的用户气质,前校内网、开心网的用户气质,Twitter 中文用户的气质。 这就引发下面要说的问题: 2、国内数据少或偏 不足以回答 What’s popular 先强调,还是有一些例外的,如百度贴吧的数据,如果好好挖掘一下,是可以充分反映中国的 Hot Trends、What’s popular 的。 但对于 Social Media 的数据,往往由于中国固有的问题,以至于用户不得不在多个网站(官方和非官方的都要找一两个)流连才能获取足够的资讯和读物,而北美用户基本可以只依赖 Digg 就够了,科技用户只看 Techmeme 就够了。 比如同样模仿 Rssmeme 或 Tweetmeme 做应用,显然数据有严重的偏好,不是科技(而且还只是某一部分科技资讯)就是政治(而且还倾向性强得要命),为广大人民所喜闻乐见的情色都很少。 3、加入其他测量维度 交叉验证 (下面的延伸更偏重于 memeTracker 方向的 Readings Filter ,并不是通用的解决方案。) 为了弥补这一问题,就需要把不同 Social Media 的各种内部数据都当成是一个一个的测量维度,然后交叉验证。或者叫“基于mashup的数据挖掘模式”。 举一个非常简单的例子: 按照我写的《如何测量Google Reader用户的分享活跃度》遍历Google Reader 中文用户,计算他们的Rank值,从而得到来自于RSS阅读器的分享权重,将一少部分分享质量不高、Rank值低的用户摒弃掉后,得到一批可靠的阅读器用户(简称reader A-List)。 按照我写的《来,做一个社会化推荐引擎》遍历Twitter等微博客核心用户的Followers/Followings关系,计算中文微博客用户的Rank,并确立微博客中的一小部分用户为高质量的可靠用户(简称miniblog A-List)。 对 Twitter 进行扫描,获取中文用户都在分享什么链接,简称为 SRCBacks Links。 其他 Social Media 的数据也会算在推荐源内,但由于数据不多,所以不再阐述。 有了这些数据之后,希望 SR 首页(现被维护中)能反映中文世界的 What’s popular ,不管是值得阅读的Readings材料,还是突然暴起流行的笑料八卦,抑或时效性很强的Breaking News。 但由于阅读器的时效性有问题,微博客虽然够快但发链接太容易所以质量不高的链接泛滥,为了综合这些因素,可以如下交叉验证: reader A-List 和 miniblog A-List 的推荐都算作相同权重的一票,并适当参考A-List用户自身的Rank。 票数足够多,就可以进入 SR 首页。 来自A-List的票数有但不足够多,此时如果 SRCBacks Links 的数据中有足够多的微博客链接,也可以推上首页。单独依靠 SRCBacks Links 容易遭致 spammer ,所以必须用A-List 来保证质量。 这样,这个信息过滤器既引入了微博客的实时性,又抑制了它的过度泛滥,还参考了Social Media用户在原体系中的活跃度、受欢迎程度等指标,能有效地摒弃质量不高的社会化媒体用户,算法不复杂,最终达到一个较好的过滤效果和效率。 郑昀 20090919 北京报道
郑昀@玩聚SR 20090924 康爷释出两篇pubsubhubbub入门开发教程《PubSubHubbub工作原理及使用入门》和《[教程]如何使用PubSubHubbub协议》,这里补充几点: 一、关于订阅过期和自动订阅刷新: 我们简称第一个请求“Subscriber Sends Subscription Request”(Subscriber-->Hub)为Subscription Request,称第二个请求“Hub Verifies Intent of the Subscriber”(Hub-->Subscriber)为Verification。 还存在一个保持心跳的策略,即Automatic Subscription Refresh。这样,当你的 subscriber 掉线或者宕机一段时间后,hub就不用再 push 数据给你了。 1: 当发起Verification请求时,hub还会附带发过来一个参数: hub.lease_seconds=3600 表明一小时后这个订阅就会过期(subscription expiry)。 2: hub.lease_seconds这个值可以是第一次请求订阅时subscriber告诉hub的,也可以是hub自己决定的。 3: 如果Subscription Request请求中并未设定hub.lease_seconds,说明是打算永久订阅,那么hub保持心跳的策略是: 订阅过期前,hub会主动给subscriber重新发一个确认请求,要求subscriber再次确认是否订阅。 在这个确认请求中,所有参数与Verification请求的一样,只是challenge code是新的。 所以,subscriber不需要专门处理Automatic Subscription Refreshing问题,因为第一是hub主动发起是否继续订阅的请求,第二subscriber还是按照惯例回复challenge code以及200 OK即可延续订阅。 二、关于处理更新内容: 对于订阅了Google Reader Shared Items的Subscriber,当内容更新时,hub由于是“无状态”的,所以第一次订阅时hub会发送过来这个Shared Items Feed的所有数据,通常是8条数据。 之后通常它只会把最新Shared的那篇文章发送过来,这就说明它还是有状态的。 Hub Server保存状态也是有一定的时间限制的 ,假如某一个用户长时间没有分享过文章,比如睡觉去了,那么第二天他再次分享文章时,Hub会把所有数据(8条)都推送过来。这说明在一段时间内,比如一小时内,Hub缓存了推送给Subscriber的数据状态,过期就清了 。Hub 不再记得曾经给你发送过哪些数据。 举例,某一次hub发送过来的Request.content为: <?xml version="1.0" encoding="utf-8"?> <feed xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/"xmlns:idx="urn:atom-extension:indexing" idx:index="no" xmlns:gr="http://www.google.com/schemas/reader/atom/"><generator uri="http://www.google.com/reader">Google Reader</generator><id>tag:google.com,2005:reader/user/15221435823542888940/state/com.google/broadcast</id><link href="http://pubsubhubbub.appspot.com/" rel="hub"/><title>zhengyun's shared items in Google Reader</title><gr:continuation>CPbs2qqhq5wC</gr:continuation><link href="http://www.google.com/reader/public/atom/user%2F15221435823542888940%2Fstate%2Fcom.google%2Fbroadcast" rel="self"/><author><name>zhengyun</name></author><updated>2009-09-24T15:22:57Z</updated> <entry gr:crawl-timestamp-msec="1253805777401"><id gr:original-id="">tag:google.com,2005:reader/item/adfecf99f68d6292</id><title type="html">【幸福课】心灵之旅:如果不知道真正想做的事情,你可以 | 幸福课_传播有益的心理学</title><published>2009-09-24T15:22:57Z</published><updated>2009-09-24T15:22:57Z</updated><link href="http://www.xingfuke.net/psyblog/xingfuke1861.html" type="text/html" rel="alternate"/><link href="http://www.xingfuke.net/" rel="related" title="www.xingfuke.net"/><content type="html" xml:base="http://www.xingfuke.net/psyblog/xingfuke1861.html">现在没有方向和答案的话,那么先读100本书、锻炼好身体,这两件事永远都是对的、永远都是立马应该去做的。</content><author gr:unknown-author="true"><name>(author unknown)</name></author><source gr:stream-id="user/15221435823542888940/source/com.google/link"><id>tag:google.com,2005:reader/user/15221435823542888940/source/com.google/link</id><title type="html">www.xingfuke.net</title><link href="http://www.xingfuke.net/" type="text/html" rel="alternate"/></source></entry> </feed> anyway,subscriber要: 1: 接到数据,最好异步处理(解析、入库等),要确保尽快返回,尤其是当你接收的是Shared Items Feed XML 时,以防万一数据字节数太大。 2: 虽然Hub有状态,但你自己也要保证有状态,知道哪些Items是新发布的,毕竟Hub是否只推送最新分享的文章取决于Google Reader用户的分享频率。 3: 当你要接收成百上千Feed的更新时,可以针对每一个Feed设定不一样的callback地址,比如: 对于 keso 的 Shared Items,指定callback地址是: http://Server/subscribe/keso ,这样,当你收到数据时,不需要解析Feed XML内容就知道这是哪一个Shared Items的更新。 三、hub何时通知你有更新 并不是像通常想像的,你一在Google Reader里点击了某篇文章的Shared按钮,hub就立刻推送更新到subscriber。未必 。 多数情况下,几秒钟就Push新数据过来了。但有时,可能是hub的策略设定,是两次shared点击才会触发一次hub推送,推送的数据内容就是这个批次分享的那两篇文章。 郑昀@玩聚SR 20090924 北京报道
郑昀 20090925 1、 几十年前,一帮钻研国外开源代码、研究过各个国家杰出软件工程理论的程序员们,产生了建立一个自主架构的愿望。 这些人有的在外企底层做过coding或者打杂,其中一位若干年后成为了 company 的首席架构师。一些人目睹了另一个庞大软件王国的四处扩张,威慑天下,心向往之并奉为圭臬,劝说其他人沿袭那个帝国的架构和思想,殊不知那个远古时代骨子里就流着邪恶之血的帝国也早已病入膏肓。更多人还是土狼,在国内红海中历经磨砺,几乎每个人都是人中之龙。 这样一群人聚集起来,在几十年前定下来了一个大架构,并设定了一个非常宏伟的 Vision,但这原本是一位圣贤为他的国度应付几百万独立访问者设定的,又如何能像古代的那些帝国开创者一样定为“祖宗之法不可变”呢? 2、 庞大的工程就这么建立了起来,它确实让 company 获益,创造了世人瞩目的诸多成就,顶住了巨大的以亿计的流量冲击。可人们都知道它隐隐有一个硬伤,没有太多实践经验的那群程序员,虽然早已学习过很多优秀的、行之有效的设计模式,但不知为什么却没有贯彻到他们亲手打造的工程里。 他们留下了太多生硬的接口,工作流多如牛毛,但指导它们运转的原则却不那么清晰,甚至蹩脚。随着时间的流逝,在这些接口上衍生出了太多的应用,有太多的新程序员在这些接口和服务上继续开发,并尝试升级系统,但就像多数工程一样,在一个跑了几十年的生产系统上,要让系统继续不间断地应付与日俱增的访问量,你只能零敲碎打修修补补。 无数新程序员学习这个系统的代码时,都呼吁“重构”“敏捷”。但项目总监、产品总监、运营总监们知道,“重构”谈何容易,谁能承受为了升级而暂停服务?谁能承担重构失败的责任?没有测试环境,只有生产环境,你怎么保证你的重构是正确的。 在原有系统上挂接的各个运营单位要在每一次系统小升级中分一杯羹,在每一个小补丁上都要嵌入自己的推广代码,所以也都不愿意自己被砍掉。 你有什么好办法? 郑昀 20090925 北京报道
郑昀@玩聚SR 20091003 中科院的xlvector(即项亮,他所在的团队The Ensemble在7月份获得Netflix大奖赛公开测试排名第一,但在9月22日Netflix宣布BPC获胜,原因据说只是因为项亮他们提交结果晚了20分钟)最近发布了一个小工具GRSuggest,有点像之前Kuber在FeedzShare所做过的“个性化阅读”,都属于“基于某个Google Reader用户的Shared Items中的文章,为该用户推荐他可能感兴趣的其他文章”,基本都是基于 User-based Collaborative Filtering 算法原理。 项亮在《关于GRSuggest的一些思考》中说:“去重的问题,这个问题在文章推荐中非常常见,很多文章都被转载N次了,经常发现一个几年前的老贴又被转载,其实我的推荐系统本身也是转载”。 这个延伸出来的是三个常见问题,确实不好解决。 一、火星人现象 我前一段发了一个tweet:“不知道 Digg 能否解决火星帖频频被推荐的问题,这应该是所有digg类社区共同面临的问题:不管一个帖子或段子有多近期老多频繁被digg,隔一段时间总会有一个人当成宝贝发出来并被一大批火星人推荐。” 有人认为,火星帖如果是优秀的,当然有权利被翻出来啊。但请注意,在某一个单一社区中,可以假设用户群有相似的知识结构,那么以往的老贴子可以被翻出来,是可以的,天涯社区就屡屡这么搞,但在一个推荐系统中,如果还是不顾用户的知识结构,屡屡出现很多老段子,那就真的是在驱赶用户了。 火星人现象的关键是,以前大家也讨论过很多遍的:“推荐系统无法获知用户以前的知识结构”的问题。也就是说,一个单一的、新出现的个性化推荐系统,由于不知道用户的知识结构(即以往的阅读经历、经验),推荐的很多Item一定是用户已经熟知和阅读过的,这对于应用创始人和用户来说都是一个很不好的体验,但又完全无法规避。我们举一个很简单的例子,如果你在豆瓣中厮混时间不长的话,总会被豆瓣猜按照你的寥寥无几的动作推荐很多你看过、听过、读过的东西,而且是屡屡如此,你被逼的不得不一个一个点击掉,来让豆瓣了解你许久以来的经历。 从Google Reader Shared Items衍生出来的推荐系统就存在这个问题,Shared Items并不能反映用户的阅读经历,因为你在GReader里看了一篇文章,不代表你会Shared它,也不一定会Like它。这是问题一:从根子上就无法完整反映用户的阅读经历。 经过对Shared Items中文用户统计,相当一部分用户(我估计在50~60%)分享的文章所属之channels(即博客源)数量不会超过5个,10%的用户甚至只分享至多2个源的文章。多数中文用户分享的文章都出自“名人榜|LeaderBoard”所列出的这些站点。这是问题二:如此大量的阅读视野狭窄的用户,推荐系统能否发挥作用呢? 二、有时效性和无时效性 以前刘未鹏针对玩聚SR曾经提过一个很好的建议:“应该将文章分为"有时效性(如新闻时政类)"和"无时效性"(如读书笔记、GTD方法等等),看上去这需要手工分配或者高级的自然语言处理,但我意识到一个很好的办法:一般人们是在greader里面共享时效性文章,在twitter上讨论时效性文章,但"无时效性",或者timeless的文章会收藏到delicious上面,因为greader/twitter代表分享讨论交流,而delicious则代表收藏以后翻查。” 他观察到一个技巧:“无时效性的文章一般很久以后还会有人往delicious上面收藏,这是个极好的判断依据。而时效性强的文章就不会存在这个属性。”也就是说,你可以通过检查一个文章在delicious的被用户收藏的时间,从中发现哪些文章是有时效性的。 项亮也提到:“究竟要不要把老帖子翻出来,这个首先要解决一个新闻和文章的区别,对于新闻,翻出来是没有意义的,但对于知识性的文章,还是可以翻出来的。” 这就是基于Google Reader的推荐系统的另一个问题:要不要推荐时效性强的文章? 如果真的能分辨一篇文章的时效性,那么可以针对“火星人现象”加一个规则:推荐系统不推荐时效性强的文章,因为一是用户完全有可能通过各种渠道早已看到,比如论坛比如twitter比如IM,二是虽然用户不一定看过,但让使用推荐系统频度不高的用户总看过时的文章也会产生这个系统很烂的印象。毕竟,阅读和电影不一样,你可以推荐很老的电影,但不能推荐很老的新闻资讯。 无时效性的文章还可以这么搞:刘未鹏认为可以“判断时效性是为了增加信噪比,将无时效性的文章单立一个tab来做榜单,可以使后来的用户持续访问到以往一段时间的精华文章,而不是大量的八卦或时政,timeless的精华文章列表的好处是一下能够建立新读者对玩聚SR的高质量的信任。”我后来虽然提供了存档入口,但并没有区分时效性。 三、惊喜很难吗? 项亮认为:“推荐文章除了要和用户的兴趣相关,还要起到帮助用户拓展眼界的作用,这个方面的研究这几年已经有不少了,也就是找出所谓的能让用户惊喜的东西,但是这方面的算法的主要问题是无法评测,因为不知道什么东西是用户惊喜的。” 是的,惊喜很难。 何谓惊喜?就是在用户的知识结构之外,又是用户当下喜欢的条目(文章、电影、音乐、图片、视频)。所谓提及“当下”,是因为一个用户的兴趣点是动态的。 stumbleupon为何总能给用户带来惊喜? stumbleupon的算法设计师Garrett Camp曾给出一张流程图,描述了当按下stumbel!按钮时,stumbleupon的后台流程: 图中列出了三个因子: A、Your Topics,也就是你对网页的动作,比如like、dislike、quick stumbles(指当一个用户stumble!到一个页面时,没有对这个页面做任何投票行为,而直接再次点击stumble!按钮跳转到另一个页面的动作,他们将这个动作定义为:“soft not for me” or “down-vote”)。 B、Socially Endorsed Pages,就是你的站内好友所like的那些条目。 C、Peer Endorsed Pages,是系统计算出来的、跟你有相似投票习惯的人所like的条目。 从中我们可以总结以下要点: 1、一个能让用户有“惊喜”的推荐系统,必须捕捉足够多的用户行为细节。显然,基于Google Reader的第三方推荐系统,拿到的数据是严重不足的,你无法知道用户有意忽略了哪些文章,你很难拿到他的好友列表,Google不像FriendFeed那样提供Dislike/Hide的按钮;你只知道他何时Share或like了某篇文章从何处(值得注意的一个细节是,如果用户是自己订阅了煎蛋并推荐其中一篇文章,显然煎蛋对用户来说更加重要;相比而言,用户只是从其他人的Shared Items订阅中share了煎蛋的某篇文章,却不去订阅煎蛋,说明煎蛋对他来说可能还不算重要。这个细节有点像“quick stumbles”的思路)。 2、一个能让用户有“惊喜”的推荐系统,必须拥有海量用户,处理海量数据。今年2月份,stumbleupon 即已突破七百万用户,每天估计处理1千万以上次投票行为,至少新增3万以上个新推荐条目。Google Reader中文用户还是太少,而且用户行为太集中,单凭Shared Items出来的新增文章数目太少。 这两点都限制了第三方挖掘“惊喜”的力度。 目前貌似只有twitter能毫无保留地提供各种用户行为细节以及海量数据。 郑昀@玩聚SR 北京报道
郑昀@玩聚SR 20091013 一、现象 Python 中执行左移操作(即将一个数的二进制位整体向左移若干位,移位后在低位补零,高位溢出部分舍弃): >>> 1000<<25结果是:33554432000L而在 C#、C++等语言中执行同样的左移操作,结果却迥然不同: Console.WriteLine(1000<<25); 结果是:-805306368 再举几个 Python 例子: >>> 1000L<<25 (注:L后缀代表Long数据类型)33554432000L >>> -1<<100-1267650600228229401496703205376L >>> 1<<1001267650600228229401496703205376L 而C#中执行同样代码,结果有着巨大的差异: Console.WriteLine(1000L << 25); 33554432000 (LONG类型的左移结果是一致的) Console.WriteLine(-1 << 100); -16 Console.WriteLine(1 << 100); 16 Javascript的结果也是对的,把下面的代码保存为html文件: <html> <head> <script type="text/javascript"> alert(2083363589<<5); </script> </head> <body> </body> </html> 浏览器打开这个html后得到对话框提示:-2051841888 ,也是正确的。 那么,Python 的左移操作为何计算结果如此偏颇呢? 问题何在? 即使是 Python 2.5 乃至最新的 Python 3.1.1 都是这个结果 (只不过Python3执行 1000<<25 的结果是 33554432000 ,没有加L后缀), 莫非这么多年来没人做左移操作吗? 我们先来了解Python 怎么定义的吧: 二、Python Doc 对左移的定义 参照 Python Doc 对左移操作符的说明: A right shift by n bits is defined as division by pow(2,n). A left shift by n bits is defined as multiplication with pow(2,n); for plain integers there is no overflow check so in that case the operation drops bits and flips the sign if the result is not less than pow(2,31) in absolute value. Negative shift counts raise aValueError exception. (译文:右移n位可以定义为除以pow(2,n),左移n位可以定义为乘以pow(2,n);对于普通整数是没有溢出检查的,因此若结果的绝对值不小于pow(2,31), 这个运算会截掉相应的位并且符号位也在移位处理之列. ) Python的 x<<y 相当于直接调用: int(x * 2**y) 函数。 还不要说负数的左移操作所遇到的问题了: Shifting negative numbers doesn't have consistent interpretation between python and C.(译文:负数的位移操作,python与C语言的解释是不一致的。) --Douglas Leeder 三、为什么会这样? Python 创始人 Guido van Rossum ,在今年2月份的博文 《Early Language Design and Development :From ABC to Python》 中讲述了当初设计 Python 整数类型时犯下的严重错误,以至于“在特定情况下,integer和long两种整数实现会有语义上的细微不同”,并进一步导致: “In addition, the int type, while normally considered signed, was treated as an unsigned number by bitwise and shift operations and by conversions to/from octal and hexadecimal representations. Longs, on the other hand, were always considered signed. Therefore, some operations would produce a different result depending on whether an argument was represented as an int or a long.” (译文:int类型通常情况下是有符号数,在位操作、位移操作、和8进制/16进制互相转换时则当做无符号数。而相对应的,long类型则总是有符号数。因此,某些操作会因为参数是由int还是long类型表达而产生不同的结果。) 他举例说:在32位运算中,1<<31(1左移31位)是一个32位的大负数,而1<<32结果为0。然而1L<<31(long类型的1左移31位)产生一个long类型整数,值为2**31,1L<<32的结果为2**32。 最开始,他通过让运算结果超出存储范围时抛出溢出异常(OverflowError)修正这一错误, 但很快有人抱怨这一点,于是他修正为: I should have made integer operations that overflow promote their result to longs. This is the way that Python works today, but it took a long time to make this transition. (译文:我应该让溢出的int整数操作结果升级为long类型。这也是今天Python采用的方式,可惜这个修正太晚了。) 但不管怎样,位移操作的问题始终没有被修正。 甚至有人曾报告 Python 2.4 下 int 数左移操作会导致内存泄漏: while True: x = 1 << 64 会导致内存泄漏;而 while True: 1L << 64 则不会。 四、怎么办? 不知道。 我们把左移操作放入C++中,让Python调用。 五、背景介绍 左移运算: 就是将一个数的二进制位整体向左移若干位,移位后在低位补零,高位溢出部分舍弃。所以1<<2就是把整数1的二进制补码 00000000 00000000 00000000 00000001(Python的整型数据的位宽是32位,所以要补这么长)整体左移2位,舍弃溢出的高位并在低位补零后得到结果00000000 00000000 00000000 00000100,正好是十进制数4即22的补码。实际上,将一个数左移几位,就相当于将这个数乘以2的几次幂。 类型长度 Python的整型数据的位宽是32位,8个字节。int 最大值是2147483647 (sys.maxint),而long 长度仅受内存大小限制。 C/C++ 中,int 的长度 与 机器字长 相同,16位的编译器上int长16位,32位的编译器上int长32位。 郑昀@玩聚SR 北京报道
郑昀 @玩聚SR 200909 早前写的注意事项。现放出来,也许对 PubSubHubbub 爱好者有帮助。 一、启用 PubSubHubbub 协议更新玩聚SR数据的好处: 快。 几乎是一个Google Reader用户分享一篇文章之后的几秒钟时间,我们就可以把数据入库。 而依靠轮询每一个用户的 Google Reader Shared Items Feed,可能需要十几乃至几十分钟才能让一个更新入库。而且随着监听的用户数越来越多,轮询会越来越慢,而且也会因为某用户分享的某一篇文章有敏感词,导致Socket链接被重置一段时间;另外,请求过于频繁并发过多,Google也会重置你的链接。 但也会存在一些问题,稍后会说明。 二、PubSubHubbub协议入门参考读物: kangye 的 PubSubHubbub工作原理及使用入门 ; kangye 的 [教程]如何使用PubSubHubbub协议 ; 我的 对康爷PubSubHubbub教程的一些补充 ; Tim 的 PubSubHubbub的价值 。 三、了解Hub Server跟你的交互: 协议中定义,Hub Server 做以下三件事: 1、发送 Verify Subscriber 请求,要求 订阅者 返回 200状态码 以及 challenge挑战码 。 2、推送订阅过的 Google Reader Shareds Items 之更新数据给订阅者。 3、发送过期是否继续续订的请求,要求订阅者确认。 一般来说,你的程序角色就是 订阅者(Subscriber),http:// pubsubhubbub .appspot.com/ 就是Hub Server。 所以,玩聚SR 把这三件事都放在一个 Web Server 上处理,该程序由 Twisted 提供 Web 入口以及处理框架。 四、Callback 地址的设计 虽然 Hub Server 发送过来的数据(XML格式)指明了是哪一个 Shared Items ,通过以下字段: <id>tag:google.com,2005:reader/user/15221435823542888940/state/com.google/broadcast</id> ,但SR要想按照此id字段匹配到数据库中对应的 User ,还需要做一次 select 查询。 所以,在最开始发出订阅请求时,特别为不同 User 指定了不一样的 Callback 地址。Callback 地址就可以揉入必要参数。 五、为 User 增加订阅状态 某些时候需要将一个 Shared Items 退订(分享文章质量太差等原因),不让 Hub 继续发送更新过来,那么就需要一个特定的用户状态。 退订的流程和订阅一样,只是 hub.mode 值为 'unsubscribe'。 所以 User 要有一个订阅状态,这也是为了第六条考虑。 六、一段时间后要确保订阅不失效 发现 Hub Server 隔若干天就会失去对一些Feed的订阅,也就是,明明用户分享了文章,但 Subscriber 却迟迟接收不到任何更新通知。 这就需要重新发请求订阅。 原因可能有二:Hub发“订阅过期重新确认”请求给订阅者(Subscriber)时,订阅者忙而未能及时应答;Hub Server 丢失了订阅状态。 七、PubSubHubbub 的一些常见问题: 1、更新数据不一定只是最新的那些条目: Hub Server保存状态也是有一定的时间限制的 ,假如某一个用户长时间没有分享过文章,比如睡觉去了,那么第二天他再次分享文章时,Hub会把所有数据(8条)都推送过来。这说明在一段时间内,比如一小时内,Hub缓存了推送给Subscriber的数据状态,过期就清了 。Hub 不再记得曾经给你发送过哪些数据。 2、订阅者宕机后可能需要重新发送所有订阅请求: 当你的 Subscriber 掉线或者宕机一段时间后(比如一小时无法连线),hub 就认为你不再续订,于是不会再 push 数据给你了。你需要重新发起所有订阅请求。 3、更新速度: 并不是像通常想像的,你一在Google Reader里点击了某篇文章的Shared按钮,hub就立刻推送更新到subscriber。未必 。 多数情况下,几秒钟就Push新数据过来了。但有时,可能是hub的策略设定,是两次shared点击才会触发一次hub推送,推送的数据内容就是这个批次分享的那两篇文章。
郑昀@玩聚SR 20091105 一、冷启动 Greg Linden针对最新的一篇论文:"The Wisdom of the Few: A Collaborative Filtering Approach Based on Expert Opinions from the Web" (PDF,即《少数人的智慧:基于网络专家意见的协同过滤研究》) 做了如下点评: “ What they do say is that using a very small pool of experts works surprisingly well. 论文说的是,用很小一个专家池,推荐效果惊人地好。 In particular, I think it suggests a good alternative to content-based methods for bootstrapping a recommender system. 我认为它为一个推荐系统的自启动指出了一个很好的替代选择。 If you can create a high quality pool of experts, even a fairly small one, you may have good results starting with that while you work to gather ratings from the broader community. ” 即,选择一个高质量专家池,可以是你组建的团队,也可以是你选中的专家群,即使是相当小的一个群体,你的推荐系统也会有一个非常好的开端。少数人的智慧,此时此刻,可以解决推荐系统的冷启动问题。这也是玩聚SR最开始选择Experts Pool作为起源,一上来就有很好信息过滤器效果的原因。 二、论文的摘要: 为了方便理解,下面意译一下该论文: 最近邻协同过滤(Nearest-neighbor collaborative filtering)是一个很有效的推荐方法。但它总受困于这几个问题: 数据稀疏和噪音;冷启动问题(cold-start);可扩展性问题。 所以论文作者提出一个新方法,一个传统协同过滤方法的变种: 并不是对用户打分数据(User-rating data)实施最近邻算法,而是用一个专家邻居(expert neighbors)集合作为比对样本,去计算这批人与目标用户的相似度。 这个方法至少没有太大可扩展性问题,相当于缩小了比对的基准集合。最近邻原方法可近似理解为做两两比对,计算肯定花时间,而且当新用户(尤其是某某观光团的到来会让数据噪音多得一塌糊涂)比比皆是时,没有几条数据能够让你进行相似性计算。 作者定义专家为,在给定领域,能够产生思虑周全的、始终如一的和可靠的评估(评分)、我们可信任的独立个体。 (原文: We define an expert as an individual that we can trust to have produced thoughtful, consistent and reliable evaluations (ratings) of items in a given domain. ) 我们比较关注论文作者们的以下两个探讨问题的角度: (a) study how preferences of a large population can be pre- dicted by using a very small set of users; 研究用一小群用户去预测海量用户到底有多大的可参考价值; (c) analyze whether professional raters are good predictors for general users; 如果这几个角度是可行的话,那么实际上并不需要拿到一个海量用户社区的所有数据,只要锁定Experts Pool即可为用户进行推荐。 附录: Greg Linden在被封的BlogSpot的原文如下: Wednesday, November 04, 2009 Using only experts for recommendations A recent paper from SIGIR, "The Wisdom of the Few: A Collaborative Filtering Approach Based on Expert Opinions from the Web" (PDF), has a very useful exploration into the effectiveness of recommendations using only a small pool of trusted experts.The results suggest that using a small pool of a couple hundred experts, possibly your own experts or experts selected and mined from the web, has quite a bit of value, especially in cases where big data from a large community is unavailable.A brief excerpt from the paper:Recommending items to users based on expert opinions .... addresses some of the shortcomings of traditional CF: data sparsity, scalability, noise in user feedback, privacy, and the cold-start problem .... [Our] method's performance is comparable to traditional CF algorithms, even when using an extremely small expert set .... [of] 169 experts. Our approach requires obtaining a set of ... experts ... [We] crawled the Rotten Tomatoes web site –- which aggregates the opinions of movie critics from various media sources -- to obtain expert ratings of the movies in the Netflix data set. The authors certainly do not claim that using a small pool of experts is better than traditional collaborative filtering.What they do say is that using a very small pool of experts works surprisingly well. In particular, I think it suggests a good alternative to content-based methods for bootstrapping a recommender system. If you can create a high quality pool of experts, even a fairly small one, you may have good results starting with that while you work to gather ratings from the broader community.
郑昀@玩聚RT 20090609 玩聚RT是什么? 玩聚RT,即中文锐推榜,实时追踪中文微博客世界的最新鲜、传播最广的锐推。 最开始只是自动聚合Twitter 中文用户的RT(即转发)行为,统计得出最新鲜锐推榜单,并转发到官方帐号:@rtmeme 上。用户 follow @rtmeme 即可收看。或订阅 RSS Feed 。 为何加入对饭否RT行为的统计? 在饭否站方提供“转发”按钮之前,饭否用户也会自发地转发消息,但行为并不规范,多种格式并用,不像 Twitter用户有被各种第三方工具认可的 RT 规范。所以,统计会遭遇很大麻烦。 但站方提供此按钮,并自动加“转:”字样代表转发后,情况就好多了,有利于第三方统计。 饭否和Twitter的合并计算 饭否消息和Twitter Tweet经过剔除各种符号标点,经过语义处理,得到净化后的传播本体,然后统计出传播最多的锐推消息。 此消息同时发送给 Twitter的 @rtmeme 和 饭否的 @中文锐推榜 两个官方帐户,欢迎订阅。 提高统计阈值 Twitter 被封恢复之后,人民群众的热情高涨,尤其是加入饭否的监测后,显然面对 RT 信息量的增加,我们要提高入榜的门槛。 三种收看中文锐推榜的方式: 订阅 @rtmeme ; 订阅 @中文锐推榜 ; 订阅 RSS Feed 。
郑昀@玩聚SR 20090707 北京Open Party是一个一次性拥有众多开放性主题的技术大Party,他们自称“非会议(Unconference)”形式,每月一次。 通常都会有一个核心主题,它的演讲者都拥有一定声誉。其他与会者会在一个大白板上张贴自己的即时贴,标明自己的演讲主题。在核心主题演讲后,这些副主题就开始了,听众可以随时加入,在不同讨论组之间穿插。 http://www.beijing-open-party.org/ 是他们的官方网站。 光是看这个Blog形式网站上的6月期会议系列文章,看得出是下了功夫写的,既有前期的铺垫,又有后期的总结,有一定传播力。 一、还是传播不够? 我的资讯主要渠道是社会化媒体,工具主要是RSS阅读器、Twitter、饭否和我们制作的几个热门资讯聚合管道,差不多涵盖了科技圈子,但真的很 少看到OpenParty的消息和反馈,不知道为何。哪怕是pongba刘未鹏的小范围TL聚会,我都能看到刘未鹏自己的推和一些人的Blog,但 OpenParty真的很少见到讯息。难道是大家混的不是一个互联网圈子或技术圈子的缘故? 这一直是我奇怪的地方。 其实演讲人都是一些有名气有资历的人,比如6月期的“方军:浅谈“从专业人士从管理者/创业者的观念转变””,多好的主题啊。有时候Open Party的总结串联也不错:4月期的。 这不是我follow不follow,订阅不订阅的问题,如果够精彩够重要,一定会被社会化媒体自动推送出来的。 并不是少有人锐推这些消息,还是有不少的:譬如搜索#beijingopenparty,但一看就是来来去去就那几个人。 二、寻找原因 我曾经参加过一期,所以试着寻找了几个原因: 1: Open Party 更多的是事先与演讲人沟通,然后发布会议信息,但很多临时主题都是当时才揭晓的。 这些演讲人演讲之后,未必能得到多少现场用户事后的反馈,会议组织者也没有就此与演讲人沟通,基本是失去控制了。 从而造成部分演讲者没有动力发布消息(不排除演讲人本身就低调),听众回去可能写了反馈,但很少反馈回到演讲人那里。 当然,官方网站还是努力收集并显示了部分Blog和部分tweets。但,参与会议并演讲了的毕竟是更多人。这些人的力量没有被很好地利用起来。 2: 会议主办者们本身在twitter等社会化媒体上也不是很积极,也不是意见领袖。所以好东西的传播效力大打折扣。 三、建议 1: 演讲人演讲完毕的两天之内,主办方务必敦促他主动: 发布幻灯片(最好能下载), 撰写Blog文章, 撰写tweets, 主动推介beijing-open-party关于他本人的演讲信息页面。 要让演讲人动起来,而不是说完就完了。他需要贡献出他的社交圈和影响力。 2: 会议主办者不要在自己的小圈子里发布信息,要走出来,多follow 多关注其他twitter和饭否好友,尤其是技术意见领袖和传播的关键节点。必要时,要学会说“下个月11号Open Party的会议主题是XXX,有谁谁和谁谁演讲,欢迎大家锐推”。 3: 我注意到 @openparty 基本是机器发布消息,格式很死板。反观beta沙龙的官方帐号 @betasalon 则是人发布消息,甚至直播会议现场。后者的做法显然更符合传播特性。 4: 我一直认为像Open-Party这种会议,由于每一次都拥有很多演讲主题,所以并不适用于Blog单篇发布,而应该是像UCDChina那样以Topic形式的发布。 每个Topic下面包含很多子页面,每个子页面就是一个演讲人的以下信息: 姓名、自我描述、头像、联系方式(Blog、twitter等); 本次演讲的主题,以及简单介绍; 本次演讲的PPT,可以嵌入slideshare的幻灯片插件; 本次演讲者自己写的Blog全文(或链接); 聚合对此演讲的tweet评价和Blog评价。 这样,就可以要求演讲者自己传播这个页面了,满足他的虚荣心和成就感。 比如我最近关注的一个技术会议的网站:http://www.semantic-conference.com/ ,就是每一个演讲主题都是一个单独页面。 5: 官方网站的聚合很重要,聚合twitter反馈,聚合Blog,聚合资源(如视频、相片和幻灯片)。 6: 官方微博客帐号要学会锐推别人对会议的Tweets,学会@回应别人的反馈。 四、学习beta沙龙 反观奇遇花园的每一次beta沙龙演讲,还是有很强传播力的。 从 FriendFeed 能搜索出一堆信息。原因有几个: 1:有意见领袖主动传播,比如virushuo tinyfool等人; 2:事后又有ppt下载又有blog讲述,而且还被大量twitter锐推和GReader分享; 3:每次都只有一个主题,便于传播给明确的受众。 我自己对有用的会议之总结: 1:会议主题必须只有一个,且非常明确,不要太高端,以防止大家无法参与讨论; 2:与会人数必须限定在20人以下;超过此人数,基本会议时间有60~70%都是浪费掉的,且与会者良莠不齐,很难将讨论升华。控制与会人数,形成 小圈子的私密氛围,可以促使人说出更多干货。像微软、IBM、Sun等举办的几千人大会,除了广告效应外,真实价值基本等于0。 拥有以上特点的会议,对于空闲时间不多的人来说,收获是最大化的。
郑昀@玩聚RT 20090714 一、对Twitter神经网络的个案分析 上周从玩聚SR上看到一篇《作为神经网络存在的Twitter(数据篇)》,作者对自己的一个Twitter消息的传播路径做了详尽的分析,并配发了节点图、时间线、延迟时间图、Followings数量图等5张图,罗列出了消息传播过程中涉及的: 用户; 转发时间; 用户的Followings/Followers数字; 谁传递给谁; 的数据。 数据挖掘功课做得非常足,作者并没有给出明确的结论,但大致可以得知: 某些核心节点虽然可以加快信息传播的速率和广度,但核心节点引发的RT行为数量与核心节点的Followers数量未必成正比; 当后续传播用户的Followers/Followings数值大幅度下降时,传播也就快要结束了。也就是说,当消息传播到圈子的边缘用户时,基本上也就不会再继续传播了。 作者的这张MindMap引起了我的兴趣: 它简明扼要地给出了传播的路线。玩聚RT作为中文微博客世界唯一追踪锐推行为的应用,掌握着大量的锐推行为真实数据,完全可以计算出类似的图形。 二、中文锐推榜上榜消息的传播路线图 大家都知道,中文锐推榜 是可以追踪微博客中文世界中最流行的锐推、语录和段子,每一条上榜消息都可以查询到对应的所有 Twitter或饭否 用户转发的消息。那么,从每一次转发中提取转发用户名以及转发顺序,加以合并统计,即可得到每一条上榜消息的传播总路线图。 下面是 @keso 作为核心传播的例子: 可以看出 @keso 后续的两个节点 @rtmeme(又传播给了5个人) 和 @secretaryzhang(又传播给了4个人) 是又一组核心节点。 有的时候,一个消息是由两个或多个渠道分别传播出去的。可能是传播中隐去了原作者的ID,比如这条饭否避难手册的广播 。也有可能是某个惊爆新闻由多个敏感人士第一时间各自独立发起,比如这个坐直升飞机上学的新闻: 这种热门新闻,自然会有不同的用户发起传播。我们拥有语义计算能力的做法,可以把某一个消息的大范围内传播进行合并,而不仅仅是追踪某一个人发布的消息如何传播。 三、Javascript Mind Map效果 这种传播路线图,大家应该可以看出来,属于脑图(Mind Map)的画法。 它是由javascript绘制于一个Canvas(画布)上的,不仅拥有动态自动漂移效果,而且每一个节点都可以自由拖拽。 这套javascript脚本是 Kenneth 的杰作,名叫“JavaScript MindMap”,你也可以叫它“SpiderMap”。 代码: http://code.google.com/p/js-mindmap Demo: http://kenneth.kufluk.com/google/js-mindmap/
郑昀@玩聚RT 20090723 关于Google Reader 增加的 likes 操作是什么,可以参考文后列出的资源,此处不做解释了。 请浏览Jason Ng的Shared items feed。 在这个XML中,某一个 entry 节点 下,你可以看到类似于: <gr:likingUser>14304339855091962513</gr:likingUser> <gr:likingUser>09764207824283973452</gr:likingUser> <gr:likingUser>12510702745844320133</gr:likingUser> 的数据,这就是曾经表示喜欢的用户名单,据说最多只会显示100个用户ID。 存在的问题: 1、Google不知为何,gr:likingUser 节点是并列的,而不是放在 gr:likingUsers 节点下,也许会给一些 FeedParser 解析器带来困扰。 2、这些用户ID号需要你自己查询对应的用户到底是谁。 有以下方式: A、通过构造这样的URL浏览:http://www.google.com/profiles/114737385332619574269?hl=en B、通过构造这样的URL嵌入Widget: http://www.google.com/s2/widgets/ProfileCard?uid=114737385332619574269 (0919注:UserID并不是ProfileID,你可以从shared items 主页的html代码里找到Profile ID。) 参考资源: 1:Google Reader更新,越来越社会化了| 谷奥 2:Google Reader更新:增加Following,Like及好友搜索功能| 天涯海阁 3:Wangtam: Google Reader 模仿Twitter 并不成功 4:月光:Google Reader新增跟随、喜欢和用户搜索
郑昀@玩聚SR 20090810 我们一直使用 Django ,玩聚的各个产品前端都是架设于 Django 之上,如:SR/RT。如果你对 Django 有所了解,可以跳过下面的简介: 什么是 Django ? Django 是一个开放源代码的Web应用框架,由Python写成,主力开发者是Adrian Holovaty。它采用了MVC的设计模式。 Django 的名字来自于比利时的爵士吉他手Django Reinhardt,他是欧陆爵士乐发展的奠基人,也是爵士史上最伟大的吉他巨匠,中国乐迷称之为“三指琴魔”。 Django 源自一个在线新闻 Web 站点,于 2005 年7月在BSD许可证下发布。Django 框架的核心组件有: 用于创建模型的对象关系映射 为最终用户设计的管理界面 一流的 URL 设计 设计者友好的模板语言 缓存系统 如何上手 Django ? 新手入门参考Django Step by Step之limodou版。 DjangoProject.com 是 Django 框架的主页。其中的文档包括: How to install Django,展示了如何在一台开发机器上设置 Django Database API reference,使用 Django ORM 库的指南 Django template language,为模板作者准备的一份简单指南 How to serve static files,如何在开发过程中通过设置 Django 来提供静态文件服务的介绍(不要在产品环境中这样做) How to use Django with mod_python,这是一份有关利用 mod_python 组合使用 Django 和 Apache 的指南 Generic views,展示了如何使用 Django 的通用视图更快地实现通用的 Web 应用程序模式 Django+Apache+mod_python 的用法 一般上手搭配 Django 的是 Apache+mod_python ,从这个早年间的翻译文档可以了解 Apache怎样处理请求以及mod_python到底做了什么。 不过,既然 Django 都已经在文档中说“it has been mostly superseded by the simpler mod_wsgi deployment option./mod_python 多半已被更简单的 mod_wsgi 替代了。”那么我们就应该一上手就用 mod_wsgi 。 为何要切换到 mod_wsgi ? mod_python 其实也不差。 但我一直困惑于 Apache+mod_python 的莫名频繁崩溃,错误日志中会出现大量的如下错误: [error] [client X.X.X.X] OSError: [Errno 0] Error [notice] Parent: child process exited with status 3221225477 -- Restarting. 这个状态号 3221225477 基本没什么意义,再加上 OSError 0 ,这个异常日志让人很无奈。甚至在 Windows 平台下,Apache 最恶劣情况下竟然会弹出一个崩溃错误框,此时事件日志会报告: cation Popup:0:29722:26:EVENTLOG_INFORMATION_TYPE:弹出应用程序: Microsoft Visual C++ Runtime Library: Runtime Error! Program: C:\Apache2.2\bin\httpd.exe This application has requested the Runtime to terminate it in an unusual way. 一直无法对症下药。于是,期冀 mod_wsgi 能让我摆脱这两个问题。 另一个就是性能。 modwsgi 性能评估中列出这样的数字对比: Mechanism Requests/sec mod_cgi (ScriptAlias) 10 mod_python (PythonHandler) 400 mod_wsgi (WSGIDaemonProcess) 700 mod_wsgi (.htaccess/SetHandler) 850 mod_wsgi (WSGIScriptAlias) 900 从数字可以看出,WSGIScriptAlias 是最优选择。当然这个数字也没什么,后面人家说了“任何性能提高,都将被你使用的大型Web框架如Django或TurboGears吃掉,尤其是用了数据库后端,大多数的 overhead 源自Python Web Framework以及任何访问数据库时的瓶颈。mod_wsgi上的 overhead 只是很小的一块,这一小块上的任何性能提高都很难被注意到。” 切换到 mod_wsgi 很容易 郑昀@玩聚RT 20090810 1、下载 可以从 modwsgi 的下载目录 下载你所需要的版本。 对于 Windows 平台,可能需要阅读“Installation On Windows”,并从这个页面上提示的mod_wsgi_py25_apache22 目录 下载 mod_wsgi.so (即我们的平台是Apache2.2+Python2.5+Django1.0.3)。 2、放置 把 mod_wsgi 放到你的Apache安装目录下的 modules 文件夹内。 3、配置Apache 在Apache配置文件httpd.conf中,增加一行: 4、修改Virtual Host配置 Apache 可以配置很多个 Named-based Virtual Hosts ,可以在一个服务器上部署多个Web Sites。 mod_python 下的 VirtualHost 配置主要复杂在 Python 配置这块,如下所示: <VirtualHost *:80> ServerName rt.ju690.com ServerAlias rt.ju690.cn DocumentRoot d:/SocialRecommend <Location "/"> SetHandler python-program PythonPath "['d:/SocialRecommend']+sys.path" PythonHandler django.core.handlers.modpython SetEnv DJANGO_SETTINGS_MODULE settings_yourproject PythonAutoReload Off PythonDebug Off </Location> Alias /static d:/SocialRecommend/static <Location "/static"> SetHandler None </Location> <Directory "d:/SocialRecommend/static"> Order Deny,Allow Allow from all </Directory> </VirtualHost> 现在改为 mod_wsgi ,只需要稍加改变即可: <VirtualHost *:80> ServerName rt.ju690.com ServerAlias rt.ju690.cn DocumentRoot D:/SocialRecommend WSGIScriptAlias / D:\SocialRecommend\wsgi\yourproject.wsgi Alias /static d:/SocialRecommend/static <Location "/static"> SetHandler None </Location> <Directory "d:/SocialRecommend/static"> Order Deny,Allow Allow from all </Directory> <Directory "d:/SocialRecommend/wsgi"> Order Deny,Allow Allow from all </Directory> </VirtualHost> WSGIScriptAlias 的定义参见 modwsgi wiki ,主要是映射一个URL到一个文件系统地址并委派目标文件为WSGI Script。 赋予本地wsgi文件夹Allow from all权限,是因为这样才能执行WSGI Script。 5、建立wsgi script 在你的web site django总目录下新建一个文件夹wsgi。 在 wsgi 文件夹下新建一个文件 yourproject.wsgi ,内容如下所示: # complete_project.wsgi is configured to live in projects/complete_project/deploy. # If you move this file you need to reconfigure the paths below. import os import sys # redirect sys.stdout to sys.stderr for bad libraries like geopy that uses # print statements for optional import exceptions. sys.stdout = sys.stderr from os.path import abspath, dirname, join from site import addsitedir from django.core.handlers.wsgi import WSGIHandler sys.path.insert(0, abspath(join(dirname(__file__), "../"))) sys.path.insert(0, abspath(join(dirname(__file__), ". . /. . /"))) os.environ["DJANGO_SETTINGS_MODULE"] = "yourprojectname.settings_yourproject" #你的settings module名 application = WSGIHandler() 6、重启Aapche即可。 参考资源: 1、mod_wsgi / mod_python ; 2、Performance Estimates for mod_wsgi ; 3、How to use django with mod_wsgi 。 郑昀@玩聚SR 20090810