python源代码学习笔记(一)

这段时间又懒下来了,加上事情也比较忙,完全荒废了这里啊,罪过罪过。

这一系列文章是我根据《Python源码剖析——深度探索动态语言核心技术》学习python源代码的笔记,书中分析的是python2.5的代码,我下载的是python2.6的,有些许不同,不过大致一样。

先来看看python总体的架构吧。如下图(从源码剖析中截图,以下如果未有说明的话都是从书中截的)。
python总体架构

在左边是python和各种模块、库及第三方模块。模块一般是由python编写而成,不过在一些对速度要求比较严格的地方,也可以用C写,python和C的兼容性是相当好的恩。不过这一块并不是学习的主要部分,它对应着python docs里的Library Reference,而书中主要讲解的是Language Reference的部分。图的右边是python的运行环境,可以理解为系统运行一个进程时堆栈,寄存器等所保存的上下文环境。中间是python虚拟机,所有的代码都是在它上面运行的。在虚拟机的上层,scanner, parser, compiler其实和一般的编译过程没有太大区别,只不是一般的C编译器最终是将代码翻译成机器码,再由操作系统作为运行环境来执行;而python则是把代码翻译成字节码,以python虚拟机来作为运行环境而执行。python解释器和python运行环境是我们学习的重点。

让我们从python中的对象开始吧。python中一切都是对象。从内建的int、string到用户自定义的class,这些都是对象。进一步的,它们的类型也是对象。对于一般的对象来说,它们是不能被静态初始化的,并且都是在堆上,但有一些例外,就是内建的类型对象,比如int类型这个对象,它们是被静态初始化并在栈上分配内存的。python中的对象一旦被创建,在内存中的大小就再也不变化了,这样是为了保证设计的简单性,其实这种思想在各种地方都是有体现,比如hbase对外提供的接口,也就是get, put, scan不多几个,如果在保证可扩展性和可靠性的前提下还要设计诸如关系型数据库中的那些比如事务性,原子性等,那将会相当复杂。

对象所对应的类型定义在object.h中。代码如下

typedef struct _object
{
    PyObject_HEAD
} PyObject;
#ifdef Py_TRACE_REFS
/* Define pointers to support a double-linked list of all live heap objects. */
#define _PyObject_HEAD_EXTRA       \
    struct _object *_ob_next;
    struct _object *ob_prev;

#define _PyObject_EXTRA_INIT 0, 0,

#else
#define _PyObject_HEAD_EXTRA
#define _PyObject_EXTRA_INIT
#endif

/* PyObject_HEAD defines the initial segment of every PyObject. */
#define PyObject_HEAD                  \
    _PyObject_HEAD_EXTRA           \
    Py_ssize_t ob_refcnt;                  \
    struct _typeobject *ob_type;

上面的代码可以简化为

typedef struct _object
{
    int ob_refcnt;
    struct _typeobject *ob_type;
} PyObject;

ob_refcnt是用来实现引用户数的gc。ob_type指向了这个对象的类型对象,换句话说,它存储了这个对象的类型。对象可以分为两种,一种是定长对象,比如int,另一种是变长的对象,比如list。对于前一种,只用在实际的对象中再分配一个固定的区域用来存储值就可以了,但这种方法对后一种对象就失效了,因此python中定义了表示变长对象的结构体。

#define PyObject_VAR_HEAD          \
    PyObject_HEAD                         \
    Py_ssize_t ob_size;

typedef struct
{
    PyObject_VAR_HEAD
} PyVarObject;

可以看出来,其实就是将一般的对象包装了一下,加了一个ob_size。表示所容纳的元素个数,比如一个list里面有5个string的话,那这个size就为5。从这里我们可以看出,只需要一个PyObject *指针就可以引用任何一个对象。这对于python实现各种语言特性很关键。接下来我们来看看_typeobject中到底有什么。

typedef struct _typeobject
{
    PyObject_VAR_HEAD
    const char * tp_name; /* For printing, in format "<module>.<name>" */
    Py_ssize_t tp_basicssize, tpitemsize; /* For allocation */

    /* Methods to implement standard operations */
    destructor tp_dealloc;
    printfunc tp_print;
    ....

    /* Method suites for stadard classes */
    PyNumberMethods *tp_as_number;
    PYSequenceMethods *tp_as_sequence;
    PyMappingMethods *tp_as_mapping;

    /* More stadard operations (here for binary compatibility) */
    hashfunc tp_hash;
    ....
} PyTypeObject;

