ExcelHome技术论坛

 找回密码
 免费注册

QQ登录

只需一步,快速开始

快捷登录

搜索
EH技术汇-专业的职场技能充电站 妙哉!函数段子手趣味讲函数 Excel服务器-会Excel,做管理系统 Excel Home精品图文教程库
HR薪酬管理数字化实战 Excel 2021函数公式学习大典 Excel数据透视表实战秘技 打造核心竞争力的职场宝典
300集Office 2010微视频教程 数据工作者的案头书 免费直播课集锦 ExcelHome出品 - VBA代码宝免费下载
用ChatGPT与VBA一键搞定Excel WPS表格从入门到精通 Excel VBA经典代码实践指南
楼主: liu-aguang

[原创] 网页数据采集---网页文档解析篇(json/html/xml)

  [复制链接]

TA的精华主题

TA的得分主题

发表于 2016-9-27 13:29 | 显示全部楼层
[广告] Excel易用宝 - 提升Excel的操作效率 · Excel / WPS表格插件       ★免费下载 ★       ★ 使用帮助
本帖已被收录到知识树中,索引项:网页交互
请教老师,我想从0学习 网页数据采集,需要先看你的哪个帖子?

TA的精华主题

TA的得分主题

 楼主| 发表于 2016-9-27 17:25 | 显示全部楼层
小花鹿 发表于 2016-9-27 13:29
请教老师,我想从0学习 网页数据采集,需要先看你的哪个帖子?

网页采集涉及的科目很多,有时不知道“开头”在哪里。你可以先看看论坛上的一些帖子,能懂多少是多少,学会简单网页的采集,逐渐你就会知道该去学习哪些内容了。
网页采集大体上就两个步骤:网页请求和网页解析。网页请求论坛上有大帖子,你可以去学习模仿。我没有写过这方面的帖子。

评分

1

查看全部评分

TA的精华主题

TA的得分主题

发表于 2016-9-27 17:44 | 显示全部楼层
老师终于写html这方面的教程了,由于html解析json没有32位和64位的区别,一直想学但却不得要领,通过翻阅您的帖子对html多少了解一点,但却不能灵活运用。老师的大作必须要顶,支持老师继续写下去

TA的精华主题

TA的得分主题

发表于 2016-9-27 18:54 | 显示全部楼层

TA的精华主题

TA的得分主题

发表于 2016-9-27 20:28 | 显示全部楼层
liu-aguang 发表于 2016-9-27 17:25
网页采集涉及的科目很多,有时不知道“开头”在哪里。你可以先看看论坛上的一些帖子,能懂多少是多少,学 ...

谢谢老师,我的正则就是跟你学的,而且学得很辛苦,不过现在总算熬过来了,对正则有点开窍了,不知道什么时候能入网页数据采集的门

TA的精华主题

TA的得分主题

 楼主| 发表于 2016-9-28 08:56 | 显示全部楼层
[广告] VBA代码宝 - VBA编程加强工具 · VBA代码随查随用  · 内置多项VBA编程加强工具       ★ 免费下载 ★      ★使用手册
本帖最后由 liu-aguang 于 2016-10-29 07:56 编辑

二. Json与Json内容解析

1. 什么是Json?
设想一个显示及时股价的网页,股价变化是频繁的。假如股价每次变动服务器都向客户端发送一个完整的网页,显然通信流量会是很大,并且速度也会受影响。解决的办法是: 显示股价的网页中固定不变的内容设计为单独网页;股价数据也单独为一个网页。这样每次股价变化服务器只需要传输变化的股价数据。当股价数据到达客户端后,由事先编制好的javaScript脚本解析股价文本,把数据写入显示股价的网页中。这个单独存放股价的网页文件一般就是Json文件,其扩展名为.json。
Json是一个纯文本文件,唯一功能就是用来存储,传输数据。由于是纯文本且格式简单,所以广泛应用于各种平台。
为了便于JavaScript脚本编程解析, Json数据的存放格式完全按JavaScript数组或对象格式设计.一个完整的Json文本内容, 它有时简单到仅是一个数组或对象形式,再复杂也不过是数组或对象自身嵌套或相互嵌套.比如:

用数组存放一组名单的Josn:
[“张三”,”李四”,”王五”,”孙六”]

