엑셀 시트에 검색어를 넣고 매크로를 돌리면 쿠팡 검색 결과의 상품명을 페이지마다 읽어 오는 뼈대 코드입니다. 브라우저를 띄우지 않고 HTTP 요청으로 HTML만 받아서, 엑셀 안에서 DOM처럼 다루는 방식이에요. 완성본이라기보다는 "여기서 상품 정보를 뽑으면 된다"는 지점까지 만들어 둔 틀이라, 실제로 쓸 때는 원하는 항목을 꺼내는 부분을 채워 넣어야 합니다.

Sub 검색()

'/****************** 기존코드
    Dim s As Shape
    For Each s In ActiveSheet.Shapes
        Debug.Print s.Name
        If s.Name = "이미지" Then s.Delete
    Next
    Range("5:10000").ClearContents
'/****************** << 여기까지

'/****************** 변수선언
    Dim URL As String, query As String, page As Integer
    Dim IE As Object, T As String, Referer As String, Cookie As String
    Dim v As Variant, i As Integer
    Dim htmlfile As Object, productList As Object, elem As Object

'/****************** 설정
    Set IE = CreateObject("WinHttp.WinHttpRequest.5.1")
    Set htmlfile = CreateObject("htmlfile") ': htmlfile.Open: htmlfile.Write ""
    Cookie = "sid=; "   '// 필수 쿠키(없을시 응답 없음, 시간 초과)
    query = fn.encode([D2])

'/****************** 무한루프 시작
    Do
        page = page + 1
        URL = "https://www.coupang.com/np/search?"
        URL = URL & "rocketAll=false"
        URL = URL & "&q=" & query
        URL = URL & "&isPriceRange=false"
        URL = URL & "&page=" & page
        URL = URL & "&filterSetByUser=true"
        URL = URL & "&channel=user"
        URL = URL & "&rating=0"
        URL = URL & "&sorter=scoreDesc"
        URL = URL & "&listSize=100"

        With IE
            .Open "GET", URL
            .SetRequestHeader "Host", "www.coupang.com"
            .SetRequestHeader "User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/110.0"
            .SetRequestHeader "Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"
            If Len(Cookie) Then .SetRequestHeader "Cookie", Cookie
            .SetRequestHeader "Origin", "https://www.coupang.com/"
            .Send
            .WaitForResponse: DoEvents
            T = .ResponseText
        End With

        SetClipboard T: Stop    '// 클립보드에 가져온 HTML 소스 넣고 브레이크
        'htmlfile.Write T
        htmlfile.body.innerHTML = T
        Set productList = htmlfile.getElementById("productList"): i = 0
        Do
            i = i + 1
            Set elem = getXPathElement("/li[" & i & "]", productList)
            If Not elem Is Nothing Then
'// 이곳에서 상품 크롤링하면 됩니다.
                Debug.Print getXPathElement("/a/dl/dd/div/div[2]", elem).innerTEXT    '// 상품명 출력
            End If
        Loop Until elem Is Nothing
        Stop    '// 브레이크
    Loop Until i < 101
    MsgBox "모든 상품을 가져왔습니다."
End Sub

'// 클립보드에 텍스트 넣기
Public Function SetClipboard(ByRef sText As String) As Boolean ' ### 리턴값: 성공 여부
    On Error GoTo nErr
    Static Clipboard As Object
    If Clipboard Is Nothing Then Set Clipboard = CreateObject("new:{1C3B4210-F441-11CE-B9EA-00AA006B1A69}")  '// Microsoft Forms 2.0 Object Library
    Clipboard.SetText sText: Clipboard.PutInClipboard
    SetClipboard = True
nErr:
End Function

