Technology[Back]

11번가 쇼핑API(1)

Thunderland 2023. 8. 14. 09:50

이번 프로젝트에서 대량의 쇼핑데이터가 필요했는데 제가 고려한 방법들은 아래와 같습니다.

(1). Jsoup : java의 정적 크롤링 라이브러리로 처음 시도했던 방법입니다. 그런데 정적이다보니 이미지로딩을 기다리지 않아 이미지가 대부분 없거나 Loading 시 보여지는 대체이미지가 크롤링되어서 이미지를 크롤링할 때는 정적 크롤링은 사용하기 쉽지 않습니다.

(2). Selenium : java의 동적 크롤링 라이브러리로 이미지를 기다려여하는 등 javaScript의 행위가 필요한 경우 사용할 수 있는 크롤링 라이브러리입니다. 크롤링이 원하는대로 이뤄지긴하는데 실제로 사용자가 브라우저를 띄워서 검색하는 것과 동일한 방식으로 크롤링이 이뤄지다보니 웹드라이버를 메모리상에 올리는데 메모리 공간이 많이 필요합니다. AWS 프리티어 서버를 사용하는 입장에서 단일쓰레드 환경에서는 실행할 수 있으나 저의 경우 적은 계정으로 다수의 데이터를 요하다보니 다중쓰레드 환경이 필수인데 적은 메모리 환경에서 웹드라이버를 여러개 띄우기는 쉽지 않았습니다.

(3). NAVER API + Jsoup : 자체적인 크롤링으로 온전히 구현할 수 없자 OPEN API를 사용했고 처음 시도한 것이 NAVER API였습니다. 네이버 쇼핑 API의 경우 하루 호출 제한이 25,000회이고 1회당 최대 데이터 개수가 100개여서 총 2,500,000개의 데이터를 하루에 적재할 수 있어 부족하지 않은 데이터 양이었으나 배송비 정보를 제공해주지 않았습니다. 배송비 정보는 이미지 데이터가 아니어서 정적 크롤링 Jsoup을 이용해 추가적으로 얻고자 했으나 네이버 쇼핑의 경우 상품번호와 같은 고유한 정보로 검색을 지원하지 않아 직접 검색어로써 해당 상품을 찾아야하는데 검색엔진을 사용하다 보니 동일한 제목을 검색해도 해당 상품이 아닌 상품이 검색되거나 아예 상품검색이 되지 않는 등 실행결과가 예측되지 않는 크롤링 환경이 구축되자 해당 방법을 포기하였습니다.

(4). 11번가 API : API에서 제가 원하는 모든 정보(이미지, 배송비, 상품가격 등)를 제공해줄 수 있는 API를 찾던 중 발견한 API입니다. 상세상품API까지 제공해줘서 배송비 등의 디테일한 정보까지 개발자가 얻을 수 있어 이번 프로젝트에 사용하였습니다.

이제부터는 11번가 API 사용한 방법에 대해 설명드리겠습니다.

( 주의 : 일반적인 크롤링과 동일하게 API방식 또한 해당 서버에 요청을 보내는 방식이라 해당 서버의 응답지연, 응답없음 , API 호출에 실패했다는 등의 500번 502번 오류뿐 아니라 여러 오류가 발생할 수 있어 데이터 적재 시스템의 안정성을 위해 재시도 로직을 추가적으로 작성하거나 오류를 제어할 수 있는 별도의 로직이 반드시 필요합니다. )

 

서비스 등록·확인 | 11번가 OPEN API (11st.co.kr)

 

고객감동 No.1, 11번가 - 11번가

 

openapi.11st.co.kr

11번가에서 제공해주는 OPEN API 사이트로 접속한 후 로그인을 진행하고 상단의 서비스 등록확인 탭에 들어가셔서 해당 API를 사용할 서비스의 주소와 간략한 설명 등을 작성하시면 됩니다. 

등록을 완료하셨다면 위와 같은 화면으로 접근이 가능할텐데 우측의 11ST OPEN API KEY를 API 요청시 사용하시면 됩니다.

