지금 들어온 프로젝트에서는 application.properties의 DB 접속정보가 모두 암호화되어있다. 생전 이런건 또 처음이라 뒤져보니 역시나 보안을 위한 암호화라고. 뭐 내부망에서만 배포되고 서버내에서만 존재하는 *properties까지 암호화할일은 무엇인가 싶긴 한데 뭐 어디선가는 또 쓰기 마련이지. 아무튼 대략적으로 구현해보고 에러 트라이도 해보고 잘 볶아보았다.

 

1. pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!-- Spring Boot 2.7.8 에서 설정함 -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.8</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>
 
<!-- 암복호화 모듈 jasypt -->
<dependency>
    <groupId>com.github.ulisesbocchio</groupId>
    <artifactId>jasypt-spring-boot-starter</artifactId>
    <version>3.0.5</version>
</dependency>
 
<!-- 테스트를 수행해본 mariadb -->
<dependency>
    <groupId>org.mariadb.jdbc</groupId>
    <artifactId>mariadb-java-client</artifactId>
    <version>2.7.11</version>
</dependency>
cs

대충 스프링부트 2.7.8에 jasypt 3.0.5 버전을 적용했다, 이마리야

 

2. JasyptConfig.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
package com.test.comm.util;
import org.jasypt.encryption.StringEncryptor;
import org.jasypt.encryption.pbe.PooledPBEStringEncryptor;
import org.jasypt.encryption.pbe.config.SimpleStringPBEConfig;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.ulisesbocchio.jasyptspringboot.annotation.EnableEncryptableProperties;
@Configuration
@EnableEncryptableProperties
public class JasyptConfig {
        
        @Value("${password}")
        private String passwd;
        
        @Bean("jasyptStringEncryptor")
        public StringEncryptor stringEncryptor() {
            PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
            SimpleStringPBEConfig config = new SimpleStringPBEConfig();
            config.setPassword(passwd); // 암호화키
            config.setAlgorithm("PBEWithMD5AndDES"); // 알고리즘
            config.setKeyObtentionIterations("1000"); // 반복할 해싱 회수
            config.setPoolSize("1"); // 인스턴스 pool
            config.setProviderName("SunJCE");
            config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator"); // salt 생성 클래스
            config.setIvGeneratorClassName("org.jasypt.iv.NoIvGenerator"); // PBEWithMD5AndDES 사용시에는 이걸 해줘야함
            config.setStringOutputType("base64"); //인코딩 방식
            encryptor.setConfig(config);
            return encryptor;
        }
}
 
cs

 

별로 어려운 것 없이 이거 하나 구현해주면 다 끝난다. 그럼 암호화된 properties의 값들을 복호화해서 쓰게 됨.

자 그럼 데이터를 어떻게 암호화 하냐면,

 

3. Junit Test

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
package com.test;
 
import org.jasypt.encryption.pbe.StandardPBEStringEncryptor;
 
public class Test {
 
    @org.junit.Test
    public void test() {
            String url = "jdbc:mariadb://127.0.0.1:3306/mysql";
            String username = "root";
            String password = "1234";
 
            System.out.println(jasyptEncoding(url));
            System.out.println(jasyptEncoding(username));
            System.out.println(jasyptEncoding(password));
        }
 
        public String jasyptEncoding(String value) {
 
            String key = "sssss";
            StandardPBEStringEncryptor pbeEnc = new StandardPBEStringEncryptor();
            pbeEnc.setAlgorithm("PBEWithMD5AndDES");
            pbeEnc.setPassword(key);
            return pbeEnc.encrypt(value);
        }
}
cs

 

난 그냥 junit test로 돌렸는데 아무 클래스 하나 파고 하드코딩(?)같이 해서 암호화 돌려도 상관없다.

algorithm의 경우에는 고객사에서 사용하는 방식으로 적용했고, key는 복호화할때도 써야하는것이니까 잘 보관해야 한다.

쟤네들을 암호화 하면 salt값 포함, 다음과 같은 값이 추출된다.

 

4. application.properties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
 
#암호화 적용
spring.datasource.url=ENC(VIM2Q6Cr058I98tDUqk9t5AzhbOBtg7prpvmLVo4RMMAvb0vIvzPnggGZlJMOXmH)
spring.datasource.username=ENC(AGGJxx/HldORQzlVuuGTXQ==)
spring.datasource.password=ENC(mCuZLaeAjXHejmp6gW4Yuw==)
 
