ExcelHome技术论坛

 找回密码
 免费注册

QQ登录

只需一步,快速开始

快捷登录

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

[原创] 不懂html也来学网抓(xmlhttp/winhttp+fiddler)

    [复制链接]

TA的精华主题

TA的得分主题

 楼主| 发表于 2014-10-23 16:12 | 显示全部楼层
本帖已被收录到知识树中,索引项:网页交互
本帖最后由 wcymiss 于 2014-10-30 18:19 编辑

缓存的困扰
用xmlhttp对象GET数据时,会优先从缓存中调取。

比如下面这段代码:
  1. Sub Main()
  2.     Dim strText As String
  3.     With CreateObject("MSXML2.XMLHTTP")
  4.         .Open "GET", "http://www.1396me.com/shishicai/", False
  5.         .Send
  6.         strText = .responsetext
  7.         Debug.Print "最新开奖期数:"; Left(Split(strText, "<p class=""p"">")(1), 12)
  8.         Debug.Print "最新开奖时间:"; Left(Split(strText, "<p class=""t"">")(1), 5)
  9.     End With
  10. End Sub
复制代码
运行后,不要关闭该excel,过10分钟再运行,仍然出现之前的结果,数据没有更新。但网页上已有更新。

这种现象是因为xmlhttp调用了缓存的数据。

这个缓存不是指浏览器的缓存,而是excel的缓存。不信你清除浏览器的缓存试试,代码结果仍然不会更新。

这个缓存随excel的进程结束而消失。

所以我们在网抓的调试中,为了验证一段代码能否真正获取到数据,除了清除浏览器的Cookie缓存种种,还必须关闭excel再重新打开excel后再运行代码。这时的运行结果才是代码的真正结果。

那么,除了关闭excel,有没有其他方法能避免这样的现象呢?

回答当然是“有”,而且有多种方法可以避免xmlhttp调用缓存的数据。

方法如下:

1、在URL后面添加随机参数。
   比如上述代码中,我们把Open语句改成:
   .Open "GET", "http://www.1396me.com/shishicai/?=" & Rnd(), False   然后代码就能实时更新了。

   这个方法最简单,但它未必对所有的网页适用。

2、添加setRequestHeader
   在代码的Send语句前加一句:
   .setRequestHeader "If-Modified-Since", "0"
   这句语句的具体含义请百度。在这里我们用它使代码效果实时更新。
   同样,未必对所有的网页都适用,而且效率比上种方法低下。

3、用winhttp代替xmlhttp
   winhttp不会从缓存中调取数据。

点评

就是winhttp被释放掉就没缓存,如果持续运行时,依然有缓存的。  发表于 2014-10-24 21:32
ServerXMLHTTP 依赖于 HTTP 客户端堆栈 WinHTTP,按demon意思,winhttp也是有缓存的。缓存也分为会话型和持久型,winhttp是不支持持久的,但依然支持会话型的缓存。  发表于 2014-10-24 21:30
winhttp有缓存的,http://demon.tw/programming/msxml2-xmlhttp-msxml2-serverxmlhttp-cache-again.html  发表于 2014-10-24 21:28
我也遇到了这个问题,及时雨啊,吴姐太伟大了  发表于 2014-10-23 16:40

评分

4

查看全部评分

TA的精华主题

TA的得分主题

 楼主| 发表于 2014-10-23 23:16 | 显示全部楼层
[广告] Excel易用宝 - 提升Excel的操作效率 · Excel / WPS表格插件       ★免费下载 ★       ★ 使用帮助
本帖最后由 wcymiss 于 2014-10-24 07:00 编辑
blanksoul12 发表于 2014-10-23 22:56
我直接COPY妳那個程序出來的

晚上正在找重定向的例子进行测试的时候,发现了我电脑winhttp控件居然有个bug。

我的winhttp对于301重定向的网页运行正常,对于302重定向的网页,不管option(6)怎么设置,都不会重定向到新的网页。汗!!

34楼的这个网页,正好是一个302重定向的网页,所以我的winhttp没有进行重定向,返回的信息是原网页的。我还以为option(6)默认是false,测试了才发现默认是true