1. 검색어를 통한 상품리스트 조회

// 검색어를 통한 상품리스트 조회 URL
apiUrl = "http://openapi.11st.co.kr/openapi/OpenApiService.tmall" + "?key=" + API_KEY + "&apiCode=ProductSearch" + "&keyword=" + encodedQuery + "&pageNum=" + pageNumber.get() + "&pageSize=" + display;

// API 요청을 위한 HttpURLConnection 객체 생성
url = new URL(apiUrl);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");

// API 응답 확인
responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
    // API 응답 데이터 읽기
    in = new BufferedReader(new InputStreamReader(connection.getInputStream(), "EUC-KR"));
    response = new StringBuilder();
    while ((line = in.readLine()) != null) {
        response.append(line);
    }
    in.close();

    responseXml = response.toString();
}
else throw new Exception("API 요청에 실패했습니다. 응답 코드: " + responseCode);

JAXBContext jaxbContext = JAXBContext.newInstance(ProductSearchResponse.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
// XML 응답을 ProductSearchResponse 객체로 만들어서 사용
ProductSearchResponse responseString = (ProductSearchResponse) unmarshaller.unmarshal(new StringReader(responseXml));

Request request = responseString.getRequest();
log.get().info("Processing Time: " + request.getProcessingTime());

Products products = responseString.getProducts();
log.get().info("Total Count: " + products.getTotalCount());

List<Product> productList = products.getProductList();

검색어를 통한 상품리스트 조회 시 앞서 발급받은 11ST OPEN API KEY를 API_KEY 변수에 할당해서 사용하고apiCode=ProductSearch 파라미터를 사용하고 keyword 파라미터에 원하는 검색어를 넣으시면 됩니다.

11번가 API의 모든 응답은 XML방식으로 받기 때문에 저는 JAXBContext를 이용해서 XML 정보를 파싱해서 객체로 변경하여 사용하였습니다. 위에서 사용한 객체의 CLASS 정보는 다음과 같습니다.

(1). ProductSearchResponse

import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlType;

@XmlRootElement(name = "ProductSearchResponse")
@XmlType(propOrder = {"request", "products"})
// XML정보를 매핑할 CLASS
public class ProductSearchResponse {
    private Request request;
    private Products products;

    public Request getRequest() {
        return request;
    }

    @XmlElement(name = "Request")
    public void setRequest(Request request) {
        this.request = request;
    }

    public Products getProducts() {
        return products;
    }

    @XmlElement(name = "Products")
    public void setProducts(Products products) {
        this.products = products;
    }
}

(2). Request

import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "Request")
// XML정보를 매핑할 CLASS
public class Request {
    private Arguments arguments;
    private String processingTime;

    public Arguments getArguments() {
        return arguments;
    }

    @XmlElement(name = "Arguments")
    public void setArguments(Arguments arguments) {
        this.arguments = arguments;
    }

    public String getProcessingTime() {
        return processingTime;
    }

    @XmlElement(name = "ProcessingTime")
    public void setProcessingTime(String processingTime) {
        this.processingTime = processingTime;
    }
}

(3) Products

import java.util.List;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "Products")
// XML정보를 매핑할 CLASS
public class Products {
    private int totalCount;
    private List<Product> productList;

    public int getTotalCount() {
        return totalCount;
    }

    @XmlElement(name = "TotalCount")
    public void setTotalCount(int totalCount) {
        this.totalCount = totalCount;
    }

    public List<Product> getProductList() {
        return productList;
    }

    @XmlElement(name = "Product")
    public void setProductList(List<Product> productList) {
        this.productList = productList;
    }
}

(4). Product

