Translate

2018년 12월 23일 일요일

[Python] 네이버 밴드 크롤링 + 글자 수 세기


Laptop
운영체제 Windows 10 Home 64bit
개발환경 Python 3.4.4
PyCharm 2018.1.3 (Community Edition)

[2019.07.21 멤버 구분 기능 추가 내용 수정]

Naver_Band_Comment_Count_Crawler.exe

친구 요청으로 간단하게 만들어본 프로그램이다.

지금 올리는 것은 작성글 보기 페이지만을 크롤링하는 특정 목적이 있는 프로그램이지만,
다른 페이지를 크롤링할 때도 태그만 수정하면 되니 동일한 절차를 거치면 될 것 같다.

Selenium 및 라이브러리를 활용하여, 로그인만 해주면 댓글 페이지의 댓글을 수집, 각각의 공백 포함 및 미포함 글자 수를 세어 리스트를 작성해 엑셀 파일로 출력한다.
또한 멤버명을 태그하여 언급하여 한 댓글에 여러명에게 얘기할 시 여러개의 댓글로 인식하여 결과를 낸다.

로그인도 자동화시키고 싶었지만 네이버 자체 보안 절차 때문에 불가능한 것 같다.

프로그램 실행 전에 두 가지 작업이 필요하다.


1. chromedriver 설치

selenium에 지원하는 버전 제한이 있는것 같아 내가 썼던 버전 링크를 적겠다.
https://chromedriver.storage.googleapis.com/index.html?path=72.0.3626.69/

여기 들어가 window용을 다운받아 압축을 풀면 chromedriver.exe라는 파일이 있다.
(아래 그림 참고)

chromedriver.exe를 위에서 첨부한 프로그램과 동일한 폴더에 위치시키면 된다.


2. URL 확인

확인할 페이지(여기서는 작성글 보기)의 링크 주소가 필요하다.
접속 방법은 아래 캡쳐한 메뉴대로 들어가면 된다.



필요한 값은 바로 위 스크린샷의 URL부분이다.


프로그램을 실행시키면 URL를 입력하는 란이 있으니 그곳에 해당 값을 입력하고,
START 버튼을 누르면 아래 그림과 같이 크롬 창이 새로 하나 실행되며 로그인 화면이 나온다.







참고할 점은 로그인할 때 이 절차가 보통 크롬으로 접속할 때보다 유독 좀 길다.
아무래도 프로그래밍적으로 접근하고 있다는 걸 인식해서 그런것 같다.

어쨌든 페이지가 완전히 나오면 OK버튼 클릭하면 알아서 스크롤을 내리면서 모두 조회한 후 엑셀 파일을 생성한다.


그리고 마지막으로 저장 창이 뜬다. 기본 파일명은 Naver_Band_Comment_Count_Result.xlsx로 설정되어 있다.


테스트로 쓴 댓글 구조는 다음과 같으며, 댓글과 멤버에 대한 매칭은 나온 순서로만 본다. 즉,  [멤버1댓글1]과 [댓글1멤버1] 은 동일게 취급한다. (아래 결과 참조)

또한 연속으로 붙여적은 멤버는 모두 한 댓글에 대한 언급으로 취급한다.



그렇게 저장된 파일 형태는 다음과 같다.



전체 소스는 다음과 같다.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
from selenium import webdriver
from tkinter import messagebox
import time
from openpyxl import Workbook

from tkinter import *
from tkinter.filedialog import asksaveasfilename

root = Tk()

root.title('Naver Band Comment Count Crawler')

url = StringVar()

lbl = Label(root, text="URL")
lbl.grid(sticky="W", row=0, column=0)
txt = Entry(root, textvariable=url, width=60)
txt.grid(sticky="W", row=0, column=1)

def enter(event):
    startCommentCount()