用对象存放一个人相关信息的Json:
{“name”:”娇娇”,”sex”:”女”,”age”:18,”address”:”娘家”}
说明: 与JavaScript对象唯一不同处是,键名(名称)都加了引号.

例1:
Sub Json1()
    Set oHTML = CreateObject("htmlfile")
    Set oWindow = oHTML.parentWindow
    Set http = CreateObject("Msxml2.XMLHTTP")
    http.Open "GET", "http://pv.sohu.com/cityjson", False
    http.send
    strHtml = http.responseText
    'oWindow.clipboardData.SetData "text", strHtml
    '得到 var returnCitySN = {"cip": "218.72.14.14", "cid": "330100", "cname": "浙江省杭州市"}; 这就一个json文本。
    oWindow.execScript strHtml   
    MsgBox oWindow.returnCitySN.cip
End Sub
讨论: json文本内容strHtml本身就是一条创建对象语句. 用execScript解析后,即可读取对象returnCitySn中的各项值.

下面的例2-4返回的json数据是:

{"work":4,"fans":281,"art":4,"album":1,"fortune":351702,"article":0,"workVideo":1,"care":195,"workAudio":3,"view":1313023,"playlist":0,"listen":288237,"collect":1}

例2:
Sub Json2()
    Set html = CreateObject("htmlfile")
    Set Window = html.parentWindow
    Set http = CreateObject("Msxml2.XMLHTTP")
    http.Open "GET", "http://kzone.kuwo.cn/mlog/UserVal?uid=1237357&from=profile", False
    http.send
    strHtml = http.responseText ' 得到数据
    ‘Window.clipboardData.SetData "text", strHtml '写入剪贴板
    Window.execScript "var js= " & strHtml  ' 改写成对象创建语句
    Set kuwo = Window.js ' 获取解析后的对象
    MsgBox "访问量:" & kuwo.view
End Sub讨论:
(1)在json中,某键值代表什么意义,这是代码编制者定义的,可以结合网页数据比对确定;
(2)如json不是对象创建格式, 需要改写, 如本例.

2. 遍历对象或数组的方法
例3 遍历提取部分数据的技巧
Sub Json3()
    Set html = CreateObject("htmlfile")
    Set Window = html.parentWindow
    Set http = CreateObject("Msxml2.XMLHTTP")
    http.Open "GET", "http://kzone.kuwo.cn/mlog/UserVal?uid=1237357&from=profile", False
    http.send
    strHtml = http.responseText ' 得到数据
    Window.execScript "var js= " & strHtml  ' 解析 json
    ar = Array("work", "fans", "fortune", "view", "listen") '数组列出需要提取的项目
    For Each k In ar
        MsgBox Window.eval("js." & k)
    Next
End Sub

例4 遍历全部数据的技巧
Sub Json4A()
    Set html = CreateObject("htmlfile")
    Set Window = html.parentWindow
    Set http = CreateObject("Msxml2.XMLHTTP")
    http.Open "GET", "http://kzone.kuwo.cn/mlog/UserVal?uid=1237357&from=profile", False
    http.send
    strHtml = http.responseText ' 得到数据
    Window.execScript "var js= " & strHtml & ";s='';for(k in js){s=s+' '+k}"  'for语句得到由空格分隔的键名字符串对象s
    ar = Split(Trim(Window.s), " ")  '注意字符串s前有一个空格
    For Each k In ar
        MsgBox Window.eval("js." & k)
    Next
End Sub
讨论:
(1)语句s=s+' '+k, 用VBA语句的说法是把键名用空格(' ')连接起来。当然你也可以用其它联结字符。
(2)在VBA中,如果不事先定义s,那么s默认为空字符;但在JavaScript中必然显式定义它为空字符(s=’’)。你试试不定义会发生什么。

Sub Json4B()
    Set html = CreateObject("htmlfile")
    Set Window = html.parentWindow
    Set http = CreateObject("Msxml2.XMLHTTP")
    http.Open "GET", "http://kzone.kuwo.cn/mlog/UserVal?uid=1237357&from=profile", False
    http.send
    strHtml = http.responseText ' 得到数据
    Window.execScript "var js= " & strHtml & ";a=[];for(k in js){a.push(k)}"  'for语句得到键名数组a
    ar = Split(Window.a, ",")  '数组a到VBA环境中自动转换为由逗号分隔的字符串
    For Each k In ar
        MsgBox Window.eval("js." & k)
    Next