import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "Product")
// XML정보를 매핑할 CLASS
public class Product {
    private String productCode;
    private String productName;
    private int productPrice;
    private String productImage;
    private String productImage100;
    private String productImage110;
    private String productImage120;
    private String productImage130;
    private String productImage140;
    private String productImage150;
    private String productImage170;
    private String productImage200;
    private String productImage250;
    private String productImage270;
    private String productImage300;
    private String text1;
    private String text2;
    private String sellerNick;
    private String seller;
    private int sellerGrd;
    private int rating;
    private String detailPageUrl;
    private int salePrice;
    private String delivery;
    private int reviewCount;
    private int buySatisfy;
    private String minorYn;
    private Benefit benefit;
    private String basicImage;
    private String addImage1;
    private String addImage2;
    private String addImage3;
    private String imageL300;
    private String point;
    private String chip;
    private String installment;
    private String shipFee;
    private String sellSatisfaction;
    private String sellGrade;

    public String getProductCode() {
        return productCode;
    }

    @XmlElement(name = "ProductCode")
    public void setProductCode(String productCode) {
        this.productCode = productCode;
    }

    public String getProductName() {
        return productName;
    }

    @XmlElement(name = "ProductName")
    public void setProductName(String productName) {
        this.productName = productName;
    }

    public int getProductPrice() {
        return productPrice;
    }

    @XmlElement(name = "ProductPrice")
    public void setProductPrice(int productPrice) {
        this.productPrice = productPrice;
    }

    public String getProductImage() {
        return productImage;
    }

    @XmlElement(name = "ProductImage")
    public void setProductImage(String productImage) {
        this.productImage = productImage;
    }

    public String getProductImage100() {
		return productImage100;
	}

    @XmlElement(name = "ProductImage100")
	public void setProductImage100(String productImage100) {
		this.productImage100 = productImage100;
	}

	public String getProductImage110() {
		return productImage110;
	}

	@XmlElement(name = "ProductImage110")
	public void setProductImage110(String productImage110) {
		this.productImage110 = productImage110;
	}

	public String getProductImage120() {
		return productImage120;
	}

	@XmlElement(name = "ProductImage120")
	public void setProductImage120(String productImage120) {
		this.productImage120 = productImage120;
	}

	public String getProductImage130() {
		return productImage130;
	}

	@XmlElement(name = "ProductImage130")
	public void setProductImage130(String productImage130) {
		this.productImage130 = productImage130;
	}

	public String getProductImage140() {
		return productImage140;
	}

	@XmlElement(name = "ProductImage140")
	public void setProductImage140(String productImage140) {
		this.productImage140 = productImage140;
	}

	public String getProductImage150() {
		return productImage150;
	}

	@XmlElement(name = "ProductImage150")
	public void setProductImage150(String productImage150) {
		this.productImage150 = productImage150;
	}

	public String getProductImage170() {
		return productImage170;
	}

	@XmlElement(name = "ProductImage170")
	public void setProductImage170(String productImage170) {
		this.productImage170 = productImage170;
	}

	public String getProductImage200() {
		return productImage200;
	}

	@XmlElement(name = "ProductImage200")
	public void setProductImage200(String productImage200) {
		this.productImage200 = productImage200;
	}

	public String getProductImage250() {
		return productImage250;
	}

	@XmlElement(name = "ProductImage250")
	public void setProductImage250(String productImage250) {
		this.productImage250 = productImage250;
	}

	public String getProductImage270() {
		return productImage270;
	}

	@XmlElement(name = "ProductImage270")
	public void setProductImage270(String productImage270) {
		this.productImage270 = productImage270;
	}

	public String getProductImage300() {
		return productImage300;
	}

	@XmlElement(name = "ProductImage300")
	public void setProductImage300(String productImage300) {
		this.productImage300 = productImage300;
	}

	public String getText1() {
		return text1;
	}

	@XmlElement(name = "Text1")
	public void setText1(String text1) {
		this.text1 = text1;
	}

	public String getText2() {
		return text2;
	}

	@XmlElement(name = "Text2")
	public void setText2(String text2) {
		this.text2 = text2;
	}

	public String getSellerNick() {
		return sellerNick;
	}

	@XmlElement(name = "SellerNick")
	public void setSellerNick(String sellerNick) {
		this.sellerNick = sellerNick;
	}

	public String getSeller() {
		return seller;
	}

