python网络爬虫备忘记
分类:
编程
|
jeff 发表于:2008-10-22 23:52:39 |
1条评论 |
最近写网络爬虫多了,总结出一些心得,记录以备忘记.
网络爬虫的要求有几点:
一. 一个好的网页解释器,python推荐BeautifulSoup,java推荐htmlparser.
二.良好的性能,不能太慢,当遇上网络状况不大好的时候,慢更受不了,所以通常需要同时启动N个网络爬虫进行工作.使用队列可以很好解决这个问题.
三.稳定性,容错性.网络本来就存在不稳定性,连接超时,连接重置等是常见的不稳定现象,网络爬虫程序要充分妥当地处理这些问题,保证爬虫输出结果的完整性.
一.Beautiful Soup 美丽的汤
BeautifulSoup是一个纯py的html(xml)解释器,为许多python开发者所钟爱.其官方网站已有详尽的文档可作参考,再不然,google一下也大把中文文档.所以这里就不对其基本用法多加描述.仅对我走过的弯路进行一下小结.
1 <ul>的contents并不全是<li>,<head>的nextSibling并不一定是<body>
代码说话:
python 代码
- >>>from BeautifulSoup import BeautifulSoup
- >>>soup = BeautifulSoup('<ul><li>abc</li><li>def</li></ul>')
- >>> ul = soup.find('ul')
- >>> ul.contents
- [<li>abc</li>, <li>def</li>]
上面的代码说明<ul>标签下面的子结点全是<li>,试过这小段HTML代码有回车也得到同样的结果,于是我想当然地认为从网 页上扒下来的HTML内容也会得到同样的结果..但实践很快证明并不是这样的,实际上很多网页制作过程中使用了一些不可见字符(如空格,制表符等),这些 字符并不会被忽略,而是被汤看作是NavigableString类型的元素.
所以当我想当然地for li in ul时,很快发现里面有很多杂质而导致错误.更好的方式是自己写个小函数,只取标签即可:
python 代码
- def right_tag(tag,name=None):
- if not name or tag.name == name:
- return tag
-
- def tag_children(tag,name=None):
- """
- 找到所有类型为标签的子元素,可通过name参数指定标签种类.
- """
- children = []
- contents = tag.contents
- for content in contents:
- if isinstance(content,Tag):
- child = right_tag(content,name)
- if child:
- children.append(child)
- return children
同样的道理,nextSibling,preSibling也不一定稳妥地帮你找到相邻的标签,下面的函数可以帮你:
python 代码
- def next_tag(tag,name=None):
- """
- 找到下一个邻近的标签,可指定标签名
- """
- current = tag.nextSibling
- while current:
- if isinstance(current,Tag):
- return right_tag(current,name)
- current = current.nextSibling
2 编码问题
先复习一下Python的编码机制.
python的内部编码为unicode,如果需要将字符串从编码A转为编码B时,顺序为:
编码A ---(decode)--->内部编码 ---(encode)--->编码B
BeautifulSoup在解释网页文件的时候会把文字转换成unicode的.既然是这样,那应该不用担心编码问题了.友人网的网页是GB2312编 码,在不用指定特殊编码的情况下可以获取到正常的中文,而在我扒当当网的时候,问题出现了,同样是GB2312的编码,但扒下来存在文件的却是乱码.(其 实友人网也有部分乱码,但大部分正常).我检查控制台的输出时发现,BeautifulSoup并没有把中文转换成为真正的双字节unicode,或许说 转换得并不成功,它仅仅是在GB2312的编码串前面加了一个u,如 u'\xe4\xb8\xad\xe6\x96\x87' ,把原本是GB编码的代码假装成了unicode,所以在处理的时候成了乱码.
Beautiful Soup 的文档说:会按顺序尝试不同的编码将你的文档转换为Unicode:
* 可以通过fromEncoding参数传递编码类型给soup的构造器
* 通过文档本身找到编码类型:例如XML的声明或者HTML文档http-equiv的META标签。如果Beautiful Soup在文档中发现编码类型,它试着使用找到的类型转换文档。但是,如果你明显的指定一个编码类型,并且成功使用了编码:这时它会忽略任何它在文档中发 现的编码类型。
* 通过嗅探文件开头的一下数据,判断编码。如果编码类型可以被检测到,它将是这些中的一个:UTF-*编码,EBCDIC或者ASCII。
* 通过chardet 库,嗅探编码,如果你安装了这个库。
* UTF-8
* Windows-1252
我在构造BeautifulSoup对象的时候并没有指定编码,那么看来是第二点起作用了,但效果并不理想,所以最好的方式还是在构造器指定编码.
如soup = BeautifulSoup(page,fromEncoding='gb2312')
实践证时,在构造器指定编码是最佳实践.
二.优化性能
使有队列辅助多线程提速
假如使用单线程去运行网络爬虫,那速度一定让人受不了,由于网络的延时随时可以把一条线程阻塞.
多线程可以充分利用带宽和计算机资源,给网络爬虫提速.
IBM文档中心这篇<<使用python正行线程编程>>正好使用网络爬虫来做为示例讲述如何进行多线程编程以及如何使用队列来优化多线程编码的.很值得参考.
三.容错处理
网络爬虫最常见的错误莫过于连接超时和连接被重置了.而这些错误又是偶然性的,就是说,同一个网页,第一次可能连接超时,第二次可能就正常了.这要求在程 序的设计时考虑到如何这些错误的连接,我的做法是把出问题的连接记录下来,等大队列工作完成后再循环重试有问题的连接,直到全部正常.
另外,关于网络超时的问题.如果超时时间过长的话,网速慢的时候会大大影响网络爬虫的执行速度,所以通常有人希望设置网络连接的超时 值.python2.5的urlib2的urlopen函数并没有接受timeout参数,(尽管python2.6提供了,目前没敢用).不过可以用 socket.setdefaulttimeout()来设定.当然,可以到pythoncn参考一些行者给出的建议.
OpenSourceCamp现场
分类:
编程
|
jeff 发表于:2008-09-20 15:19:30 |
2条评论 |
因为搬家的原因,到沙河鼎龙大酒店的时间是2:05分,错过签到,会议开始了,错过了OSCamp的很酷的T恤..看一会能不能拿一套..卡卡..
twitter果然是最方便的工具..reply to oscamp.
品高上场SHow了一下短信业务平台的架构,现在Peter在介绍OW2,Object Web Gen2..
还没有进入小团体讨论时间...先上两张图:


上面右边的是vingel,他的Eee PC好酷..电池可以持续使用8小时,让我的Mqc汗颜...
camp 继续..

左边是传说中的vingel...卡卡.

Eee pc呀!!!3K6大洋,1G内存,8小时power..自用,送人佳礼!
Django1.0阿尔法发布
分类:
编程
|
jeff 发表于:2008-07-23 17:14:46 |
2条评论 |
真是一个振奋人心的消息,Django终于要结束三年以上的版本长跑,出1.0版了.苦了社区里面的朋友,一直在苦苦等待.
这次发布的直接原因估计是前段时间Django易主,由新的基金会支持Django的发展.
本站是基于django开发D,当时的版本还是0.96,大概在去年9月份上的线,等到1.0正式版出来,马上就升级啦!
期待Django的正式版早日发布,期待更多的朋友加入django的行列.
Nuxeo WebEngine -- java的plone?
分类:
编程
|
jeff 发表于:2008-07-15 00:52:50 |
0条评论 |
今天看到一则新闻“Nuxeo WebEngine发布”,仔细看了一下,咦,这东西的概念怎么这么像python世界的plone?
说到plone,不得不提的就是它的开发框架Zope了。介绍Zope并不是本文的目的,有兴趣者可以Google之。
一、WebEngine依赖 Nuxeo内容管理框架提供基于组件的程序模型和web开发模型来创建以内容为中心的组件化应用,包括Wiki,博客,内容为主的web网站等。
注:Nuxeo内容管理框架(Nuxeo core?)应该是类似Zope 的CMF(Content Management Framework )东东了。plone也一样是作为一个应用框架,用来开发以内容为中心的组件化应用。Plone内置的内容类型有页面、文件、图片、文件夹以智能文件夹,而Webengine则提供了更多内容相关的类型,如Blog,Wiki等。
二、WebEngine主要依赖REST方式:URLs映射到分层的内容存储,内容通过GETs获取,用户行为通过GETs和POSTs请求等。这种方式方便通过WebEngine使用和架构RESTful应用。
注:如果你了解RESTFul风格的URL并知道Zope以模型为中心,URL以节点漫游的方式进行映射,你就会发现两者的目的都差不多,REST是以资源为中心的。我后来看了一下WebEngine的实现,它使用了内容仓库Jackrabbit(什么是内容仓库)作为数据存储的一部分,真正实现多层次结构数据的存储。表现出来的效果和ZODB有点类似了。
三、WebEngine完全是可扩展的,组件化的,通过OSGI方式和Nuxeo运行来扩展。
注:WebEngine的扩展可看作是OSGI 的插件。由于OSGI,WebEngine在部署组件方式同样可以达到热部署,在Zope里面,这样的组件被称为产品(product)。
四、WebEngine有自己独立的服务器,并且兼容JBoss,允许嵌入式运行等。现在暂时不清楚其独立服务器的用意(不过从下载回来的Standalone版本来看,服务器是基于Jetty6的),但看起来有点模仿zope的意思,zope是提供独立的服务器。
我下载了一份Standalone的发行版下来试验了一下。发现WebEngine这一次的发布真是摆了个大乌龙,在它的网站的任何地方都找不到登录系统的初始密码,启动了服务器的我在门外徘徊了好久,终于在Google到一个可能的帐号Administrator/Administrator,试一下真的能进去。
界面还很简陋,用户管理的创建用户看起来有点Bug,我创建的每一种类型的内容最终都显得像一个目录,并且点击编辑的时候都出现这样的错误提示:
This is a placeholder page. Don't know how to edit generic documents.
Add an edit page for your document type please. (Your document type is: Blog)
不知道是我RP有问题还是系统根本就还不完善。
总而言之,这东西看起来不错,不过现成的Demo却有点令人失望。不知以后是否会有惊喜,继续关注。如果该产品发展至成熟,将会为Java平台上的内容管理领域带来一个更好的解决方案。到时除了plone,又多了一样选择,是好事!
安装MoinMoin--mod_python篇
分类:
编程
|
jeff 发表于:2008-01-22 01:23:28 |
0条评论 |
最近需要一个Wiki来进行写作和进行翻译工作。Confluence无疑是一个功能超强的Wiki产品,可惜是商业产品,而最近投靠Python的大营,所以Python世界的Wiki代表MoinMoin成了我的首选。
从MoinMoin的部署结构来看,她可以分为三个部分:一、程序文件,二、静态文件,三、Wiki实例。下面以moin-1.6.0为例,讲解MoinMoin在Windows下,Apache及Mod_python的环境下部署的步骤。
一、确保本地已安装好Apache + mod_python。
二、下载MoinMoin-1.6.0。执行setup.py install进行安装。安装程序将会在%PYTHON_HOME%/lib/site-packages/下面创建MoinMoin文件夹并复制程序文件到这里,并在%PYTHON_HOME%/share下面建立moin文件夹,复制MoinMoin的模板到此。config和server文件夹下面存放的文件分别是Wiki实例的配置及用于不同应用服务器的脚本、Data和underlay是数据和一个Wiki的初始化内容,通常新建一个Wiki实例的时候都会复制这两个文件夹的文件、htdocs主要是存放静态文件,如Js,css,html等,应该暴露给服务器访问的。
三、创建一个文件夹,把share文件夹下面Moin里面的date和underlay文件夹复制进来。并把config文件夹下面的wikiconfig.py复制到新创建的文件夹下。假设该文件夹的路径为:F:/develope/tools/moin-1.6.0/instance
四、配置Apache。修改httpd.conf文件。apache要配置一个别名用来访问静态文件和配置一个Location,用来访问Wiki实例的:
httpd.conf
- Alias /wiki D:/Python25/share/moin/htdocs
-
- <Directory D:/Python25/share/moin/htdocs>
- Order allow,deny
- Allow from all
- </Directory>
-
-
- <Location /moin>
- SetHandler python-program
- PythonPath "['F:/develope/tools/moin-1.6.0/instance'] + sys.path"
- PythonHandler MoinMoin.request.request_modpython::Request.run
- </Location>
在Location的配置了,我指定了moin作为访问的地址,由Mod_python来处理/moin的请求,在PythonPath加上Wiki实例所在的目录的原因是该目录下有配置的代码。
五、修改wikiconfig.py。打开Wiki实例根目录下的wikiconfig.py,需改几个地方如下:
python 代码
-
- sitename = u'fallever'
-
- logo_string = u'<img src="/wiki/common/moinmoin.png" alt="MoinMoin Logo">'
-
- data_dir = 'F:/develope/tools/moin-1.6.0/instance/data'
- data_underlay_dir = 'F:/develope/tools/moin-1.6.0/instance/underlay'
-
- url_prefix_static = '/wiki'
然后重启Apache,访问http://localhost/moin 就可以看到运行的MoinMoin了。
要使用Mod_python来跑MoinMoin,除了把PythonHandler设为MoinMoin.request.request_modpython::Request.run,还有一个办法就是把模板文件夹下面的server/moinmodpy.py文件复制到Wiki实例文件夹下,把logpath的路径修改为有效路径,把Location里面的PythonHandler 改成moinmodpy就可以了。这样的好处是可以自己指定日志文件的保存地点。
仅仅知道如何使用Mod_python来部署MoinMoin是不够的。我的空间使用的是FastCgi,并且没有安装版的MoinMoin,所以我接下来要做的是:使用Fastcgi跑起MoinMoin、实现非安装版的部署方式、实现多实例并存。
DVDRip字幕修正程序桌面版
分类:
编程
|
jeff 发表于:2007-12-21 01:00:12 |
0条评论 |
不久之前写了个DVDRip字幕偏移修正程序,不过没有交互界面。后来用WxPython做了一个交互版的。截图如下:

现在使用很简单,找到原来的字幕文件,选择输出的目录,再计算好时间误差(单位为MS)点击开始修正即可。考虑下一个版本加上误差的计算功能。
DVDRip字幕偏移修正程序
分类:
编程
|
jeff 发表于:2007-12-09 20:50:43 |
0条评论 |
相信你也有过和我一样的经历:
花了九牛二虎的力气把星球大战的DVDRip下载回来,沿着Emule给的字幕连接去找,居然失败了,最后在射手网找回来的字幕都是不对板的,我的是3CD的版本,而字幕却是1CD版的。
或者,我找到了3CD的字幕,却发现该字幕与自己的版本还是有差别,字幕与声音延迟太让人难受了!
当互联网帮不上自己的时候,我知道,是时候靠自己了。于是,我用python写了一小段脚本,专门用来修正DVDRip字幕偏移。代码将贴在下面。使用方法也很简单,参考代码即可。主要是设置源字幕文件和输出文件以及一个延迟参数,延迟参数是指源字幕相对于实际情况延迟的毫秒数,如10秒则设置为10000,如果是超前了则设置为负数。
本程序只针对srt格式的字幕有效,后缀名有srt的文本格式字幕,你可以使用记事本打开来看。
srt格式的字幕是指这样的:
2
00:00:30,024 --> 00:00:35,018
很久以前 在遥远的银河系
最后,如何把1CD的版本分解成3CD呢?很简单,使用第1个CD修正后,截取余下的字幕另存一个,继续使用第二个CD修正。这个功能目前只能让安装有Python的朋友使用,目前我准备做成Web应用程序的版本,需要用的时候只需要上传一个字幕文件就OK。
python 代码
- import re
- import os
-
- delay = 0
-
-
- def str2ms(instr):
- parts = instr.split(',')
- big = parts[0].split(':')
- ms = int(big[0]) * 3600000 + int(big[1]) * 60000 + int(big[2]) * 1000 + int(parts[1])
- return ms
-
- def fix(src,l):
- '''fix 1 to 001'''
- start = 0
- ret = ''
- srcstr = str(src)
- while start < (l - len(srcstr)):
- ret += '0'
- start += 1
- ret = ret + srcstr
- return ret
-
- def ms2str(ms):
- def part1(second):
- ret = []
- ret.append(fix(second/3600,2))
- second = second - int(ret[0]) * 3600
- ret.append(fix(second/60,2))
- ret.append(fix(second - int(ret[1]) * 60,2))
- return ret
- return ','.join([':'.join(part1(ms/1000)),fix(ms%1000,3)])
-
-
- def istimeline(rawstr):
- pattern = '^([0-9]{2}):([0-5]{1}[0-9]{1}):([0-5]{1}[0-9]{1}),([0-9]{3}).*$'
- return re.search(pattern,rawstr)
-
-
- def replace(rawstr):
- times = rawstr.split('-->')
- newtimes = []
- for t in times:
- newtimes += [ms2str(str2ms(t.strip()) - delay)]
- return '%s --> %s%s' % tuple(newtimes + [os.linesep])
-
- def parse_file(f):
- ret = ''
- try:
- for line in f:
- if istimeline(line):
- ret += replace(line)
- else:
- ret += line
- finally:
- f.close()
- return ret
-
- if __name__ == '__main__':
- f = open('E:\\imcoming\\Star Wars 1.chs.srt','r')
- delay = 8500
- s = parse_file(f)
-
- nf = open('E:\\imcoming\\Star Wars 1.chs2.srt','w')
- nf.write(s)
- nf.close()