正确的代码应该还要加一句“.option(6)=false
  1. Sub Main()
  2.     Dim strText As String
  3.     Dim strCookie As String
  4.     With CreateObject("WinHttp.WinHttpRequest.5.1")
  5.         .Option(6) = False ' 禁止重定向,以获取原网页信息
  6.         .Open "GET", "http://www.gzgczj.com:8080/costRegulatory/user.do?method=changeIndex&fareaId=1", False
  7.         .Send
  8.         strText = .getAllResponseHeaders '获取所有的回应头信息
  9.         Debug.Print strText
  10.         strCookie = Split(Split(strText, "JSESSIONID=")(1), ";")(0) '取出Cookie值
  11.         
  12.         .Open "GET", "http://www.gzgczj.com:8080/costRegulatory/project.do?method=showProjectList&isVisitor=1&f_id=11011&t1413902083242", False
  13.         .setRequestHeader "Referer", "http://www.gzgczj.com:8080/costRegulatory/user.do?method=changeIndex&fareaId=1"
  14.         .setRequestHeader "Cookie", strCookie '模拟Cookie
  15.         .Send
  16.         strText = .responsetext
  17.         Debug.Print strText
  18.         
  19.     End With
  20. End Sub
复制代码

TA的精华主题

TA的得分主题

 楼主| 发表于 2014-10-23 23:55 | 显示全部楼层
本帖最后由 wcymiss 于 2014-10-30 10:05 编辑

重定向

重定向用到的不多,可以稍微了解一下。
你向服务器申请访问地址A,服务器回应你,给你一个新地址B,让你访问它,这就是重定向。(具体请百度)
34楼的例子就是一个重定向的例子。
重定向.png

xmlhttp会自动重定向到B地址,发送B地址的请求。
winhttp有参数可以设置是否重定向:
.Option(6)=false'禁止重定向
.Option(6)=true'允许重定向,默认设置

需要获取重定向之前网页信息的时候,就需要用winhttp设置Option(6)=false来获取了。
比如获取原网页的Cookie。可以参考106楼的代码。

题外话:
发现我电脑有bug:
对于301重定向网页,xmlhttp访问出错,winhttp正常,设置option(6)有效;
对于302重定向网页,xmlhttp可以正常重定向,winhttp不管怎么设置option(6)都无法进行重定向,返回的一直都是原网页信息。
34楼的例子是302重定向。
301重定向例子:http://widget.wumii.com/ext/relatedItemsWidget.htm
我重新下载了一个winhttp.dll也无效。估计是window问题。改天有空重新找个版本GHO

======================================================
经过大师xmyjk的提醒,xmlhttp访问301重定向网页出错的原因是Internet禁止了通过域访问数据源。
Iinternet选项----安全----Internet----自定义级别----其他----通过域访问数据源,选择“启用”即可。


TA的精华主题

TA的得分主题

 楼主| 发表于 2014-10-24 10:03 | 显示全部楼层
[广告] VBA代码宝 - VBA编程加强工具 · VBA代码随查随用  · 内置多项VBA编程加强工具       ★ 免费下载 ★      ★使用手册
本帖最后由 wcymiss 于 2014-10-24 10:05 编辑
liucqa 发表于 2014-10-24 09:50
对好多有采集需求的人来说,看看教程,获取数据并不难。难的是不知道如何处理采集到的数据,并生成自己需要 ...


大哥,您老怎么老是看帖看一半啊。。。。

46楼就是模拟cookie欺骗服务器的例子。。。

还有70楼也是。

关于Cookie,我也是尽可能把我所知道的都写出来了。

TA的精华主题

TA的得分主题

 楼主| 发表于 2014-10-24 10:57 | 显示全部楼层
代理
如果服务器对同一IP有访问次数的限制,我们就要使用代理服务器了。

winhttp对象有SetProxy方法可以设置代理服务器,具体语句是:

SetProxy 2,"xxx.xxx.xxx.xxx:xxxx"

