Translate

2017년 7월 25일 화요일

[R] conftest.c:1:17: fatal error: jni.h: No such file or directory 오류 해결법


Laptop
운영체제Ubuntu 16.04 LTS 64bit
개발프로그램RStudio Version 1.0.143
(R version 3.4.1)

(Java 설치, 환경변수까지 모두 되어 있다는 가정)

R에 Java를 연결하는 과정에서

$ sudo R CMD javareconf
명령어를 입력하면

conftest.c:1:17: fatal error: jni.h: No such file or directory
이 문장이 포함된 에러 메시지가 출력되면서 실패할 때가 있다.

이것은 Java 요소가 전부 설치되지 않았다는 뜻이며,
재설치 후 다시 하면 해결된다. 8은 버전이므로 바꿔도 상관없다.
$ sudo apt install openjdk-8-*

이 때, JAVA_HOME은 /usr/lib/jvm/java-8-openjdk-amd64 로 하면 된다. (마지막 파일명은 Ubuntu 버전에 따라 좀 다를 수 있다.)

[R] ERROR: dependencies ‘jsonlite’, ‘plyr’ are not available for package 오류 해결법


Laptop
운영체제Ubuntu 16.04 LTS 64bit
개발프로그램RStudio Version 1.0.143
(R version 3.4.1)

ERROR: dependencies ‘jsonlite’, ‘plyr’ are not available for package


install.packages 를 사용하다보면 종종 나타나는 오류.
json을 사용하는 패키지에서 나타나는 것 같다.
r-cran-jsonlite를 설치하면 해결된다.

통합 패키지인 것 같은데, 다른 dependencies 오류의 경우에도 해결방안이 될 수 있을 것 같다.

$ sudo apt-get install r-cran-jsonlite

참고 사이트: https://stackoverflow.com/questions/25721884/how-should-i-deal-with-package-xxx-is-not-available-for-r-version-x-y-z-wa

R CRAN에서 삭제된, 혹은 구버전 패키지 다운로드하기


Laptop
운영체제Ubuntu 16.04 LTS 64bit
개발프로그램RStudio Version 1.0.143
(R version 3.4.1)

CRAN이란, R 언어에서 사용되는 패키지 등의 자료의 모음이라고 보면 된다.

이 중 같은 기능을 수행하는 다른 패키지로 대체되고 사라지는 패키지들이 존재한다.
https://cran.r-project.org/web/packages/[패키지명]/index.html
[패키지명]부분을 바꿔 주소창에 입력해 들어가면 확인해 볼 수 있다.
Package ‘[패키지명]’ was removed from the CRAN repository. 라는 메시지가 뜰 것이다.

이 경우 install.packages('[패키지명]') 을 입력해도
package ‘[패키지명]’ is not available (for R version [R 버전])
라는 메시지만 돌아올 뿐이다.

하지만 경우에 따라 그 패키지를 사용하고 싶을 수 있다.
혹은 install.packages를 사용하는 경우 최신 버전만 설치되므로, 그 이전 버전을 설치하고 싶을 수도 있다.

당연히 방법이 없는 건 아니고, 현 시점에서는 삭제되었더라도 CRAN에서 기록을 모두 가지고 있다.

여기서 Ctrl+F 로 패키지명을 찾고, 들어가서 버전명을 확인하면 된다.
혹은 상단 링크에 들어간 상태로 두번째 줄에 archive를 클릭해도 같은 경로로 연결된다.
https://cran.r-project.org/src/contrib/Archive/


설치 방법은 두가지가 있는데

첫번째는 다운로드 링크를 그대로 입력하는 방법 (인터넷 연결 필수):
install.packages("https://cran.r-project.org/src/contrib/Archive/[패키지명]/[원하는 버전 파일명].tar.gz", repos = NULL, type="source")

두번째는 wget 등을 이용하여 tar.gz 파일을 다운로드 후, 로컬 안에 저장하여 그 경로를 입력하는 방법이다.
/home 에 저장했다면
install.packages("/home/[파일명].tar.gz", repos = NULL, type="source")