def saveFile(driver, wb):
    # 저장 파일명 설정
    file = asksaveasfilename(initialfile="Naver_Band_Comment_Count_Result.xlsx")
    if (file):
        try:
            wb.save(file)
        except:
            messagebox.showerror(title='Error', message=file + " 를 저장할 수 없습니다.\n파일이 열려있다면 종료해 주세요.")
            saveFile(driver, wb)
        else:
            messagebox.showinfo(title='Complete', message=file + " 저장 완료!")
            wb.close()
            driver.quit()
    # 취소 선택 시
    else:
        if(not messagebox.askyesno("Cancel", "파일이 저장되지 않습니다. 정말로 취소하시겠습니까?")):
            saveFile(driver, wb)
        else:
            wb.close()
            driver.quit()

def startCommentCount():

    if(url.get() == ''):
        messagebox.showinfo(title='Info', message='URL을 입력하세요.')
        return

    try:
        driver = webdriver.Chrome("chromedriver")
    except:
        messagebox.showerror(title='Error', message='chromedriver가 같은 위치에 있는지 확인하세요.')
        return

    try:
        driver.get(url.get())
    except:
        messagebox.showwarning(title='Warning', message='입력하신 URL에 연결 실패했습니다.\n다시 확인해 주세요.')
        driver.quit()
        return

    # 로그인 대기
    messagebox.showinfo(title='로그인', message='로그인 완료 후 페이지가 나타나면 OK 버튼을 눌러주세요.')

    # 더이상 refresh가 되지 않을 때까지 스크롤 내리기
    SCROLL_PAUSE_TIME = 0.5

    # Get scroll height
    last_height = driver.execute_script("return document.body.scrollHeight")

    while True:
        # Scroll down to bottom
        driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")

        # Wait to load page
        time.sleep(SCROLL_PAUSE_TIME)

        # Calculate new scroll height and compare with last scroll height
        new_height = driver.execute_script("return document.body.scrollHeight")
        if new_height == last_height:
            break
        last_height = new_height

    # 댓글 리스트 크롤링
    comment_list = driver.find_elements_by_css_selector("p[class='comment']")

    # 엑셀 작성
    wb = Workbook()
    ws = wb.active

    ws.cell(row=1, column=1).value = "순번"
    ws.cell(row=1, column=2).value = "태그 멤버"
    ws.cell(row=1, column=3).value = "댓글"
    ws.cell(row=1, column=4).value = "공백 포함 글자수"
    ws.cell(row=1, column=5).value = "공백 미포함 글자수"

    i = 0
    row = 2

    for comment in comment_list:

        comment_text_list = []
        member_text_list = []

        # 언급 member 유무 확인
        member_list = comment.find_elements_by_tag_name("strong")

        if (member_list.__len__() == 0):
            member_text_list.append("")
            comment_text_list.append(comment.text)
        else:
            for member in member_list:
                member_text_list.append(member.text)

            comment_text = comment.get_attribute('innerHTML')

            # 다중 언급 댓글 분리
            m = 0
            curr = 0
            while (True):
                # strong 태그 시작을 찾아 마지막 탐색 지점과의 간격을 구함
                start = comment_text.find("<strong>", curr)
                diff = start - curr

                if (start == -1):
                    # 더이상 멤버가 없을 경우, 댓글로 끝나는 경우를 처리(추가)하고 종료
                    if (curr + 9 != len(comment_text)):
                        comment_text_list.append(comment_text[curr + 9:len(comment_text)].strip())
                    break

                if (curr == 0):
                    # 처음에 댓글 후 멤버명 언급 시 댓글 처리
                    if (comment_text[curr:start].strip() != ''):
                        comment_text_list.append(comment_text[curr:start].strip())
                else:
                    # 멤버명끼리 붙어있는 경우 (닫는 태그 길이 = 9)
                    if (diff == 9):
                        member_text_list[m] = member_text_list[m] + ", " + member_text_list[m + 1]
                        member_text_list.pop(m + 1)
                    # 댓글인 경우, 닫는 태그를 제외하고 내용 추출
                    else :
                        comment_text_list.append(comment_text[curr + 9:start].strip())
                        m += 1

                # 마지막 탐색 지점을 strong 태그 닫힌 구간으로 변경
                end = comment_text.find("</strong>", start)
                curr = end

        # 엑셀 줄 입력
        j = 1
        m = 0

        for comment in comment_text_list:

            # 다중 언급 댓글 번호 매기기
            child = ''
            if (comment_text_list.__len__() > 1):
                child = "-" + str(j)
                j += 1

            ws.cell(row=row, column=1).value = str(i + 1) + child
            ws.cell(row=row, column=2).value = member_text_list[m]
            ws.cell(row=row, column=3).value = comment
            ws.cell(row=row, column=4).value = len(comment)
            ws.cell(row=row, column=5).value = len(re.findall("[\S]", comment))

            m += 1
            row += 1

        i += 1

    saveFile(driver, wb)

