python渐进-html和json解析

从网络中取得一个文件后,就进入到了处理文件的阶段了。

从网络取回的字节流,可能会是乱码。这个问题可能由两个原因产生。

一个是在请求的时候,在http头中加入了accept-encoding域,比如说加入了“accept-encoding:gzip,deflate,sdch,br”。这样服务器就认为你可以接受压缩文件,于是给你返回了一个压缩的字节流。此时就需要根据对应的解压算法来对字节流进行解压。否则这一串字节流就没法使用。

另一个原因是网页的编码和程序默认的不对应。比如网页是gb2312,而程序默认为utf-8格式的编码,这样也会导致处理失败。

对于第一个问题,不带accept-encoding域访问就可以避免压缩。

而第二个问题,其实就是一个字符编码的问题,在之前讲字符的时候已经涉及过了。这里的问题主要是怎么获取网页字符编码的问题。之前讲过使用urllib2拿到返回的网页,可以通过f.info()取得http协议返回头。而这个http头的content-type域,就包含了字符编码信息,这个域的值可能是长这样的“text/html; charset=utf-8”。所以可以通过处理这个字符串的方式,来获取到charset=后面的那个字符编码信息,整个处理的代码可能是这样:

contenttype=f.info()['content-type'] contentypelist = contenttype.split(';') for ct in contentypelist: if ct.find('charset=')>=0: ctl=ct.split('=') if ctl[1] != '': codingtype = ctl[1] print(codingtype) try: if codingtype!='utf-8': data=data.decode(codingtype,'ignore') data=data.encode(utf-8) print('utf-8 encode') except Exception,e: print('decode page failed!!')

上面的代码获取了codingtype之后,如果发现不是utf-8编码,就把它解码后再编码成utf-8格式。这样就保证了data最后是utf-8格式,和程序默认编码一致。

把字符压缩和字符编码问题解决了之后,就是一个文本解析的问题了。而从网络中取得的文件,绝大部份是html文件或者是json对象。

python的基本库中,使用htmlparser来进行html文件的解析。使用json类来进行json文件的解析。

18.1 htmlparser类

htmlparser类提供了很多的方法供重写,只要htmlparser类碰到了相应的标签,就会调用相应的方法。

htmlparser在遇到类型的标签会调用handle_starttag(tag,attrs)方法,比如说碰到会调用handle_starttag,并且参数tag=a,而attrs是它的属性(key,value)对;

遇到类型的标签会调用handle_endtag(tag);

遇到类型的同时是开始标签也是结束标签的,会调用handle_startendtag(tag,attrs);

遇到 data类型的数据会调用handle_data(data);

遇到>类型的转义字符会调用handle_entityref(name);

遇到>类型的转义会调用handle_charref(name);

遇到类型的注释数据会调用handle_comment(data)。

一般而言,重写这些方法,就可以获取到每个标签和数据的内容了。

htmlparser只关注当下的标签和数据,不会记住当前标签的状态,更不会知道标签的嵌套。如果想要通过路径的形式来定位某个标签,需要在进入标签的时候加上本标签,同时在退出标签的时候减去本标签。比如说进入标签就把本标签加上变成'/html',再进入标签,又把标签加上变成'/html/head',就这样一层一层下去。而退出标签的时候把'/head'从路径中拿掉,又变成了'/html',接着进入标签又变成了'/html/body'。这样就可以知道当前的标签路径是什么了。

一个完整的自定义解析类可能是这样:

import urllib2import tracebackfrom HTMLParser import HTMLParserclass wxhtml(HTMLParser): def __init__(self): HTMLParser.__init__(self) self.sd='share data' self.tagpath="" def handle_starttag(self,tag,attrs): self.tagpath=self.tagpath+'/'+tag print('enter '+self.tagpath) for attr in attrs: print(attr) def handle_endtag(self,tag): i=len('/'+tag) self.tagpath=self.tagpath[:-i] print('exit '+self.tagpath) def handle_startendtag(self,tag,attrs): self.tagpath=self.tagpath+'/'+tag print('startend '+self.tagpath) for attr in attrs: print(attr) i=len('/'+tag) self.tagpath=self.tagpath[:-i] def handle_data(self,data): print('data '+data) def handle_entityref(self,name): print('entityref '+name) def handle_charref(self,name): print('charref '+name) def handle_comment(self,comment): print('comment '+comment)

要注意的是,如果要重写__init__()方法的话,就需要调用父类的__init__()。不然会没有办法工作。在上面的代码中,__init__()方法初始化了两个实例变量,一个是记录标签路径的self.tagpath,一个是和外部代码共享的变量self.sd。有了self.sd,就可以在网页解析完毕之后,从self.sd中获取自己想要的内容。

定义好了这个类之后,使用htmlparser.feed()可以启动一个html的解析。解析完毕之后使用close()来关闭一个html句柄。示例代码如下:

wxparser=wxhtml() wxparser.feed(data) sd=wxparser.sd

使用微信的主页作为演示对象,整体的代码如下:

import urllib2import tracebackfrom HTMLParser import HTMLParserclass wxhtml(HTMLParser): def __init__(self): HTMLParser.__init__(self) self.sd='share data' self.tagpath="" def handle_starttag(self,tag,attrs): self.tagpath=self.tagpath+'/'+tag print('enter '+self.tagpath) for attr in attrs: print(attr) def handle_endtag(self,tag): i=len('/'+tag) self.tagpath=self.tagpath[:-i] print('exit '+self.tagpath) def handle_startendtag(self,tag,attrs): self.tagpath=self.tagpath+'/'+tag print('startend '+self.tagpath) for attr in attrs: print(attr) i=len('/'+tag) self.tagpath=self.tagpath[:-i] def handle_data(self,data): print('data '+data) def handle_entityref(self,name): print('entityref '+name) def handle_charref(self,name): print('charref '+name) def handle_comment(self,comment): print('comment '+comment)httpheaders=dict()httpheaders["Connection"]="keep-alive"httpheaders["User-Agent"]="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36"httpheaders["Accept"]="*/*"httpheaders["Accept-Language"]="zh-CN,zh;q=0.8"try: ustr='http://weixin.qq.com' rq=urllib2.Request(ustr,headers=httpheaders) f=urllib2.urlopen(rq,timeout=15) data=f.read() contenttype=f.info()['content-type'] contentypelist = contenttype.split(';') for ct in contentypelist: if ct.find('charset=')>=0: ctl=ct.split('=') if ctl[1] != '': codingtype = ctl[1] print(codingtype) try: if codingtype!='utf-8': data=data.decode(codingtype,'ignore') data=data.encode(utf-8) print('utf-8 encode') except Exception,e: print('decode page failed!!') wxparser=wxhtml() wxparser.feed(data) sd=wxparser.sd print(sd) wxparser.close()except: print(traceback.format_exc())finally: f.close()

因为返回太长了,所以就不贴了。这段代码是所有的标签和数据都打印出来了,如果只是想要其中某个路径下面的数据,可以在获取数据的时候判断一下当前的self.tagpath是否自己想要的,再打印出来。

18.2 json数据的解析

json格式的数据解析比html格式的要简单许多。不过要注意的是,有时候服务器返回的直接就是一个json格式的序列化的数据流,有时候会把一个json格式的数据流包含在类似于'jsoncallback('和')'的字符串之间,有时候会包含在类似于'var=([{'和‘}])’的字符串之间。这都是为了前端代码好直接运行设置的。如果要做解析的话,就必须把前后的无关紧要的字符全部去掉,还原一个真正的json格式,这样才可以解析成功。

因为json默认是utf8编码。所以在解析之前要把字节流的编码调整到utf-8。

一般情况下,使用json.loads()来载入数据。这个方法会把json格式的数据流转成python里面对应的list、dict、字符串、数字等格式。接着就可以按照python的数据结构来访问数据内容了

使用json.dumps()可以把一个python的数据对象变成json格式序列化数据流。

以下的代码从凤凰财经的cgi当中读取5分钟线,并且把返回的json格式的数据打印出来。

import urllib2import tracebackimport jsonhttpheaders=dict()httpheaders["Connection"]="keep-alive"httpheaders["User-Agent"]="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36"httpheaders["Accept"]="*/*"httpheaders["Accept-Language"]="zh-CN,zh;q=0.8"try: ustr='http://api.finance.ifeng.com/akmin/?scode=sh600000&type=5' rq=urllib2.Request(ustr,headers=httpheaders) f=urllib2.urlopen(rq,timeout=15) data=f.read() codingtype='utf-8' contenttype=f.info()['content-type'] contentypelist = contenttype.split(';') for ct in contentypelist: if ct.find('charset=')>=0: ctl=ct.split('=') if ctl[1] != '': codingtype = ctl[1] print(codingtype) try: if codingtype!='utf-8': data=data.decode(codingtype,'ignore') data=data.encode(utf-8) print('utf-8 encode') except Exception,e: print('decode page failed!!') d=json.loads(data) stockdatalist=d['record'] for stockdata in stocklist: print(stock) #print(d)except: print(traceback.format_exc())finally: f.close()

凤凰财经的5分钟线cgi返回的数据是一个字典,并且只有一个键值 record。这个键值对应的就是一个装满了五分钟线的数据列表,列表中的每一项就是一个五分钟k线的数据。使用json.loads()方法很快就把这个数据流转化为python的字典了,接着就可以访问k线数据了。

更新到这里,基本上python的基本知识点都覆盖了,明天把时间相关的内容整理一下。python渐进的系列就先告一段落了。以后的更新内容和时间均未定。休息一下。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180711G1XFC000?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励