물론 다운받을 때는 인터넷 연결이 필요하지만, 그 후에 혹시 R 개발환경을 다시 설정할 때
tar.gz 파일을 갖고 있다면 그 때는 연결이 필요하지 않다.

+ RStudio 에서는 메뉴에서 탐색기를 이용하여 더 간편하게 선택이 가능하다.




이 후 탐색기 창이 뜨는데, 거기서 tar.gz 파일을 열고 Install 버튼을 눌러 마치면 된다.

Install from을 기본 선택 상태인 Repository(CRAN)으로 하면 CRAN에 존재하는 패키지의 경우에도 더 쉽게 설치할 수 있다.
(여러 개의 패키지를 동시에 다운받을 경우 이름을 , 로 구분하라고 적혀있다.)




2017년 7월 24일 월요일

Ubuntu E: Could not get lock /var/lib/dpkg/lock - open (11 Resource temporarily unavailable) E: Unable to lock the administration directory (/var/lib/dpkg/) is another process using it? 오류 해결법



Laptop
운영체제Ubuntu 16.04 LTS 64bit

E: Could not get lock /var/lib/dpkg/lock - open (11 Resource temporarily unavailable) 
E: Unable to lock the administration directory (/var/lib/dpkg/) is another process using it?

apt install 작업을 하던 중 종종 발생하는 오류.

이전 apt 프로세스가 정상적으로 끝나지 않았을 때 잠금상태가 풀리지 않은 상태라는 듯 하다.

아래 명령어를 입력 후 다시 해보면 된다.


$ sudo rm /var/lib/dpkg/lock
$ sudo dpkg --configure -a

아마 잠금 파일을 삭제함으로써 해제하고, 패키지 관리에 쓰이는 dpkg 명령어로 현재 설정을 전체 패키지에 적용하는 것 같다.




2017년 6월 26일 월요일

[Android Studio] Spinner setRotation 시 dropdown 창 함께 회전하기


Laptop
운영체제Windows 10 Home 64bit
개발프로그램Android Studio 2.3.3
(buildToolsVersion "25.0.2")
프로젝트명Spinner-rotation-test

Mobile
모델LG X5
운영체제Android 6.0.1 Marshmallow


Android Studio에서 Spinner 뷰에 setRotation(90); 메소드를 적용하면, 아래와 같은 문제점이 발생한다.


 
바로 dropdown 창이 함께 회전하지 않는다는 것이다.
아쉽게도 뷰 메소드 자체에서는 해결방법이 없는 듯하다.
좀 번거롭긴 하지만, 커스텀 뷰를 제작할 때 쓰는 ArrayAdapter를 사용하면 어느정도 해결할 수 있다. 정확히는 오버라이딩이 필요하다. 

먼저 res/layout에 spinner_adapter.xml 라는 레이아웃을 하나 만들어주자.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<?xml version="1.0" encoding="utf-8"?>

<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="20dp"
    >
    <!-- padding을 적용하지 않을 시 화면에 표시되지 않는다. -->

</TextView>


그리고 MyArrayAdapter 라는 ArrayAdapter를 상속받는 클래스를 하나 생성하고, 생성자와 getDropDownView 메소드를 오버라이딩한다.

그리고 아래와 같이 소스를 작성한다.
(setRotation 메소드는 각도 값을 입력받기 위해 임의로 만들었다.)

MyArrayAdapter.java
 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
package com.spinner_rotation_test;

import android.content.Context;
import android.support.annotation.LayoutRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;

import java.util.List;

public class MyArrayAdapter extends ArrayAdapter<String> {

    private int m_degree;

    public MyArrayAdapter(@NonNull Context context, @LayoutRes int resource, @NonNull List<String> objects) {
        super(context, resource, objects);
    }


    @Override
    public View getDropDownView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
        parent.setRotation(m_degree);

        // spinnerMode 가 dialog 일 때는 위치 조정을 해야 화면에 나타난다.
        // parent.setY(120);