'// xPath를 이용한 크롤링을 위한 함수
Public Function getXPathElement(sXPath As String, objElement As Object) As Object
    On Error GoTo ErrPass
    Dim sXPathArray() As String
    Dim sNodeName As String, sNodeNameIndex As String
    Dim sRestOfXPath As String
    Dim lNodeIndex As Long, lCount As Long
    sXPathArray = Split(sXPath, "/")
    sNodeNameIndex = sXPathArray(1)
    If Not InStr(sNodeNameIndex, "[") > 0 Then
        sNodeName = sNodeNameIndex
        lNodeIndex = 1
    Else
        sXPathArray = Split(sNodeNameIndex, "[")
        sNodeName = sXPathArray(0)
        lNodeIndex = CLng(Left(sXPathArray(1), Len(sXPathArray(1)) - 1))
    End If
    sRestOfXPath = Right(sXPath, Len(sXPath) - (Len(sNodeNameIndex) + 1))
    Set getXPathElement = Nothing
    For lCount = 0 To objElement.ChildNodes().Length - 1
        If UCase(objElement.ChildNodes().Item(lCount).nodeName) = UCase(sNodeName) Then
            If lNodeIndex = 1 Then
                If sRestOfXPath = "" Then
                    Set getXPathElement = objElement.ChildNodes().Item(lCount)
                Else
                    Set getXPathElement = getXPathElement(sRestOfXPath, objElement.ChildNodes().Item(lCount))
                End If
            End If
            lNodeIndex = lNodeIndex - 1
        End If
    Next lCount
ErrPass:
End Function

요청 부분

맨 앞 "기존코드" 블록은 원래 시트에 있던 정리 작업입니다. 이름이 "이미지"인 도형을 지우고 5행부터 아래 데이터를 비웁니다. 검색어는 D2 셀에서 읽어 fn.encode 로 URL 인코딩하는데, fn 은 원래 프로젝트에 있는 별도 모듈이라 여기에는 없습니다. 엑셀 2013 이상이면 WorksheetFunction.EncodeURL([D2]) 로 대신할 수 있습니다.

광고

검색 URL에는 정렬(sorter=scoreDesc), 한 페이지 100개(listSize=100), 페이지 번호가 들어갑니다. 주석에 적어 둔 대로 당시에는 sid= 쿠키가 비어 있기라도 해야 응답이 왔고, 쿠키 헤더가 아예 없으면 응답 없이 시간 초과가 났습니다. 이런 식으로 사이트마다 "이게 없으면 안 되는" 헤더가 있어서, 브라우저 개발자 도구의 요청 헤더를 하나씩 빼 보면서 찾는 수밖에 없습니다.

HTML을 DOM으로 다루기

받은 HTML 문자열을 CreateObject("htmlfile") 로 만든 빈 HTML 문서의 body.innerHTML 에 넣으면, IE 엔진이 파싱해서 getElementById 같은 DOM 메서드를 쓸 수 있게 됩니다. 브라우저 없이 HTML을 구조적으로 읽는 가장 가벼운 방법입니다.

상품 목록은 id="productList" 인 요소 아래에 li 로 하나씩 들어 있어서, getXPathElement("/li[" & i & "]", productList) 로 i번째 상품을 꺼냅니다. getXPathElement 는 진짜 XPath 엔진이 아니라 /태그[번호]/태그... 형태의 경로만 따라 내려가는 간단한 함수입니다. 경로의 첫 단계를 잘라 자식 노드 중 같은 태그명의 n번째를 찾고, 나머지 경로가 있으면 자기 자신을 다시 호출합니다. 찾지 못하면 Nothing 을 돌려주니 Loop Until elem Is Nothing 으로 목록 끝을 판단할 수 있습니다.

페이지 넘김과 Stop

바깥 루프의 종료 조건 Loop Until i < 101 이 페이지 넘김의 핵심입니다. 안쪽 루프가 끝나면 i 는 "찾은 상품 수 + 1"이 됩니다. 한 페이지에 100개가 꽉 차 있으면 i 가 101이라 다음 페이지로 넘어가고, 100개보다 적으면 마지막 페이지로 보고 끝냅니다.

코드에 Stop 이 두 번 들어 있는데 둘 다 디버깅용입니다. 첫 번째는 받은 HTML을 클립보드에 넣고 멈추니, 메모장에 붙여 넣어서 원하는 데이터가 응답에 실제로 들어 있는지 확인할 때 씁니다. 자바스크립트로 나중에 그려지는 내용은 이 HTML에 없으니 이 단계 확인이 중요합니다. 실제로 돌릴 때는 두 Stop 을 지우거나 주석 처리하세요.

상품명 경로 /a/dl/dd/div/div[2] 처럼 태그 구조에 기대는 방식은 사이트가 조금만 바뀌어도 깨집니다. 그리고 쇼핑몰은 자동 수집을 강하게 막는 편이라 짧은 간격으로 반복 요청하면 바로 차단될 수 있으니, 페이지 사이에 충분히 쉬고 이용 약관도 확인하고 쓰는 게 좋습니다.