root.bind('<Return>', enter)

btn = Button(root, text="START", width=15, command=startCommentCount)
btn.grid(row=3, column=1)

root.mainloop()

2018년 9월 17일 월요일

[jQuery] 채팅 스크롤 구현 (최하단 자동 포커싱 + 이동 버튼 및 안 읽은 수 표시)


Laptop
운영체제 Windows 10 Home 64bit
브라우저 Chrome 버전 68.0.3440.106

채팅창처럼 수시로 행이 늘어나는 table의 경우 몇몇 편의상의 스크롤 제어가 필요하다.
일단 기능 자체는 메신저 텔레그램에서 영감을 많이 얻었다.

수행하는 기능은
1. 새로운 행 추가 시 자동으로 부드럽게 최하단 스크롤
2. 이전 기록으로 (위로) 스크롤 시 다시 보던 위치로 이동할 수 있는 버튼 플로팅
3. 버튼이 떠있는 동안은 자동 스크롤이 되지 않음
4. 버튼이 떠있는 동안 새로운 라인이 추가될 시 안 읽은 수가 표시

html구조는 스크롤을 제어할 div안에 동적 table이 있고, 버튼은 div 두개를 써서 하나는 실제 보이는 버튼, 하나는 버튼이 숨김 상태일 때 가리기 위한 용도이다. 즉, 보임/숨김은 단순히 top 조정으로 제어된다.

결과물은 다음과 같다.
css는 기본적인 것 외에는 거의 적용하지 않았고, 버튼 디자인은 다른 소스를 참고한 것이니 세부적인 건 하단 참고 사이트 링크에서 확인하면 된다.






- 참고 사이트
동적 테이블 추가: https://qkrrudtjr954.github.io/jquery/2018/01/29/jquery-dynamic-table.html

스크롤 애니메이션: http://jamesdreaming.tistory.com/215

슬라이드 버튼: http://brilliantcoding.tistory.com/entry/JQuery-%EC%8A%A4%ED%81%AC%EB%A1%A4%EC%97%90-%EB%94%B0%EB%9D%BC-%EC%8A%AC%EB%9D%BC%EC%9D%B4%EB%93%9C-%EB%90%98%EB%8A%94-%ED%97%A4%EB%8D%94-%EB%A7%8C%EB%93%A4%EA%B8%B0


2018년 9월 8일 토요일

[PostgreSQL] COPY FROM CSV 사용 시 Date, Time Format 지정하기



개발프로그램
Postgresql 9.6


csv파일의 값 형태가 컬럼형에 맞지 않아 직접 넣을 수 없을 때, 
formatting을 해야하는 경우가 있다.
예를 들면 timestamp형 데이터로 09:30:10을 넣어야 하는데
csv에 093010라고 적혀있어서 오류가 뜨는 경우이다.

결론만 말하자면 to_timestamp()를 이용하는 간편한 방법은 없다.

총 3가지가 있는데,
2, 3번의 경우 임시 데이터를 생성하므로 사용할 데이터가 대용량일 경우 
작업 PC의 하드 용량에 어느 정도 여유분이 필요하다는 주의사항이 있다.

사용할 table의 컬럼은 대충
test_table (
id INT4,
date DATE,
time TIMESTAMP)
라는 전제로 하겠다. (time 외 다른 컬럼에 별 의미는 없다)
csv 파일명은 test.csv, 테이블과 동일한 컬럼 형태에 ','로 구분됐다는 전제를 두겠다.


1. 초반에 VARCHAR형으로 선언한 후 ALTER문으로 TYPE 변경하기
일단 VARCHAR형으로 선언하면 문자열이기 때문에 어떤 형식이든 입력은 성공한다.
그리고 to_timestamp()를 써서 컬럼형을 변경하는 방법이다.