首先可以看到,这个类型变量也是一个对象,因为它有PyObject_VAR_HEAD,接下来的tp_name是它的类型名。tp_basicsize和tb_itemsize给定了分配内存时的相关信息。在这个结构体中还主要定义了三类方法,一是基本操作,比如打印对象,析构对象等。一类是对于在用运算符做运算时所应该调用的函数,比如加减、取键值等等。还有一类比如使用hash(oneobject)时调用的函数。其实这个类中还有很多信息,不过为了简明起见,先列出这些。

我们已经知道,一切都是对象,每个对象都有类型。那么对一个int来说,它的类型是,这是一个对象,再精确一点,是一个类型对象,分配在栈上的静态初始化的对象,那么,这个类型对象的类型是什么呢?答案是。在源码中就是PyType_Type。

PyTypeObject PyType_Type =
{
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "type", /* tp_name */
    sizeof(PyHeapTypeObject), /* tp_basicsize */
    sizeof(PyMemberDef), /*tp_itemsize*/
    ...
}

看看整数对象是怎么和它联系起来的。

PyTypeObject PyInt_Type =
{
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "int",
    ...
}

下图可以更好地说明这个问题

python运行时整数对象及其类型之间的关系

python运行时整数对象及其类型之间的关系

淘宝开源网店笔记(二)

这一段时间都在弄HBase、Hadoop相关的东西,事情多,压力也挺大的,一直来不及写博,今天算是忙里偷闲把这篇补完吧。自己本身也只用到了一部分,有些还是比较模糊的,等以后用的时候再看吧,现在算是总结一下。

用top开独立网店的话,其实很多东西都是要用taobao的API的,其实操作都是在taobao上完成的,首先要理解这一点,比如以前的用户购买记录之类的,一般都是存放在taobao上的,如果你要读的话当然也可以从那边抓来。可以参照ucenter模块,再看看它里面用到的membergrade,大概就可以清楚这个流程了。

模块启用完毕后,在template里添加对它的引用,比如<tb:modele name=”comment” id=”1″>,这样的一个引用被称作模块的实例,instance。加入这个概念应该是因为模块在不同的地方被引用可能表现出不同的行为吧,可以在sellercmd等模块中看一下它的用法(这个地方我理解不是很深入,可能有错误)。

接下来也不知道从哪说起,反正都是很散的东西,虽然大的框架理解了,但这些小的地方还是花了我比较多时间,在自带的那些module里面去找哪些功能怎么实现。于是就按照开发手册左侧的类的顺序来吧。

Tom_Admin_Module Tom_Admin_Template 用来管理的模块,可以得到已经安装的模块内容、配置文件、后台使用的模板等。如果不做后台开发的话应该用不到这一块

Tom_Biz_Item_Skuitem这一块应该是将taobao的商品与专家自有的ERP结合起来吧,就是将它们的字段对应一下。在这里我必须吐一下槽,这个我之前没看懂因为我根本不知道这sku是个啥玩艺。后来好像是套餐。就不能有一个地方稍微说明一下吗。然后这ERP是哪里来的我也不知道。找了找没有什么信息。

Tom_Biz_Order_* Tom_Biz_Trace_* 开头的这一串,就是订单交易相关吧,没啥好说的。

Tom_Util_* 这些则提供了一些常用的函数,比如过滤啊,分页啊,是静态的可以直接Tom_Util_Filter::filterContent这样调用。分页的比较复杂,我相信只看手册肯定是看不懂的。不过看看article里的listAction大概便明白了吧。

Tom_Biz_Item_* 通过API从taobao查询取得商品信息

Tom_Biz_Combo_* 你没有看错,是Combo而不是sku。但实际是一样的,有点混乱的命名吧,不过我觉得Combo更好理解一点恩。这一块基本同上,不过这里的操作大多是在本地数据库中进行,这里我不是很理解。

Tom_Admin_Menu – Tom_Template 这块都还比较好理解的,有关配置、错误、工厂类之类的东西,看看它本身的就够了。

接下来又是一堆Util比较有用的应该是Tom_Util_Common(可以得到BossNick)、Tom_Util_Memcache(缓存,加快速度)、Tom_Util_PageCache(缓存整个页面)、Tom_Util_TMCookie(我不知道这哥们和上面的TMACookie到底有啥区别,猜测上面的是给店长或店员用的?)、Upload那些类应该是添加宝贝时上传图片用的比较多,别的场合可能还不太用的上。

在core的部分,以Admin开头的主要是后台,Core开头的则是前台,Commands一般都用不到。Action相当于一般controller,主要是实现控制逻辑,一般的功能主要是转向、渲染视图。注意这里的转向有好几种:redirect, redirectError, forward等等,不要用错了。而render()之后程序不会自动退出的,要手动调用return or exit()。

