【问题标题】:html parsing of cricinfo scorecardscricinfo记分卡的html解析
【发布时间】:2012-02-06 13:38:54
【问题描述】:

瞄准

我希望从Cricinfo website 中抓取 20/20 板球记分卡数据,最好将其转换为 CSV 表单,以便在 Excel 中进行数据分析

例如,当前的 Australian Big Bash 2011/12 记分卡可从

获得

背景

我精通使用VBA(自动化IE或使用XMLHTTP然后使用正则表达式)从网站上抓取数据,即 Extract values from HTML TD and Tr

在同一个问题中,发布了一条评论,建议进行 html 解析 - 我以前没有遇到过 - 所以我查看了诸如 RegEx match open tags except XHTML self-contained tags 之类的问题

查询

虽然我可以编写一个正则表达式来解析下面的板球数据,但我想了解如何通过 html 解析有效地检索这些结果。

请记住,我的偏好是可重复的 CSV 格式,其中包含:

  • 比赛日期/名称
  • 团队 1 名称
  • 输出应为 Team 1 转储最多 11 条记录(玩家未击球的空白记录,即 “未击球”
  • 团队 2 名称
  • 输出应为 Team 2 转储最多 11 条记录(球员未击球的空白记录)

Nirvana 对我来说是一个可以使用 VBA 或 VBscript 部署的解决方案,因此我可以完全自动化我的分析,但我认为我必须使用单独的工具来解析 html。

要提取的示例站点链接和数据

【问题讨论】:

  • 只是一个简单的查询,我认为爬取cricinfo是非法的!

标签: html regex excel xml vba


【解决方案1】:

我对“VBA”使用了 2 种技术。我将一一描述。

1) 使用 FireFox / Firebug Addon / Fiddler

2) 使用 Excel 的内置工具从 Web 获取数据

由于这篇文章会被很多人阅读,所以我什至会介绍显而易见的内容。请随意跳过您知道的任何部分


1) 使用 FireFox / Firebug 插件 / Fiddler


火狐:http://en.wikipedia.org/wiki/Firefox 免费下载(http://www.mozilla.org/en-US/firefox/new/)

萤火虫插件:http://en.wikipedia.org/wiki/Firebug_%28software%29 免费下载 (https://addons.mozilla.org/en-US/firefox/addon/firebug/)

提琴手:http://en.wikipedia.org/wiki/Fiddler_%28software%29 免费下载(http://www.fiddler2.com/fiddler2/)

安装 Firefox 后,请安装 Firebug 插件。 Firebug 插件可让您检查网页中的不同元素。例如,如果您想知道按钮的名称,只需右键单击它并单击“Inspect Element with Firebug”,它将为您提供该按钮所需的所有详细信息。

另一个示例是在网站上查找包含您需要报废的数据的表的名称。

我只在使用 XMLHTTP 时才使用 Fiddler。当您单击按钮时,它可以帮助我查看传递的确切信息。由于抓取网站的机器人数量增加,现在大多数网站为了防止自动抓取,捕获您的鼠标坐标并传递该信息,fiddler 实际上可以帮助您调试正在传递的信息。我不会在这里详细介绍它,因为这些信息可能会被恶意使用。

现在让我们举一个简单的例子来说明如何抓取问题中发布的 URL

http://www.espncricinfo.com/big-bash-league-2011/engine/match/524915.html

首先让我们找到包含该信息的表的名称。只需右键单击表格并单击“Inspect Element with Firebug”,它将为您提供以下快照。

所以现在我们知道我们的数据存储在一个名为“inningsBat1”的表格中。如果我们可以将该表格的内容提取到 Excel 文件中,那么我们绝对可以使用这些数据进行分析。这是将表格转储到 Sheet1 中的示例代码

在我们继续之前,我建议关闭所有 Excel 并启动一个新实例。

启动 VBA 并插入一个用户窗体。放置一个命令按钮和一个浏览器控件。您的用户表单可能如下所示

