[Ubuntu Cron] Automating a Chrome Driver Crawler

목적: 정해진 시간에 반복하여 Crawler를 돌리기

크롤러를 만들었고 이제 데이터를 수집하고 싶거나, 정해진 시간에 크롤러가 특정 작업을 하게 하고 싶다. 매번 사람이 번거롭게 프로그램을 실행하기 보다, 컴퓨터가 알아서 작업을 하게 스케쥴을 작성하고 싶어진다.

우분투에서는 Cron을 이용하여, 정해진 시간 단위/패턴 마다 특정 command를 실행하게 할 수 있다.

기본 문법 예시는 다음과 같다.

*/10 * * * * /usr/bin/python script.py

앞에서부터 분 / 시간 / 일 / 월 / 요일 command 순서이다. */10은 매 10분 마다를 의미하고, 5번째 별에 4-6을 입력하면 목, 금, 토에 작동을 한다. 자세한 사용법은 인터넷에 찾아보면 잘 나와있다.

터미널에서 crontab -e 로 편집기를 열어서 위 스크립트를 붙여넣고 저장하면 그 때부터 작동한다.

그러나…….. script.py가 selenium + chromedriver를 사용한 코드이면… 아래와 같은 에러메세지를 출력한다….

Exception in thread <name>:
Traceback (most recent call last):
  File "/usr/lib64/python3.6/threading.py", line 916, in _bootstrap_inner
    self.run()
  File "/usr/lib64/python3.6/threading.py", line 864, in run
    self._target(*self._args, **self._kwargs)
  File "/path/to/script.py", line 53, in start
    self.site_scrape(test_run)
  File "/path/to/script.py", line 65, in site
    self.driver = webdriver.Chrome(chrome_options=options)
  File "/home/<user>/.virtualenvs/selenium/lib64/python3.6/site-packages/selenium/webdriver/chrome/webdriver.py", line 69, in __init__
    desired_capabilities=desired_capabilities)
  File "/home/<user>/.virtualenvs/selenium/lib64/python3.6/site-packages/selenium/webdriver/remote/webdriver.py", line 98, in __init__
    self.start_session(desired_capabilities, browser_profile)
  File "/home/<user>/.virtualenvs/selenium/lib64/python3.6/site-packages/selenium/webdriver/remote/webdriver.py", line 188, in start_session
    response = self.execute(Command.NEW_SESSION, parameters)
  File "/home/<user>/.virtualenvs/selenium/lib64/python3.6/site-packages/selenium/webdriver/remote/webdriver.py", line 256, in execute
    self.error_handler.check_response(response)
  File "/home/<user>/.virtualenvs/selenium/lib64/python3.6/site-packages/selenium/webdriver/remote/errorhandler.py", line 194, in check_response
    raise exception_class(message, screen, stacktrace)
selenium.common.exceptions.WebDriverException: Message: unknown error: Chrome failed to start: exited abnormally
  (Driver info: chromedriver=2.38.552522 (437e6fbedfa8762dec75e2c5b3ddb86763dc9dcb),platform=Linux 4.14.12-x86_64-linode92 x86_64)

마음이 아프다…

인터넷에서 검색했을 때는 다들 쉬워보였는데…

해결법으로 아래와 같은 세 가지를 시도해볼 수 있다.

0. 혹시나 selenium에서 chromedriver를 호출할 때 실제로 browser가 켜지도록 (보이도록) 되어 있는지 확인.. 꺼서 background에서 작동하게 한다..

