[스파르타 코딩클럽 8기] 개발일지 #3 20.06.19

이홍희·2020년 6월 19일
0
post-thumbnail

1. 도서관 홈페이지 크롤링

지난 번에 서강대 로욜라 도서관에서 특정 도서의 도서 제목, 이미지, 저자, 출판사, 출판연도, 위치, 대출현황을 가져와서 터미널에서 print하는 작업까지 했다. 그 이후 연세대, 이대 도서관에서도 같은 방식으로 크롤링하는 함수를 만들었고 그 정보를 print하는 함수가 아니라 dictionary 형태로 저장하여 ajax 통신으로 html에 전달하는 함수로 변경하고자 했다. 하지만 그 과정에서 여러 어려움을 겪게 되는데..

  • 대학 도서관 홈페이지마다 제공하는 정보와 그 형태가 다름

연대는 캠퍼스가 두 개여서 도서관도 두 개였고, 대출현황 정보를 표로 제공하지 않았으며 이대 도서관은 전자책을 지원해주는 첨단 도서관이었다. 그래서 처음에는 가져온 정보를 jsonify할 때 대학별로 다른 형태로 정리했다. 하지만 html에서 그 정보를 보여줄 때는 각 학교별 정보의 형태가 일관되어 있는게 구현하기 훨씬 편리했다.

  • Pandas의 dataframe을 ajax로 전달하고 html에서 구현하기 -> 포기

도서관 홈페이지에서 대출 현황에 대한 표를 크롤링할 때 표를 그대로 가져온 후 read_html()을 이용해서 dataframe 형태로 변경한 후 이걸 html에서 그대로 보여주려고 한 야심찬 계획을 세웠다. 하지만 아래와 같은 에러를 마주하며 표의 정보를 따로 크롤링해 javascript로 동적으로 표를 추가하는 것으로 변경했다.

TypeError: Object of type 'DataFrame' is not JSON serializable

  • 여러 무수한 고민과 에러들

큰따옴표 안에서 따옴표를 또 쓰려면 작은따옴표를 써야하는데 무심결에 큰따옴표를 쓰기도 하고, 함수를 확장할 것을 고려하지 않고 변수의 이름을 정해서 변수가 모호해지고, 다중 for문 안에서 list나 dictionary 형태의 변수를 사용할 때 변수의 영역이 헷갈려서 변수를 어디서 정의할지 고민하고 등등등..

이 과정을 거쳐서 아래와 같은 형태로 정보를 구성하기로 결정했다. 이를 dictionary에 각 도서관 홈페이지에서 가져온 도서 정보를 담고 원하는 수만큼 list에 그 dictionary를 담는 방식으로 구현했다.

api = {'libraryStatus':
{
'sogang': [],
'yonsei': [],
'ewha' : []
}
}

def sogang_search(keyword):
    url_sogang = "https://library.sogang.ac.kr/searchTotal/result?st=KWRD&si=TOTAL&q=" + keyword
    driver.get(url_sogang)
    time.sleep(2)
     # 대출현황 보기 위해 토글 열기
    jss = driver.find_elements_by_xpath("//p[@class='location']")
    js_count = 0
    for js in jss:
        js.click()
        time.sleep(1)
        js_count += 1
        if js_count == 2:
            break

    # 크롤링 토대
    req = driver.page_source
    headers = {'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36'}
    soup = BeautifulSoup(req, 'html.parser')
    lis = soup.select("#catalogs > ul > li")
    sogang_list = []
    count = 0

    for li in lis:
        sogang_dict = {}
        status_list = []
        loc_list = []
        # 가져와야 할 도서 정보 경로 지정
        title = li.select_one("p > a").text
        bookcover = li.select_one("div.information > p.bookCover > img")['src']
        author = li.select_one("div.information > p:nth-child(2)").text
        company = li.select_one("div.information > p:nth-child(3)").text
        year = li.select_one("div.information > p:nth-child(4)").text
        book_url = "https://library.sogang.ac.kr/" +li.select_one("p > a")['href']
        loc = li.select_one("div.holdingInfo > div > p > a").text
        loc_list.append(loc)
        trs = li.select("div.holdingInfo > div > div > div > table > tbody > tr")
        
        # 표 안에 있는 정보들은 한번 더 타고 들어가야 함.
        for tr in trs:
            status_dict = {}
            book_loc = tr.select_one("td.location").text
            callnum = tr.select_one("td.callNum").text
            borrow = tr.select_one("td.bookStatus").text

            status_dict['book_loc'] = book_loc
            status_dict['callnum'] = callnum
            status_dict['borrow'] = borrow
            status_list.append(status_dict)
        
        # dict에 추가
        sogang_dict['title']=title
        if (bookcover[0] != '/'):
            sogang_dict['bookcover']=bookcover
        sogang_dict['author']=author
        sogang_dict['company']=company
        sogang_dict['year']=year
        sogang_dict['book_url'] = book_url
        sogang_dict['loc']=loc_list
        sogang_dict['status'] = status_list

        sogang_list.append(sogang_dict)
        
        # 원하는 권수만큼 크롤링하고 반복문 종료.
        count += 1
        if count == 2:
            break

        # api에 추가
        api['libraryStatus']['sogang']= sogang_list
        api['libraryStatus']['url_sogang'] = url_sogang

