test 테이블의 데이터

test1
abcd
1111
1234


array로 변경

select array_agg(test1) from test {abcd,1111,1234}

string으로 변경

select string_agg(test1, ',') from test abcd,1111,1234


array 데이터를 각 로우로 변경

unnest(array타입)


-- 예시
select unnest(ARRAY[1,2])


1
2
(2 rows)

array 타입에 데이터 추가

array_append(array, 값)


-- 예시
array_append(ARRAY[1,2], 3)


{1,2,3}

array 타입끼리 협차기

array_cat(array, array}


-- 예시
array_cat(ARRAY[1,2], ARRAY[3,4])


{1,2,3,4}

array에서 string(단일컬럼)으로 변경

array_to_string(array, 구분자, [NULL 값 대체 구분자])


-- 예시
array_to_string(ARRAY[1, 2, 3, NULL, 5], ',', '*')


1,2,3,*,5

string을 array로 변경

string_to_array(string, 구분자, [null로 변경할 값])


-- 예시
string_to_array('xx~^~yy~^~zz', '~^~', 'yy')
{xx,,zz}


Django 에서는 crontab 기능을 지원하여 손쉽게 배치를 추가/실행/삭제를 할 수 있습니다.

설치

$ pip3 install django-crontab

crontab 등록

스크립트 등록을 위한 함수를 생성합니다.

# app/cron.py


from my.app import run


def scheduler():
	run()
	pass


이후 settings.py에서 아래와 같은 형식으로 등록합니다.

CRONJOBS = [
	('* * * * *', 'app.cron.scheduler')
]


CRONJOBS 내에 들어가는 데이터 형식은 아래와 같습니다.

(' 분 시 일 월 요일 ', '앱이름.파일명.함수명')

스케쥴링에 대한 자세한 내용은 http://brownbears.tistory.com/15 에서 확인할 수 있습니다.


위 작업까지 완료했으면 이제 crontab 명령어를 사용해 처리하면 됩니다.

crontab 명령어

목록

$ python3 manage.py crontab show

추가

$ python3 manage.py crontab add

삭제

$ python3 manage.py crontab remove


위 명령어를 통해 등록했을 경우 실제 서버의 crontab에도 등록된 것을 확인할 수 있습니다.

$ crontab -l


* * * * * /bin/python3 /manage.py crontab run 49fb9b8a9b695f26de0c796631105b3e # django-cronjobs for base


백업과 아카이브는 엄연히 다른 기능입니다.

백업이란?

백업(Backup)은 데이터가 손상되거나 손실될 경우를 대비해 저장하는 데이터의 사본입니다. 원본 데이터는 백업을 생성한 후에도 지우지 않습니다. 백업의 예로 휴대폰의 사진을 클라우드에 복사하는 것부터 중요한 파일을 USB에 복사하는 등 다양합니다. 또한 파일 서버(비정형 데이터)와 데이터베이스(구조화 데이터)도 백업합니다. 이처럼 백업은 데이터를 그대로 복사하는 것에에 중점을 둡니다.

백업은 사고가 났을 때 데이터를 복원(Restore)하는 것입니다. 만약 휴대폰의 모든 사진을 클라우드로 백업했는데 휴대폰이 고장났으면 클라우드에 백업한 파일을 그대로 내려받아 사용할 수 있으며 서버 내 랜섬웨어가 걸렸다면 랜섬웨어 해결 후, 백업한 데이터를 받아 정상적으로 운영할 수 있습니다.

아카이브란?

아카이브(Archive)는 참고용으로 생성한 데이터 사본입니다. 종종 아카이브를 만든 후에는 원본 데이터를 지우기도 합니다. 백업의 목적이 어떤 것을 정상적인 상태로 되돌리는 것이라면, 아카이브는 여러 가지 목적이 있는데 보편적으로는 이전 데이터에서 일부 데이터를 찾는 것입니다. 예를 들어, 몇년 전에 고객이 서명한 계약서처럼 아주 중요한 내용을 담은 파일 하나를 찾는 것입니다.