测试代码(代理服务器地址可能已失效):
  1. Sub Main()
  2.     Dim strText As String
  3.     With CreateObject("WinHttp.WinHttpRequest.5.1")
  4.         .SetProxy 2, "218.75.100.114:8080"
  5.         .Open "GET", "http://20140507.ip138.com/ic.asp", False
  6.         .send
  7.         strText = ByteToStr(.Responsebody, "GB2312")'请自行拷贝之前的常用函数
  8.         Debug.Print strText
  9.     End With
  10. End Sub
复制代码
---------------------------------------------------------------
小贴士:
不知大家在测试中发现没,fiddler可以抓到vba中xmlhttp发送请求,但无法抓到winhttp的发送请求。

有时我们调试时,会需要比较代码的发送请求与我们实际操作的发送请求是否一致,但fiddler无法抓到winhttp,怎么办呢?

你当然可以用其他抓包软件抓包。

但更快捷的做法就是,在winhttp中设置代理为"127.0.0.1:8888"。
  1. .SetProxy 2,"127.0.0.1:8888"
复制代码
因为fiddler监视的8888端口,我们设定winhttp的数据由8888进出,fiddler就能抓到包了。

点评

“非常代理”软件能自动设置代理,但它却不提供显示端口、用它提示的加“1080”端口是不正确的  发表于 2014-10-24 16:23
http://20140507.ip138.com/ic.asp有时是不能用的。  发表于 2014-10-24 16:21
提供一个可以用的代理服务器地址:.SetProxy 2, "14.18.16.67:80"  发表于 2014-10-24 13:18

TA的精华主题

TA的得分主题

 楼主| 发表于 2014-10-24 10:59 | 显示全部楼层

TA的精华主题

TA的得分主题

 楼主| 发表于 2014-10-24 12:07 | 显示全部楼层
本帖最后由 wcymiss 于 2014-10-24 13:15 编辑

提取数据-下载文件
知道下载文件的真实地址,就能很方便的用xmlhttp或winhttp下载文件。

下载文件同样可以通过fiddler抓包。

打开fiddler,点击下载链接,出现文件下载窗口时点击“取消”即可。

在fiddler里查看抓到的Session前面的图标,文档的下载地址前面的图标一般是 图标-文本.png
音乐、图片也都有明确的图标,一看便知。

得到下载地址后,用xmlhttp/winhttp获取文件流(二进制数据),然后把文件流转成文件。

