月度存档: 四月 2010

批量下载音乐

soso,我被你打败了…

一直想弄个eason全集来着的,上次用gmbox在谷歌music下了100多首,一听发现不行啊,有的都是rock版的那种,还有很多现场的,质量太差了,不过mp3里都有音乐元信息,就这点好。后来到soso上查了下居然有1000+首,eason还真是多产啊。再看了一下,多的是重复的,于是还是决定写个脚本,一次先只下一张专辑,然后手动弄一个专辑列表让它慢慢下去。

开始都比较顺利,分析专辑页面的源代码啊,写个正则匹配一下。先找到显示歌曲信息的tr,提取里面的歌曲名等内容,再写个封装一下,post到服务器,这样得到歌曲下载那个页面。本来以为再在里面匹配一下第一个地址就ok了的,结果发现不是这么easy的事情。匹配出来所有的都是链接加载中…,再一看,居然是后来通过ajax load的json信息。其实早该发现了的,在Fiddler里点击一次下载明显有两个200的response,第一个是大体结构,第二个才是链接等数据。我在这里也走了弯路,没有直接去分析第二个request的地址,而是在它页面里茫茫多的js中开始debug。虽然FireBug的确好用,但面对这种情况还是比较乏力,下了断点后刷新页面,断点和没有一样,激活在下一条js语句处中断的话,那就是从第一条一直开始debug,断点也没有用。

到这里又犯晕了,准备直接看代码,每次调用一个函数就复制出来,再恢复格式,话说这里我觉得vim肯定能自动完成的,但我实在不知道怎么替换能够让;后面多一个换行,^M, \r, \n, \r\n全都不行,我真是晕死了,只有手动调,痛苦啊,在复制了n多函数后(这里其实好些都不用看直接跳过的,找到GET 数据的url就好了,但我就 不信邪,巴不得每个函数都弄清楚,= =真是脱线啊),终于找到,ok,马上构造请求,传回来的数据一看,又sb了,怎么就没有第一个url呢!

还好这次处理json的基本只用到了一个js函数,我全都复制到FireBug 的Console里,因为不能加断点,于是就一行一行地alert出来看。终于….被我找出来了,当场我就吐血了。居然就是把stream后的数字加10,再把文件名加18000000,最后换后缀 .mp3!!!唉。早知道这样的话就自己去总结规律了,不过工科男好像都比较喜欢刨根问底,希望全部搞清楚= =。

我想着,这下子总ok了吧,下载文件的话之前准备wget,先找一个test一下,失败,居然是403。shit,估计是cookie的问题,于是去网上google 了一下,一般都是用 –load-cookies,但是我从 ie 导出的 cookies 都不能用,自己写模板往里面填内容也不行,最后还是在 wget manual 里发现有一个 –no-cookies –header “Cookie: ….” ,换了这种方式来,行是行了,不过实在是太不优雅了,但也找不到什么别的方法了。

把这个弄好后又觉得 wget 是单线程下载,一次下一个文件,估计等死了,于是想自己写一个一次下一盘专辑的,应该会快一些。写好后发现下下来的文件全是0,很奇怪,每次 read(1024) 出来的全是0,在网上看看别人的代码也都是这样啊,不知道到底是哪里出了问题,最后暴力了一些,直接 read() 读所有的一次写入,现在又好了= =真是好囧的问题。

update: 最终下载文件这块还是出错了,写的是一盘专辑每首歌开一个线程下,但是,我的多线程果然是超弱啊,直接用 threading.Thread( target = …, args = …),做的,最后再run() 一下,完全没有任何控制,于是,到了下一盘专辑的时候上一盘的还没有下完,又新开线种,最后又是10061 socket错误再加上非常不稳定,还是决定用 wget吧,把所有的地址写入一个文件,再 wget -i file ,最后挂了10H,终于弄完了一大部分,还有三盘专辑出错。还有文件的重命名啊,整理 mp3 里的信息,不过都是小问题了。

淘宝开源网店笔记(一)

代码早就拿到了,但是一直没有时间研究,今天估计再不看项目来不及了。= =真想分个身啊。