	@XmlElement(name = "Seller")
	public void setSeller(String seller) {
		this.seller = seller;
	}

	public int getSellerGrd() {
		return sellerGrd;
	}

	@XmlElement(name = "SellerGrd")
	public void setSellerGrd(int sellerGrd) {
		this.sellerGrd = sellerGrd;
	}

	public int getRating() {
		return rating;
	}

	@XmlElement(name = "Rating")
	public void setRating(int rating) {
		this.rating = rating;
	}

	public String getDetailPageUrl() {
		return detailPageUrl;
	}

	@XmlElement(name = "DetailPageUrl")
	public void setDetailPageUrl(String detailPageUrl) {
		this.detailPageUrl = detailPageUrl;
	}

	public int getSalePrice() {
		return salePrice;
	}

	@XmlElement(name = "SalePrice")
	public void setSalePrice(int salePrice) {
		this.salePrice = salePrice;
	}

	public String getDelivery() {
		return delivery;
	}

	@XmlElement(name = "Delivery")
	public void setDelivery(String delivery) {
		this.delivery = delivery;
	}

	public int getReviewCount() {
		return reviewCount;
	}

	@XmlElement(name = "ReviewCount")
	public void setReviewCount(int reviewCount) {
		this.reviewCount = reviewCount;
	}

	public int getBuySatisfy() {
		return buySatisfy;
	}

	@XmlElement(name = "BuySatisfy")
	public void setBuySatisfy(int buySatisfy) {
		this.buySatisfy = buySatisfy;
	}

	public String getMinorYn() {
		return minorYn;
	}

	@XmlElement(name = "MinorYn")
	public void setMinorYn(String minorYn) {
		this.minorYn = minorYn;
	}

	public Benefit getBenefit() {
        return benefit;
    }

    @XmlElement(name = "Benefit")
    public void setBenefit(Benefit benefit) {
        this.benefit = benefit;
    }

	public String getBasicImage() {
		return basicImage;
	}

	@XmlElement(name = "BasicImage")
	public void setBasicImage(String basicImage) {
		this.basicImage = basicImage;
	}

	public String getAddImage1() {
		return addImage1;
	}

	@XmlElement(name = "AddImage1")
	public void setAddImage1(String addImage1) {
		this.addImage1 = addImage1;
	}

	public String getAddImage2() {
		return addImage2;
	}

	@XmlElement(name = "AddImage2")
	public void setAddImage2(String addImage2) {
		this.addImage2 = addImage2;
	}

	public String getAddImage3() {
		return addImage3;
	}

	@XmlElement(name = "AddImage3")
	public void setAddImage3(String addImage3) {
		this.addImage3 = addImage3;
	}

	public String getImageL300() {
		return imageL300;
	}

	@XmlElement(name = "ImageL300")
	public void setImageL300(String imageL300) {
		this.imageL300 = imageL300;
	}

	public String getPoint() {
		return point;
	}

	@XmlElement(name = "Point")
	public void setPoint(String point) {
		this.point = point;
	}

	public String getChip() {
		return chip;
	}

	@XmlElement(name = "Chip")
	public void setChip(String chip) {
		this.chip = chip;
	}

	public String getInstallment() {
		return installment;
	}

	@XmlElement(name = "Installment")
	public void setInstallment(String installment) {
		this.installment = installment;
	}

	public String getShipFee() {
		return shipFee;
	}

	@XmlElement(name = "ShipFee")
	public void setShipFee(String shipFee) {
		this.shipFee = shipFee;
	}

	public String getSellSatisfaction() {
		return sellSatisfaction;
	}

	@XmlElement(name = "SellSatisfaction")
	public void setSellSatisfaction(String sellSatisfaction) {
		this.sellSatisfaction = sellSatisfaction;
	}

	public String getSellGrade() {
		return sellGrade;
	}

	@XmlElement(name = "SellGrade")
	public void setSellGrade(String sellGrade) {
		this.sellGrade = sellGrade;
	}
}

다음 게시글로 이어집니다.