示例代码如下:(注意此时用的是Responsebody而不是Responsetext
  1. Sub Main()
  2.     Const strFileName As String = "C:\测试EH下载文件.rar"
  3.     With CreateObject("MSXML2.XMLHTTP") 'CreateObject("WinHttp.WinHttpRequest.5.1")
  4.         .Open "GET", "http://club.excelhome.net/forum.php?mod=attachment&aid=MTA2MjQ1MHw0MDQxMTAzOHwxNDE0MTIxNTg0fDIxODkxN3w4MDk5MjQ%3D", False
  5.         .Send
  6.         ByteToFile .responsebody, strFileName
  7.     End With
  8. End Sub
复制代码
小贴士:
1、登录才能下载的文件,真实地址是在登录后服务器临时生成的一个地址。地址得到后,是否登录就无关紧要了。
2、最好事先知道要下载的文件的类型。否则,如果地址中能看出文件类型最好,不然你就只能根据文件结构去判断文件类型了。这个很麻烦。

TA的精华主题

TA的得分主题

 楼主| 发表于 2014-10-24 14:04 | 显示全部楼层
本帖最后由 wcymiss 于 2014-11-1 09:17 编辑

转码

1、有时我们获取到的数据,有很多乱码。
这是因为,Responsetext是按照UTF-8的编码格式来解析获取到的数据的,如果数据不是UTF-8编码的字符,就无法正确显示。
这时需要我们获取ResponseBody(这是源生态的没有经过任何编码的数据),然后自行对其进行解析。

比如,116楼例子的网址:http://20140507.ip138.com/ic.asp,因为字符编码是GB2312(charset=gb2312),我们直接显示ResponseText时,中文字符就显示为乱码。

转码1.png

转码最方便的就是利用adostream控件
转码的自定义函数参考11楼代码


2、"\uxxxx"类型的转码
17楼作业中的例子的responsetext包含了很多"\uxxxx"这样的字符,

转码2.png

这是一种unicode格式编码。我们需要对其还原本身的unicode字符。
jscript的unescape就是专门干这个的。

自定义转码函数:
  1. Function unescape(strTobecoded As String) As String
  2.     With CreateObject("msscriptcontrol.scriptcontrol")
  3.         .Language = "JavaScript"
  4.         unescape = .Eval("unescape('" & strTobecoded & "');")
  5.     End With
  6. End Function
复制代码
其实,jscript的eval可以直接转换"\uxxxx"
  1. Function JSEval(s As String) As String
  2.     With CreateObject("MSScriptControl.ScriptControl")
  3.         .Language = "javascript"
  4.         JSEval = .Eval(s)
  5.     End With
  6. End Function
复制代码
不管是用unescape还是直接Eval,都要注意文本的引号问题。这就好比你在excel的单元格内输入 ="excel" 不会出错,输入 =excel 就会提示出错一样。


3、HTML字符实体的转换
HTML字符实体就是诸如
  1. &nbsp;  &quot;
复制代码
  1. &#20320;///这里应该是半角分号,但我写半角分号发帖时会直接解析为汉字
复制代码
这样的字符串。

例子我暂时还没有找到。
先上转换的自定义函数,因为没找到网页测试,如果有bug请一定告诉我。
  1. Function EnCodeByHTML(strText As String)
  2.     With CreateObject("htmlfile")
  3.         .write strText
  4.         EnCodeByHTML = .body.innertext
  5.     End With
  6. End Function
复制代码
4、如果你手头还有其他转码类型,请一定告诉我。

================================================================
有坛友问ResponseBody和ResponseText的区别,这里补充说下:
1、ResponseBody是二进制的数据,是服务器传来的没有经过任何加工的数据。在网络中,文本一般都是以utf-8编码,所以xmlhttp/winhttp对象的ResponseText是按照utf-8编码把ResponseBody转换而成,也就是:ResponseText=ByteToStr(ResponseBody,"UTF-8")
至于问“为什么ByteToStr(ResponseText,"GB2312")没有结果”,原因是:一是参数类型不对,ByteToStr的第一参数是二进制数据的Byte数组类型,ResponseText是文本类型,系统提示出错;二是,即使进行了将文本转成二进制数据的转换(如下面代码里的b7=s这样的转换),这种转换也是按照某种编码进行的,这样的二进制已经进行过一次编码加工了,你再用ByteToStr就得不到原来的字符了。

2、对于StrConv和BytetoStr有什么区别,可以测试下面的代码:
  1. Sub Test()
  2.     Const s As String = "a我"
  3.     Dim b1() As Byte, b2() As Byte, b3() As Byte, b4() As Byte, b5() As Byte, b6() As Byte, b7() As Byte
  4.     b1 = StrToByte(s, "GB2312")
  5.     b2 = StrConv(s, vbFromUnicode) '系统默认
  6.     b3 = StrConv(s, vbFromUnicode, "2052") '内地中文
  7.     b4 = StrConv(s, vbFromUnicode, "1028") '台湾中文
  8.     b5 = StrToByte(s, "BIG5")
  9.     b6 = StrConv(s, vbUnicode)
  10.     b7 = s
  11.     Stop '查看本地窗口里以上几个变量
  12. End Sub
复制代码
有兴趣的还可以看看asc、ascw、chr、chrw这几个函数。

点评

scriptcontrol可以在64位下运行,需要做个包装  发表于 2016-9-28 20:39
回头我把我的解决方案给你吧,不过也不彻底,唉。  发表于 2014-10-24 22:53
还是老话,scriptcontrol在64位OFFICE跑不了。。。曾经很苦恼。现在我这里也还是不彻底。  发表于 2014-10-24 21:38

评分

2

查看全部评分

TA的精华主题

TA的得分主题

 楼主| 发表于 2014-10-25 09:05 | 显示全部楼层
本帖最后由 wcymiss 于 2014-10-26 10:10 编辑

处理数据的通用方法

1、数组法:
   用split和数组,循环将所需数据取出。
   优点:不需其他对象辅助,起点低,会数组即可。
   缺点:需要分析数据结构,对于复杂结构的数据,需要多步才能完成。
   以作业一的第1题为例:
  1. Sub Main()
  2.     Dim strText As String
  3.     Dim arrRow, arrCell
  4.     Dim i As Long, j As Long, n As Long
  5.     Dim arrColumn
  6.     Dim arrData(1 To 1000, 1 To 10)
  7.    
  8.     With CreateObject("MSXML2.XMLHTTP")
  9.         .Open "GET", "http://data.bank.hexun.com/lccp/jrxp.aspx", False
  10.         .Send
  11.         strText = .responsetext
  12.     End With
  13.    
  14.     arrColumn = Array(, , 9, 12, 14, 16, 18, 20, 22, 24, 26)
  15.     arrRow = Split(strText, "name='proTest' ")
  16.     For i = 1 To UBound(arrRow)
  17.         arrCell = Split(arrRow(i), ">")
  18.         n = n + 1
  19.         arrData(n, 1) = Split(Split(arrCell(0), "value='")(1), "'")(0)
  20.         For j = 2 To 10
  21.             arrData(n, j) = Split(arrCell(arrColumn(j)), "<")(0)
  22.         Next
  23.     Next
  24.    
  25.     Cells.Clear
  26.     Range("a1:j1").Value = Split("产品名称 是否在售 银行 起售日 停售日 币种 管理期(月) 产品类型 预期收益(%) 收益类型", " ")
  27.     Range("a2").Resize(n, 10).Value = arrData
  28. End Sub
复制代码
2、正则法:
   用正则拆解字符串,提取匹配数据,循环取出。
   优点:即便复杂结构的数据,也有可能一步到位。
   缺点:需要学习正则知识。
   70楼获取到QQ群成员清单后,用正则提取成员的昵称和QQ号:
   
  1. Sub Main()
  2.     Const gc As String = "" '群号
  3.     Const bkn As String = "" '从fiddler中获取
  4.     Const uin As String = "" 'QQ号
  5.     Const skey As String = "" '从fiddler中获取
  6.     Dim strText As String
  7.     Dim RegMatch As Object
  8.     Dim arrData(1 To 1000, 1 To 2)
  9.     Dim n As Long
  10.    
  11.     With CreateObject("WinHttp.WinHttpRequest.5.1")
  12.         .Open "GET", "http://qinfo.clt.qq.com/cgi-bin/qun_info/get_group_members_new?gc=" & gc & "&bkn=" & bkn, False
  13.         .setRequestHeader "Cookie", "uin=o" & uin & "; skey=" & skey
  14.         .Send
  15.         strText = .responsetext
  16.         Debug.Print strText
  17.     End With
  18.    
  19.     With CreateObject("VBScript.Regexp")
  20.         .Global = True
  21.         .Pattern = "{""b"":\d+,""g"":\d+,""n"":""([^""]*)"",""u"":(\d+)}"
  22.         For Each RegMatch In .Execute(strText)
  23.             n = n + 1
  24.             arrData(n, 1) = RegMatch.submatches(0)
  25.             arrData(n, 2) = RegMatch.submatches(1)
  26.         Next
  27.     End With
  28.    
  29.     Set RegMatch = Nothing
  30.     Cells.Clear
  31.     Range("a1:b1").Value = Array("昵称", "QQ号")
  32.     Range("a2").Resize(n, 2).Value = arrData
  33. End Sub
复制代码


以上两种方法对于处理获取到的数据一般都能用,但都需先行分析获数据结构。对于复杂结构数据,需要时间和耐心。

table、xml、json各自还有一些专用的提取方法。

评分

1

查看全部评分

TA的精华主题

TA的得分主题

 楼主| 发表于 2014-10-25 10:43 | 显示全部楼层
本帖最后由 wcymiss 于 2014-10-27 13:07 编辑

处理table
table数据处理,除了之前的两种通用方法外,还有以下几种方法:

1、html法
   将table数据写入htmldocument对象,然后循环取出表格的各个元素。
   优点:可以利用htmldocument对象整理表格。
   缺点:需要学习html相关知识。
   以17楼作业二为例:
  1. Sub Main()
  2.     Dim strText As String
  3.     Dim arrData(1 To 1000, 1 To 3)
  4.     Dim i As Long, j As Long
  5.     Dim TR As Object, TD As Object
  6.    
  7.     With CreateObject("MSXML2.XMLHTTP")
  8.         .Open "POST", "http://www.pinble.com/Template/WebService1.asmx/Present3DList", False
  9.         .setRequestHeader "Content-Type", "application/json"
  10.         .Send "{pageindex:'1',lottory:'TC7XCData_jiangS',pl3:'',name:'江苏七星彩',isgp: '0'}"
  11.         strText = Split(JSEval(.responsetext), "<script")(0) '本例的script运行会提示错误,所以去除这部分script代码
  12.     End With
  13.    
  14.     With CreateObject("htmlfile")
  15.         .write strText
  16.         i = 0
  17.         For Each TR In .all.tags("table")(2).Rows
  18.             i = i + 1
  19.             j = 0
  20.             For Each TD In TR.Cells
  21.                 j = j + 1
  22.                 arrData(i, j) = TD.innerText
  23.             Next
  24.         Next
  25.     End With
  26.    
  27.     Set TR = Nothing
  28.     Set TD = Nothing
  29.     Cells.Clear
  30.     Range("C:C").NumberFormat = "@" '设置文本格式以显示数字前面的0
  31.     Range("a1").Resize(i, 3).Value = arrData
  32. End Sub

  33. Function JSEval(s As String) As String
  34.     With CreateObject("MSScriptControl.ScriptControl")
  35.         .Language = "javascript"
  36.         JSEval = .Eval(s)
  37.     End With
  38. End Function
复制代码
2、QueryTable法:
   这个是excel自带的网抓利器。个人觉得它最大的优势就是处理table很方便。
   优点:处理table方便,代码简短。
   缺点:会产生定义名称。多页循环时每页都会产生行字段名称,需要后续处理删除。
   仍以作业一的第1题为例:
  1. Sub Main()
  2.     Cells.Delete
  3.     With ActiveSheet.QueryTables.Add("url;http://data.bank.hexun.com/lccp/jrxp.aspx", Range("a1"))
  4.         .WebFormatting = xlWebFormattingNone '不包含格式
  5.         .WebSelectionType = xlSpecifiedTables '指定table模式
  6.         .WebTables = "2" '第2张table
  7.         .Refresh False
  8.     End With
  9. End Sub
复制代码
代码相当简短。


3、复制粘贴法:
   table部分的文字可以直接复制到单元格内,且保留数据原格式。
   优点:只需取出table部分,不需分析数据内部结构。代码编写简便。
   缺点:有时格式反而是累赘。
  1. Sub Main()
  2.     Dim strText As String
  3.     With CreateObject("MSXML2.XMLHTTP")
  4.         .Open "GET", "http://data.bank.hexun.com/lccp/jrxp.aspx", False
  5.         .Send
  6.         strText = .responsetext
  7.     End With
  8.     strText = "<table" & Split(Split(strText, "<table")(2), "</table>")(0) & "</table>"
  9.     CopyToClipbox strText
  10.     Cells.Clear
  11.     Range("a1").Select
  12.     ActiveSheet.Paste
  13. End Sub

  14. Sub CopyToClipbox(strText As String)
  15.     '文本拷贝到剪贴板
  16.     With CreateObject("new:{1C3B4210-F441-11CE-B9EA-00AA006B1A69}")
  17.         .SetText strText
  18.         .PutInClipboard
  19.     End With
  20. End Sub
复制代码
小贴士:
点击Fiddler的Response框的WebView按钮可以看到HTML代码在网页上的显示效果。

评分

9

查看全部评分

您需要登录后才可以回帖 登录 | 免费注册

本版积分规则

关闭

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

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

GMT+8, 2024-4-25 08:32 , Processed in 0.060055 second(s), 9 queries , Gzip On, MemCache On.

Powered by Discuz! X3.4

© 1999-2023 Wooffice Inc.

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

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

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