Python网页解析
续上篇文章,网页抓取到手之后就是解析网页了。
在Python中解析网页的库不少,我最开始使用的是BeautifulSoup,貌似这个也是Python中最知名的HTML解析库。它主要的特点就是容错性很好,能很好地处理实际生活中各种乱七八糟的网页,而且它的API也相当灵活而且丰富。
但是我在自己的正文提取项目中,逐渐无法忍受BeautifulSoup了,主要是因为下面几个原因:
- 由于BeautifulSoup 3(当前的版本)依赖于Python内建的sgmllib.py,而sgmllib.py有好些无法容忍的问题,因此在解析某些网页的时候无法得到正确的结果
 - 由于BeautifulSoup 3的上层和底层解析完全都是Python的,因此速度简直是无法容忍,有时候简直比网络还要慢
 
下面我们来一一说说上述的问题。
首先是解析上的问题,看看下面的Python代码:
from BeautifulSoup import BeautifulSoup html = u'<div class=my-css>hello</div>'
print BeautifulSoup(html).find('div')['class']
html = u'<div class=我的CSS类>hello</div>'
print BeautifulSoup(html).find('div')['class']
html = u'<div class="我的CSS类">hello</div>'
print BeautifulSoup(html).find('div')['class']
第一次print的结果my-css,第二次是啥都没有,第三次print的结果是我们期待的”我的CSS类”。
原来,sgmllib在解析属性的时候使用的正则表达式没有考虑到非ASCII字符的问题。这个问题相对好解决一些,只要我们在程序的开头导入sgmllib模块,然后修改它对应的属性正则表达式变量就可以了,就像下面这样:
import re, sgmllib sgmllib.attrfind = re.compile(r'\s*([a-zA-Z_][-.:a-zA-Z_0-9]*)(\s*=\s*(\'[^\']*\'|"[^"]*"|[^\s^\'^\"^>]*))?')
第二个问题就稍微麻烦一些了,还是看代码:
from BeautifulSoup import BeautifulSoup html = u'<a onclick="if(x>10) alert(x);" href="javascript:void(0)">hello</a>'
print BeautifulSoup(html).find('a').attrs
打印的结果是[(u'onclick', u'if(x>10) alert(x);')]。
显然,a元素的href属性被弄丢了。原因就是sgmllib库在解析属性的时候一旦遇到了>等特殊符号就会结束属性的解析。要解决这个问题,只能修改sgmllib中SGMLParser的parse_starttag方法,找到292行,即k = match.end(0)这一行,添加下面的代码即可:
if k > j:
match = endbracket.search(rawdata, k+1)
if not match: return -1
j = match.start(0)
至于BeautifulSoup速度慢的问题,那就无法通过简单的代码修改搞定了,只能换一个HTML解析库。实际上,我现在使用的是lxml,它是基于C语言开发的libxml2与libxslt库的。经我个人测试,速度比BeautifulSoup 3平均要快10倍。
使用lxml库解析HTML非常简单,兼容性也非常高,大部分实际网站上的网页都可以正确解析。而且lxml使用非常方便的XPath语法进行元素查询,它支持string-length、count等XPath 1.0函数(参见XPath and XSLT with lxml)。不过2.0的函数,如序列操作的函数就不行了,这需要底层libxml2和libxslt库的开发者升级这两个库,添加对XPath 2.0函数的支持才行。
假设我们得到了unicode类型的网页,想要获取所有带href属性的链接,代码如下:
import lxml.html dom = lxml.html.fromstring(html)
all_links = dom.xpath('.//a[@href]')
使用lxml也有一些注意事项:
- 不能向
lxml.html.fromstring传递长度为0的字符串,不然会抛出解析异常。需要事先判断一下,如果长度为零,可以传入<html></html>作为内容 - 某些网页不知什么原因,网页内容里有\x00,也就是ASCII编码为0的字符。由于lxml底层是C语言开发的,\x00在C语言里表示字符串结尾,因此需要将这些字符替换一下(
html.replace('\x00', '')) - 一般情况下,为了减少编码猜测的错误,我们传递给
lxml.html.fromstring的网页字符串都是unicode字符串,也就是经过编码检测和decode后的字符串。但是如果网页是<?xml开头,而且有编码设定的(如<?xml version="1.0" encoding="UTF-8" ?>这样)的,也就是说是一个XML包裹的HTML,则我们必须将原始字符串传递给lxml,不然lxml也会报异常,因为针对这种文档,lxml会尝试使用自己的解码机制来做 - lxml兼容性是有限的,没有主流浏览器那么宽容。因此,有少部分浏览器能大致正常显示的网页,lxml仍然无法解析出来
 