        return super.getDropDownView(position, convertView, parent);
    }

    public void setRotation(int degree)
    {
        m_degree = degree;
    }

}


이것들을 이용해서, MainActivity.java를 작성한다.
 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
package com.spinner_rotation_test;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.Spinner;

import java.util.Arrays;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    private Spinner mSpinner;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mSpinner = (Spinner) findViewById(R.id.spinner);
        mSpinner.setRotation(90);

        List<String> item_list = Arrays.asList(new String[]{"item 1", "item 2", "item 3"});
        MyArrayAdapter myArrayAdapter = new MyArrayAdapter(this, R.layout.spinner_adapter, item_list);
        myArrayAdapter.setRotation(90);

        mSpinner.setAdapter(myArrayAdapter);


    }
}

activity_main.xml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent" android:layout_height="match_parent">


    <Spinner
        android:id="@+id/spinner"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true"
        />

</RelativeLayout>


결과 화면은 다음과 같다.



+ spinnerMode가 dialog 일 때

spinner에 android:spinnerMode="dialog" 를 추가하고
MyArrayAdapter.java의 Line 27의 주석을 해제하면 다음과 같이 dialog 모드도 적용된다는 걸 확인할 수 있다.




2017년 6월 15일 목요일

Python 프로그램 exe 파일로 만들기 (+ openpyxl, requests 라이브러리 포함)


Laptop
운영체제Windows 10 Home 64bit
개발프로그램PyCharm Community Edition 2017.1.3
Python 3.4.4

개인적인 용도로 엑셀파일을 생성하는 프로그램을 만들었는데, 파이썬 개발환경이 없는 컴퓨터에서 실행시킬 필요가 있었다.

그러기위해선 exe파일로 만들 필요가 있다.

알아본 결과 대표적으로 3가지 라이브러리가 있는데 각각 테스트 결과는 이렇다.

1. cx_freeze
공식에서 배포를 잘못한건지 내가 못하는건지 모르겠지만 실행파일이 없음. 테스트 실패.

2. py2exe
exe 생성은 잘 되나 openpyxl을 포함하지 못함.

3. pyinstaller
성공.


그래서 pyinstaller를 사용한 예제를 써보겠다.
설치는 pip을 이용하면 된다. (pip install pyinstaller)

단 파이썬 버전 2.7과 3.3~3.5 에서 잘 동작한다고 한다. 최신 버전이 3.6 정도 된 걸로 알고있는데 참고하자. 

적당히 openpyxl 공식사이트에 있는 샘플 소스를 exe로 만들어 보겠다.

excel_test.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
print 'hello world!'from openpyxl import Workbook
wb = Workbook()

# grab the active worksheet
ws = wb.active

# Data can be assigned directly to cells
ws['A1'] = 42

# Rows can also be appended
ws.append([1, 2, 3])

# Python types will automatically be converted
import datetime
ws['A2'] = datetime.datetime.now()

# Save the file
wb.save("sample.xlsx")

print("Complete!")

# 라인 정지
a = input()

추가한 Line 20~ 부분은, 완료 확인과 exe 실행 시 프로그램이 종료하면 같이 꺼지는 커맨드창을 고려하여 의미없는 입력 메소드를 하나 추가했다. (이로써 Enter를 쳤을 때 종료하게 된다.)

이제 커맨드 상에서 소스파일이 있는 위치로 이동한다.
cmd에서 cd로 이동할 수도 있지만 탐색기에서 이동 후 Shift + 마우스 오른쪽버튼 - 여기서 명령 창 열기 를 선택하면 더 쉽다.

그리고 아래 명령어를 입력한다.

> pyinstaller -F -n excel.exe excel_test.py

-F: 한 개의 파일로 만들기. 용량은 당연히 조금더 커진다. (없을 시 dll 파일들과 분리되어 폴더로 생성됨)
-n: 이름짓기. 이름은 바로 오른쪽에 기입 (없을 시 소스파일명과 동일한 파일이 생성됨)
--noconsole: 콘솔창 띄우지 않기



이렇게 ~ successfully 라 뜨면 성공이다.