#암호화 미적용
#spring.datasource.url=jdbc:mariadb://127.0.0.1:3306/mysql
#spring.datasource.username=root
#spring.datasource.password=1234
 
#JasyptConfig 에서 설정한 BeanName
jasypt.encryptor.bean=jasyptStringEncryptor
#JasyptConfig 에서 사용할 passkey
password=sssss
 
jasypt.encryptor.iv-generator-classname=org.jasypt.iv.NoIvGenerator
 
cs

주석에 내용이 다 달려있어서 문제는 없다.

다만, PBEWithMd5AndDES 암호화를 사용하는 경우 datasource.password를 복호화하지 못하는 이슈가 발생하였고, 

JasyptConfig 에 iv-generator-classname을 위와 같이 설정해주어야 한다.

properties에 설정해도 먹히기도 하고, 안먹히기도 한다. 뭐야 이거 양자역학이야?

 

이 이슈는 위의 암호화 방식만 해당되는 내용이므로 다른 방식으로 적용할 경우에는 문제가 없을것이라 판단된다.

블로그 이미지

김생선

세상의 모든것을 어장관리

,

몇년만에 밑바닥부터 SpringBoot를 설정해서 개발하는지 모르겠다. 이하는 추후 활용을 위한 개인 기록용.

 

1. pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
(중략)
 
<!-- Spring Boot 2.7.8 버전으로 활용 -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.8</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>
 
<!-- Mybatis Maven -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.3.2</version>
</dependency>
 
<!-- Maria DB -->
<dependency>
    <groupId>org.mariadb.jdbc</groupId>
    <artifactId>mariadb-java-client</artifactId>
    <version>2.7.11</version>
</dependency>
cs

 

maven dependency는 대략적으로 위와 같이 잡아주었다.

 

2. TestMapper.java / TestService.java / TestServiceImpl.java / TestController.java / TestSql.xml

1
2
3
4
5
6
7
8
9
10
package com.test.common.mapper;
 
import org.apache.ibatis.annotations.Mapper;
 
 
@Mapper
public interface TestMapper {
    public String getSysDate();
}
 
cs

 

 

1
2
3
4
5
6
package com.test.comm.service;
 
public interface TestService {
    String getSysdate();
}
 
cs

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.test.comm.service.impl;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
 
import com.test.comm.service.TestService;
import com.test.common.mapper.TestMapper;
 
@Service
public class TestServiceImpl implements TestService {
    @Autowired
    TestMapper testMapper;
    
    @Override
    public String getSysdate() {
        String result = testMapper.getSysDate();
        return result;
    }
 
}
 
cs

 

 

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
package com.test.comm;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
 
import com.test.comm.service.TestService;
 
@RestController
public class testController {
    @Autowired
    TestService testService;
    
    @PostMapping("/test")
    public String test(@RequestBody String a) {
        String result = "";
        result = testService.getSysdate();
        System.out.println("============= :::: " +result);
        return null;
    }
    
}
 
cs
 
 
1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 
<mapper namespace="com.test.common.mapper.TestMapper">
    <select id="getSysDate" resultType="java.lang.String">
        select sysdate() from dual
    </select>
</mapper>
cs

 

 

1
mybatis.mapper-locations=mapper/**/*.xml
cs

 

패키지 구조는 다음과 같다.

com.test.comm.TestController.java

com.test.comm.service.TestService.java

com.test.comm.service.impl.TestServiceImpl.java

com.test.common.mapper.TestMapper.java

src.main.resources.mapper.TestSql.xml

 

application.properties 에는 mybatis.mapper-locations 을 설정해준다.

 

오류가 난 상황으로는 SpringBoot 2.7.8 버전에서 mybatis-spring-boot-starter를 3.x 버전으로 설정해주었더니, TestServiceImpl 에서 @Autowired로 설정한 TestMapper.java를 인식할 수 없다는 오류가 발생했었다.

여러 시행착오 끝에 SpringBoot 2.7.8과 mybatis-spring-boot-starter 3.x과 호환이 안되는것을 인지하고, 현재는 2.x 버전대로 맞추어주었다.

 

에혀 힘들었다.

블로그 이미지

김생선

세상의 모든것을 어장관리

,

개발하는데 java 배치로 Windows Powershell을 파라미터 방식으로 호출할 일이 생겼다. 일단 powershell Script 코드는 다음과 같다.

 