장점은 임시적인 컬럼이나 파일이 필요없다는 것,
단점은 동일한 형식의 csv파일을 추가적으로 COPY할 수 없기 때문에
필요할 때는 결국 2 또는 3번 방법을 이용해야 한다는 것이다.

(HEADER는 헤더를 없애고 읽는다는 뜻, ENCODING은 말 그대로 csv 파일의 인코딩이다.)
COPY test_table(id, date, time) FROM 'test.csv'
DELIMITER ','
CSV HEADER ENCODING 'UTF-8';

ALTER TABLE test_table ALTER COLUMN time TYPE TIME USING (to_timestamp(time_char, 'HH24MISS')); 


2. VARCHAR형 임시 컬럼 생성 후 본래 컬럼에 다시 UPDATE하기
예를 들어 time_char라는 임시 컬럼을 만들고 거기에 csv의 자료형을 넣고,
본래 써야 할 time 컬럼은 null로 둔 뒤
UPDATE문을 써서 변경하는 방법이다.

time_char은 이후에 컬럼을 제거해도 되지만
이후에 또 동일한 형식의 csv파일을 추가적으로 COPY할 수도 있을 경우에는
일단 냅두고 UPDATE문에 null 조건을 달아 동일한 작업을 하면 되지 않을까 싶다.

COPY test_table(id, date, time_char) FROM 'test.csv'
DELIMITER ','
CSV HEADER ENCODING 'UTF-8';

UPDATE dtg_data SET time = to_timestamp(time_char, 'HH24MISS');


3. csv 파일 자체를 해당 포맷에 맞게 변환하기
데이터의 용량이 클 경우 무조건 이 방법을 택하기를 권한다.
실제로 나는 csv파일만 약 30GB인 파일들로 작업을 했는데,
하나당 COPY에도 약 3일이 걸리고 UPDATE에도 마찬가지로 3일정도가 걸려서
실제로 많은 시간을 소모했으나
이 방법을 마지막에서야 깨닫고 시도해보니 훨씬 시간이 단축되었다.
(하지만 COPY할때 걸리는 3일은 어쩔 수가 없었던...)

언어는 무얼 쓰든 자유이나, 나는 이전에 Python으로 csv 관련 작업을 한 적이 있어 익숙한 편이라 이것을 택했다.

지금 예제의 프로그램을 써보자면 (csv파일의 위치는 C:/ 바로 아래라고 하자)

import csv
with open('C:\\test.csv', 'r') as csvfile:
    reader = csv.DictReader(csvfile)
    with open('C:\\test2.csv', 'w', newline='') as csvfile2:
        writer = csv.writer(csvfile2, delimiter = ',')
        writer.writerow(reader.fieldnames)
        for row in reader:
            split_time = row['time']
            time = split_time[0] + split_time[1] + ':' + split_time[2] + split_time[3] + ':' + split_time[4] + split_time[5]
            row['time'] = time
            writer.writerow(row.values())

test2의 newline='' 속성은 개행을 없앤다는 뜻이다.
쓰지 않을 경우 각 행들이 모두 한 줄씩의 공백(즉 엔터를 두번 친 모양)을 가진 채 생긴다.

단순히 time값을 한글자씩 쪼개어 사이사이에 ':'를 입력하고 time행을 수정,
그 후 행 단위로 csv를 입력해나가는 간단한 프로그램이다.

이렇게 하면 포맷이 설정된 새로운 test2.csv라는 파일이 형성된다.
(정확히는 측정하지 못했으나 30GB 처리 소요시간이 2~3시간쯤으로 추정된다.)

그리고 앞의 예제들과 마찬가지로 COPY를 쓰면 된다.

COPY test_table(id, date, time) FROM 'test2.csv'
DELIMITER ','
CSV HEADER ENCODING 'UTF-8';

데이터 추가시에도 이와 같이 새로운 csv 파일을 생성하고, COPY하면 된다.

참고 사이트: https://www.postgresql.org/message-id/55BA474E.7080605%40aklaver.com

2018년 9월 7일 금요일

[JSP] _jspService(HttpServletRequest, HttpServletResponse) is exceeding the 65535 bytes limit error 해결법