탐색기를 확인해보면 4가지 파일이 새로 생성되어 있다.



여기서 실제 사용될 exe 파일은 dist 폴더에 저장되어 있다.

실행시켜 보면, 정상적으로 엑셀 파일이 생성되는 것을 볼 수 있다.
(파이썬이 설치되지 않은 컴퓨터에서의 테스트도 완료했다.)



+ requests 라이브러리가 import 되어 있을 때, 위와 같은 과정을 거쳐 만든 exe파일을 실행해보면 이런 오류가 발생한다.



마지막을 읽어보면 queue 라는 모듈이 없다고 한다.
해결법은 간단하다. 생성할 때 queue 모듈을 포함해주면 된다.

>pyinstaller -F excel_test.py --hidden-import=queue

참고 사이트: https://stackoverflow.com/questions/36400111/pyinstaller-doesnt-import-queue

Python BeautifulSoup 네이버 블로그 크롤링 + txt 파일 만들기



Laptop
운영체제Windows 10 Home 64bit
개발프로그램PyCharm Community Edition 2017.1.3
Python 3.4.4


크롤링(Crawling)은 웹페이지의 데이터를 가져오는 것을 말한다.

Beautifulsoup라는 라이브러리가 이 역할을 수행하는데, pip을 통한 별도의 설치가 필요하다.

설치과정은 다른 블로그에 소개된 곳이 많으니 링크로 대체한다.

pip 설치: http://codingdojang.com/scode/371
Beautifulsoup 설치: http://shaeod.tistory.com/900

또한 requests 라는 연결 확인을 위한 라이브러리도 필요하다. 
같은 방식으로 커맨드창에 pip install requests 라고 입력하면 된다.

Beautifulsoup에 대한 기본적인 튜토리얼은 공식사이트에 잘 나와 있다.
id검색, class 검색 등 다양한 예제가 있으니 처음 접한다면 읽어보는 게 좋다.
http://beautifulsoup-korean.readthedocs.io/ko/latest/


프로그램 방식은 먼저 블로그 ID와 시작과 끝 페이지 수를 입력받고, 그 범위 안의 포스트(글)들을 긁어와 출력하면서 txt 파일에 저장한다.

naver_blog_crawling.py
  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
# --*-- coding: utf-8 --*--
import codecs

import re

import collections
import requests

from bs4 import BeautifulSoup


# 에러 리스트 생성 함수
def insert_error(blog_id, error, error_doc):
    for i in error_doc:
        error_log = str(error_doc["page"]) + "page / " + str(error_doc["post_number"]) \
                    + "th post / " + error + " / http://blog.naver.com/PostList.nhn?blogId=" + blog_id + "&currentPage=" + str(error_doc["page"])
    error_list.append(error_log)

total_num = 0;

error_list = []

print("블로그 ID->")
blog_id = input()

print("\n탐색 시작 페이지 수->")
start_p = int(input())

print("\n탐색 종료 페이지 수->")
end_p = int(input())

print("\nCreating File Naver_Blog_Crawling_Result.txt...\n")

# 파일 열기
file = codecs.open("Naver_Blog_Crawling_Result.txt", 'w', encoding="utf-8")

