这次的目标是sohu的相册, http://pp.sohu.com,免费相册,图片公园。它的首页,美女没有yahoo的那么多,但是比163的光秃秃好点,所以作为第二位。
Sohu使用的技术向来比较成熟稳健,而又有技术含量。例如很久以前,它的新闻和论坛就使用了CMS系统搭建,非常的规范。而博客则使用了Ajax技术,异步加载页面的不同部分,速度很快。所以在对它的相册动手之前,就做好了打硬仗的心理准备。
同样,先进入专辑的第一张图片,这样页面的图片信息比较明确,方便提取,随便找个美女相册进先
http://pp.sohu.com/photo.jhtml?method=view
&id
=74529157#74529157
1. 定位图片source
照例,view page source,先看看有没有图片部分的源代码,还好,正常
<
A
id
="photoHref"
href
="javascript:gPhotoPage.nextPhoto(onPhotoChange);"
>
< img id ="photodisplay" src ="http://img44.pp.sohu.com/images/2007/3/20/15/9/11205330d6b.jpg" title ="点击图片查看下一张"
alt ="post-3-1173601339.jpg_搜狐免费相册" border =0 hspace =02 vspace =02 />
</ A >
< img id ="photodisplay" src ="http://img44.pp.sohu.com/images/2007/3/20/15/9/11205330d6b.jpg" title ="点击图片查看下一张"
alt ="post-3-1173601339.jpg_搜狐免费相册" border =0 hspace =02 vspace =02 />
</ A >
没163那么变态,全部藏在javascript里面。这样还是比较简单,只要能够定位到翻页的代码,就可以比较简单和机械的提取相册图片了。
2. 定位上下页
这里遇到了麻烦,sohu的页面尽管给出很好的注释,
<!--
这里是导航区
--><!--
导航区结束
-->
<!-- 这里是面包屑目录区&管理模块区 --><!-- 面包屑目录区&管理模块区结束 -->
<!-- 这里是面包屑目录区&管理模块区 --><!-- 面包屑目录区&管理模块区结束 -->
但是都没能找到上下页的地方,怎么办?看看页面,发现有Ajax的踪迹,
< SCRIPT LANGUAGE ="JavaScript" type ="text/javascript" >
<!--
function addContact(userId) {
var url = " /relations.jhtml?m=add&userId= " + userId;
var actionItem = new ActionItem();
new Ajax.Request(url,{method:'get', onLoading:actionItem.onLoading.bind(actionItem),onComplete:actionItem.jsonBack.bind(actionItem)});
}
// -->
</ SCRIPT >
果然不好啃啊。
3. 下载js文件
利用firefox的CacheViewer,过滤后排序,找到了pp.sohu.com的相关十几个js文件,果然是庞大啊。但是都是那种去掉分行的, 非常的难看,回头用python写个程序,专门把它们变回远样。据称最好的js编辑器,Aptana居然没有代码排版功能,感觉有点废啊。
找了下gPhotoPage.nextPhoto,发现是都是在Photos.js中,这个js文件是个很重要的类,对photo的操作都是封装在里面,后面用到了一个地方,但是没有找到获得图片列表的操作。
4. 重新分析页面的源代码
在源代码的最后一部分,找到
<
script
type
="text/javascript"
>
function loadPage() {
var rpccall = new JSONRpcClient( " /rpccall " , null , null , " photoMgr " );
photosList = rpccall.getAllPhotos( 74529157 )
gPhotoPage = new Photos(photosList.list,getPhotoIdFormUrl() != null ? getPhotoIdFormUrl() : 74529157 );
gLoginUser = rpccall.getLoginUser();
initRequestPhoto(gPhotoPage, gPhotoPage.photo);
}
</ script >
function loadPage() {
var rpccall = new JSONRpcClient( " /rpccall " , null , null , " photoMgr " );
photosList = rpccall.getAllPhotos( 74529157 )
gPhotoPage = new Photos(photosList.list,getPhotoIdFormUrl() != null ? getPhotoIdFormUrl() : 74529157 );
gLoginUser = rpccall.getLoginUser();
initRequestPhoto(gPhotoPage, gPhotoPage.photo);
}
</ script >
该部分是在body load的时候调用,呵呵,看了这里是关键了,通过JSON RPC调用,获得所有的photo列表,得来不费功夫,果然是越高级的东西就越方便。
5. JSON RPC
如果把它的javascript全部跑起来,当然可以获得一样的效果,不过太麻烦。即然用python,当然要用python来进行这个调用。
使用google,找到了jsonrpclib的开源项目,python和java一样,开源的支持就是多,这点我喜欢。
jsonrpclib只有一个py,非常小巧,但是倚赖于simplejson的包,jsonrpc主要是对json的远程调用的封装,而simplejson则是对json数据的解析,两个模块各司其责,清晰明了。
看看源代码的版权信息,寒一个
#
a port of xmlrpclib to json.
#
#
# The JSON-RPC client interface is based on the XML-RPC client
#
# Copyright (c) 1999-2002 by Secret Labs AB
# Copyright (c) 1999-2002 by Fredrik Lundh
# Copyright (c) 2006 by Matt Harrison
#
#
# The JSON-RPC client interface is based on the XML-RPC client
#
# Copyright (c) 1999-2002 by Secret Labs AB
# Copyright (c) 1999-2002 by Fredrik Lundh
# Copyright (c) 2006 by Matt Harrison
原来这东西,国外99年就在研究了,自己现在才知道...
对于Ajax我没有什么经验,JSON RPC也没有,不过凭着程序员的直觉,进行使用测试。
网上例子不多,找到了两个例子
一个python例子
s
=
jsonrpclib.ServerProxy(
"
http://jsolait.net/services/test.jsonrpc
"
)
reply = s.echo( " foo bar " )
print reply
reply = s.echo( " foo bar " )
print reply
一个javaScript例子
var
jsonrpc
=
new
JSONRpcClient(
"
/<%=ApplicationHelper.getWebAppName(request)%>/JSON-RPC
"
);
jsonrpc.myObject.setName(“Vince”);
jsonrpc.myObject.setName(“Vince”);
仔细研究这两个例子,再参考sohu的页面代码,猜到,其实
var
rpccall
=
new
JSONRpcClient(
"
/rpccall
"
,
null
,
null
,
"
photoMgr
"
);
photosList = rpccall.getAllPhotos( 74529157 )
photosList = rpccall.getAllPhotos( 74529157 )
是json rpc 的一种javascript写法,可以改成
var
jsonrpc
=
new
JSONRpcClient(
"
/rpccall
"
);
photosList = jsonrpc.photoMgr.getAllPhotos( 74529157 )
photosList = jsonrpc.photoMgr.getAllPhotos( 74529157 )
那么换成python就是
s
=
jsonrpclib.ServerProxy(
"
http://pp.sohu.com/rpccall
"
)
reply = s.photoMgr.getAllPhotos( 74529157 )
reply = s.photoMgr.getAllPhotos( 74529157 )
当然了,这个写法也是改了好几次才得到的,结果是好长的一个dict对象,看到结果的时候,我得意的笑,得意的笑..
动态语言的优势这个时候就体现了,同为动态语言的python和javascript,都是不需要什么类型强制转换,直接获得的对象,再调用方法就可以了,换成了严谨的java,要如何处理才好呢?恐怕要写长好几句了。
得到后就简单了,分析一下reply的dict结构,提取出imgList
imglist
=
reply.get(
'
result
'
).get(
'
list
'
))
imglist是个dict的list,每个dict中,最主要的就是hosturl和imgNames属性,imgNames属性一般有3个图片名,全 部下载后,结合photos.js的_getImgeUrl方法的分析,知道最后一个非空的,就是最大的图片,这个就是我需要的啦,剩下的事情就简单了。
imgSrcs = []
for imgurl in imglist:
hosturl = imgurl[ " hosturl " ]
imgNames = imgurl[ " imgNames " ].split( " ; " )
for i in range(len(imgNames) - 1 , 0, - 1 ):
if imgNames[i]:
imgSrc = hosturl + imgNames[i]
break
imgSrcs.append(imgSrc)
现在sohu的图片公园也没有防止外链了,直接用SimpleDownloader下载就可以了,至此,大功告成。