Laptop
운영체제 Windows 10 Enterprise 64bit
개발프로그램 Eclipse Photon (4.8.0)

말 그대로 서블릿 java 파일이 제한인 65535 bytes (약 64KB)를 초과하여 생기는 현상이다.

해결법은 실행 중인 서버의 web.xml 파일에서

<servlet-name>jsp</servlet-name>
<servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>

를 찾아 아래 소스를 추가해준다.

<init-param>
            <param-name>mappedfile</param-name>
            <param-value>false</param-value>
</init-param>

만약  Eclipse + Tomcat 이라면 다음과 같이 바로 찾을 수 있다.


servlet-name값이 default인 것도 있으니 헷갈리지 않게 주의...(그걸로 몇 시간 헤맸다)


원리를 대충 설명하자면 
jsp 내에 html코드가 들어갈 경우 (모든 html인지, jsp와 혼합된 html만인지는 모르겠다)

out.write("<!DOCTYPE html>\r\n");
out.write("<html>\r\n");
out.write("<head>\r\n");

이런 식으로  out.write로 변환해 버리는 구조로 되어있는데, 이것을 out.write("\r\n<!DOCTYPE html>\r\n<html>\r\n<head>\r\n"); 처럼 한줄로 바꾸는 별거 아닌 설정이다.

덤으로 마찬가지로 용량을 줄이기 위한 다른 설정들은 다음과 같다.

<init-param>
 <param-name>genStringAsCharArray</param-name>
 <param-value>true</param-value>
</init-param>
<init-param>
 <param-name>trimSpaces</param-name>
 <param-value>true</param-value>
</init-param>

하지만 이것들도 String을 char배열로 바꾸거나 공백을 없애는 등 소소한 설정들이고

사실 가장 적절한 해결법은 jsp 소스에서 최대한 참조로 뺄 수 있는 걸 빼는 거지만 규모가 크다면 매우 번거로운 작업이므로...



참고 사이트:
http://s4-ba.hatenablog.jp/entry/2016/06/19/095937 https://docs.oracle.com/cd/E19146-01/821-1489/ggkhi/index.html

2018년 9월 2일 일요일

[Blogger / Blogspot / 구글 블로그] 로그인 시에만 보이는 태그 만들기 (글쓰기, 글수정 등)




내 블로그로 예를 들어보면 위가 로그인 전, 아래가 로그인 후이다.

자세히 차이점을 찾아보면 왼쪽메뉴 최하단의 글쓰기와 각 글 옆의 펜모양이 보일 것이다.
이렇게 로그인 시에만, 즉 자신에게만 보이는 버튼을 만들어 보자.

사실 기본 블로그 테마들을 이용하면 상단에 navbar라는 게 있어서 글쓰기 버튼에 대한 고민은 하지 않아도 되고,
글 수정 버튼도 기본적으로 있지만,
나처럼 외부 템플릿을 불러왔는데 이것들이 없을 경우에 참고하면 좋은 글이다.

그리고 위치 지정의 경우 스스로의 블로그에 대한 html 소스를 대략 알고 있어야 가능해서,
소스에 대해서만 설명하겠다.

우선 글쓰기 버튼은, 확인해야할 사항이 하나 있다.
blogger.com으로 들어가서 글쓰기 버튼을 누르고 URL을 복사해온다.

https://www.blogger.com/blog/post/edit/★
같은 형식일텐데, ★ 부분은 랜덤한 숫자처럼 보이는 걸로 봐선 아마 계정별로 다를 것이다.

그리고 원하는 위치에 다음 소스를 붙히면 된다. (href 속성 안에 URL를 붙이면 된다)
<span class='item-control blog-admin'>
    <a target='_blank' href='https://www.blogger.com/blog/post/edit/★'>글쓰기</a>
</span>


두번째로 수정 버튼은, 사실 기본 테마에서 그대로 가져온 소스긴 하지만 어쨌든 적어보자면

