웹 크롤링
Python으로 크롤링하는 법은 많았지만, 주로 Java를 사용하기 때문에 Java를 통해서 크롤링하기 위해 열심히 찾게 되었다.
그때 알게 된 것은 웹 크롤링하는 법은 2가지 방법이 있는데 정적인 크롤링 방법과 동적인 크롤링 방법이 있다.
정적인 크롤링 방법에는 주로 Jsoup을 활동하고
웹페이지 내용이 동적으로 로드되는 경우에는 Selenium이라는 라이브러리를 활용해야한다.
해당 크롤링은 이미 등록된 메뉴를 가져오면 되기 때문에 정적인 페이지이기 때문에 나는 jsoup을 활용하여 데이터를 가져왔다.
먼저 jsoup을 사용하기 위해선 라이브러리 추가가 필요하다.
pom.xml에 해당 항목을 추가하면 된다.
<!-- 크롤링 테스트 -->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.14.3</version>
</dependency>
먼저 jsoup을 통해서 데이터를 가져오기 위해선 Jsoup.connect(url).get();을 통해서 해당 HTML의 요소를 다 가져오고 그 요소에서 얻고싶은 정보에 해당하는 클래스나 id값을 가져와 값을 저장하는 방식으로 구현하여
csv로 데이터를 저장하도록 하였다. 해당하는 내용은 아래 코드이다.
package kr.or.ddit;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Comment;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.Node;
import org.jsoup.select.Elements;
import java.io.IOException;
public class Crawler {
public static void main(String[] args) {
try {
// URL
String url = "https://frankburger.co.kr/html/menu_1.html";
// 크롤링할 URL과 연결하고 해당 문서의 HTML을 가져올 연결을 생성한다
Document document = Jsoup.connect(url).get();
System.out.println(document);
// select 메서드는 html 문서에서 원하는 요소를 선택할 수 있고
// css선택자를 그대로 사용하기 때문에 id는 #, class는 .dlek
// .product_list 클래스 내의 dt 요소 선택
// 단품
Elements menuElements = document.select(".swiper-slide");
// 세트
// Elements drinkElements = document.select(".set_cont");
// CSV 헤더 출력
System.out.println("음료명;이미지 URL;설명;가격");
for (Element menuElement : menuElements) {
// 메뉴명 추출
String menuName = menuElement.select(".text_area .menu_ko").text();
// 이미지 URL 추출 (style 속성에서 background-image의 URL 추출)
String styleAttr = menuElement.select(".img_area").attr("style");
String imageUrl = "";
// 이미지 url을 가져올 때 해당 경로를 변환한다.
if (styleAttr.contains("background-image")) {
imageUrl = styleAttr.split("url\\(")[1].split("\\)")[0].replace("'", "").replace("\"", "").replace("..", "");
imageUrl = "https://frankburger.co.kr" + imageUrl; // 상대경로를 절대경로로 변경
}
// 메뉴 설명
String info = menuElement.select(".text_area .stext").text();
// 이미지 URL이 상대경로라면 절대경로로 변경
if (!imageUrl.startsWith("http")) {
imageUrl = "https:" + imageUrl;
}
// 출력
// System.out.println("음료명: " + drinkName);
// System.out.println("이미지 URL: " + imageUrl);
// System.out.println("설명: " + info);
// System.out.println("-------------------------");
// 가격 주석 추
String price = "";
for (Element element : menuElement.select(".text_area .stext")) {
for (Node node : element.childNodes()) {
if (node instanceof Comment) {
String comment = ((Comment) node).getData();
if (comment.contains("원")) {
price = comment.replace("<br>", "").replace(",","").trim();
price = price.substring(0, price.length()-1); // 숫자데이터만 필요하므로 원 제거
break;
}
}
}
}
// CSV 형식으로 콘솔에 출력
System.out.println(menuName + ";" + imageUrl + ";" + info + ";" + price);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
정렬
해당 기능은 본사의 입장으로 데이터를 뽑으면서 모든 데이터 마다의 정렬조건이 있으면 좋겠다는 생각으로 만들게 되었다
설정한 정렬조건 대로 desc, asc를 바꿔가는 형식으로 작업을 수행할 수 있도록 하였다
예시
코드
먼저 SQL에서 정렬조건을 넣기 위해 sql구문을 따로 작성해 include하였다
<select id="selectFrcs" resultMap="frcsMap" parameterType="hashMap">
SELECT *
FROM(
SELECT ROW_NUMBER() OVER (<include refid="sort_frcs"/> <include refid="orderby"/>) AS RNUM
, C.COM_NM AS FRCS_TYPE_NM
, B.REG_YMD
, O.COM_NM AS RGN_NM
, F.FRCS_NO
, F.FRCS_TYPE
, F.WARN_CNT
, F.OPBIZ_YMD
, F.CLSBIZ_YMD
, F.OPEN_TM
, F.DDLN_TM
, B.MBR_ID
, B.MNGR_ID
, B.BZENT_TELNO
, B.BZENT_NM
, B.RGN_NO
, B.BZENT_ZIP
, B.BZENT_ADDR
, B.BZENT_DADDR
, B.BZENT_TYPE
, M.MBR_NM
, M.MBR_ZIP
, M.MBR_ADDR
, M.MBR_DADDR
, M.MBR_TELNO
, M.MBR_BRDT
, M.MBR_EML_ADDR
, M.MBR_IMG_PATH
, N.MBR_NM AS MNGR_NM
, N.MBR_ZIP AS MNGR_ZIP
, N.MBR_ADDR AS MNGR_ADDR
, N.MBR_DADDR AS MNGR_DADDR
, N.MBR_TELNO AS MNGR_TELNO
, N.MBR_BRDT AS MNGR_BRDT
, N.MBR_EML_ADDR AS MNGR_EML_ADDR
FROM FRCS F
JOIN BZENT B ON F.FRCS_NO = B.BZENT_NO
JOIN COM_CODE C ON F.FRCS_TYPE=C.COM_NO
JOIN COM_CODE O ON B.RGN_NO=O.COM_NO
LEFT OUTER JOIN MEMBER M ON B.MBR_ID = M.MBR_ID
LEFT OUTER JOIN MEMBER N ON B.MNGR_ID = N.MBR_ID
WHERE 1=1
<include refid="frcs_search"/> -- 검색조건
)
WHERE RNUM BETWEEN ((#{currentPage} - 1) * #{size}) + 1 AND #{currentPage} * #{size}
</select>
여기서 sort_frcs가 정렬 조건 ORDER BY (칼럼명) 이고, orderby가 desc, asc를 표현한다
그리고 바로 이와같다
<!-- 정렬 조건 시작 -->
<sql id="sort_frcs">
<!-- 운영 상태 순 -->
<if test="sort == 'frcsType'">
ORDER BY F.FRCS_TYPE
</if>
<!-- 가맹점 이름 순 -->
<if test="sort == 'bzentNm'">
ORDER BY B.BZENT_NM
</if>
<!-- 가맹점주 이름 순 -->
<if test="sort == 'mbrNm'">
ORDER BY M.MBR_NM
</if>
<!-- 관리자 이름 순 -->
<if test="sort == 'mngrNm'">
ORDER BY N.MBR_NM
</if>
<!-- 최근 개업 일자 순 -->
<if test="sort == 'opbizYmd'">
ORDER BY F.OPBIZ_YMD
</if>
<!-- 폐업 일자 순 -->
<if test="sort == 'clsbizYmd'">
ORDER BY F.CLSBIZ_YMD
</if>
<!-- 지역순 -->
<if test="sort == 'rgnNo'">
ORDER BY B.RGN_NO
</if>
<!-- 등록일자순 -->
<if test="sort == 'regYmd'">
ORDER BY B.REG_YMD
</if>
</sql>
<sql id="orderby">
<if test="orderby == 'desc'">
DESC
</if>
<if test="orderby == 'asc'">
ASC
</if>
</sql>
<!-- 정렬 조건 끝 -->
그렇다면 이러한 형식을 전해줄 sort나 orderby같은 형식이 필요한데
나는 jsp 내에서 sort와 orderby를 전역변수로 선언하여 만약 해당 버튼을 누를 경우 그에 맞춰 sort와 orderby를 바꿔준다
데이터를 보내기 위해 th의 형식을 이런식으로 data-sort을 써서 무슨 정렬 조건인지에 대한 정보를 보냈다
그리고 무슨 정렬조건이 실행되었는지 확인하기 쉽게 active 효과를 넣어 확인할 수 있도록 하였다
thead
<thead>
<tr>
<th class="center" style="width: 5%;">번호</th>
<th class="center sort sort-frcs" data-sort="bzentNm" style="width: 20%;">가맹점명
<div class="sort-icon">
<div class="sort-arrow">
<span class="sort-asc">▲</span>
<span class="sort-desc">▼</span>
</div>
</div>
</th>
<th class="center sort sort-frcs" data-sort="mbrNm" style="width: 10%;">가맹점주명
<div class="sort-icon">
<div class="sort-arrow">
<span class="sort-asc">▲</span>
<span class="sort-desc">▼</span>
</div>
</div>
</th>
<th class="center sort sort-frcs" data-sort="mngrNm" style="width: 10%;">관리자명
<div class="sort-icon">
<div class="sort-arrow">
<span class="sort-asc">▲</span>
<span class="sort-desc">▼</span>
</div>
</div>
</th>
<th class="center sort sort-frcs active" data-sort="regYmd" style="width: 10%;">등록일자
<div class="sort-icon">
<div class="sort-arrow">
<span class="sort-asc">▲</span>
<span class="sort-desc active">▼</span>
</div>
</div>
</th>
<th class="center sort sort-frcs" data-sort="opbizYmd" style="width: 10%;">개업일자
<div class="sort-icon">
<div class="sort-arrow">
<span class="sort-asc">▲</span>
<span class="sort-desc">▼</span>
</div>
</div>
</th>
<th class="center sort sort-frcs" data-sort="opbizYmd" style="width: 10%;">폐업일자
<div class="sort-icon">
<div class="sort-arrow">
<span class="sort-asc">▲</span>
<span class="sort-desc">▼</span>
</div>
</div>
</th>
<th class="center sort sort-frcs" data-sort="rgnNo" style="width: 15%;">지역
<div class="sort-icon">
<div class="sort-arrow">
<span class="sort-asc">▲</span>
<span class="sort-desc">▼</span>
</div>
</div>
</th>
<th class="center sort sort-frcs" data-sort="frcsType" style="width: 10%;">상태
<div class="sort-icon">
<div class="sort-arrow">
<span class="sort-asc">▲</span>
<span class="sort-desc">▼</span>
</div>
</div>
</th>
</tr>
</thead>
그리고 이걸 변환시켜주는 javascript는 이러한 형식이다
<script>
let sort = 'regYmd'; // 초기 검색을 위해 기본값인 sort조건을 미리 넣었다
let orderby = 'desc';
$(function(){
$('.sort').on('click',function(){
// 첫 번째 자식인 .sort-asc와 두 번째 자식인 .sort-desc를 선택
var sortAsc = $('.sort-arrow', this).find('.sort-asc');
var sortDesc = $('.sort-arrow', this).find('.sort-desc');
sort = $(this).data('sort'); // data-sort를 통해 변수를 받음
$('.sort').removeClass('active');
$(this).addClass('active');
// 첫 번째 자식이 active 클래스가 있는지 확인
if (sortAsc.hasClass('active')) { // desc
// 모든 th의 active 클래스를 제거
$('.sort-arrow .sort-asc, .sort-arrow .sort-desc').removeClass('active');
sortDesc.addClass('active');
orderby = 'desc';
} else{ // asc
// 모든 th의 active 클래스를 제거
$('.sort-arrow .sort-asc, .sort-arrow .sort-desc').removeClass('active');
sortAsc.addClass('active');
orderby = 'asc';
}
currentPage=1;
selectFrcsAjax(); // 테이블 정보를 받는 비동기 ajax보내는 함수를 실행
})
})
</script>
CSS
정렬의 화살표의 위치를 맞추기 위해 선택한 css
@charset "UTF-8";
/* 정렬 */
.sort{
position: relative; /* 버튼을 TH 내부에 고정하기 위해 필요 */
cursor: pointer;
}
.sort.active {
color : var(--green--5);
}
th .sort-icon {
position: absolute;
top: 50%;
margin-left: 5%;
transform: translateY(-50%);
display: inline-block;
}
.sort-arrow .active{
color : var(--green--5);
}
.sort-arrow span{
color : var(--border--primary);
font-size: 0.5rem;
margin: 1px;
}
.sort-arrow{
display: grid;
}
한번 정렬에 대한 조건을 만들고 난 후, 이후 데이터 뽑아낼 때는 똑같은 폼에 data-sort형식만 바꾼 형태로 정렬에 대한 조건이 어렵지 않게 수행할 수 있었다!
'Project' 카테고리의 다른 글
ERP 프로젝트 - 최저가 표 (0) | 2024.10.11 |
---|---|
ERP 프로젝트 - 오라클 함수 FN (0) | 2024.10.11 |
[Project] 학사관리 프로젝트 (0) | 2024.07.08 |
[Project] db를 활용해 콘솔 통해 구현하는 도서관 시스템 (0) | 2024.04.20 |
미니 프로젝트 - Scanner 계산기 만들기 (0) | 2024.03.21 |