将此代码粘贴到用户窗体代码区

Option Explicit

'~~> Set Reference to Microsoft HTML Object Library

Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)

Private Sub CommandButton1_Click()
    Dim URL As String
    Dim oSheet As Worksheet

    Set oSheet = Sheets("Sheet1")

    URL = "http://www.espncricinfo.com/big-bash-league-2011/engine/match/524915.html"

    PopulateDataSheets oSheet, URL

    MsgBox "Data Scrapped. Please check " & oSheet.Name
End Sub

Public Sub PopulateDataSheets(wsk As Worksheet, URL As String)
    Dim tbl As HTMLTable
    Dim tr As HTMLTableRow
    Dim insertRow As Long, Row As Long, col As Long

    On Error GoTo whoa

    WebBrowser1.navigate URL

    WaitForWBReady

    Set tbl = WebBrowser1.Document.getElementById("inningsBat1")

    With wsk
        .Cells.Clear

        insertRow = 0
        For Row = 0 To tbl.Rows.Length - 1
            Set tr = tbl.Rows(Row)
            If Trim(tr.innerText) <> "" Then
                If tr.Cells.Length > 2 Then
                    If tr.Cells(1).innerText <> "Total" Then
                        insertRow = insertRow + 1
                        For col = 0 To tr.Cells.Length - 1
                            .Cells(insertRow, col + 1) = tr.Cells(col).innerText
                        Next
                    End If
                End If
            End If
        Next
    End With
whoa:
    Unload Me
End Sub

Private Sub Wait(ByVal nSec As Long)
    nSec = nSec + Timer
    While Timer < nSec
       DoEvents
        Sleep 100
    Wend
End Sub

Private Sub WaitForWBReady()
    Wait 1
    While WebBrowser1.ReadyState <> 4
        Wait 3
    Wend
End Sub

现在运行您的用户窗体并单击命令按钮。您会注意到数据被转储到 Sheet1 中。查看快照

同样,您也可以抓取其他信息。


2) 使用 Excel 的内置工具从网络获取数据


我相信您使用的是 Excel 2007,因此我将以此为例来抓取上述链接。

导航到 Sheet2。现在导航到数据选项卡,然后单击最右侧的“来自 Web”按钮。查看快照。

在“New Web Query Window”中输入url,点击“Go”

页面上传后,单击快照中显示的小箭头,选择您要导入的相关表。完成后,点击“导入”

然后,Excel 会询问您要将数据导入到哪里。选择相关单元格,然后单击“确定”。你完成了!数据将被导入到您指定的单元格中。

如果您希望可以录制宏并自动执行此操作 :)

这是我录制的宏。

Sub Macro1()
    With ActiveSheet.QueryTables.Add(Connection:= _
    "URL;http://www.espncricinfo.com/big-bash-league-2011/engine/match/524915.html" _
    , Destination:=Range("$A$1"))
        .Name = "524915"
        .FieldNames = True
        .RowNumbers = False
        .FillAdjacentFormulas = False
        .PreserveFormatting = True
        .RefreshOnFileOpen = False
        .BackgroundQuery = True
        .RefreshStyle = xlInsertDeleteCells
        .SavePassword = False
        .SaveData = True
        .AdjustColumnWidth = True
        .RefreshPeriod = 0
        .WebSelectionType = xlSpecifiedTables
        .WebFormatting = xlWebFormattingNone
        .WebTables = """inningsBat1"""
        .WebPreFormattedTextToColumns = True
        .WebConsecutiveDelimitersAsOne = True
        .WebSingleBlockTextImport = False
        .WebDisableDateRecognition = False
        .WebDisableRedirections = False
        .Refresh BackgroundQuery:=False
    End With
End Sub

希望这会有所帮助。如果您仍有疑问,请告诉我。

席德