<b:include data='post' name='postQuickEdit'/>
<b:if cond='data:post.editUrl'>
   <span expr:class='&quot;item-control &quot; + data:post.adminClass'>
       <a target='_blank' expr:href='data:post.editUrl' expr:title='data:top.editPostMsg'>
           <img alt='' class='icon-action' height='18' src='http://img2.blogblog.com/img/icon18_edit_allbkg.gif' width='18'/>
       </a>
   </span>
</b:if>

이건 내부적으로 blogger 자체 태그를 쓰는거라 따로 변경해줄 부분은 없다.
여기도 문자로 바꾸고 싶다면 img 태그 대신 문자를 그대로 쓰면 된다.


2018년 7월 23일 월요일

[Java] String to JSON 라이브러리 정리



Laptop
운영체제Windows 10 Home 64bit
개발프로그램Eclipse Oxygen (4.7)

예제 String:
String jsonString = "{" +
 "'name' : 'Ronaldo', " +
 "'age' : 25, " +
 "'id' : 121.5, " +
 "'lastScores' : [ 2, 1, 3, 5, 0, 0, 1, 1 ]" +
 "}";

1. json Path
https://mvnrepository.com/artifact/com.jayway.jsonpath/json-path

import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;

...

System.out.println(document.read("$.name") + " / " + document.read("$.name").getClass().getName());
System.out.println(document.read("$.age") + " / " + document.read("$.age").getClass().getName());
System.out.println(document.read("$.id") + " / " + document.read("$.id").getClass().getName());
System.out.println(document.read("$.lastScores") + " / " + document.read("$.lastScores").getClass().getName());
System.out.println(document.read("$.lastScores[3]") + " / " + document.read("$.lastScores[3]").getClass().getName());

결과:







- 입력된 자료에 따라 반환값 타입이 고정됨. 다른 타입 사용 시 형변환 필요.
- accessors-smart, json-smart, slf4j-api, slf4j-jdk 라이브러리가 함께 필요


2. json simple
https://mvnrepository.com/artifact/com.googlecode.json-simple/json-simple

import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;

...

JSONParser parser = new JSONParser();
Object obj = null;

// 따옴표 변환 필요
jsonString = jsonString.replace("'", "\"");

try {
 obj = parser.parse(jsonString);
} catch (ParseException e) {
 e.printStackTrace();
}
JSONObject jsonObj = (JSONObject) obj;

System.out.println(jsonObj.get("name") + " / " + jsonObj.get("name").getClass().getName());
System.out.println(jsonObj.get("age") + " / " + jsonObj.get("age").getClass().getName());
System.out.println(jsonObj.get("id") + " / " + jsonObj.get("id").getClass().getName());
System.out.println(jsonObj.get("lastScores") + " / " + jsonObj.get("lastScores").getClass().getName());

JSONArray jsonArray = (JSONArray) jsonObj.get("lastScores");
System.out.println(jsonArray.get(3) + " / " + jsonArray.get(3).getClass());

결과:


입력된 자료에 따라 반환값 타입이 고정됨. 다른 타입 사용 시 형변환 필요.
- JSONArray 사용 시 추가적인 JSONArray 선언이 필요


3. Gson

import com.google.gson.JsonElement;
import com.google.gson.JsonParser;

...

JsonParser parser = new JsonParser();
JsonElement element = parser.parse(jsonString);

System.out.println(element.getAsJsonObject().get("name").getAsString());
System.out.println(element.getAsJsonObject().get("age").getAsFloat());
System.out.println(element.getAsJsonObject().get("lastScores").getAsJsonArray().get(3));

결과:




- .getAs... 메소드로 형변환 가능 
- JSONArray의 경우 getAsJsonArray 사용

4. boon

POJO 형식의 클래스 필요.

Player.class

package jsonTest;

import java.util.ArrayList;

public class Player {
 String name;
 int age;
 float id;
 ArrayList lastScores;
 
 public String getName() {
  return name;
 }
 public void setName(String name) {
  this.name = name;
 }
 public int getAge() {
  return age;
 }
 public void setAge(int age) {
  this.age = age;
 }
 public float getId() {
  return id;
 }
 public void setId(float id) {
  this.id = id;
 }
 public ArrayList getLastScores() {
  return lastScores;
 }
 public void setLastScores(ArrayList lastScores) {
  this.lastScores = lastScores;
 }
}

