最近写网络爬虫多了,总结出一些心得,记录以备忘记.
网络爬虫的要求有几点:
一. 一个好的网页解释器,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参考一些行者给出的建议.