淘宝开源网店(简称TOP)是一套基于PHP的MVC架构建站系统,安装要求apache + mysql + php,版本不太旧就ok了。安装的话由于现在要求通过淘宝验证的网站才可能完全激活,因此本机上是要修改程序的。文件解压,用浏览器访问http://127.0.0.1/install/install.php,按它的要求一步一步来,到建完数据库,要到淘宝验证身份时就可以了,后面的你也验证不了,没有意义。接下来修改代码,在config的site_config.php里面修改 tom_site_open,tom_install_finished 分别为1 和true,如果开启了apache 的 rewrite 模块的话建议将 tom_rewrite_url 也设为 true。然后修改 /lib/tom/admin/filter 下的security.class.php,将35行的判断修改为恒真。删除 install 目录,看一下数据库里的表,如果只有15个的话,也就是只有前台的数据库的话,那就在根目录下搜索 *.sql,再用tools 里的tom 程序导入这些 sql 文件,具体用法参照 tom default/list。全部建好后应该是46个表( 我只有45个,但好像也没有什么影响)。做完这些再在 employee 表里加入一个 status 为1的用户,密码为 md5 加密。这样就可以在后台作为后台用户登录了,切记不要用店长登录,那个要授权过的。

安装完后了解一下它的大概机制,首先说 url 风格吧,它将地址分成两种,一种是 url,这是外部使用的地址,一种是 uri,表示网站内部的地址,你可以在site_config.php 的 tom_router_rules 里面配置。默认已经有几条规则了,看了下还是挺简单的,以 detail 为例,url 为 detail-:iid 对应着 content/detail 的uri,:iid表示 – 后面的作为 iid 这个变量传给接下来的文件。网页默认后缀是 html,也可以用 tom_url_suffix 修改。url 的产生的话可以调用 router 的 genUrl 函数,将 uri 和变量都传入进去即可,类似下面这样: $router->genUrl(array(‘uri’=>’content/detail’, ‘iid’=>’2d5d377e0df53a1026aee829bddaa540′));。另外如果要生成网址用于利用GET请求在页面之间传递值,那应该用genPartialUrl 方法,如果在后台生成前台 url,那应该使用 genFrontendUrl 方法。

uri 对应着 module/action,比如 content/index ,在 dispatch 的时候,程序会在 modules/content 下面找到 actions.class.php ,再调用里面的 indexAction 函数。程序员要手动地在 action 里指定应该 render 哪个 view,view文件应该在content/views 下,以 view名,比如 index,开头,以.tpl.php 结尾。在 view 里能够调用 action里的 $this 中的所有 public 变量,其实有系统自带的比如 request等,也可能是你自己赋值的。

如果 module 操作数据库的话,非系统自带的数据库创建脚本应该在 modules/modulename/data/install.sql 中,注意,表名要写成 #__tablename,创建的时候前面的#__会被自动替换为前缀。简单的交互可以用 Tom_Factory::getTable()方法得到一个表对象,再调用它的查询函数。如果想把数据操作部分分开(我觉得这里设计有点问题的,它的Model 方面过多地参考了ZF 的设计,我觉得过于繁琐,还不如用 JSP了,个人比较喜欢 CakePHP的方式),那就在 module文件夹下再新建一个 lib 文件夹,新建文件名为 modelname.class.php的文件,其中的类名为 modulename_Model_modelname ,继承自 Tom_Db_Record。内容的话可以参考已经有的,我个人建议用 Tools里的程序自动生成。

疑问一:在layout 里如果用 的方式可以引用一个 module,调用的 action是名为 moduleAction的函数,那怎么给这个函数传递参数呢?

疑问二:module 和 plugin 的区别,现在我只知道它们的文件组织形式不同,url 也有区别,plugin 的url 甚至是 pluginname/filename/actionname 的形式,让人觉得不够一致。其实后台文件夹,即 admin文件里还有它本身的modules, plugins ,我觉得这个设计不是很合理

用python抓取网站数据

淘宝网站有一个后台,记录了和卖家进行交易过的所有买家信息,现在需要把它们抓下来,得到用户名与消费额度。这个还有点意思,整好继续锻炼python。