(ArrayList 대신 int[]형으로 써도 무방함)

import org.boon.json.JsonFactory;
import org.boon.json.ObjectMapper;

import jsonTest.Player;

...

ObjectMapper mapper = new JsonFactory().create();
Player p = mapper.fromJson(jsonString, Player.class);

System.out.println(p.getName());
System.out.println(p.getLastScores().get(3));

결과:



- POJO로 형변환 문제 해결 가능
- Java의 데이터형 객체를 활용 가능

참고 사이트:
http://www.java67.com/2016/10/3-ways-to-convert-string-to-json-object-in-java.html

2018년 6월 22일 금요일

[Blogger / Blogspot / 구글 블로그] 사진, 동영상 크기 관련 팁


1. 사진 여러 장 업로드 시 일괄 크기 지정법





기본적으로 글쓰기 탭에서 사진을 여러 장 삽입할 경우,
별 중간과정 없이 그대로, 그것도 Blogger 기준 '크게' 사이즈로 삽입되기 때문에
그 크기를 바라지 않을 경우 일일히 노가다를 하는 수밖에 없다.

그런데 사실 Blogger에는 이를 보완할 기능이 있다.

바로 글쓰기 옆의 HTML 탭으로 변경 후 사진을 여러 장 삽입해보면,
아까와는 달리 이런 창이 등장한다.




딱 보면 알겠지만, 사진들을 일괄처리할 수 있는 기능이다.

추측으로는 만들었다가 현재 글쓰기 메뉴에 적용을 실수로 안한 게 아닐까 생각한다.



2. 동영상 크기 변경법

Blogger에서 제공하는 기능으로 개인 동영상을 올리든, 유튜브 동영상을 올리든
공통적으로 크기가 너무 작다.

html 값을 보면 희한하게도 크기를 320 * 266 으로 딱 고정해놓았다.

CSS 추가로 강제로 이 기본값을 바꿀 수 있다.


Blogger에서 테마 - 맞춤설정을 선택,
고급 탭에서 맨 아래의 CSS추가를 선택한다.
(간혹 외부 템플릿을 적용한 경우는 너비 조정 탭에서 나온다.)


그리고 아래 소스를 입력한다.


.b-hbp-video, .YOUTUBE-iframe-video
{
  width: 90%;
  height: 56vh;
}

간단히 설명하자면 b-hbp-video 와 YOUTUBE-iframe-video라는 클래스(각각 업로드 동영상, 유튜브 동영상)의 크기를 조정하는 것인데,

가로는 포스트창 기준으로 %가 적용되지만,
세로는 기준이 뭔지 %는 이상한 결과가 나오고
그 대신 vh라는 브라우저 높이값에 비례하는 값을 썼다.
이게 모바일에서도 제일 무난하게 나오는 것 같다.

위 값대로 하면 이 정도 사이즈가 되고,




만약 변경하고 싶다면 F12를 누르면 나오는 개발자모드에서 미리보기를 할 수 있다.
왼쪽 상단에 커서 버튼을 클릭하고 동영상을 누르면 해당 태그를 쉽게 찾을 수 있다.
(선택한 채로 위, 아래 방향키로 값 조정이 가능하다.)


계속 느끼는 지만, Blogger는 html 지식이 없는 사용자에게는 상당히 불편한 플랫폼인 것 같다.

2018년 6월 10일 일요일

[Blogspot / Blogger / 구글 블로그] 외부 템플릿 불러오기 시 Google+ 댓글이 나오지 않는 현상 해결


대부분의 외부 템플릿이 기본적으로 Blogger 자체의 댓글이 보이도록 되어있는데,
댓글을 Google+로 변환 시 아예 다른 api를 사용하게 되는 모양인지 댓글이 전부 없어져 버리는 현상이 발생한다. 

다시 제공 템플릿으로 돌아오면 보이는 걸 보면 제공 템플릿에는 자동적으로 소스가 변경되도록 어떤 장치가 되어있는 것 같은데, 그게 뭔지는 잘 이해를 못했다.

어쨌든 변경해야 할 부분은 댓글 수 부분과 댓글이 보이는 부분이다.