Filter类主要是访问权限控制和修饰处理结果。

Request Response 包含了对这个页面请求的信息和返回的信息,比如获取和设置cookie都是在里面。Response里有设置javascript和css文件的函数,但是我怎么都调用都没有效果。最后是用的actions里的类似函数。

Router 的作用一看名字就知道,大概就是操作url,设置url映射,生成外部url之类的,这里还有一些另外的函数,看说明便可以知道。

Db段有这么多class,其实基本只有两个有用:Tom_Db_Table和Tom_Db_Record,很简单,一用便知。

log类我没用过。Acl访问权限这边估计下一个就要用到了,到时候再看吧。

top段下面全都是可以用的api,觉得这里的说明不够详细的话就看taobao官方的api文档吧,使用方式就是先得到一个Tom_Top_Client的对象,然后再用$client->userGet()这种方式就可以调用了,在Request_后面的是API名称,将第一个字母小写就是函数名了,比如Tom_Top_Request_UserGet对应的就是userGet()这个函数。

就写这些吧,有些遗漏的话是我觉得不是很重要的,等用到的时候再看就好了,多去看本身自带的module和源码,应该是不会碰到什么问题的,就是开发效率低下一些。

上次写的一个管理系统,用CakePHP写的,都是默认设置,最后访问一个页面居然需要4.几秒钟。实在是让人受不了,提高速度的方法倒是很多的,但是,这让我开始思考一个问题,CakePHP有必要吗?

之前CakePHP用的最熟,写起来开发效率真的是很快,甚至觉得不用框架写PHP太dirty了,太低级了。以前实验室的项目用的ZF,其实基本就只用了一个url重写,别的全是手写的,特别是model层,裸的sql语句嵌入在php代码中。现在回想一下,的确是质量很差的代码,但写起来效率也高啊,访问速度也快。

接下来一段时间都比较纠结,如果要继续用CakePHP的话,那么其实虽然开发快了,但是要额外地对它的性能进行优化的话,加起来的工作量不会比写原始的php小多少。换一个框架也是一个选择,不过我开始想,我到底需要框架吗?特别是CakePHP这种比较重量级的,即使我写一个最简单的Hello World,它也要经历一个完整的bootstrap过程,甚至还会去实例化model对象,会describe 你需要的表。那么我最需要的到底是什么呢?最后总结出来其实很少:一个简单的mvc分层,一个url dispatcher。于是最后我选择了smarty,自己手动实现model层,url dispacther的话是美观要求,可以以后再加。这样又写了一个后发现其实开发效率也还是比较快的,由于逻辑也比较简单,所以维护代价也能让人接受。

现在想想ZF,以前觉得太像java,太笨重了,完全不符合php的理念,但现在却觉得它的耦合度还是很松的,你可以只用里面的一部分而重写另一部分,而这在CakePHP里就很难做到了。据说Kohana挺符合我的要求的,很轻量级。不过如果以后自己写的代码复杂起来了,那慢慢再将里面的东西抽象出来,也会形成一个框架了,所以,还不如写自己的吧。

一个上传客户端的python实现(二)

FTP、C/S通讯都已经搞定了,接下来主要就是界面编程以及客户端逻辑了。本来随便写的一个界面,给老师看了后,说实在太土了,得重新写。现在想想当时写的真是不能用啊,都不知道设置layout,这样程序在窗口缩小的时候,里面的元素都不会变小的,很容易就看不见了。重写之前老老实实地看了Rapid_GUI_Programming_with_PyQt,果然还是得系统地学习。主要有三点:

1、用layout来布局,最后要给顶级窗口设置layout属性,这样缩放的时候就能自动改变大小

2、可以设置label 和其它控件的buddy关系,这样能利用快捷键迅速让某个控件获得焦点。

3、QStackedWidgete来设置安装程序时上一步下一步,主窗口内容切换的那种效果。不用我自己设置几个Group然后再手动控制。

界面画好了,接下来将里面都填充好,SIGNAL和SLOT都连接好。然后qt除了提供信号与槽这种方式外,还有和MFC类似的事件处理机制(其实SIGNAL、SLOT都是用event来实现的)。如果要简单地定义响应一个按钮被单击的函数,可以直接这样写:

@pyqtSignature(“”)
def on_ButtonName_clicked:

这样就不用再connect了,适合于事件和函数一对一的简单关系,不过也看个人习惯了。