# 페이지 단위
for page in range(start_p, end_p + 1):
    print("=" * 50)
    file.write("=" * 50 + "\n")

    doc = collections.OrderedDict()

    url = "http://blog.naver.com/PostList.nhn?blogId=" + blog_id + "&currentPage=" + str(page)
    r = requests.get(url)
    if (not r.ok):
        print("Page" + page + "연결 실패, Skip")
        continue

    # html 파싱
    soup = BeautifulSoup(r.text.encode("utf-8"), "html.parser")

    # 페이지 당 포스트 수 (printPost_# 형식의 id를 가진 태그 수)
    post_count = len(soup.find_all("table", {"id": re.compile("printPost.")}))

    doc["page"] = page

    # 포스트 단위
    for pidx in range(1, post_count + 1):
        print('-' * 50)
        file.write('-' * 50 + "\n")

        doc["post_number"] = pidx
        post = soup.find("table", {"id": "printPost" + str(pidx)})

        # 제목 찾기---------------------------

        title = post.find("h3", {"class": "se_textarea"})

        # 스마트에디터3 타이틀 제거 임시 적용 (클래스가 다름)
        if (title == None):
            title = post.find("span", {"class": "pcol1 itemSubjectBoldfont"})

        if (title != None):
            doc["title"] = title.text.strip()
        else:
            doc["title"] = "TITLE ERROR"

        # 날짜 찾기---------------------------

        date = post.find("span", {"class": re.compile("se_publishDate.")})

        # 스마트에디터3 타이틀 제거 임시 적용 (클래스가 다름)
        if (date == None):
            date = post.find("p", {"class": "date fil5 pcol2 _postAddDate"})

        if (date != None):
            doc["date"] = date.text.strip()
        else:
            doc["date"] = "DATE ERROR"

        # 내용 찾기---------------------------

        content = post.find("div", {"class": "se_component_wrap sect_dsc __se_component_area"})

        # 스마트에디터3 타이틀 제거 임시 적용 (클래스가 다름)
        if (content == None):
            content = post.find("div", {"id": "postViewArea"})

        if (title != None):
            # Enter 5줄은 하나로
            doc["content"] = "\n" + content.text.strip().replace("\n" * 5, "\n")
        else:
            doc["content"] = "CONTENT ERROR"

        # doc 출력 (UnicodeError - 커맨드 창에서 실행 시 발생)
        for i in doc:

            str_doc = str(i) + ": " + str(doc[i])
            try:
                print(str_doc)
            except UnicodeError:
                print(str_doc.encode("utf-8"))

            # 파일 쓰기
            file.write(str_doc + "\n")

            # 에러 처리
            if ("ERROR" in str(doc[i])):
                insert_error(blog_id, doc[i], doc)

        # 전체 수 증가
        total_num += 1


# 결과 출력 (전체 글 수, 에러 수)
print("=" * 50)
file.write("=" * 50 + "\n")

print("Total : " + str(total_num))

error_num = len(error_list)
print("Error : " + str(error_num))

# 에러가 있을 경우 출력
if (error_num != 0):
    print("Error Post : ")
    for i in error_list:
        print(i)

# 파일 닫기
file.close()


Line 42:
doc 을 초반에 dict 형식으로 선언했다가 출력할 때 순서가 바뀐다는 것을 알고 OrderedDict로 변경했다. 그 후 값 입력 등 사용 방법은 dict 형식과 동일하다.

Line 71:
개발 중 에러가 발생하는 글들을 보면 블로그 페이지 소스보기에서 <!--스마트에디터3 타이틀 제거 임시 적용--> 이라는 주석이 붙은 글들이 있길래 (아마도 구버전으로 추정) 살펴봤더니 제목이나 내용의 태그 속성이 아예 다르다. if문으로 따로 구분해 두었다.
(가끔 이 두 형태가 섞여있는 블로그도 종종 있었다.)

페이지 상에서도 모양이 다르다.



현재 일반적인 모양

스마트에디터3 제거



Line 112:
UnicodeError는 커맨드 창에서 print 출력 시 종종 발생하는데, Pycharm의 경우 결과창 인코딩이 utf-8이기 때문에 문제없이 출력된다.
그래서 커맨드 출력의 경우 유니코드상태로 그대로 출력되는 경우가 많을텐데, txt 파일에 쓸 때는 별 문제 없으니 크게 신경쓰지 않아도 된다.



결과는 적당히 네이버 공식 블로그(http://blogpeople.blog.me/) 를 파싱한 결과로 캡쳐했다.


PyCharm 결과 화면






Cmd 결과 화면





생성된 txt 파일 (Naver_Blog_Crawling_Result.txt)
메모장으로 열면 \n가 깨진다. 인코딩을 선택할 수 있는 Microsoft Word 등의 프로그램으로 정상적인 결과를 확인할 수 있다.