【讨论】:

  • 这个答案是明确而详尽的。我希望这会对 brettdj 有所帮助。
  • 谢谢希德。虽然这与我预期的结果不同,但直接引用适当的 html 表优于解析。
  • excel 的力量 bwhaahahah :)
  • 请注意,未来无需使用 WebBrowser 控件创建用户窗体,因为代码会处理所有事情。
【解决方案2】:

对于其他对此感兴趣的人,我最终根据Siddhart Rout's 较早的答案使用下面的代码

  • XMLHttp 比自动化 IE 快​​得多
  • 代码为要下载的每个系列生成一个 CSV 文件(保存在 X 变量中)
  • 代码将每场比赛转储到常规的 29 行范围内(无论有多少球员击球),以便以后更轻松地进行分析

    Public Sub PopulateDataSheets_XML()
    Dim URL As String
    Dim ws As Worksheet

    Dim lngRow As Long
    Dim lngRecords As Long
    Dim lngWrite As Long
    Dim lngSpare As Long
    Dim lngInnings As Long
    Dim lngRow1 As Long
    Dim X(1 To 15, 1 To 4) As String

    Dim objFSO As Object
    Dim objTF As Object

    Dim xmlHttp As Object
    Dim htmldoc As HTMLDocument
    Dim htmlbody As htmlbody
    Dim tbl As HTMLTable
    Dim tr As HTMLTableRow
    Dim strInnings As String

    s = Timer()

    Set xmlHttp = CreateObject("MSXML2.ServerXMLHTTP")
    Set objFSO = CreateObject("scripting.filesystemobject")

    X(1, 1) = "http://www.espncricinfo.com/indian-premier-league-2011/engine/match/"
    X(1, 2) = 501198
    X(1, 3) = 501271
    X(1, 4) = "indian-premier-league-2011"
    X(2, 1) = "http://www.espncricinfo.com/big-bash-league-2011/engine/match/"
    X(2, 2) = 524915
    X(2, 3) = 524945
    X(2, 4) = "big-bash-league-2011"
    X(3, 1) = "http://www.espncricinfo.com/ausdomestic-2010/engine/match/"
    X(3, 2) = 461028
    X(3, 3) = 461047
    X(3, 4) = "big-bash-league-2010"

    Set htmldoc = New HTMLDocument
    Set htmlbody = htmldoc.body


    For lngRow = 1 To UBound(X, 1)
        If Len(X(lngRow, 1)) = 0 Then Exit For
        Set objTF = objFSO.createtextfile("c:\temp\" & X(lngRow, 4) & ".csv")

        For lngRecords = X(lngRow, 2) To X(lngRow, 3)
            URL = X(lngRow, 1) & lngRecords & ".html"

            xmlHttp.Open "GET", URL
            xmlHttp.send
            Do While xmlHttp.Status <> 200
                DoEvents
            Loop
            htmlbody.innerHTML = xmlHttp.responseText

            objTF.writeline X(lngRow, 1) & lngRecords & ".html"
            For lngInnings = 1 To 2
            strInnings = "Innings " & lngInnings
                objTF.writeline strInnings

                Set tbl = Nothing
                On Error Resume Next
                Set tbl = htmlbody.Document.getElementById("inningsBat" & lngInnings)
                On Error GoTo 0
                If Not tbl Is Nothing Then
                    lngWrite = 0
                    For lngRow1 = 0 To tbl.Rows.Length - 1
                        Set tr = tbl.Rows(lngRow1)
                        If Trim(tr.innerText) <> vbNewLine Then
                            If tr.Cells.Length > 2 Then
                                If tr.Cells(1).innerText <> "Extras" Then
                                    If Len(tr.Cells(1).innerText) > 0 Then
                                        objTF.writeline strInnings & "-" & lngWrite & "," & Trim(tr.Cells(1).innerText) & "," & Trim(tr.Cells(3).innerText)
                                        lngWrite = lngWrite + 1
                                    End If
                                Else
                                    objTF.writeline strInnings & "-" & lngWrite & "," & Trim(tr.Cells(1).innerText) & "," & Trim(tr.Cells(3).innerText)
                                    lngWrite = lngWrite + 1
                                    Exit For
                                End If
                            End If
                        End If
                    Next
                    For lngSpare = 12 To lngWrite Step -1
                        objTF.writeline strInnings & "-" & lngWrite + (12 - lngSpare)
                    Next
                Else
                    For lngSpare = 1 To 13
                        objTF.writeline strInnings & "-" & lngWrite + (12 - lngSpare)
                    Next
                End If
            Next
        Next
    Next
    'Call ConsolidateSheets
End Sub

【讨论】:

  • 我投了赞成票,但其中的硬编码信息太多了,我不喜欢,你可以想出一个比 X 更好的变量名。:)
  • @rickhenderson 感谢支持 ;) 不确定您的硬编码评论指的是什么,除了初始设置部分以将代码指向适当的匹配系列?
  • @brettdj 你能分享几行带标题的示例 CSV 吗?我正在寻找评论数据,其中我还想知道投球手打了什么样的球以及他在什么样的投篮中的位置。