테마 HTML 편집을 보면 다음과 같은 부분이 두 군데 있다. (showThreadedComments 를 검색)

<b:if cond='data:blog.pageType == &quot;static_page&quot;'>
  <b:if cond='data:post.showThreadedComments'>
 <b:include data='post' name='threaded_comments'/>
  <b:else/>
 <b:include data='post' name='comments'/>
  </b:if>
</b:if>
<b:if cond='data:blog.pageType == &quot;item&quot;'>
  <b:if cond='data:post.showThreadedComments'>
 <b:include data='post' name='threaded_comments'/>
  <b:else/>
 <b:include data='post' name='comments'/>
  </b:if>
</b:if>


그 두 부분을 모두 이렇게 변경한다.

<b:if cond='data:blog.pageType == &quot;static_page&quot;'>
  <b:include data='post' name='comment_picker'/>
</b:if>
<b:if cond='data:blog.pageType == &quot;item&quot;'>
  <b:include data='post' name='comment_picker'/>
</b:if>


이러면 댓글을 보이게 하는데까진 성공이나,
아직 댓글 수는 Blogger 댓글 수로 나오기 때문에 바뀌지 않은 상태일 것이다.


이 부분도 역시 HTML 편집에서 comment_count_picker를 검색하고, 해당 태그를 아래와 같이 변경한다. (만약에 없다면, comment_picker 등 유사한 이름을 찾아보거나 새로 만들면 된다.)

<b:includable id='comment_count_picker' var='post'>
  <b:if cond='data:post.commentSource == 1'>
    <span class='cmt_count_iframe_holder' expr:data-count='data:post.numComments' expr:data-onclick='data:post.addCommentOnclick' expr:data-post-url='data:post.url' expr:data-url='data:post.canonicalUrl'>
    </span>
  <b:else/>
    <a class='comment-link' expr:href='data:post.addCommentUrl' expr:onclick='data:post.addCommentOnclick'>
      <data:post.commentLabelFull/>:
    </a>
  </b:if>
</b:includable> 


그리고 댓글 수가 나오는 부분을 찾는다.
보통 다음과 같은 구성이다. (post-comment-link를 검색)

<span class='post-comment-link'>
                <b:if cond='data:blog.pageType != &quot;item&quot;'>
                  <b:if cond='data:blog.pageType != &quot;static_page&quot;'>
                    <b:if cond='data:post.allowComments'>
                      <a class='comment-link' expr:href='data:post.addCommentUrl' expr:onclick='data:post.addCommentOnclick'><b:if cond='data:post.numComments == 1'>1 <data:top.commentLabel/><b:else/><data:post.numComments/> <data:top.commentLabelPlural/></b:if></a>
                    </b:if>
                  </b:if>
                </b:if>
</span>


이것을 아래와 같이 바꾼다. (아까 작성한 comment_count_picker를 사용하는 걸 볼 수 있다.)


<span class='post-comment-link'>
                <b:if cond='data:blog.pageType != &quot;item&quot;'>
                  <b:if cond='data:blog.pageType != &quot;static_page&quot;'>
                    <b:if cond='data:post.allowComments'>
                      <b:include data='post' name='comment_count_picker'/>
                    </b:if>
                  </b:if>
                </b:if>
</span>


다만 제공해주는 api를 이용하는 것이기 때문에 디자인은 기본 템플릿에서 벗어날 수 있다.
이 부분은 css등을 이용하여 조정해주면 되고,
그게 싫다면 Google+ 댓글을 안쓰거나 제공 템플릿을 쓰는 수밖에...

주의사항은, Blogger 댓글을 사용하다가 Google+ 댓글로 전환하고 이 방법을 썼을 경우
보이는 데는 문제가 없으나, 왜인지 댓글 수가 2배로 나오고 뭔가가 별개로 구분되어 버리는 건지 삭제 등의 작업을 할 수 없게 된다.

결국 둘 다 기능 문제 없이 사용하고 싶다면 제공 템플릿을 쓸 수밖에 없고,
혹은 처음부터 Google+ 댓글을 쓸건지 말건지를 확실히 하는 것 같다.



참고 사이트:

댓글 보이게 하기:

댓글 수: