python渐进---html和json解析

原载于

https://mp.weixin.qq.com/s/uVlcqRFo_QngoQQ7rRhVfA


从网络中取得一个文件后,就进入到了处理文件的阶段了。
从网络取回的字节流,可能会是乱码。这个问题可能由两个原因产生。

一个是在请求的时候,在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在遇到<tag>类型的标签会调用handle_starttag(tag,attrs)方法,比如说碰到<a href='test'>会调用handle_starttag,并且参数tag=a,而attrs是它的属性(key,value)对;
遇到</tag>类型的标签会调用handle_endtag(tag);
遇到<img/>类型的同时是开始标签也是结束标签的,会调用handle_startendtag(tag,attrs);
遇到 <>data</>类型的数据会调用handle_data(data);
遇到&gt;类型的转义字符会调用handle_entityref(name);
遇到&#62类型的转义会调用handle_charref(name);
遇到<--! -->类型的注释数据会调用handle_comment(data)。
一般而言,重写这些方法,就可以获取到每个标签和数据的内容了。

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

import urllib2
import traceback
from HTMLParser import HTMLParser

class 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 urllib2
import traceback
from HTMLParser import HTMLParser

class 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 urllib2
import traceback
import json

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://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渐进的系列就先告一段落了。以后的更新内容和时间均未定。休息一下。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值