또 하나의 데이터는 특정 시점을 증명할 수 있는 이메일이나 파일 같은 것입니다. 예를 들어, 3년간 홍길동이란 이름이 들어간 모든 메일을 찾는 행위입니다. 보통 데이터를 실시간 상태로 유지하고 싶어 하지만 데이터는 아카이브로 보존하며, 아카이브는 인덱스를 제공해 사용자는 오래된 콘텐츠에서 과거의 데이터를 되찾을 수 있도록 합니다. 일부 서버에서의 아카이브 시스템은 아카이브의 크기나 액세스 기간으로 아카이브를 삭제할 수 있습니다. 이러한 행동으로 시스템을 최적화하고 저장공간을 절약할 수 있습니다.

복원과 회수의 차이점

백업 시스템은 데이터를 복원(Restore)하고 아카이브 시스템은 데이터를 회수(Retrieval)합니다. 복원은 단일 파일이나 서버, 데이터베이스에 해당하는 경우가 대부분입니다. 하지만 뭔가를 회수한다고 하면, 보통은 관련 데이터 모음입니다. 이러한 데이터는 동일한 서버나 형식으로 저장되어 있지 않을 가능성이 큽니다.

복원은 특정 시점을 기준으로 이루어집니다. 예를 들어, 데이터베이스를 어제와 같은 상태로 복원하는 것입니다. 반면에 회수는 일정 시간대를 사용합니다. 예를 들어, 지난 3년 간의 모든 이메일과 같은 방식입니다. 복원을 하기 위해서는 데이터가 언제 어디에 저장되었는지 알아야 합니다. 1개라도 모를 경우 진행을 할 수 없습니다. 데이터가 있던 서버 이름부터 데이터베이스나 디렉토리를 알아야 하고, 복원하려는 파일 이름이나 테이블, 마지막으로 본 날짜와 시간도 알아야 합니다. 회수의 경우는 아카이브가 되어 있는 서버에서 찾고자 하는 특정 파라미터만 있으면, 파라미터와 일치하는 모든 데이터를 가져오면 끝납니다.

Python에는 beautifulsoup 패키지가 존재해 Go에서도 없을까 찾아보던 도중 유사한 패키지를 찾았습니다. 해당 패키지를 사용하여 웹 크롤러를 만들거나 html에서 필요한 정보를 검색할 때, 편리하게 코딩할 수 있습니다.

설치

$ go get github.com/PuerkitoBio/goquery

사용법

아래는 현재 최신버전인 1.5 버전으로 설명합니다. 1.4이하 버전에서는 NewDocument()가 존재했지만 1.4버전에서 더이상 사용하지 않으므로 언급하지 않습니다.

package main

import (
  "fmt"
  "log"
  "net/http"

  "github.com/PuerkitoBio/goquery"
)

func ExampleScrape() {
  // Request the HTML page.
  res, err := http.Get("http://metalsucks.net")
  if err != nil {
    log.Fatal(err)
  }
  defer res.Body.Close()
  if res.StatusCode != 200 {
    log.Fatalf("status code error: %d %s", res.StatusCode, res.Status)
  }

  // Load the HTML document
  doc, err := goquery.NewDocumentFromReader(res.Body)
  if err != nil {
    log.Fatal(err)
  }

  // Find the review items
  doc.Find(".sidebar-reviews article .content-block").Each(func(i int, s *goquery.Selection) {
    // For each item found, get the band and title
    band := s.Find("a").Text()
    title := s.Find("i").Text()
    fmt.Printf("Review %d: %s - %s\n", i, band, title)
  })
}

func main() {
  ExampleScrape()
}


위는 타겟 사이트를 호출하여 원하는 데이터를 찾아가는 예시입니다. 아래에서는 test html을 생성 후 기능을 하나씩 설명한 예제입니다.

test html 생성 후 원하는 태그 찾기

검색은 html을 찾는 방식과 동일합니다.

태그 .클래스명 #아이디명

예시)

div 안에 있는 모든 p태그 찾기

  • div p

class=name 명 찾기

  • .name

id=err 명 찾기

  • #err

<div class=name> 찾기

  • div.name

<div class=name> <p>test</p> </div> 에서 p태그 내의 내용찾기

  • div.name p


아래는 a 태그를 찾는 예시입니다.

package main

