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 的严 ...
随机推荐
- springmvc web应用程序 java
搭建普通 springmvc 1.如图建立相关文件 建立在WEB-INF下比较安全,不能直接访问资源. 2.建立Controller控制器,如图 3.需要导入的jar包 commons-logging ...
- hdu5593/ZYB's Tree 树形dp
ZYB's Tree Memory Limit: 131072/131072 K (Java/Others) 问题描述 ZYBZYB有一颗NN个节点的树,现在他希望你对于每一个点,求出离每个点距 ...
- JDBC第四次学习
传智播客李勇老师的JDBC系列学习终于接近尾声了,好开心,能学到这么多的东西,还不赶快记录下来,留待以后回味! 如何使用开源项目DBCP(实际项目中常用) 主要分为三个步骤: 使用DBCP必须用 ...
- Java IO(三)
File File类的常见方法: 1.创建. boolean createNewFile():在指定位置创建文件,如果该文件已经存在,则不创建,返回false.和输出流不一样,输出流对象一建立就创建文 ...
- 创建DB2数据库时报错--SQL1052N 数据库路径不存在(Windows)(转载)
用DB2 v9.7新建数据库的时候,默认路径为:D:\ 把缺省路径“写的是D:\XXX(此目录存在),新建时提示如下:SQL1052N 数据库路径 "D:\XXX" 不存在.如下: ...
- Ibm-jQuery教程学习笔记
一.概述 1.虽然 jQuery 本身并非一门新的语言.但是,学习其语法有助于我们熟练.灵活地使用它.回顾下我们熟悉的 CSS 语法,不难发现 jQuery 的语法与 CSS 有相似之处. jQuer ...
- c# 计算1-100之间的所有质数(素数)的和
求1-100之间的所有质数(素数)的和: 所以先必须知道什么是质数? 一个数是不是素数 “素数”是指除了能被1和自身整除外,不能被任何其它整数整除的自然数. 然后知道有哪些是质数: 记忆的方法: ...
- JS身份证真实性校验(二)
var Wi = [ 7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2, 1 ]; // 加权因子 var ValideCode = [ 1, 0 ...
- IntelliJ IDEA12.1.1 使用 相关记录
最近在切换ide 从eclipse平台上转移到 IDEA 其它的都比较容易,让我烦恼的是使用idea 发布工程,使用tomcat 调试 热部署.期间碰到各种问题,纠结好久了:终于成功完成了,特意记 ...
- 查看linux系统常用的命令,Linux查看系统配置常用命令
一.linux CPU大小 cat /proc/cpuinfo |grep "model name" && cat /proc/cpuinfo |grep &qu ...