2. 프론트엔드에서 구현하기


Ajax GET 방식으로 전달받은 데이터를 도서관별 도서 검색 결과 형식으로 프론트엔드에서 구현하고자 했다. 이를 위해 각 학교별로, 각 도서 정보별로 접근해서 변수를 선언해준 뒤에 그 정보가 들어가야할 자리에 넣어주고 $("#id").append(); 방식으로 html에 추가해주었다. 이 작업을 연세대 도서관 검색 결과를 보여주는 것부터 시작해 완성하고 그 코드를 다른 학교에도 적용할 수 있도록 함수로 만들었다. 연세대 도서관은 2개여서 위치정보가 2개인 점, 이대 도서관에는 위치정보와 대출현황이 없는 전자책이 있는 점을 고려해서 조건문으로 예외 처리를 했다. 또한 함수로 만들 때 여러 필요한 자리에 인수를 꼼꼼하게 잘 넣어주는 것이 중요했다.

또한 fontawesome(https://fontawesome.com/v4.7.0/icons/)에 있는 아이콘을 활용해 누르면 대출현황 표를 보여주는 버튼을 만들었고 대출현황 표는 표를 간편하게 HTML 태그로 만들어주는 사이트(https://www.tablesgenerator.com/html_tables)를 활용해 만들었다.

function make_card(uni_name, uni_list=[]) {
                for (let i = 0; i < uni_list.length; i++) {
                    let title = uni_list[i]['title']
                    let author = uni_list[i]['author']
                    let bookcover = uni_list[i]['bookcover']
                    let book_url = uni_list[i]['book_url']
                    let year = uni_list[i]['year']
                    let company = uni_list[i]['company']
                    let loc = uni_list[i]['loc']
                    let status = uni_list[i]['status']

                    if (loc.length == 1) {
                        let temp_html = `<div class="media">
                                                <img src="${bookcover}" onerror="this.src='https://library.yonsei.ac.kr/image/ko/solution/common/ico/type_m_M.png'" class="align-self-center mr-3"
                                                style="width: 85px; height: 125px; border: 1px solid #d0d0d0;">
                                                <div class="media-body">
                                                    <a href="${book_url}">
                                                        <h5 class="mt-0">${title}</h5>
                                                    </a>
                                                    <p>
                                                        ${author} | ${company} | ${year}
                                                    </p>
                                                    <p class="mb-0">
                                                        ${loc[0]}
                                                    </p>
                                                    <div onclick="openclose('${uni_name}-status${i}')">
                                                        <i class="fa fa-caret-down" aria-hidden="true"></i>
                                                    </div>
                                                    <div class="status" style='display : none' id="${uni_name}-status${i}">
                                                        <table class="tg">
                                                            <thead>
                                                                <tr>
                                                                    <th class="tg-id4c">No.</th>
                                                                    <th class="tg-ufv0">소장처 / 자료실</th>
                                                                    <th class="tg-id4c">청구기호</th>
                                                                    <th class="tg-id4c">도서상태</th>
                                                                </tr>
                                                            </thead>
                                                            <tbody id='${uni_name}-table${i}'>
                                                            </tbody>
                                                        </table>
                                                    </div>
                                                </div>
                                            </div>`
                        $("#" + uni_name + "-blocks").append(temp_html);
                    } else if (typeof loc[0] == 'undefined') {
                        let temp_html = `<div class="media">
                                                <img src="${bookcover}" onerror="this.src='https://library.yonsei.ac.kr/image/ko/solution/common/ico/type_m_M.png'" class="align-self-center mr-3"
                                                style="width: 85px; height: 125px; border: 1px solid #d0d0d0">
                                                <div class="media-body">
                                                    <a href="${book_url}">
                                                        <h5 class="mt-0">${title}</h5>
                                                    </a>
                                                    <p>
                                                        ${author} | ${company} | ${year}
                                                    </p>
                                                    <p class="mb-0">
                                                    
                                                    </p>
                                                    <p class="mb-0">
                                                    
                                                    </p>                                                    
                                                    <div>
                                                    
                                                    </div>
                                                </div>
                                            </div>`
                        $("#" + uni_name + "-blocks").append(temp_html);
                    } else {
                        {
                        let temp_html = `<div class="media">
                                                <img src="${bookcover}" onerror="this.src='https://library.yonsei.ac.kr/image/ko/solution/common/ico/type_m_M.png'" class="align-self-center mr-3"
                                                style="width: 85px; height: 125px; border: 1px solid #d0d0d0">
                                                <div class="media-body">
                                                    <a href="${book_url}">
                                                        <h5 class="mt-0">${title}</h5>
                                                    </a>
                                                    <p>
                                                        ${author} | ${company} | ${year}
                                                    </p>
                                                    <p class="mb-0">
                                                        ${loc[0]}
                                                    </p>
                                                    <p class="mb-0">
                                                        ${loc[1]}
                                                    </p>                                                    
                                                    <div onclick="openclose('${uni_name}-status${i}')">
                                                        <i class="fa fa-caret-down" aria-hidden="true"></i>
                                                    </div>
                                                    <div class="status" style='display : none' id="${uni_name}-status${i}">
                                                        <table class="tg">
                                                            <thead>
                                                                <tr>
                                                                    <th class="tg-id4c">No.</th>
                                                                    <th class="tg-ufv0">소장처 / 자료실</th>
                                                                    <th class="tg-id4c">청구기호</th>
                                                                    <th class="tg-id4c">도서상태</th>
                                                                </tr>
                                                            </thead>
                                                            <tbody id='${uni_name}-table${i}'>
                                                            </tbody>
                                                        </table>
                                                    </div>
                                                </div>
                                            </div>`
                        $("#" + uni_name + "-blocks").append(temp_html);
                    }
                    }


                    for (let j = 0; j < status.length; j++) {
                        let book_loc = status[j]['book_loc'];
                        let callnum = status[j]['callnum'];
                        let borrow = status[j]['borrow'];
                        let num = j + 1;

                        let temp_html2 = `<tr>
                                                <td class="tg-8jgo">${num}</td>
                                                <td class="tg-v0mg">${book_loc}</td>
                                                <td class="tg-8jgo">${callnum}</td>
                                                <td class="tg-8jgo">${borrow}</td>
                                            </tr>`

                        $('#' + uni_name + '-table' + i).append(temp_html2);

                    }
                }
            }

3. TIL

  1. 다중 for문에서 list와 dictionary 형태의 변수를 사용할 때 어떤 block에서 정의되었는지 변수 정의 위치에 따라 값이 초기화되거나 저장하고자 하는 값을 담을 수 있거나 달라진다.

  2. 데이터를 어떤 방식으로 저장할 것인지, 어떤 함수를 만들 건지 미리 생각해보고 그림을 그려보고 들어가야 그 안에서 허우적대지 않을 수 있다.

  3. selenium을 통해 브라우저 제어할 때 웹페이지 별로 로딩속도가 달라 일괄적으로 time.sleep()을 적용한다면 에러가 발생하기도 한다.

  4. 튜터님의 도움으로 여러 문제들을 잘 헤쳐나갈 수 있었다. 대부분의 문제는 오타나 기본적인 개념의 혼동에서 비롯되었는데 에러를 만나면 남의 문제 보듯이 다시 찬찬히 보며 기본적인 것부터 점검해봐야겠다.

  5. static 폴더에 있는 css 파일을 수정했는데도 브라우저에서 보면 HTML에서 적용되지 않아있는 경우가 있었다. 이는 css 우선순위, 파일 저장 여부, 캐시 사용 등 다양한 이유가 있지만 캐시 때문인 경우가 많았다. 브라우저가 캐시를 사용하기 때문에 브라우저의 캐시에 적재된 css 파일 내용으로 계속 처리되는 현상이다. Ctrl + F5 : 캐시 및 메모리 새로 고침, Ctrl + Shift + R : 크롬 캐시 초기화 등으로 해결할 수 있었다.

profile
개발꿈나무 무럭무럭 자라는 중!

0개의 댓글