首先到客户管理页面一看,比我想象中的要复杂一些,不是比如通过get方式在url里指定开始的页数之类。不能被吓到恩,分析了一下下一页的按钮,发现 href 为 javascript:navigate(‘customerListform’,2,425); 。继续,navigate干了什么呢?有意义的只有两步:document.getElementById(‘__CUR_PAGE’).value=page; gotoPageNew(formname); 后面的formname就是customerListform,继续gotoPageNew,也是两件事:document.getElementById(‘__IS_REPLY_SELECT_COUNT’).value=”N”; document.getElementById(formname).submit();

那回头看名为customerListform的form,里面有很多input,根据名字猜测一下含义,大概有第几页,每页多少之类。于是我来了个暴力的,直接把每页改成4200行(总共4147),提交,结果服务端真的没有判断的,于是整个页面就卡在那里了,慢慢开。我汗一下,并且服务端记下了每个用户的设置,也就是说我再打开,还是每页4200的。幸好一直很悲剧的网络现在比较争气,吃了个饭回来居然打开了,赶紧改掉,然后网页保存下来,其实现在就可以开始提取数据了,但这样完全达不到要求啊,每次要手动改网页里的东西,网速慢还可能打不开。

有个保底的了,就可以慢慢研究了,现在的情况就是要构造一个POST了,LiveHttpHeader看了下,然后又搜了搜python构造http请求,最后用了下面的方法:

raw_dict = {"name" : "test", "pwd" : "test"}
args = urllib.urlencode(raw_dict)
con2 = httplib.HTTPConnection("eshop.alisoft.com")
con2.request("POST", "/qbuilder/c2cCustomer!customerList.jspa", args, headers)
r2 = con2.getresponse()
while 1:
    ttt = r2.read(2048)
    if not ttt:
        break;
    temp += ttt
con2.close()

发送的数据的话本来是准备自己构造的,就是把那些input全都发送了,但返回总是500错误。= =这个我也无法调试了,谁知道服务器内部发生了什么,以为是自己参数发少了,又把所有的input全发送了,但还是这个问题。后来干脆将LiveHTTPHeader里的所有数据全拷过来了,再加上所有的Headers,果然200了。

数据是有了,但还没处理,要分析html,用sax我是没想出来怎么办,minidom据说很慢,于是改用lxml,然后,悲剧的事出现了,又是字符编码问题,如果python没有这个问题的话真的是完美了,怎么也无法用etree生成一棵树,如果直接从string parser的话,不管是print 出来还是转换成utf-8后写入utf-8文件,永远有错误。最后放弃掉了,只好用re。还真是悲剧啊!
每一个用户的话都是一行,id都是9位数字加tr,还是比较好匹配的,再对每一行配置span和td,取得中间的内容就是用户名和钱了。

当然,一个程序写起来只占一小部分时间,debug才是要人命的地方。乱码又出现了,最后解决方案是在抓数据时decode成unicode,然后处理的时候先转换成utf8,最后连MySQL的时候,不要忘记了数据库、表都要是utf8的,并且要set names utf8。

完了吗?NO,还有问题,这次是网络问题了,如果每页40的话,总共有105页,而抓下来一个就处理的话,多了就会出现10060 socket错误,oh,这就是我悲剧的网络啊,没办法,每抓一个页面都sleep(1)。然后亲爱的MySQL也有问题了,取完数据马上insert,速度过快,会出现命令不同步的错误,我觉得不至于吧应该,这每秒才几十条语句啊,后来找到据说是因为MySQLdb模块太old了,= =|||。没办法,每次execute后都commit一下。最后还被网速卡了下,又是10060,没办法,上50m VPN吧,uu,我爱你!

可以改进的地方:用urllib2实例化一个cookie对象,然后可以要用户输入用户名密码,先连taobao的网站,取得cookie,再在接下来的请求里使用cookie这样就不用手动更改cookie了,但是由于最后的网站后台是在eshop.alisoft.com,我估计中间还有另外的setcookie之类的过程,应该有点麻烦,不过思路是很清晰的了。