【解决方案3】:

RegEx 不是解析 HTML 的完整解决方案,因为它不能保证是正则的。

您应该使用HtmlAgilityPack 来查询HTML。这将允许您使用 CSS 选择器来查询 HTML,就像使用 jQuery 一样。

【讨论】:

  • 虽然链接受到赞赏 - 我会进一步研究它 - 我期待有关方法,工具的优缺点等的详细反馈,因为提供了赏金。
【解决方案4】:

由于很多人可能会看到这一点,我想我会借此机会展示一些我很少看到人们在 VBA 网络抓取中使用的功能:deleteRow, querySelector 和使用clipboard 来写一个表格(包含格式和超链接)到基于table.outerHTML 的工作表。

deleteRow 用于删除不需要的行。 querySelector 用于更快地应用 css selectors 以匹配节点。现代浏览器/html 解析器针对 css 和类选择器(我使用的)进行了优化,是第二快的选择器类型(在 id 之后)。

使用 css 选择器并了解htmlTable 方法/属性将使您的网络抓取工作具有更大的灵活性。了解剪贴板的使用意味着一种将表格传输到 Excel 的简单复制粘贴方法。

执行可以很容易地与按钮按下和从单元格中读取的 url 相关联。


VBA:

Option Explicit

Public Sub test()

    WriteOutTable "https://www.espncricinfo.com/series/8044/scorecard/524935/hobart-hurricanes-vs-melbourne-stars-big-bash-league-2011-12"
    
End Sub

Public Sub WriteOutTable(ByVal url As String)
    'required VBE (Alt+F11) > Tools > References > Microsoft HTML Object Library ;  Microsoft XML, v6 (your version may vary)

    Dim hTable As MSHTML.HTMLTable, clipboard As Object
    Dim xhr As MSXML2.xmlhttp60, html As MSHTML.htmlDocument
   
    Set xhr = New MSXML2.xmlhttp60
    Set html = New MSHTML.htmlDocument

    With xhr
        .Open "GET", url, False
        .Send
        html.body.innerHTML = .responseText
    End With

    Set hTable = html.querySelector(".batsman")
    rowCount = hTable.Rows.Length - 1
    
    For i = rowCount To 0 Step -1
        Select Case True
        Case i = rowCount Or i = rowCount - 1 Or InStr(hTable.Rows(i).outerHTML, "wicket-details") > 0
            hTable.deleteRow i
        End Select
    Next

    Set clipboard = GetObject("New:{1C3B4210-F441-11CE-B9EA-00AA006B1A69}")
    clipboard.SetText hTable.outerHTML
    clipboard.PutInClipboard
    ActiveSheet.Cells(1, 1).PasteSpecial
    
End Sub

【讨论】:

    猜你喜欢
    • 2019-01-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-28
    • 2020-04-17
    • 1970-01-01
    • 1970-01-01
    • 2015-02-11
    相关资源
    最近更新 更多