当处理下拉框的时候碰到了问题,PyQt的QComboBox的下拉选项被更改的SIGNAL中,只提供了更改成哪一个,而没有从哪一个更改的。但是我有很多用户输入的框,我希望在用户点击下拉框更改它的选项的时候,保存前一个,再把这些框的值更新为当前被选中的元素的值。想了半天,没有什么好的办法,除非把每一个input的focusout都与保存的函数连接起来,但这样太累了。上网搜了下,最终确定要自己更改ComboBox对事件的处理方式。

PyQt中的app.exec_()函数其实就是启动了主窗口的事件循环,程序会保持着一个事件队列,里面的事件可能是系统发出的,可能是用户发出的。一般如果新产生一个事件,它会被利用postEvent()方法添加到队列尾,等待被处理,也就是说是异步处理,如果想要马上被处理的话,可以用sendEvent(),窗口则会不断地在某些时候处理这些事件(也可手动强制处理,通过processEvent()),简单地来说,它接收事件,然后查看是不是有对应的处理函数,如果有的话就调用相关函数进行处理,否则就丢弃。QT中还有一个事件过滤器的概念,如果把A对象添加为B对象的事件过滤器,那么所有送到B的事件都会先到达A,然后A再处理这些事件,一般A有两种选择,丢弃掉事件或转发给B。

知道了这些要解决这个问题就比较简单了,我自己创建了一个继承QComboBox的对象,在它的focusin事件中,emit一个信号,然后再在主窗口中把这个信号连接到处理函数。不过这样有一点坏处就是UI的代码是通过pyuic4编译ui文件得到的,每次都得重新手动去改那个文件,因为改一下ui,重新编译下我自己的改动就被覆盖了。不过在QtDesigner中好像也是可以使用自定义的累的,如果那样的话就不用这么麻烦了,但我没有去研究,估计也不会太难吧。

在文件上传的时候,我希望界面能够不被锁死,能继续和用户交互。在最初的版本中,我就直接用继承了threading.Thread类,每一个文件开一个线程然后调用它们的run,甚至都没有用一个list存储它们的名称。重写的时候这块当然是得大改的。首先要知道怎么管理多线程,线程间怎么通信。

在看了这篇后,对多线程大概有一个了解了。Queue其实就相当于是缓冲区,而往里面放数据的则是生产者,取数据的是消费者。Lock之类的也都比较简单,想想当时OS的知识,设计一下之间的通信流程管理逻辑也不难了。但是等我开始动手时,偶然在邮件列表看到有人说子线程不能直接操作GUI的,又去网上搜了下,发现绝大多数GUI框架都不是线程安全的。这下可又要重新想了。

主线程给子线程分配任务,这个可以直接对过Queue解决,子线程完成后如果通知主线程呢?我采用的方法是再开一个结果队列,子线程往上面放东西,而主线程去检测,当子线程没有再运行了并且两个队列都为空的时候处理过程完成,并且所有的结果都被主线程更新到GUI上去了。但如果去检测这个结果队列呢?

再次求助Google后,我找到了这篇,当时以为问题应该可以解决了,但是动手编完了后一试发现不行。分析了一下,limodou那篇文章里更新GUI就是在Controller里面,而我这里一定要在主窗口上,所以如果有Controller的话也要通知主窗口,所以主窗口一定要有一种机制去查询事件处理的状态。而不管用time.sleep(),还是用ChildThread.join()都无一会锁死窗口。我给limodou发了邮件询问,他也并没有说出一个解决的方法。不过大概地提供了几个子线程和主线程通信的方法,分别有queue,全局变量,lock,socket,事件循环等。

后来乱翻python的官方手册,发现在threading里有一个Timer类,当时就想如果这个能设定为一段时间后调用某个方法不是正好?比如每隔一秒调用一下检测函数,如果结果队列里有新数据的话就更新到主窗口,但仔细阅读后才发现它的用法是多长时间后调用一个子线程执行某个操作,大失所望,子线程就算执行了检测发现有新数据的话还是要和主线程通信的,又绕回来了。有没有这样一种时间函数,让主线程过一段时间来执行一个函数,但在这一段时间内没有阻塞呢?后来翻来翻去终于在PyQt里发现了QTimer这个类,它可以定时,并且不会阻塞!那问题就解决了,设定一个定时器,比如0.5秒钟,再把它的timeout SIGNAL连接到检测函数。在函数中先停止这个记时器,如果满足退出条件的话就退出,如果不满足的话,进行一些处理后让记时器重新开始记时。这样问题就解决了。

后来我开始思考这个问题时想到,不可能存在这样的一种记时函数的,让某个线程即不阻塞,又会在一段时间后来响应记时器,除非记时这个操作本身就是在另一个线程里进行的,它们到时间后进行通信,而这些都对用户透明,所以才会觉得它即不会阻塞线程,又能达到记时的目的。如果是在QT中的话,那它应该是通过事件来实现的,主线程可以和子线程通过事件来通信的。本来我也可以直接用事件,但当时觉得事件这块太大了,程序又比较急,以后再系统地学习一下比较好。其实到现在想到一个看起来最简单的方法,就是子线程emit一个SIGNAL,主线程来接收,不过我不知道信号能不能跨线程。因为connect里要指定发出SIGNAL的对象,我都不知道应该怎么写。

一个上传客户端的python实现(一)

实验室之前要做一个可以上传动漫、视频和海报的客户端。正好当时正在研究如何实现我的小闹钟,于是就决定继续用PyQt做了。其实当时老师是说要MFC,可我完全不会,就说用QT好了(虽然我当时QT也只会Hello World,囧)。

功能需求:

客户端:1、能添加动画信息,如作者,集数、海报等;2、能对视频进行描述,标题、tags等;3、上传要可以断点续传,因为视频可能比较大,所以不能每次从头开始传。

服务端:1、要能同时处理多个请求;2、最后要把文件排列成一个动画一个文件夹,插入数据库时要保证完整性,因为要同时操作多表。

工作流程:先由用户输入元数据,然后从服务器取得海报、视频的上传的远程地址,上传完毕后再调用服务方法移动文件、重命名、插入数据库。

需要技术术:1、GUI编程;2、C/S 编程;3、ftp 协议的实现。

(我fk曹楼的这网络,让我写了快一小时的博没了!)

C/S编程和FTP协议的实现都得用到socket编程。socket是对Tcp/ip 族的一个抽象,可以认为它实现了这些协议并向用户提供了诸如bind(),accept()这些类似的API,不过同时也要记住,socket并不仅限于Tcp/ip协议族。socket用通信双方的地址和端口号来标识一个连接,因而它可以为两台机器上或同一台机器上占用不同端口号的两个进程提供通信方式。有关更多的资料可以参考以下两个地址:

http://www.chinaunix.net/jh/4/198859.html

http://learn.akae.cn/media/ch37.html

等我看了挺多的资料准备开始动手编的时候,我发现了另一个好东西:rpyc。它将这些通信过程封装起来,使客户端和服务端的通信就像调用普通的函数一样简单,真是相当方便啊,不过有一个缺点就是它使用了BaseException,这使得你必须手动打印错误信息,如果不使用log的话。虽然最后没有直接用socket,不过看了这么多还是很有好处的。

FTP协议的实现方面先在网上找了找现有的代码,发现都太繁琐了,又由于其实上传文件和和服务端完全可以分开,因而不用修改普通的FTP服务器。于是决定自己用ftplib来实现客户端,服务端的话就用普通的FTP服务器如Serv-U,Xlight之类的。自己写了写简单的代码来测试下ftplib,发现里面的函数太多了,眼睛都看花了,于是除了真正的传输二进制信息的时候外,基本都是自己用ntransfercmd()得到socket连接,自己发送命令。有个小trick,在得到服务端当前目录文件列表的时候,如果用ftp.nlst()的话,在最后得到结果时是调用了retrlines(),它会设定TYPE A,因而在传输前要手动地TYPE I,否则传输出出错。

在具体实现的时候碰到了一个问题。RFC959里讲到:STOR 会引起服务器DTP接受经过数据连接传送的数据并将这些数据存储为服务器端的一个文件,如果在路径参数里指定的文件在服务器端已经存在,那么这个文件会被传送过来的数据覆盖。如果指定的文件不存在则会在服务器端新建一个文件。而APPE 则引起服务DTP接受从数据连接传送过来的数据并存储在服务器端的一个文件里。如果指定的文件在服务器端已经存在,则这个数据会附加到文件的后面;否则服务器端会创建这个文件。

但是在实际的程序里测试的结果是,如果文件不存在,那么STOR就正常地工作,就像平常上传一个文件一样,APPE则报错说找不到文件,如果文件存在的话,那么STOR也会在设置了REST的地方开始传输,APPE表现相同,也就是说STOR并没有被传送过来的数据覆盖。我猜测可能和所用的ftp服务端对命令的实现的不同有关吧,本机上使用的是Xlight FTP服务器。为了防止在别的FTP服务器上得到不同的结果,在传输前先做判断,如果文件已经存在,则用APPE,否则STOR。

批量下载音乐

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之类的过程,应该有点麻烦,不过思路是很清晰的了。