1
2
echo 'TEST-1' $test
echo 'TEST-2' $userid
cs

대충 파라미터 두 개( test/userid)를 받아 echo로 찍어주는건데 기타 하위에는 물론 Azure Portal과의 통신이 있긴 하다. 근데 그게 중요한 것은 아니니까.

아무튼, 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
//중략
 
final Runtime rt = Runtime.getRuntime();
 
//powershell.exe 명령어를 통해서, 해당 경로의 ps1 파일을 실행함
String commands = String.format("powershell.exe \"F:\\powershell.ps1  ");
 
Process proc = null;
String s = null;
 
// PowerShell 명령 시도 및 메시지 출력
try {
    proc = rt.exec(commands);
 
    BufferedReader stdInput = new BufferedReader(new InputStreamReader(proc.getInputStream());
    while (( s = stdInput.readLine()) != null ) {
        System.out.println(s);
    }
 
    BufferedReader stdError = new BufferedReader(new InputStreamReader(proc.getErrorStream());
    while (( s = stdError.readLine()) != null ) {
        System.out.println(s);
    }
 
catch (IOException e) {
    e.printStacTrace();
 
}
 
//생략
cs

요기까지는 잘 했는데, 위의 powershell 코드와 같이 parameter를 넘기는 방법을 구글링해보아도 죄다 제각기 방법이 달라서, 츄라이를 해보니 다음과 같은 소스코드로 성공했다.

 

위의 java 소스에서 commands를 다음과 같은 방법으로 변경해주면 된다.

1
2
3
4
5
6
7
//중략
 
String[] arr = {"test_msg" , "test_user"}
//powershell.exe 명령어를 통해서, 해당 경로의 ps1 파일을 실행함
String commands = String.format("powershell.exe \"F:\\powershell.ps1  " + arr[0+ " " + arr[1]);
 
//생략
cs

PowerShell 코드는 다음과 같이 수정해주면 된다.

1
2
3
4
5
$test = $args[0]
$userid = $args[1]
 
echo 'TEST-1' $test
echo 'TEST-2' $userid
cs

java 에서 공백문자열로 구분된 string 값들이 args로 잘 매핑이 된다.

 

테스트를 해보니 해당 ps1(powershell)을 호출할 때 각 String 형태의 parameter(arguments)들을 넘길 때, 그냥 공백문자열로 구분하는 것으로 보인다. 실제로 여러방법으로 테스트 해보니 잘 되기도 하고. 이로써 java로 powershell script를 parameter 포함하여 execute 하는것에 성공했다. 앞으로 근데, 이러한 플젝을 몇번이나 할 지는 모르겠지만 말이다 =_=;;

 

 

java - how pass string array as a parameters to a powershell 

블로그 이미지

김생선

세상의 모든것을 어장관리

,

한컴오피스의 한글파일(hwp)에서 텍스트 추출할 일이 생겼다.

대충 뒤져보니 대단하신 분 께서 한글문서 파서 라이브러리를 만드셨는데, 아직까지도 일부기능에 대해 개선작업을 진행중이신 것 같다.

뭐 표 라거나 그림파일 등에 대해서는 정상동작하지 않는 듯 하지만 나는 텍스트만 추출할 것이기 때문에 당장은 문제없이 사용가능할것으로 보인다.

 

자세한 지원범위는 이 분의 깃으로 들어가보면 될 듯.

https://github.com/neolord0/hwplib

 

GitHub - neolord0/hwplib: hwp library for java

hwp library for java. Contribute to neolord0/hwplib development by creating an account on GitHub.

github.com

아무튼, 대충 임포트하고 대충 써보기로 한다. 생각보다 텍스트 추출이 아주 잘 되어서 다행이다.

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/kr.dogfoot/hwplib -->
<dependency>
  <groupId>kr.dogfoot</groupId>
  <artifactId>hwplib</artifactId>
  <version>1.0.1</version>
</dependency>
cs

이렇게 잡아주고,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*
* hwplib hwp document text extraction example
*/
 
import kr.dogfoot.hwplib.object.HWPFile;
import kr.dogfoot.hwplib.reader.HWPReader;
import kr.dogfoot.hwplib.tool.textextractor.TextExtractMethod;
import kr.dogfoot.hwplib.tool.textextractor.TextExtractor;
 
// 중략
 
HWPFile hwpFile;
String hwpText;
try {
    hwpFile = HWPReader.fromFile("/Users/kimfish/DEV/java_workspace/"+"test.hwp");
    hwpText = TextExtractor.extract(hwpFile, TextExtractMethod.InsertControlTextBetweenParagraphText);
 
    System.out.println("===== hwp text extractor =====");
    System.out.println("hwpText = " + hwpText);
catch (Exception e) {
    e.printStackTrace();
cs

이렇게 쓰면 된다. 개꿀

블로그 이미지

김생선

세상의 모든것을 어장관리

,

일전의 포스트와 마찬가지로 pdf 에서도 텍스트를 추출할 일이 생겼다.

당연하겠지만 해당 pdf는 ocr이 된 pdf를 기준으로만 추출이 가능하다.

 

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/org.apache.pdfbox/pdfbox -->
<dependency>
  <groupId>org.apache.pdfbox</groupId>
  <artifactId>pdfbox</artifactId>
  <version>2.0.24</version>
</dependency>
cs

이야 1년쯤 전에는 2.0.18 이었는데 그새 버전업했네. 아무튼 maven repository는 이렇게 잡아주고

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
* PDFBox library PDF text Extraction Example
*/
 
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;
 
// 중략
 
try {
    File file = new File("/Users/kimfish/DEV/java_workspace/"+"/test.pdf");
    PDDocument document;
    document = PDDocument.load(file);
 
    PDFTextStripper s = new PDFTextStripper();
    String content = s.getText(document);
 
    System.out.println("===== docx text extractor =====");
    System.out.println(content); 
catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}
cs

이렇게 하면 정상적으로 OCR 처리된 text가 추출된다.

 

 

 

블로그 이미지

김생선

세상의 모든것을 어장관리

,

아주 심플하게, 회사에서 해당 문서규격(xls/xlsx/doc/docx)에 대해 텍스트를 추출하는것이 필요했다. 일전에는 잠깐 해보았는데, 정리된 적이 없었고 이번을 계기로 좀 알게된 몇가지 사실들이 있기에 간단하게 기록해본다.

 

일단 메이븐에서 다음과 같이 라이브러리를 잡아준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<dependency>
  <groupId>org.apache.poi</groupId>
  <artifactId>poi-scratchpad</artifactId>
  <version>5.2.2</version>
</dependency>
 
<dependency>
  <groupId>org.apache.poi</groupId>
  <artifactId>poi</artifactId>
  <version>5.2.2</version>
</dependency>
 
<dependency>
  <groupId>org.apache.poi</groupId>
  <artifactId>poi-ooxml</artifactId>
  <version>5.2.2</version>
</dependency>
 
cs

 

1. 워드 (doc/docx) 텍스트 추출

워드 2003 이전버전인 doc 와 이후 버전인 docx 를 추출하기 위해서는 두 가지만 기억하면 된다.

HWPFDocument 클래스는 Horrible Word Process Format 의 약자라는 점이며, poi 라이브러리만 상속받는것이 아닌 poi-scratchpad 라이브러리를 같이 상속받아야 한다. 즉, poi dependency만 잡아놓으면 HWPFDocument가 import 되지 않는다!

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
/*
* Word 2003 doc File Text Extraction Example
*/
 
import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.extractor.WordExtractor;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
 
// 중략
 
try {
    POIFSFileSystem poiFS = new POIFSFileSystem(
        new FileInputStream("/Users/kimfish/DEV/java_workspace/"+"/test.doc"));
    HWPFDocument hwp = new HWPFDocument(poiFS);
    WordExtractor we = new WordExtractor(hwp);
 
    String[] paragraphs = we.getParagraphText();
    System.out.println("===== doc text extractor ======");
    for (int i = 0; i < paragraphs.length; i++) {
        System.out.println(paragraphs[i]);
    }
 
catch (Exception e) {
    System.out.println(e); 
}
cs

 

docx 파일은 XWPFDocument를 import 받으며, docx의 text추출을 위해서는 poi 및 poi-ooxml 라이브러리에 대한 dependency가 필요하다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
* Word 2007 docx File Text Extraction Example
*/
 
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xwpf.extractor.XWPFWordExtractor;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
 
// 중략
 
try(
    FileInputStream fis = new FileInputStream
            ("/Users/kimfish/DEV/py_workspace/"+"/test.docx")) {  
    XWPFDocument file   = new XWPFDocument(OPCPackage.open(fis));  
    XWPFWordExtractor ext = new XWPFWordExtractor(file);  
 
    System.out.println("===== docx text extractor ======");
    System.out.println(ext.getText());  
}catch(Exception e) {  
    System.out.println(e);  
}  
cs

 

2. 엑셀 (xls/xlsx) 텍스트 추출

엑셀은 고려할 점이 상당히 많은데 각 셀/로우 별 텍스트 및 쉬트도 있는데다가 수식까지 가지고 있다. 현재는 샘플 토이 프로젝트라 수식이나 셀/로우 구분없이 텍스트만 가져오기를 수행해보았는데 일단 첫 쉬트에서는 잘 가져오고 있는듯. 좀 더 정교한 작업이 필요할 경우엔 공식 document 가 필요할것으로 보인다.

 

엑셀 2003 이전버전인 xls 같은 경우에는 poi Dependency 하나로도 잘 작동한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
* Excel 2003 xls File Text Extraction Example
*/
 
import org.apache.poi.hssf.extractor.ExcelExtractor;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
 
// 중략
 
try (
    FileInputStream fis = new FileInputStream("/Users/kimfish/DEV/java_workspace/"+"/test.xls")) {  
    
    String result = "";
 
    HSSFWorkbook wb = new HSSFWorkbook(fis);
    ExcelExtractor ee = new ExcelExtractor(wb);
    result = ee.getText();    
 
    System.out.println("===== xls text extractor =====");
    System.out.println(result);    
catch (Exception e) {
    //TODO: handle exception
}
cs

 

하지만 엑셀 2007 버전인 xlsx 같은 경우에는 워드2007과 마찬가지로 poi / poi-ooxml dependency 둘 다 필요하다.

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
/*
* Excel 2007 xlsx File Text Extraction Example
*/
 
import org.apache.poi.xssf.extractor.XSSFExcelExtractor;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
 
// 중략
 
try (
    
    FileInputStream fis = new FileInputStream("/Users/kimfish/DEV/py_workspace/"+"/test.xlsx")) {  
                
    // Workbook
    XSSFWorkbook wb = new XSSFWorkbook(fis);
            
    // Text Extraction
    XSSFExcelExtractor extractor = new XSSFExcelExtractor(wb);
    extractor.setFormulasNotResults(true);
    extractor.setIncludeSheetNames(false);
    
    System.out.println("===== xlsx text extractor =====");
    System.out.println( extractor.getText() );
catch (Exception e) {
    //TODO: handle exception
}
cs

 

일단은 이렇게 해서 정상적으로 출력되는것을 확인함. 어휴 poi 하나로 다 해놓든가 의존성을 걸어두던가. 왜 poi / poi-ooxml / poi-scratchpad 이렇게 세 개가 죄다 쪼개져있는지원;;

 

정리

xlsx - poi / poi-ooxml

xls - poi

docx - poi / poi-ooxml

doc - poi / poi-scratchpad

'어장 Develop > 어장 JAVA' 카테고리의 다른 글

[hwplib] hwplib을 이용한 한글파일 텍스트 추출  (1) 2022.07.06
[PDFBox] pdf 텍스트 추출  (0) 2022.07.05
[Tomcat 9.x] JNDI설정  (0) 2022.02.21
[PDFBox] java Image to PDF  (0) 2021.08.24
[xml] RestAPI XML Return (2)  (0) 2021.07.06
블로그 이미지

김생선

세상의 모든것을 어장관리

,

파이썬 개발을 하면서 가장 헷갈리는 개념이 디렉토리 참조법이다. 자바에서는 그냥 대충 import package.directory.fileName; 과 같은 형식으로 지정해주면 바로 가져다 쓸 수 있는데 반해, 파이썬은 그렇지 않은가보다.

 

아무튼 세 가지의 케이스에 대해 정리/적용해보았고 그 내용은 다음과 같다.

 

패키지 구성은 다음과 같은 형식으로 되어있다고 가정한다.

project
⌞ A_folder
   - a_file_1.py
   - a_file_2.py
 B_folder
   - b_file_1.py
   ⌞ C_folder
       - c_file_1.py
   ⌞ D_folder
       - d_file_1.py

1. 동일 경로상의 import

A_folder 내부의 a_file_1.py 에서 a_file_2.py 파일을 import 하기위해서는 다음과 같이 작성한다.

1
from . import a_file_2.py
cs

또는

1
import a_file_2.py
cs

이거는 쉽다.

 

2. 하위 폴더의 import 

B_folder 내부의 b_file_1.py 에서 C_folder 내부의 c_file_1.py를 import 하기 위해서는 다음과 같이 작성한다.

1
from C_folder import c_file_1.py
cs

이정도는 크게 어렵지 않다.

 

3. 상위 폴더의 import

여기서부터 많이 헷갈리게 된다.

C_folder 내부의 c_file_1.py 에서 D_folder 내부의 d_file_1.py를 import 하기 위해서는 꽤 여러가지 방법이 있으나, 일단 내가 해결한 방법을 위주로 작성해본다.

1
2
3
4
import os
import sys
sys.path.append(os.path.dirname(os.path.abspath(os.path.dirname(__file__))))
from D_folder import d_file_1.py
cs

여기에서 os.path.dirname(os.path.abspath( 는 한단계 상위를 의미하며, C_folder의 한단계 상위인 B_folder로 이동함을 의미한다. B_folder 내부에 D_folder가 위치해있으므로, from d_folder 을 해주는 것.

 

마찬가지로 c_file_1.py 에서 A_folder 를 참조하기 위해서는 os.path.dirname(os.path.abspath를 두 번 작성해주면 될 것이다.

 

 

java를 하다와서 그런지, from~ 쪽도 잘 이해가 안갔고 __file__과 같은 옵션네이밍(?)의 정의도 익숙하지 않다. 일단은 해결했으니까, 다음 개발로 넘어가야지...

블로그 이미지

김생선

세상의 모든것을 어장관리

,
1
2
3
4
***** Usage Dev Env *****
= OS - macOS Monterey ver 12.3.1
= IDE - VSCode
= Lang - Python 3.10.4
cs

개발하고나서보니 아래의 내용 모두 다 무쓸모했다.

내가 크롤링하려던 사이트에서 스크립트의 오류인지 뭔지 chromedriver로는 정상적으로 로딩할 수 없었고, firefox를 이용한 geckodriver를 활용하니 단박에 모두 정리가 되었다.

 

아 크롬을 이렇게 버려야하나 ㅡㅡ;

파이썬으로 크롤링 개발을 하는데 요상한 에러가 발생했다. 사실 별 소스코드도 없이 파이썬에 셀레니움, 크롬드라이버를 붙여서 브라우저 하나 띄우는건데 여기서 저런 에러를 만날 줄이야 꿈에도 몰랐다.

 

대충 아래의 소스코드에서 오류가 발생했다.

1
2
3
4
5
6
7
8
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from time import sleep
 
chrome_options = webdriver.ChromeOptions()
driver = webdriver.Chrome(options=chrome_options)
driver.get('http://kimfish.co.kr')
driver.quit()
cs

 

그리고 발생오류상황은 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
➜  ~ /usr/local/bin/python3 /Users/kimfish/DEV/py_workspace/testChromeDriver.py
Traceback (most recent call last):
  File "/Users/kimfish/DEV/py_workspace/testChromeDriver.py", line 9, in <module>
    driver.get("https://bbs.ruliweb.com")
  File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/selenium/webdriver/remote/webdriver.py", line 437, in get
    self.execute(Command.GET, {'url': url})
  File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/selenium/webdriver/remote/webdriver.py", line 425, in execute
    self.error_handler.check_response(response)
  File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/selenium/webdriver/remote/errorhandler.py", line 247, in check_response
    raise exception_class(message, screen, stacktrace)
selenium.common.exceptions.WebDriverException: Message: unknown error: session deleted because of page crash
from tab crashed
  (Session info: chrome=100.0.4896.88)
Stacktrace:
0   chromedriver                        0x0000000105383c34 chromedriver + 4406324
1   chromedriver                        0x000000010531d290 chromedriver + 3986064
2   chromedriver                        0x0000000104f7e71c chromedriver + 190236
=== 중략 ===
➜  ~ 
cs

 

Message: unknown error: session deleted because from tab crashed 와 같은 오류에 대해 검색을 해보니, 가상머신(대부분 도커)의 /dev/shm 메모리가 부족하다는둥 뭐라는둥 말이 많았는데 난 단순히 내 로컬환경에서 개발을 할 뿐이다 ㅠ

 

그래서 조금 더 찾아보니 chromedriver 호출시, 다음과 같은 옵션을 설정해주면 된다고 한다.

1
2
3
4
5
6
chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument('--no-sandbox')
chrome_options.add_argument('--headless')
chrome_options.add_argument('--window-size=1420,1080')
chrome_options.add_argument('--disable-dev-shm-usage')
chrome_options.add_argument('--disable-gpu')
cs

적용 후 돌렸다! 그런데도 똑같은 오류가 발생한다!

 

환장하겄다 싶어 옵션을 다 빼보면서 테스트 하다가 결국 원인을 찾기는 했다. 바로, --headless 옵션이 문제였다.

해당 옵션은 UI가 지원되지 않는 서버들을 위해 작동하는 옵션으로 알고 있는데, 내 개발환경에서는 왜 오류가 발생하는지는 아직 잘 모르겠다. 일단 저 옵션을 제외하고는 잘 동작하니까 개발하겠지만... 조금 불안하다. 원인을 알면 해결법도 찾아야하는데.. 으으 

블로그 이미지

김생선

세상의 모든것을 어장관리

,

* M1 MacBookPro macOS Monterey 12.3.1

 

파이썬 + selenium을 이용하여 웹 크롤러를 만드려고 한다.

그전에 셀레니움을 사용하기 위해서는 크롬 디버그모드 활용툴과 같은 개념인 chromedriver를 설치, 사용해야 한다. 설치법은 크게 두 종류로 나뉘는 것으로 보이는데, 'homebrew install'과 '공식홈페이지 다운로드 설치' 두가지의 방법이다.

 

개인적으로는 버전관리가 간편한 homebrew 설치가 좋을것이라 생각한다. 괜히 공식 홈페이지 인스톨 했다가 크롬브라우저 버전업 하면 또 가서 다운받고 압축풀고 할라면 귀찮을듯.

1.  homebrew 로 설치

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
➜  ~ brew install cask chromedriver
==> Auto-updated Homebrew!
Updated 1 tap (homebrew/core).
==> New Formulae
gops
==> Updated Formulae
Updated 2 formulae.
 
==> Downloading https://chromedriver.storage.googleapis.com/100.0.4896.60/chrome
Already downloaded: /Users/kimfish/Library/Caches/Homebrew/downloads/366342d76a11f7c61ca4b54b8d00cf63f59917eb118f2d9f5745484daa0f8598--chromedriver_mac64_m1.zip
==> Installing Cask chromedriver
==> Linking Binary 'chromedriver' to '/opt/homebrew/bin/chromedriver'
🍺  chromedriver was successfully installed!
==> Downloading https://ghcr.io/v2/homebrew/core/cask/manifests/0.8.8
Already downloaded: /Users/kimfish/Library/Caches/Homebrew/downloads/2b8b007815bfe6179d48bcdcbcce75cc494fd12d6c3dd831e60a05cb50229c7f--cask-0.8.8.bottle_manifest.json
==> Downloading https://ghcr.io/v2/homebrew/core/cask/blobs/sha256:f50a59d4337bc
Already downloaded: /Users/kimfish/Library/Caches/Homebrew/downloads/10cb17e2b53cf4b92c88efd42c0d6dcaf43570a3e7ba65939e070cb79335b3bd--cask--0.8.8.all.bottle.tar.gz
==> Pouring cask--0.8.8.all.bottle.tar.gz
==> Caveats
Emacs Lisp files have been installed to:
  /opt/homebrew/share/emacs/site-lisp/cask
==> Summary
🍺  /opt/homebrew/Cellar/cask/0.8.813 files, 152KB
==> Running `brew cleanup cask`...
Disable this behaviour by setting HOMEBREW_NO_INSTALL_CLEANUP.
Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).
==> Caveats
==> cask
Emacs Lisp files have been installed to:
  /opt/homebrew/share/emacs/site-lisp/cask
cs

한 번 지웠다가 재설치한 부분이 있어 로그가 좀 깔끔하지 않은듯 한데, 일단 설치 자체는 어렵지 않다.

그리고 이거 다른 사람들의 블로그에서 잘 다뤄주지 않던 이야기인데, 크롬 브라우저는 워낙에 버전업이 자주되다보니 소스코드가 잘 안돌아간다 싶으면 수시로 브라우저 버전과 크롬드라이버 버전이 일치하는지 확인해야 한다. 그럴 때 쓰는 버전 확인 명령어는 다음과 같다.

 

2. chromedriver 악성소프트웨어 검사

어라? 크롬드라이버 버전확인을 하려 했더니 다음과 같은 오류메시지가 발생하네?

 

‘chromedriver’() Apple에서 악성 소프트웨어가 있는지 확인할 없기 때문에 없습니다.

이는 권한 설정으로 해결할 수 있다. 다음과 같이 수행이 가능하다.

1
2
3
➜  bin git:(stable) pwd
/opt/homebrew/bin
➜  bin git:(stable) xattr -d com.apple.quarantine chromedriver
cs

주의할 점은 xattr -d 명령어 자체가 chromedriver 가 설치되어있는 디렉토리에서 수행해야 한다는 점이다.

brew 로 설치하는 경우에는 /opt/homebrew/bin 디렉토리 내부에 위치한다.

정상적으로 적용이 되어도 별다른 프롬프트가 나오지 않으니 당황하지 말고, 바로 버전 확인 명령어를 작성한다.

1
2
3
4
➜  bin git:(stable) pwd
/opt/homebrew/bin
➜  bin git:(stable) chromedriver -version
ChromeDriver 100.0.4896.60 (6a5d10861ce8de5fce22564658033b43cb7de047-refs/branch-heads/4896@{#875})
cs

현재 브라우저의 버전과 크롬드라이버 버전이 일치하는지는 이렇게 확인하면 된다.

블로그 이미지

김생선

세상의 모든것을 어장관리

,

얼마전에 맥북을 샀다. 그러면서 새롭게 개발환경을 설정했다. 이하는 OpenJdk를 설치하는 내용이다.

명령어 순서는 다음과 같다.

1
2
3
4
5
6
7
1. brew tap AdoptOpenJdk/openjdk
 
2. brew install --cask adoptopenjdk8
 
3. /usr/libexec/java_home -1.8
 
4. java -version
cs

 

1. brew tap AdoptOpenJdk/openjdk

1
2
3
4
5
6
7
8
9
10
11
12
13
14
kimfish@KimFish-MacBookPro ~ % brew tap AdoptOpenJdk/openjdk
==> Auto-updated Homebrew!
Updated 1 tap (homebrew/core).
==> Updated Formulae
Updated 4 formulae.
==> Tapping adoptopenjdk/openjdk
Cloning into '/opt/homebrew/Library/Taps/adoptopenjdk/homebrew-openjdk'...
remote: Enumerating objects: 1996done.
remote: Counting objects: 100% (60/60), done.
remote: Compressing objects: 100% (22/22), done.
remote: Total 1996 (delta 44), reused 49 (delta 38), pack-reused 1936
Receiving objects: 100% (1996/1996), 372.27 KiB | 7.16 MiB/s, done.
Resolving deltas: 100% (1424/1424), done.
Tapped 47 casks (69 files, 521.8KB).
 

 

2. brew install --cask adoptopenjdk8 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
kimfish@KimFish-MacBookPro ~ % brew install --cask adoptopenjdk8
==> Downloading https://github.com/AdoptOpenJDK/openjdk8-binaries/releases/downl
==> Downloading from https://objects.githubusercontent.com/github-production-rel
######################################################################## 100.0%
==> Installing Cask adoptopenjdk8
==> Running installer for adoptopenjdk8; your password may be necessary.
Package installers may write to any location; options such as `--appdir` are ignored.
installer: Package name is AdoptOpenJDK
installer: Installing at base path /
installer: The install was successful.
package-id: net.adoptopenjdk.8.jdk
version: 1.8.0_292-b10
volume: /
location: 
install-time: 1649472633
🍺  adoptopenjdk8 was successfully installed!
cs

 

3.  /usr/libexec/java_home -v 1.8

1
2
kimfish@KimFish-MacBookPro ~ % /usr/libexec/java_home -1.8
/Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/Home
cs

 

4. java -version

1
2
3
4
kimfish@KimFish-MacBookPro ~ % java -version
openjdk version "1.8.0_292"
OpenJDK Runtime Environment (AdoptOpenJDK)(build 1.8.0_292-b10)
OpenJDK 64-Bit Server VM (AdoptOpenJDK)(build 25.292-b10, mixed mode)
cs

 

정상적으로 설치가 된 모습이다. 설치하고나니 AdoptOpenJdk가 아닌 RedhatOpenJdk를 설치할걸 그랬나 싶긴 하지만, 일단 사용 해 보고 문제가 발생하면 나중에 다시 설치하지뭐

블로그 이미지

김생선

세상의 모든것을 어장관리

,