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 的严 ...
随机推荐
- POJ 1027 The Same Game(模拟)
题目链接 题意 : 一个10×15的格子,有三种颜色的球,颜色相同且在同一片内的球叫做cluster(具体解释就是,两个球颜色相同且一个球可以通过上下左右到达另一个球,则这两个球属于同一个cluste ...
- hdu 1538 A Puzzle for Pirates 博弈论
很经典的问题,思路转载自http://blog.csdn.net/ACM_cxlove?viewmode=contents 题目:这是一个经典问题,有n个海盗,分m块金子,其中他们会按一定的顺序提出自 ...
- linux下如何查看和修改系统BLOCK的大小
http://blog.163.com/drachen@126/blog/static/162607494201271435333906/ 查看os系统块的大小 [root]# /sbin/tune2 ...
- 搭建turnserver
参考文件: http://blog.csdn.net/kl222/article/details/20145423 为什么要搭建TURN服务器? 因为我们编写的sip客户端再和南瑞的sip服务器进行通 ...
- 让浏览器屏蔽js
有时候为了测试js是否做到了渐进增强,需要屏蔽下js 做法: Internet选项>>安全>>自定义级别 禁用java小程序脚本和活动脚本 有的浏览器调试器直接就有这个功能
- http://www.myexception.cn/program/767123.html
http://www.myexception.cn/program/767123.html
- MyBatis学习总结_01_MyBatis快速入门
一.Mybatis介绍 MyBatis是一个支持普通SQL查询,存储过程和高级映射的优秀持久层框架.MyBatis消除了几乎所有的JDBC代码和参数的手工设置以及对结果集的检索封装.MyBatis可以 ...
- iOS:核心动画之基本动画CABasicAnimation
基本动画,是CAPropertyAnimation的子类 属性说明: fromValue:keyPath相应属性的初始值 toValue:keyPath相应属性的结束值 动画过程说明: 随着动画的进行 ...
- 基于webpivottable做的透视表
1.绑定数据和配置: var wptConfig=<%= wptConfig%>; webPivotTable.setCsvData(<%=dataFields %>, < ...
- Hibernate —— ID的各种生成器(转)
Hibernate中,<id>元素下的可选<generator>子元素是一个Java类的名字,用来为该持久化类的实例生成惟一标示,所有的生成器都实现net.sf.hiberna ...