End Sub
讨论:
(1) 我们定义了一个空数组a,然后用数组的方法push将循环得到的k值逐一存在入数组a中。
(2) javaScript的数组在VBA环境中返回值时,会自动转换为由逗号分隔的字符串。

Sub Json4C()
    Set html = CreateObject("htmlfile")
    Set Window = html.parentWindow
    Set http = CreateObject("Msxml2.XMLHTTP")
    http.Open "GET", "http://kzone.kuwo.cn/mlog/UserVal?uid=1237357&from=profile", False
    http.send
    strHtml = http.responseText ' 得到数据
    Window.execScript "var js= " & strHtml & ";a=[];for(k in js){a.push(js[k])}"  'for语句得到键值数组a
    ar = Split(Window.a, ",")
    For Each k In ar
        MsgBox k
    Next
End Sub

Sub Json4D()
    Set html = CreateObject("htmlfile")
    Set Window = html.parentWindow
    Set http = CreateObject("Msxml2.XMLHTTP")
    http.Open "GET", "http://kzone.kuwo.cn/mlog/UserVal?uid=1237357&from=profile", False
    http.send
    strHtml = http.responseText ' 得到数据
    Window.execScript "var js= " & strHtml & ";a=[];for(k in js){a.push(js[k])};s=a.join('@')"  'join把数组a转换为“@”分隔的字符串s
    ar = Split(Window.s, "@")
    For Each k In ar
        MsgBox k
    Next
End Sub
讨论:有时可能内容本身就包含逗号,如果用上面数组方法在VBA中返回字符串,我们不方便进一步处理。这时我们可以用javaScript的join方法把数组转换为由指定的分隔符的字符串。

3. Jsonp解析方法
还有一种Json文件称之为Jsonp. 它的数据存放格式与普通Json不同之处是, 它把Json文本内容作为一个函数参数存放. 就象这个样子:  Callback (json)
这里有一个存入电话号码信息的简单Jsonp实例(忘记了来自哪个网站):

cb({mobile:'13012345456',province:'重庆',isp:'中国联通',stock:'1',amount:'10000',maxprice:'0',minprice:'0'});
可以看到在结构体cb(…)的括号内就是一个普通的Json. 下面给出其解析方法:

Sub jsonp1()
    Set oDom = CreateObject("htmlfile")
    Set oWindow = oDom.parentWindow
    strHtml = "cb({mobile:'13012345456',province:'重庆',isp:'中国联通',stock:'1',amount:'10000',maxprice:'0',minprice:'0'});"
    oWindow.execScript "function cb(o){ js=o };"
    oWindow.execScript strHtml
    MsgBox oWindow.js.mobile
End Sub
讨论:
(1) 我们构建了一个名为cb的函数: function cb(o){js=o}. 函数名与jsonp文档内的名字一样;
(2) 这个函数有一个参数o,函数的功能是将传入的参数赋值给js;参数o可以是任意类型的。
(3) 当execScript解析jsonp(即变量strHtml)时,实际上是在执行函数cb(o).这里是把jsonp内的json部分作为参数传递给js, 最后结果是:
  js={mobile:'13012345456',province:'重庆',isp:'中国联通',stock:'1',amount:'10000',maxprice:'0',minprice:'0'}
  这是一个标准的以对象形式存在的Json. 可按常规方法访问各项值.



评分

3

查看全部评分

TA的精华主题

TA的得分主题

 楼主| 发表于 2016-9-28 11:03 | 显示全部楼层
[广告] Excel易用宝 - 提升Excel的操作效率 · Excel / WPS表格插件       ★免费下载 ★       ★ 使用帮助
本帖最后由 liu-aguang 于 2016-11-11 10:54 编辑

4. 数组和对象自身嵌套或相互嵌套的Json/Jsonp实例

通过上面的讨论,我们明白了json的概念和一般解析方法与技巧。现在我们如果要用json文件来存放2个或多个成员的姓名/性别/年龄信息该怎样做呢?这时可以将通过数组或对象的嵌套来实现:

(1)如果用数组则表示为:
[[‘张三’,’男’,23],[’李四’,’女’,20],[’王五’,’男’,22]]
它的形式是:[[],[],[]],解析方法:

Sub a1()
    Set oDom = CreateObject("htmlfile")
    Set oWindow = oDom.parentWindow
    strHtml = "[['张三','男',23],['李四','女',20],['王五','男',22]]"
    oWindow.execScript "a=" & strHtml
    MsgBox oWindow.eval("a[0][0]")  '第一个成员的姓名
End Sub
讨论:
<1>用execScript获得数组a, “a[0]”是第一个成员,它还是一个数组,所以”a[0][0]”是第一个成员的第一项元素值。读者可以自己去研究遍历方法。
<2>在实际应用中,我们看到的是由很多数值元素构成的数组,那么每一个位置上的数据代表的是什么呢?这是json编制者自定义的,我们要结合网页显示去判断。
<3>这种情况下,也可不用数组嵌套来表示, 而表示为:
       ["张三,男,23","李四,女,20","王五,男,22"]

它用双(或单)引号来限定一个数组元素. 此时,a[0]表示第一个元素, 其值为"张三,男,23", 这是一个字符串.

(2)如果用对象来表示则为:
[{‘name’:’张三’,’sex’:’男’,’age’:23}, {‘name’:’李四’,’sex’:’女’,’age’:20}, {‘name’:’王五’,’sex’:’男’,’age’:22}]
这是数组与对象的嵌套,解法:
Sub ao()
    Set oDom = CreateObject("htmlfile")
    Set oWindow = oDom.parentWindow
    strHtml = "[{'name':'张三','sex':'男','age':23}, {'name':'李四','sex':'女','age':20}, {'name':'王五','sex':'男','age':22}]"
    oWindow.execScript "a=" & strHtml
    MsgBox oWindow.eval("a[0].name")  '第一个成员的姓名
    MsgBox oWindow.eval("a[2].age")  '第三个成员的年龄
End Sub
讨论:每个数组元素是对象,所以先得到数组元素,再根据对象方法读取值。

(3)假如上面只是某公司的一个小组成员,现在希望用json文件同时存放公司名称和每一工作组成员以及公司总人数, 又该如何呢?
可以这样处理:
{‘dw’:’宇宙公司’,’zb1’: [{'name':'张三','sex':'男','age':23}, {'name':'李四','sex':'女','age':20}, {'name':'王五','sex':'男','age':22}],‘zb2’:[{'name':'刘六','sex':'男','age':24}, {'name':'风车车','sex':'女','age':18}, {'name':'汪麻','sex':'男','age':21}],’total’:6}
这个json存放了公司名称,总人数,公司的两个工作组及组内成员信息。可以通过execScript解析后,访问任意内容:

解析方法如下:
Sub ao()
    Set oDom = CreateObject("htmlfile")
    Set oWindow = oDom.parentWindow
    strHtml = "{'dw':'宇宙公司','zb1': [{'name':'张三','sex':'男','age':23}, {'name':'李四','sex':'女','age':20}, {'name':'王五','sex':'男','age':22}],'zb2':[{'name':'刘六','sex':'男','age':24}, {'name':'风车车','sex':'女','age':18}, {'name':'汪麻','sex':'男','age':21}],'total':6}"
    oWindow.execScript "a=" & strHtml
    Debug.Print oWindow.a.dw                           '公司名称
    Debug.Print oWindow.eval("a.total")              '公司总人数. 想想为什么不用上一句方式读取total值呢?
    Debug.Print oWindow.eval("a.zb1[0].name")  '第一工作组的第一位成员姓名
    Debug.Print oWindow.eval("a.zb2[2].age")     '第二工作组的第三位成员年龄
End Sub
讨论:
(1)当execScript解析strHtml后,zb1和zb2是对象a的名称(健名)。但是zb1和zb2的值是数组,所以我们可以用zb1[0]这样的方式表示数组的第一个元素;不过这些数组元素又是对象,所以zb1[0].name就是点号法读取;zb1[0]对象属于对象a下面的一个子对象,于是读取第一工作组的第一位成员姓名的表达式就为:a.zb1[0].name
(2)根据实际提取哪些数据的要求,可以遍历。方法就是用变量来替换方括号内的数值,读者可以自己去尝试。
(3)Json语法小结:

JSON 语法是 JavaScript 对象表示法语法的子集。
  &#8226; 数据在名称/值对中
  &#8226; 数据由逗号分隔
  &#8226; 花括号保存对象
  &#8226; 方括号保存数组

最后用几个在论坛上的回复实例来结束Json解析部分。
例1
http://club.excelhome.net/forum.php?mod=viewthread&tid=1297966
例2
http://club.excelhome.net/forum.php?mod=viewthread&tid=1299461
例3
http://club.excelhome.net/thread-1301123-3-1.html
例4
http://club.excelhome.net/thread-1298157-2-1.html
Sub js()
    Dim arr(1 To 100, 1 To 5)
    sURL = "http://comment.10jqka.com.cn/tzrl/getTzrlData.php?callback=callback_dt&type=data&date=201608"
    Set oDom = CreateObject("HTMLFILE")
    Set oWindow = oDom.parentWindow
    Set oHtml = CreateObject("MSXML2.XMLHTTP")
    oHtml.Open "GET", sURL, False
    oHtml.send
    oWindow.execScript oHtml.responseText & ";function callback_dt(o){oj=o};n=oj.data.length"
    For i = 0 To oWindow.n - 1
        r = r + 1
        arr(r, 1) = oWindow.eval("var oi=oj.data[" & i & "]; oi.date+oi.week;")
        imt = oWindow.eval("s=0;for(x in oi){s+=1;if(s==3){imt=oi[x]}};imt")
        arr(r, 2) = String(imt, "★")
        For j = 0 To oWindow.eval("oi.events.length") - 1
            arr(r, 3) = oWindow.eval("oi.events[" & j & "][0]")
            For Each u In Array("field", "concept")
            arr(r, 4) = arr(r, 4) & "," & oWindow.eval("ar=[];k=oi." & u & "[" & j & "];for(x in k){ar.push(k[x].name);ar}")
            Next
            arr(r, 4) = Trim(Replace(arr(r, 4), ",", " "))
            arr(r, 5) = oWindow.eval("ar=[]'';k=oi.stocks[" & j & "];for(x in k){ar.push(k[x].name);ar}")
            arr(r, 5) = Replace(arr(r, 5), ",", " ")
            r = r + 1
        Next
        r = r - 1
    Next
    Range("a2").Resize(r, 5) = arr
End Sub
说明:代码中有一句:imt = oWindow.eval("s=0;for(x in oi){s+=1;if(s==3){imt=oi[x]}};imt")
目的是为了访问名称:import的值。采取这种特别的方式读取,是因为json编制者”误用”了javaScript关键字import作为名称。用点号法访问这个值会发生错误。

修正: 可以用另一种语法直接读取import的值. 即:
imt=oWindow.eval("oi['import']")



评分

3

查看全部评分

TA的精华主题

TA的得分主题

发表于 2016-10-3 18:42 | 显示全部楼层

TA的精华主题

TA的得分主题

发表于 2016-10-3 23:17 | 显示全部楼层
论坛很少有好文章了,支持一下。

楼主继续!

TA的精华主题

TA的得分主题

 楼主| 发表于 2016-10-4 09:11 | 显示全部楼层
[广告] VBA代码宝 - VBA编程加强工具 · VBA代码随查随用  · 内置多项VBA编程加强工具       ★ 免费下载 ★      ★使用手册
liucqa 发表于 2016-10-3 23:17
论坛很少有好文章了,支持一下。

楼主继续!

非常感谢老师的鼓励. 等我把手上的工作完成后, 再继续写下去.
您需要登录后才可以回帖 登录 | 免费注册

本版积分规则

关闭

最新热点上一条 /1 下一条

手机版|关于我们|联系我们|ExcelHome

GMT+8, 2024-4-19 15:38 , Processed in 0.044118 second(s), 7 queries , Gzip On, MemCache On.

Powered by Discuz! X3.4

© 1999-2023 Wooffice Inc.

沪公网安备 31011702000001号 沪ICP备11019229号-2

本论坛言论纯属发表者个人意见,任何违反国家相关法律的言论,本站将协助国家相关部门追究发言者责任!     本站特聘法律顾问:李志群律师

快速回复 返回顶部 返回列表