博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Python网页解析
阅读量:6421 次
发布时间:2019-06-23

本文共 3251 字,大约阅读时间需要 10 分钟。

续上篇文章,网页抓取到手之后就是解析网页了。

在Python中解析网页的库不少,我最开始使用的是,貌似这个也是Python中最知名的HTML解析库。它主要的特点就是容错性很好,能很好地处理实际生活中各种乱七八糟的网页,而且它的API也相当灵活而且丰富。

但是我在自己的中,逐渐无法忍受BeautifulSoup了,主要是因为下面几个原因:

  • 由于BeautifulSoup 3(当前的版本)依赖于Python内建的,而sgmllib.py有好些无法容忍的问题,因此在解析某些网页的时候无法得到正确的结果
  • 由于BeautifulSoup 3的上层和底层解析完全都是Python的,因此速度简直是无法容忍,有时候简直比网络还要慢

下面我们来一一说说上述的问题。

首先是解析上的问题,看看下面的Python代码:

from BeautifulSoup import BeautifulSoup html = u'
hello
'print BeautifulSoup(html).find('div')['class']html = u'
hello
'print BeautifulSoup(html).find('div')['class']html = u'
hello
'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'hello'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解析库。实际上,我现在使用的是,它是基于C语言开发的与库的。经我个人测试,速度比BeautifulSoup 3平均要快10倍。

使用lxml库解析HTML非常简单,兼容性也非常高,大部分实际网站上的网页都可以正确解析。而且lxml使用非常方便的XPath语法进行元素查询,它支持string-length、count等XPath 1.0函数(参见)。不过2.0的函数,如序列操作的函数就不行了,这需要底层libxml2和libxslt库的开发者升级这两个库,添加对XPath 2.0函数的支持才行。

假设我们得到了unicode类型的网页,想要获取所有带href属性的链接,代码如下:

import lxml.html dom = lxml.html.fromstring(html)all_links = dom.xpath('.//a[@href]')

 

使用lxml也有一些注意事项:

  1. 不能向lxml.html.fromstring传递长度为0的字符串,不然会抛出解析异常。需要事先判断一下,如果长度为零,可以传入<html></html>作为内容
  2. 某些网页不知什么原因,网页内容里有\x00,也就是ASCII编码为0的字符。由于lxml底层是C语言开发的,\x00在C语言里表示字符串结尾,因此需要将这些字符替换一下(html.replace('\x00', ''))
  3. 一般情况下,为了减少编码猜测的错误,我们传递给lxml.html.fromstring的网页字符串都是unicode字符串,也就是经过编码检测和decode后的字符串。但是如果网页是<?xml开头,而且有编码设定的(如<?xml version="1.0" encoding="UTF-8" ?>这样)的,也就是说是一个XML包裹的HTML,则我们必须将原始字符串传递给lxml,不然lxml也会报异常,因为针对这种文档,lxml会尝试使用自己的解码机制来做
  4. 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 1536000python my-prog.py

解析网页还有一些其他要做的工作,例如将非标准(网站自定义的)的HTML标签转化为span或者div,这样才能被识别为正文。剩下的就是调试的工作了。

另外,就在本文写作的时候,将要发布了,它承诺可以支持Python内建库、lxml与html5lib等不同的HTML解析引擎来进行网页解析,也许新版本的BeautifulSoup也是一个不错的选择。

转载于:https://www.cnblogs.com/wpjamer/p/4099360.html

你可能感兴趣的文章
VS Code折腾记 - (3) 多图解VSCode基础功能
查看>>
再不懂区块链,你就OUT了!
查看>>
教你玩转自定义View—手撸一个倒计时控件如此简单
查看>>
『翻译』Node.js 调试
查看>>
我的iOS开发之路总结(更新啦~)
查看>>
Java NIO之拥抱Path和Files
查看>>
微信原图泄露的只能是 Exif ,你的隐私不在这!!!
查看>>
微信小程序教学第三章(含视频):小程序中级实战教程:列表篇-页面逻辑处理...
查看>>
页面间通信与数据共享解决方案简析
查看>>
Swift 中 Substrings 与 String
查看>>
作为一个开源软件的作者是一种什么样的感受?
查看>>
移动端适配知识你到底知多少
查看>>
Java基础笔记16
查看>>
TiDB 在 G7 的实践和未来
查看>>
重新认识javascript对象(三)——原型及原型链
查看>>
小学生学“数学”
查看>>
【Vue】组件使用之参数校验
查看>>
FastDFS蛋疼的集群和负载均衡(十七)之解决LVS+Keepalived遇到的问题
查看>>
深入剖析Redis系列(二) - Redis哨兵模式与高可用集群
查看>>
上班第一天的BUG居然是chrome翻译功能导致的
查看>>