Многостраничный интернет-магазин eBay. - PullRequest
0 голосов
/ 16 января 2020

Крест размещен здесь: https://www.mrexcel.com/board/threads/storefront-web-scraping.1120494/#post -5403849

Привет всем. У меня возникли проблемы при создании веб-мастера в VBA, который может справиться со следующим. Так что в основном мне нужно прокрутить свой интернет-магазин https://www.ebay.com/str/customwheelandperformancedepot?_pgn=1 в excel.

мне нужно go просмотреть все доступные страницы (см. Внизу) и открыть каждый список. Теперь, когда список был открыт, нам нужно определить, является ли это колесом или колесом и шиной, чтобы сделать это, мы можем посмотреть в таблице «Item Specifics»; если какая-либо спецификация элемента содержит фразы «шина», «ширина сечения» или «соотношение сторон», то это колесо и комплект шин.

пример колеса: https://www.ebay.com/itm/Set-of-4-16x8-Mo970-Black-Machine-8x165-1-Wheels-Rims-SILVERADO-2500/283545274424?epid=1540162229&hash=item42049d8838: g: dZgAAOSw5wVdJ2 ~ 0

пример комплекта колес и шин: https://www.ebay.com/itm/HELO-HE878-17x9-Wheels-Rims-33-FUEL-AT-Tires-Package-5x5-Jeep-Wrangler-JK-JL/372571036378?hash=item56bef6dada: g: AhkAAOSw2 ~ NcQO35

Для комплектов колес и шин мне нужны только:

1. Title [#itemTitle] 3. Цена [# mm-saleOrgPrc], если она недоступна [#prcIsum] 4. Номер позиции Ebay [#descItemNumber] 5. HTML Внутренняя часть описание [#ds_div]

Для списков только колес мне нужно:

1. Заголовок [#itemTitle]

2. Цена [# mm-saleOrgPrc], если недоступна [#prcIsum] 3 . Номер элемента Ebay [#descItemNumber] 4. Таблица характеристик элемента [.section> таблица: nth-child (2)> tbody: nth-child (1)] 6. HTML Внутренняя часть описания [ #container]

** обратите внимание, что таблица спецификаций элемента может быть не в порядке и может отсутствовать некоторые значения (например, рисунок 2 болтов). Значения заголовка находятся в столбцах 1 и 3 (Условие, возврат, смещение и т. Д. c), а фактические значения для добавления в таблицу Excel находятся в столбцах 2 и 4 (Новый, 4,5, 0 и т. Д. c)

Вот результат, которого я добиваюсь с 3 примерами колес, за которыми следуют 3 примера пакетов колес и шин.

Excel Scrape, columns don't have to be in this order


это то, что у меня есть, поскольку я не знаю, как получить доступ к нескольким страницам, я пытался вычеркнуть только заголовок из одного списка, и, похоже, я борюсь даже с этим.

Option Explicit</p>

<p>Const sSiteName = "<a href="https://www.ebay.com/itm/1-New-20x8-5-Kmc-District-ET-35-Bronze-5x114-3-5X4-5-Wheel-Rim/372780750649?epid=24031177590&hash=item56cb76d739:g:yDYAAOSwE91diN8Q" rel="nofollow noreferrer">https://www.ebay.com/itm/1-New-20x8-5-Kmc-District-ET-35-Bronze-5x114-3-5X4-5-Wheel-Rim/372780750649?epid=24031177590&hash=item56cb76d739:g:yDYAAOSwE91diN8Q</a>"</p>

<p>Private Sub GetHTMLContents()
    ' Create Internet Explorer object.
    Dim IE As Object
    Set IE = CreateObject("InternetExplorer.Application")
    IE.Visible = False          ' Keep this hidden.</p>

<code>IE.Navigate sSiteName

' Wait till IE is fully loaded.
While IE.ReadyState <> 4
    DoEvents
Wend

Dim oHDoc As HTMLDocument     ' Create document object.
Set oHDoc = IE.Document

Dim oHEle As HTMLDivElement     ' Create HTML element (<ul>) object.
Set oHEle = oHDoc.getElementById(".vi-swc-lsp")   ' Get the element reference using its ID.

Dim iCnt As Integer

' Loop through elements inside the <ul> element and find <h2>, which has the texts we want.
With oHEle
    For iCnt = 0 To .getElementsByTagName("h1").Length - 1
        Debug.Print .getElementsByTagName("h1").Item(iCnt).getElementsByTagName("a").Item(0).innerHTML
    Next iCnt
End With

' Clean up.
IE.Quit
Set IE = Nothing
Set oHEle = Nothing
Set oHDoc = Nothing
</code>

End Sub

я получаю «переменную объекта или переменная блока не установлена» в строке .getelementsbytagname

я использовал эту статью в качестве ссылки. https://www.encodedna.com/excel/extract-contents-from-html-element-of-a-webpage-in-excel-using-vba.htm

1 Ответ

1 голос
/ 26 января 2020

Я понимаю, что вы спрашивали эти две недели go, но, возможно, вы все еще ищете ответ.

На момент написания, я думаю, что код ниже работает для меня. Я говорю во время написания, потому что у меня создается впечатление, что некоторые из id (в HTML, полученных от сервера) периодически меняются - что нарушает код.

Вот что В настоящее время у меня есть:

Output sheet

Код немного беспорядок, не стесняйтесь рефакторинг. Точка входа: ScrapeAllItemsFromEbayShop.

Option Explicit

Private Function GetUrlForShopPageN(ByVal N As Long) As String
    ' Should return the store URL for page N,
    ' where N is some 1-based page index present in the query string.
    GetUrlForShopPageN = "https://www.ebay.com/str/customwheelandperformancedepot?_pgn=" & N
End Function

Private Function GetHtmlForShopPageN(ByVal webClient As WinHttp.WinHttpRequest, ByVal N As Long) As MSHTML.HTMLDocument
    ' Should return a HTML document representing the response of server for page N,
    ' where N is some 1-based page index present in the query string.

    Dim targetUrl As String
    targetUrl = GetUrlForShopPageN(N)

    With webClient
        .Open "GET", targetUrl, False
        .setRequestHeader "user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36"
        .send
        Set GetHtmlForShopPageN = New MSHTML.HTMLDocument
        GetHtmlForShopPageN.body.innerHTML = .responseText
    End With
End Function

Private Function DoesShopPageNotContainResults(ByVal htmlResponse As MSHTML.HTMLDocument) As Boolean
    ' Should return a boolean representing whether the htmlResponse contains zero results.
    DoesShopPageNotContainResults = (htmlResponse.getElementsByClassName("srp-controls").Length = 0)
End Function

Private Function GetUrlsOfItemsToScrape() As Collection
    ' Should return a collection of strings, representings the URLs of items.
    Set GetUrlsOfItemsToScrape = New Collection

    Dim webClient As WinHttp.WinHttpRequest
    Set webClient = New WinHttp.WinHttpRequest

    Do While True
        Dim pageIndex As Long
        pageIndex = pageIndex + 1

        Dim htmlResponse As MSHTML.HTMLDocument
        Set htmlResponse = GetHtmlForShopPageN(webClient, pageIndex)

        If DoesShopPageNotContainResults(htmlResponse) Then Exit Do

        Dim anchor As MSHTML.IHTMLElement
        For Each anchor In htmlResponse.getElementsByClassName("s-item__link")
            Debug.Assert StrComp(LCase$(Left$(anchor.getAttribute("href"), 25)), "https://www.ebay.com/itm/", vbBinaryCompare) = 0
            GetUrlsOfItemsToScrape.Add anchor.getAttribute("href")
            If GetUrlsOfItemsToScrape.Count > 10 Then Exit Do ' Delete this line completely once you think everything is working.
        Next anchor

        If (0 = (pageIndex Mod 10)) Then DoEvents
    Loop
End Function

Private Function DoesTextContainAnyOf(ByVal textToCheck As String, stringsToCheck As Variant) As Boolean
    ' Should return a boolean representing whether any of "stringsToCheck"
    ' can be found within "textToCheck". Performs a case-sensitive search.
    Dim i As Long
    For i = LBound(stringsToCheck) To UBound(stringsToCheck)
        If InStr(1, textToCheck, stringsToCheck(i), vbBinaryCompare) Then
            DoesTextContainAnyOf = True
            Exit For
        End If
    Next i
End Function

Private Function IsItemAWheelOnly(ByVal htmlResponse As MSHTML.HTMLDocument) As Boolean
    ' Should return True if, based on the HTML, the item is inferred to be a "wheel".
    Dim itemSpecifics As MSHTML.IHTMLTableSection
    Set itemSpecifics = htmlResponse.querySelector(".itemAttr tbody")
    Debug.Assert Not (itemSpecifics Is Nothing)

    Dim tireAndPackageIdentifiers As Variant
    tireAndPackageIdentifiers = Array("tire", "section width", "aspect ratio")

    Dim tableRow As MSHTML.IHTMLTableRow
    For Each tableRow In itemSpecifics.Rows
        Debug.Assert 0 = (tableRow.Cells.Length Mod 2)
        Dim columnIndex As Long
        For columnIndex = 0 To (tableRow.Cells.Length - 1) Step 2
            Debug.Assert InStr(1, tableRow.Cells(columnIndex).className, "attrLabels", vbBinaryCompare)
            If DoesTextContainAnyOf(LCase$(tableRow.Cells(columnIndex).innerText), tireAndPackageIdentifiers) Then Exit Function
        Next columnIndex
    Next tableRow

    IsItemAWheelOnly = True
End Function

Private Function GetHtmlForItem(ByVal webClient As WinHttp.WinHttpRequest, ByVal urlForItem As String) As MSHTML.HTMLDocument
    ' Should return a HTML document representing the response of server for a given item.
    With webClient
        .Open "GET", urlForItem, False
        .setRequestHeader "user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36"
        .send
        Set GetHtmlForItem = New MSHTML.HTMLDocument
        GetHtmlForItem.body.innerHTML = .responseText
    End With
End Function

Private Sub ScrapeAllItemsFromEbayShop()

    Dim webClient As WinHttp.WinHttpRequest
    Set webClient = New WinHttp.WinHttpRequest

    Dim urlsOfItemsToScrape As Collection
    Set urlsOfItemsToScrape = GetUrlsOfItemsToScrape()

    Dim rowWriteIndex As Long
    rowWriteIndex = 1 ' Skip row 1/headers

    Dim destinationSheet As Worksheet
    Set destinationSheet = ThisWorkbook.Worksheets("Sheet1")

    destinationSheet.Cells.ClearContents

    Dim columnIndexes As Collection
    Set columnIndexes = New Collection

    Dim urlOfItem As Variant
    For Each urlOfItem In urlsOfItemsToScrape
        Debug.Print urlOfItem

        Dim htmlOfItemPage As MSHTML.HTMLDocument
        Set htmlOfItemPage = GetHtmlForItem(webClient, urlOfItem)

        Dim nameValuePairs As Collection
        If IsItemAWheelOnly(htmlOfItemPage) Then
            Set nameValuePairs = CreateNameValuePairsForWheelOnly(htmlOfItemPage)
        Else
            Set nameValuePairs = CreateNameValuePairsForWheelAndTirePackage(htmlOfItemPage)
        End If

        rowWriteIndex = rowWriteIndex + 1

        Dim nameValuePair As Variant
        For Each nameValuePair In nameValuePairs
            Dim columnWriteIndex As Long
            columnWriteIndex = GetColumnIndexOfHeader(columnIndexes, nameValuePair(0))

            If columnWriteIndex = 0 Then
                columnWriteIndex = columnIndexes.Count + 1
                columnIndexes.Add columnWriteIndex, Key:=nameValuePair(0)
                destinationSheet.Cells(1, columnWriteIndex).Value = nameValuePair(0)
            End If
            destinationSheet.Cells(rowWriteIndex, columnWriteIndex).Value = nameValuePair(1)
        Next nameValuePair
        DoEvents
    Next urlOfItem
End Sub

Private Function CreateNameValuePairsForWheelAndTirePackage(ByVal htmlOfItemPage As MSHTML.HTMLDocument) As Collection
    ' Should return a collection of 2-element arrays (where each 2-element array
    ' represents a name-value pair).
    Dim outputCollection As Collection
    Set outputCollection = New Collection

    Dim targetElement As MSHTML.IHTMLElement

    Set targetElement = htmlOfItemPage.getElementById("itemTitle")
    Debug.Assert Not (targetElement Is Nothing)
    outputCollection.Add CreateNameValuePair("Title", targetElement.innerText)

    Set targetElement = htmlOfItemPage.getElementById("mm-saleOrgPrc")
    If targetElement Is Nothing Then
        Set targetElement = htmlOfItemPage.getElementById("prcIsum")
        Debug.Assert Not (targetElement Is Nothing)
    End If
    outputCollection.Add CreateNameValuePair("Price", targetElement.innerText)

    Set targetElement = htmlOfItemPage.getElementById("descItemNumber")
    Debug.Assert Not (targetElement Is Nothing)
    outputCollection.Add CreateNameValuePair("eBay Item Number", targetElement.innerText)

    Set targetElement = htmlOfItemPage.getElementById("desc_div")
    Debug.Assert Not (targetElement Is Nothing)
    outputCollection.Add CreateNameValuePair("Description HTML", targetElement.innerHTML)

    Set CreateNameValuePairsForWheelAndTirePackage = outputCollection
End Function

Private Function CreateNameValuePairsForWheelOnly(ByVal htmlOfItemPage As MSHTML.HTMLDocument) As Collection
    ' Should return a collection of 2-element arrays (where each 2-element array
    ' represents a name-value pair).
    Dim outputCollection As Collection
    Set outputCollection = New Collection

    Dim targetElement As MSHTML.IHTMLElement

    Set targetElement = htmlOfItemPage.getElementById("itemTitle")
    Debug.Assert Not (targetElement Is Nothing)
    outputCollection.Add CreateNameValuePair("Title", targetElement.innerText)

    Set targetElement = htmlOfItemPage.getElementById("mm-saleOrgPrc")
    If targetElement Is Nothing Then
        Set targetElement = htmlOfItemPage.getElementById("prcIsum")
        Debug.Assert Not (targetElement Is Nothing)
    End If
    outputCollection.Add CreateNameValuePair("Price", targetElement.innerText)

    Set targetElement = htmlOfItemPage.getElementById("descItemNumber")
    Debug.Assert Not (targetElement Is Nothing)
    outputCollection.Add CreateNameValuePair("eBay Item Number", targetElement.innerText)

    Set targetElement = htmlOfItemPage.getElementById("desc_wrapper_ctr")
    Debug.Assert Not (targetElement Is Nothing)
    outputCollection.Add CreateNameValuePair("Description HTML", targetElement.innerHTML)

    Dim itemSpecifics As MSHTML.IHTMLTableSection
    Set itemSpecifics = htmlOfItemPage.querySelector(".itemAttr tbody")
    Debug.Assert Not (itemSpecifics Is Nothing)

    Dim tableRow As MSHTML.IHTMLTableRow
    For Each tableRow In itemSpecifics.Rows
        Debug.Assert 0 = (tableRow.Cells.Length Mod 2)
        Dim columnIndex As Long
        For columnIndex = 0 To (tableRow.Cells.Length - 1) Step 2
            Debug.Assert InStr(1, tableRow.Cells(columnIndex).className, "attrLabels", vbBinaryCompare)
            outputCollection.Add CreateNameValuePair(tableRow.Cells(columnIndex).innerText, tableRow.Cells(columnIndex + 1).innerText)
        Next columnIndex
    Next tableRow

    Set CreateNameValuePairsForWheelOnly = outputCollection
End Function

Private Function CreateNameValuePair(ByVal someName As String, ByVal someValue As String) As String()
    Dim outputArray(0 To 1) As String
    outputArray(0) = someName
    outputArray(1) = someValue
    CreateNameValuePair = outputArray
End Function

Private Function GetColumnIndexOfHeader(ByVal columnIndexes As Collection, ByVal header As String) As Long
    ' Should return a 1-based column index associated with "header".
    ' If "header" does not exist within collection, 0 is returned.
    On Error Resume Next
    GetColumnIndexOfHeader = columnIndexes(header)
    On Error GoTo 0
End Function

Этот код работает медленно по ряду причин:

  • Много времени тратится на ожидание ответа от сервера.
  • Каждый элемент очищается последовательно и синхронно.
  • Вывод записывается на лист по одной ячейке за раз (вместо использования массивов и сокращения числа операций чтения / записи, связанных с листом). ).
  • Нет переключения Application.Calculation или Application.ScreenUpdating.

Как только вы думаете, что код работает, вы захотите избавиться от этой строки If GetUrlsOfItemsToScrape.Count > 10 Then Exit Do в функция GetUrlsOfItemsToScrape. В противном случае вы не очистите все элементы.

Я оставил DoEvents внутри циклов Do, чтобы обеспечить адаптивность (возможно, за счет некоторой производительности)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...