1. Background에서 작동하는 cron에서 browser를 키려는 경우 충돌이 발생할 수 있다고 한다… (https://stackoverflow.com/questions/23908319/run-selenium-with-crontab-python) 위 글에서 제시한 해결책으로는 DISPLAY=:0 을 포함시키는 것이다. 예를 들어서… 아래처럼.

DISPLAY=:0

*/10 * * * * /usr/bin/python script.py

2. Cron은 무척 제한적인 PATH만 사용한다고 한다… 스크립트에 path를 적어 넣어 임시로 문제를 해결할 수 있다.. (https://stackoverflow.com/questions/2388087/how-to-get-cron-to-call-in-the-correct-paths)

터미널에서 아래와 같은 과정을 거치면…

# echo PATH=$PATH > tmp.cron
# echo >> tmp.cron
# crontab -l >> tmp.cron
# crontab tmp.cron

우분투에서 사용하고 있는 PATH가 crontab 스크립트에 복사 붙여 넣기가 되어있다.

[keychron] set function keys(f1-f12) to work on Ubuntu

According to the cheatsheet comes with the the products, one can press fn + X + L more than 3 secs to switch between function and multimedia keys.

However, this doesn’t work on Ubuntu. No matter how many times (even, odd… -_-) I try, the f1-f12 always perform as multimedia keys.

In order to change them to work as function key we can change the following settings:

ctrl + alt + t # Turn on the terminal

cd /sys/module/hid_apple/parameters
sudo nano ./fnmode

Then change the fnmode’s content from 1 to 0

try fn + X + L (more than 3 secs until the keyboard lights)

yay it works!

source: https://devlog.jwgo.kr/2019/12/13/how-to-fix-fn-key-not-working-between-keychron-and-ubuntu/

[Matplotlib] 한글 폰트 추가

https://financedata.github.io/posts/matplotlib-hangul-for-ubuntu-linux.html

위 사이트를 참고하면 굉장히 상세하게 (한글) 폰트 추가 과정이 나와있다. (감사합니닷)

Jupyter notebook으로 코딩 중 위 문제를 만나게 되면, 자칫 Jupyter notebook을 켜놓고 위의 과정을 진행하게 되어, 시키시는 대로 따라했음에도 Matplotlib에서 새로 추가한 폰트를 인식하지 못하는 경우가 생긴다.

위 사이트 [글꼴 설치] 파트에서 아래 과정을 진행할 때 꼭 Jupyter Notebook이나 이미 matplotlib를 호출해 놓고 있는 프로세스를 종료하고 해야한다!

$ sudo cp /usr/share/fonts/truetype/nanum/Nanum* /usr/local/lib/python3.4/dist-packages/matplotlib/mpl-data/fonts/ttf/
$ rm -rf /home/ubuntu/.cache/matplotlib/*

Jupyter notebook 끄기 ! -> 위의 코드 진행 -> 다시 키기 ! :::: 마법처럼 되었다! 끝!

예시:

import matplotlib as mpl

import matplotlib as mpl
import matplotlib.font_manager as fm

set(sorted([f.name for f in fm.fontManager.ttflist]))
#---> 사용 가능한 폰트들이 뜬다!!\

#이 중 NanumBarunGothic을 사용하려면, 
mpl.rcParams['font.family'] = 'NanumBarunGothic' 

#폰트를 바꾸고 그래프를 그리면 -부호가 안 나오는 경우가 있는데 아래와 같이 하면 나온당.
matplotlib.rcParams['axes.unicode_minus'] = False

[Preprocessing] (복합) 명사 추출 + 형태소 분석기 사용자 사전 등록

한글 문서를 형태소로 분석하는 경우 미등록 단어 문제로 인해 분석의 질이 낮아질 수 있음

https://github.com/lovit/soynlp/blob/master/tutorials/nounextractor-v1_usage.ipynb

주로 새로운 (복합) 명사가 생겨나는데서 문제가 발생한다면, 기존 형태소 분석기가 이러한 (복합) 명사를 인식할 수 있도록 사용자 사전에 등록하는 방식으로 문제를 해결할 수 있을 것이다.

Soynlp (https://github.com/lovit/soynlp)에서는 한국어의 L+R 구조를 이용하여, R에 위치한 글자를 바탕으로 L이 명사일 확률이 얼마인지를 계산하는 모델을 제공한다.

from soynlp.noun import LRNounExtractor_v2
from nltk import sent_tokenize

noun_extractor = LRNounExtractor_v2(verbose=True)
nouns = noun_extractor.train_extract(sent_tokenize(corpus))

LRNounExtractor_v2 를 호출하고 corpus를 리스트로 넣어준다. 이를 통해 얻은 nouns는 단어를 key로 count와 score를 담은 dictionary 객체이다. count와 score의 threshold를 정해 어느 정도 이상 빈번하게 등장하며 모델이 판단하기에 명사일 가능성이 높은 것들만 따로 저장해 볼 수 있다.

tgt_noun = []
for noun in nouns:
    if (nouns[noun][0] >= lower_count) and (nouns[noun][1]) >= lower_score:
        tgt_noun.append(noun)

이렇게 추출된 명사들에 대해 클리닝 작업을 해볼 수 있다.

non_noun_eomi_list = ('.', '?', ',', '야', '고', '는', '까', '한', '인', '들', '이', '면', '로', '은')
tgt_noun = set([item.strip(""""',.‘“△[]()”’ """) for item in tgt_noun if (item[-1] not in non_noun_eomi_list) \
                        & (sum([1 if char.isdigit() else 0 for char in item]) <= 2) \
                        & (len(re.sub('[^가-힣]', '', item)) >= 4) \
                        & (not re.search('[^가-힣0-9]', item))])

마지막으로 이렇게 얻은 (복합) 명사 리스트를 기존에 사용하는 형태소 분석기 (여기서는 Mecab과 Khaiii)의 사용자 사전에 등록할 수 있다.

#Save to Mecab
f1 = open(os.path.join('/home', 'yoonchan', 'Downloads', 'mecab-ko-dic-2.1.1-20180720',\
                       'user-dic', 'lab_strike_nouns.csv'), 'w')
for noun in nouns:
    #f1.write(noun + '\t\t\t\tNNP\t*\tT\t' + noun + '\t*\t*\t*\t*\t*\n')
    f1.write(noun + ',,,,NNP,*,T,' + noun + ',*,*,*,*\n')
f1.close()

#Build Khaiii again
#https://hanshuginn.blogspot.com/2019/02/khaiii.html
f1 = open(os.path.join('/home', 'yoonchan', 'khaiii', 'rsc', 'src', 'preanal.my'), 'w')
for noun in nouns:
    f1.write(noun + '\t' + noun + '/NNG\n')
f1.close()

Mecab 사용자 사전 업데이트에 관해서는 아래의 링크를,

http://blog.daum.net/moon0sool/162

https://medium.com/@john_analyst/konlpy%EC%9D%98-mecab-%EC%82%AC%EC%9A%A9%EC%9E%90-%EC%82%AC%EC%A0%84-%EC%B6%94%EA%B0%80%ED%95%98%EA%B8%B0-mac-1be092fe1217

Khaiii 사용자 사전 업데이트에 관해서는 아래의 링크를 확인하면 된다.

https://hanshuginn.blogspot.com/2019/02/khaiii.html

예를 들어 추출된 명사에 ‘베리알랜’이 포함되어 있었다면,

from konlpy.tag import Mecab
m = Mecab()
sent = '베리알랜은 플래쉬다'
m.morphs(sent)

베리알랜이 한 단위로 NNP로 인식이 된다!

끝!

[Preprocessing] 한글 문서 띄어쓰기 교정 Spacing!

  • 한글 문서 띄어쓰기 교정의 필요성

형태소 분석 성능 up! => 이후 모델 input의 질 up! => 모델 성능 up!

  • 사용 가능한 패키지

Soyspacing – https://github.com/lovit/soyspacing : 휴리스틱 알고리즘 기반 띄어쓰기 모델. 주어진 알고리즘으로 input으로 들어간 문장들에서 띄어쓰기 규칙을 파악하고 이를 새로운 문장에 적용. 모든 한글 문서에 광범위하게 적용할 수 있는 모델을 내기에는 한계가 있겠지만, 동질적인 주제와 형식을 가진 문서들에 대해 분석을 하는 경우 특히 유용하게 쓸 수 있을 것 같다. (특정 도메인을 위한 모델)

PyKoSpacing – https://github.com/haven-jeon/PyKoSpacing : CNN에 RNN을 쌓아 올린 모델을 뉴스 데이터로 훈련. 세종 코퍼스 등 테스트 셋에서 잘 작동한다고 한다. 하지만 accuracy measure가 성능을 over represent 할 수 있을 것 같다. 대체로 띄어쓰기가 정말 난장판으로 되어있는 경우 볼만한 문서로 바꾸는데 사용할 수 있을 것 같다.

TaKos (Alpha) – https://github.com/Taekyoon/takos-alpha : 앞서 첨부한 Youtube 영상에서 소개된 프로젝트. 아직 상용화까지 개발이 진행 중인 것 같다!

  • Soyspacing 활용예시

비슷한 주제/형태 (특정 도메인)의 문서를 다루고 있기에, Soyspacing으로 띄어쓰기 규칙을 발견하고 전체 문서에 적용해 보고자 하였다.

가지고 있는 한글 문서를 텍스트 파일로 저장

with open(os.path.join(dir_to_save_on, 'spacing_train_full.txt'), 'w') as f1:
    for parag in text:
        f1.write(parag + '\n')
f1.close()

위 텍스트 파일로 띄어쓰기 규칙 발견

from soyspacing.countbase import CountSpace

#Train a simple huristic model -- takes about 45mins with 4000000 sentences.
corpus_fname = os.path.join(dir_, 'train_sample', 'spacing_train_full.txt')
model = CountSpace()
model.train(corpus_fname)

#Save the model
model_fname = os.path.join(dir_, 'train_sample', 'spacing_model_full_0706')
model.save_model(model_fname, json_format=False)

#Load the model
model_fname = os.path.join(dir_, 'train_sample', 'spacing_model_full_0706')
model = CountSpace()
model.load_model(model_fname, json_format=False)

적용 사례

#good case
print(model.correct('이번공산베트남에'))
--> returned ('이번 공산 베트남에', [0, 1, 0, 1, 0, 0, 0, 1])

#bad case
print(model.correct('현대자동차기아자동차'))
--> returned ('현대자동차기아자동차', [0, 0, 0, 0, None, 0, 0, 0, 0, 1])

문서 뭉치에서 회사 이름의 경우 띄어쓰기가 잘 지켜지지 않는 문제가 종종 있었고, 방어적으로 띄어쓰기를 하는 이 모델의 특성으로 인해 bad case가 발생. 회사 이름 사이를 띄어 쓰게 만들기 위해서 아래의 코드로 일종의 사용자 규칙을 부여할 수 있다.

#Making a rule based dict
firms = ['현대자동차', '기아자동차', .....]
with open(os.path.join(dir_, 'train_sample', 'spacing_firm_names.txt'), 'w') as f1:
    for firm in firms:
        f1.write(firm + '\t' + '1' + '0'*(len(firm)-1) +'1' + '\n')
    f1.close()

이렇게 만든 사용자 규칙을 RuleDict 함수로 넣어주면 모델에 반영된다.

from soyspacing.countbase import RuleDict
rule_dict = RuleDict(os.path.join(dir_, 'train_sample', 'spacing_firm_names.txt'))
model.correct('현대자동차기아자동차', rules=rule_dict)[0]
--> returned '현대자동차 기아자동차'

끝!

[Khaiii] Installation on Ubuntu 20.04LTS

카카오 문서 https://github.com/kakao/khaiii/wiki/%EB%B9%8C%EB%93%9C-%EB%B0%8F-%EC%84%A4%EC%B9%98 를 따라서 설치를 진행하다 보면 우분투 20.04LTS 환경에서는 cmake 부분에서 에러가 발생한다.

아래처럼 쭉 설치 문서를 따라하다가…

mkdir build
cd build
cmake .. 

이 cmake 부분을 아래와 같이 바꿔주면 (야매로) 설치가 가능하다고 한다.

cmake -E env CXXFLAGS="-w" cmake ..

남은 부분은 그대로!

make all
make resource
make install
make package_python 
cd package_python 
pip install .

끝!

[Javascript] Entering Letter grades Automatically at UMEG

Entering letter grades automatically at UMEG

Putting letter grades in UMEG with 300 + clicks is cumbersome and prone to mistakes, especially for large sized classes.

An  alternative is to use Canvas(elms) to convert grades into UMEG. However, the functions provided by Canvas are rather restrictive (and slow). And we don’t want to create numerous alarms to be sent to students.

So we can try using the following script at UMEG to fill out the grades automatically.

var t = document.getElementsByTagName(“table”)[5];
var trs = t.getElementsByTagName(“tr”);

// input the grades you want to enter following the order in UMEG (name – ascending order)
var grades = [“A+”, “C”, “D”];

function to_num(letter) {
var i = null;
var j = 2;
if (letter[0] == “A”) {
i = 1
} else if (letter[0] == “B”) {
i = 2
} else if (letter[0] == “C”) {
i = 3
} else if (letter[0] == “D”) {
i = 4
}
if (letter.slice(-1) == “+”) {
j = 1
} else if (letter.slice(-1) == “+”) {
j = 3
}
return [i, j]
}

// change num as the number of students in your class
var num = 374
for (var i = 0; i<num; i++) {
var str_ = “grd” + i
var letter = to_num(grades[i])
var grade = trs[9*i + 1].getElementsByTagName(“td”)[letter[0]*4-2 + letter[1]].getElementsByTagName(“input”)[str_]
grade.checked = false;
grade.checked = true;
console.log(i)
}

California Route 1

20200114-_DSC5361-2

 

20200114-_DSC5279-2

 

20200105-_DSC3193

 

1번 국도(?)를 쭈욱 따라 Monterey에서 Solvang으로 가는 길

자꾸 구글 지도는 내륙으로 가는 길을 추천했지만,

바다를 옆에 두고 산에 난 아슬아슬하리 만큼 좁은 도로를 타보고 싶었었다

 

풍경에 자꾸 눈이 가 죽을 뻔도 했지만 살아 돌아왔으니 좋은 추억이라 해야겠다

 

조금은 뜬금 없이 캠핑카를 타고 낮에는 경치 좋은 곳을 여행하고, 밤에는 연구하는 그런 박사가 되면 참 좋겠다는 생각을 했다… 힘들겠지… 만 뭐 상상은… ㅎㅎ