上面几个问题中,第一二个问题好解决,但是第三个问题最麻烦。因为你事先是不知道这个网页是否是带编码的XML文档的,只有出了ValueError异常才行,但是lxml使用ValueError报告一切错误,因此为了精确一点,你要分析异常的字符串信息才知道是否是这个问题导致的解析异常,如果是的话,则将未解码的网页内容再给lxml传一次,如果不是就报错。
第四个问题很烦人,但是说句实话,我们也做不了太多工作,lxml的兼容性已经相当好了,可行的方法是放弃掉这些网页。要么就换一个工具,不使用lxml做网页解析,但是在Python库里很难找到比lxml更好的HTML解析库。
用lxml解析HTML代码的示例,可以参考正文提取程序里的__create_dom方法。
另外还有一个悬疑问题,那就是在解析网页多,内存压力大的情况下,lxml似乎会出现内存溢出的问题。
我有一个程序,每天都要扫描上万个网页,解析四五千个网页。大概每过一个月或一个半月,这个程序就会导致服务器内存全满,不管是多少内存,全部吃光。我以前一直以为可能是底层代码的重入性问题(因为我用多线程来做的程序),但是后来换成多进程和单线程模式都会出现这个问题,才跟踪到出错的代码就是在调用lxml.html.fromstring的时候溢出的。
不过这个bug超级难以重现,而且我有很多程序都会不停调用lxml(有的每天,有的每小时,有的每次会解析几百个网页)来做HTML解析的工作,可是只有这个程序会偶尔出现溢出情况,太郁闷了。
网上也有类似的报告,但是迄今仍然无法有效重现和确定这个bug。因此我只能写一个脚本限制python程序的占用内存数,然后通过这个脚本来调用python程序,像这样:
#!/bin/bash ulimit -m 1536000 -v 1536000
python my-prog.py
解析网页还有一些其他要做的工作,例如将非标准(网站自定义的)的HTML标签转化为span或者div,这样才能被识别为正文。剩下的就是调试的工作了。
另外,就在本文写作的时候,BeautifulSoup 4将要发布了,它承诺可以支持Python内建库、lxml与html5lib等不同的HTML解析引擎来进行网页解析,也许新版本的BeautifulSoup也是一个不错的选择。
Python网页解析的更多相关文章
- Python网页解析库:用requests-html爬取网页
		
Python网页解析库:用requests-html爬取网页 1. 开始 Python 中可以进行网页解析的库有很多,常见的有 BeautifulSoup 和 lxml 等.在网上玩爬虫的文章通常都是 ...
 - Python 网页解析器
		
Python 有几种网页解析器? 1. 正则表达式 2.html.parser (Python自动) 3.BeautifulSoup(第三方)(功能比较强大) 是一个HTML/XML的解析器 4.lx ...
 - 转:Python网页解析:BeautifulSoup vs lxml.html
		
转自:http://www.cnblogs.com/rzhang/archive/2011/12/29/python-html-parsing.html Python里常用的网页解析库有Beautif ...
 - ubuntu下的python网页解析库的安装——lxml, Beautiful Soup, pyquery, tesserocr
		
lxml 的安装(xpath) pip3 install lxml 可能会缺少以下依赖: sudo apt-get install -y python3-dev build-e ssential li ...
 - python 之网页解析器
		
一.什么是网页解析器 1.网页解析器名词解释 首先让我们来了解下,什么是网页解析器,简单的说就是用来解析html网页的工具,准确的说:它是一个HTML网页信息提取工具,就是从html网页中解析提取出“ ...
 - 【Python爬虫】BeautifulSoup网页解析库
		
BeautifulSoup 网页解析库 阅读目录 初识Beautiful Soup Beautiful Soup库的4种解析器 Beautiful Soup类的基本元素 基本使用 标签选择器 节点操作 ...
 - python爬虫网页解析之lxml模块
		
08.06自我总结 python爬虫网页解析之lxml模块 一.模块的安装 windows系统下的安装: 方法一:pip3 install lxml 方法二:下载对应系统版本的wheel文件:http ...
 - python爬虫网页解析之parsel模块
		
08.06自我总结 python爬虫网页解析之parsel模块 一.parsel模块安装 官网链接https://pypi.org/project/parsel/1.0.2/ pip install ...
 - Python的网页解析库-PyQuery
		
PyQuery库也是一个非常强大又灵活的网页解析库,如果你有前端开发经验的,都应该接触过jQuery,那么PyQuery就是你非常绝佳的选择,PyQuery 是 Python 仿照 jQuery 的严 ...
 
随机推荐
- java 关于mysql
			
com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: No operations allowed after co ...
 - js的in运算符与instanceof运算符
			
in运算符:希望他的左操作数是一个字符串或可以转换为字符串,希望他的右操作数是一个对象.如果右操作数的对象拥有一个名为左操作数值的属性名,那么表达式返回true. var point= {x:1,y: ...
 - 解决安装rpm时lib冲突:libstdc++-2-libc6.1-1-2.9.0.so from install of compat-libstdc++-7.3-2.96.118 conflicts with file from ...
			
sudo rpm -ivh xxx.rpm -aid --force [oracle@localhost Oracle]$ .i386.rpm compat-libstdc++-devel-.i386 ...
 - 让ie6也支持max-width,和max-height实现图片等比例缩放
			
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
 - 扩展 delphi 线程 使之传递参数.(给匿名线程增加参数)
			
新delphi的线程TThread有了CreateAnonymousThread方法,如果再为它加一个可传递的参数不就更好了吗?代码如下: TAnonymousThreadX<T> = c ...
 - [c/c++]指针数组 pk 数组指针
			
首先强调 指针数组归根结底是个数组:数组指针归根结底是个指针. 数组指针 以int (*int)[10]为例 ()的优先级大于[],因此首先它是一个指针,它指向一个数组,数组的维数是10.因此数组指针 ...
 - ubuntu添加环境变量【原创】
			
cpro放到环境变量里,并且加上可执行权限如果在本地添加,则需mkdir ~/bin mv cpro /home/zhangbh/bin/vi ~/.bashrcexport PATH=/usr/lo ...
 - mysql高可用方案比较
			
详见:High Availability Database Tools http://www.acquia.com/blog/high-availability-database-tools
 - 甲骨文推出MySQL Fabric,简化MySQL的高可用性与可扩展性
			
北京,2014年5月28日——为了满足当下对Web及云应用需求,甲骨文宣布推出MySQL Fabric.MySQL Fabric是一款可简化管理MySQL数据库群的整合式系统.该产品通过故障检测和故障 ...
 - jsonp获取服务器数据的方式
			
jsonp获取服务器的数据,有两种 一,跨域 二,不跨域 如果跨域 js的写法有两种 1, <script type="text/javascript"> $(func ...