import (
	"fmt"
	"strings"

	"github.com/PuerkitoBio/goquery"
)

func ExampleScrape() {
	// HTML 요청
	html := `
	<!DOCTYPE html>
<html>

<head>
   <title>Page title</title>
</head>

<body>
   <div>
      <p>a</p>
      <p>b</p>
      <p>c</p>
   </div>
   <div class="ex_class">
      <p>d</p>
      <p>e</p>
      <p>f</p>
   </div>
   <div id="ex_id">
      <p>g</p>
      <p>h</p>
      <p>i</p>
   </div>
   <h1>This is a heading</h1>
   <p>This is a paragraph.</p>
   <p>This is another paragraph.</p>
   <a href="http://brownbears.tistory.com" class="a"/>
</body>

</html>
	`
	rHtml := strings.NewReader(string(html))

	// HTML 문서 불러오기
	doc, err := goquery.NewDocumentFromReader(rHtml)
	if err != nil {
		panic(err)
	}

	// Find the review items
	doc.Find("a").Each(func(i int, s *goquery.Selection) {
		// 코드
		// fmt.Println(s.Text())
		
	})
}

func main() {
	ExampleScrape()
}

태그 사이에 있는 내용 출력

package main

import (
	"fmt"
	"strings"

	"github.com/PuerkitoBio/goquery"
)

func ExampleScrape() {
	// HTML 요청
	html := `
	<!DOCTYPE html>
<html>

<head>
   <title>Page title</title>
</head>

<body>
   <div>
      <p>a</p>
      <p>b</p>
      <p>c</p>
   </div>
   <div class="ex_class">
      <p>d</p>
      <p>e</p>
      <p>f</p>
   </div>
   <div id="ex_id">
      <p>g</p>
      <p>h</p>
      <p>i</p>
   </div>
   <h1>This is a heading</h1>
   <p>This is a paragraph.</p>
   <p>This is another paragraph.</p>
   <a href="http://brownbears.tistory.com" class="a"/>
</body>

</html>
	`
	rHtml := strings.NewReader(string(html))

	// HTML 문서 불러오기
	doc, err := goquery.NewDocumentFromReader(rHtml)
	if err != nil {
		panic(err)
	}

	// Find the review items
	doc.Find("h1").Each(func(i int, s *goquery.Selection) {
		fmt.Println(s.Text())

	})
}

func main() {
	ExampleScrape()
}

// This is a heading

태그 내의 속성값 출력

package main

import (
	"fmt"
	"strings"

	"github.com/PuerkitoBio/goquery"
)

func ExampleScrape() {
	// HTML 요청
	html := `
	<!DOCTYPE html>
<html>

<head>
   <title>Page title</title>
</head>

<body>
   <div>
      <p>a</p>
      <p>b</p>
      <p>c</p>
   </div>
   <div class="ex_class">
      <p>d</p>
      <p>e</p>
      <p>f</p>
   </div>
   <div id="ex_id">
      <p>g</p>
      <p>h</p>
      <p>i</p>
   </div>
   <h1>This is a heading</h1>
   <p>This is a paragraph.</p>
   <p>This is another paragraph.</p>
   <a href="http://brownbears.tistory.com" class="a"/>
</body>

</html>
	`
	rHtml := strings.NewReader(string(html))

	// HTML 문서 불러오기
	doc, err := goquery.NewDocumentFromReader(rHtml)
	if err != nil {
		panic(err)
	}

	// Find the review items
	doc.Find("a").Each(func(i int, s *goquery.Selection) {
		value, isExist := s.Attr("href")
		fmt.Println(value, isExist)

	})
}

func main() {
	ExampleScrape()
}


// http://brownbears.tistory.com true

복잡한 검색

아래는 ex_class라는 클래스명을 가진 div태그 내 p태그들의 내용을 찾는 쿼리입니다.

package main

import (
	"fmt"
	"strings"

	"github.com/PuerkitoBio/goquery"
)

func ExampleScrape() {
	// HTML 요청
	html := `
	<!DOCTYPE html>
<html>

<head>
   <title>Page title</title>
</head>

<body>
   <div>
      <p>a</p>
      <p>b</p>
      <p>c</p>
   </div>
   <div class="ex_class">
      <p>d</p>
      <p>e</p>
      <p>f</p>
   </div>
   <div id="ex_id">
      <p>g</p>
      <p>h</p>
      <p>i</p>
   </div>
   <h1>This is a heading</h1>
   <p>This is a paragraph.</p>
   <p>This is another paragraph.</p>
   <a href="http://brownbears.tistory.com" class="a"/>
</body>

</html>
	`
	rHtml := strings.NewReader(string(html))

	// HTML 문서 불러오기
	doc, err := goquery.NewDocumentFromReader(rHtml)
	if err != nil {
		panic(err)
	}

	// Find the review items
	doc.Find("div.ex_class p").Each(func(i int, s *goquery.Selection) {
		fmt.Println(s.Text())

	})
}

func main() {
	ExampleScrape()
}


만약 id명이 ex_id를 가진 div태그 내의 모든 p태그들에 대한 내용을 가지고 오고 싶을 경우, div.ex_class p 가 아닌 div#ex_id p 로 검색하면 됩니다.


더 많은 정보는 https://github.com/PuerkitoBio/goquery 에서 확인할 수 있습니다.

파이썬 3.4부터 내장함수로 pathlib가 추가되었습니다. pathlib가 추가되기 이전에는 os 모듈을 사용했습니다. pathlib는 파일위치 찾기, 파일 입출력과 같은 동작을 하는데 os모듈과 어떻게 다른지 아래에서 설명합니다.

pathlib 설명

pathlib 모듈의 기본 아이디어는 파일시스템 경로를 단순한 문자열이 아니라 객체로 다루자는 것입니다. 가령 파일의 존재성 여부를 판단하는 것은 아래와 같이 작성할 수 있습니다.

import os
from pathlib import Path

file_path = './path/to/file'

if os.path.exists(file_path):
    pass

p = Path(file_path)

if p.exists():
    pass


파일시스템을 문자열이 아닌 객체로 다루게 되면서 얻게 된 큰 이익 중 하나는 연산자를 새롭게 정의할 수 있게 되었다는 점입니다.

import os
from pathlib import Path

dir_name = 'dir'
sub_dir_name = 'sub_dir_name'
file_name = 'file_name'

# 기존 (os.path)
file = os.path.join(dir_name, sub_dir_name, file_name)

# pathlib
dir = Path(dir_name)
file = dir / sub_dir_name / file_name

pathlib은 슬래시(/)가 경로 구분 문자로 사용하여 직관적으로 이해할 수 있습니다.

사용법

파일 열기

import pathlib


path = pathlib.Path('test.txt')
file = path.open('r')

파일 읽고 쓰기

파일을 읽거나 쓰는 행동을 1번만 하게 된다면 아래와 같이 할 수 있습니다.

import pathlib

# 읽기
path = pathlib.Path('test.txt')
file = path.read_text()


# 쓰기
path = pathlib.Path('test1.txt')
path.write_text('파일쓰기')

경로분석

import pathlib


path = pathlib.Path('/user/path/to/file')

print(path)
print(path.parent)
print(list(path.parents))
print(path.parts)


# /user/path/to/file
# /user/path/to
# [PosixPath('/user/path/to'), PosixPath('/user/path'), PosixPath('/user'), PosixPath('/')]
# ('/', 'user', 'path', 'to', 'file')

실행한 스크립트 폴더경로 얻기

import pathlib

path = pathlib.Path.cwd()

현재 위치한 파일/폴더 얻기

glob 패턴을 사용하여 파일/폴더를 얻을 수 있습니다.

import pathlib


path = pathlib.Path('.')
files = path.glob('*')

print(list(files))

현재 위치에서 존재하는 모든 파일/폴더 얻기

위의 glob에서 패턴만 변경하여 현재 위치에서 모든 폴더(폴더 내 파일 포함)와 파일을 얻을 수 있습니다.

import pathlib


path = pathlib.Path('.')
files = path.glob('**/*')

print(list(files))


기존에 사용하던 os 모듈을 사용하여 경로찾기나 파일입출력을 pathlib로 대체할 수 있습니다.


+ Random Posts