예시로 알아보는 Python Heap Memory

프로세스 메모리는 크게 code, data, stack, heap 영역이 있는데 이번 글에서는 stack, heap 영역을 다룰 예정이다

그럼 heap 영역은 왜 알아야 할까?

  1. 파이썬은 모든 게 객체이기 때문, 즉 값을 heap에 저장하고 stack에서 참조한다
  2. 모든 thread는 자기 process heap memory 영역을 공유한다 이걸 이용해서 좀 더 유연한 프로세스를 만들 수 있다
  3. 객체를 무분별하게 생성하지 않기 위해서
  4. 버그를 찾기 위해
  5. 기타 등등…

간단하게 예시를 들면

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
from threading import Thread
from time import sleep

detect_word = 'dog'

# 문장에 탐지 단어가 있는지 확인하는 함수
def print_worker():
global detect_word
text = 'dogs are cute'
while 1:
check = detect_word in text
print(check)
if check is False:
break
sleep(1)

# 탐지 단어를 바꾸는 함수
def change_worker():
global detect_word
sleep(3)
detect_word = 'cat'


tread1 = Thread(target=print_worker)
tread1.start()

tread2 = Thread(target=change_worker)
tread2.start()
실행 결과는 
True
True
True
False

heap 메모리 그림을 보면 프로세스 실행 3초 뒤
thread2가 detect_word를 cat으로 바꾸었기 때문에
heap 메모리를 공유하는 thread1의 detect_word도 cat을 가리키고 있다
dog는 reference counting이 0이 되어 GC에 의해 메모리 해제된다

간단한 예시지만 이런 특징을 사용해서 좀 더 유연한 프로그램을 만들 수 있다
thread 프로그래밍이 장점이 많지만 메모리를 공유하기 때문에 연산 작업할 때는 lock을 사용하여 thread-safe하게 신경 써야 한다
하지만 lock은 처리 시간을 느리게 하므로 주의가 필요하다

Python 병렬처리로 크롤링 시간 단축하기

thread 와 concurrent.futures 사용으로 병렬 처리하기

1. thread 사용으로 병렬 처리

저번 코드에서 save_img 함수를 multi thread로 실행하면 좀 더 빠른 결과물을 얻을 수 있을 것 같다

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
import requests
import re
import threading
import time

t1 = time.time()
reg = re.compile('oimgurl: ".{0,300}", cpid')

url = 'https://search.daum.net/search?w=img&nil_search=btn&DA=NTB&enc=utf8&q=%EA%B0%95%EC%95%84%EC%A7%80'
html = requests.get(url=url)
html_raw_data = str(html.content)

reg_iter = reg.finditer(html_raw_data)
thread_list = []


def save_img(idx, img_url):
res = requests.get(img_url).content
with open(f'{idx}.jpg', 'wb') as f:
f.write(res)
print(f'save {idx}')


for idx, res_data in enumerate(reg_iter):
img_url = res_data.group().split('oimgurl:')[1].split(', cpid')[0].replace('"', '')
thread_worker = threading.Thread(target=save_img, args=(idx, img_url))
thread_worker.start()
thread_list.append(thread_worker)

for thread in thread_list:
thread.join()

t2 = time.time()
print(t2-t1)

작업결과

작업 시간이 1초대로 단축된 걸 확인 할 수 있다

2. concurrent.futures 사용으로 병렬 처리하기

concurrent.futures는 비동기 처리 고수준 인터페이스 모듈이다 이 모듈을 써서 작업 시간을 줄여보자

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
import requests
import re
import time
from concurrent.futures import ThreadPoolExecutor

t1 = time.time()
reg = re.compile('oimgurl: ".{0,300}", cpid')

url = 'https://search.daum.net/search?w=img&nil_search=btn&DA=NTB&enc=utf8&q=%EA%B0%95%EC%95%84%EC%A7%80'
html = requests.get(url=url)
html_raw_data = str(html.content)

reg_iter = reg.finditer(html_raw_data)
thread_list = []

#url 이미지를 저장하는 함수
def save_img(idx, img_url):
res = requests.get(img_url).content
with open(f'{idx}.jpg', 'wb') as f:
# 이미지 관련 작업은 pillow 모듈을 사용하는게 좋다
# 예제에서는 사용 안함 (따로 설치가 필요)
f.write(res)
print(f'save {idx}')


with ThreadPoolExecutor(max_workers=10) as executor:
for idx, res_data in enumerate(reg_iter):
# url 을 뽑아내는 전처리 작업
img_url = res_data.group().split('oimgurl:')[1].split(', cpid')[0].replace('"', '')
executor.submit(save_img, idx, img_url)

t2 = time.time()
print(t2 - t1)

작업결과

마찬가지로 작업 시간이 1초대로 단축된 걸 확인 할 수 있다

결론

  • I/O 작업이 있을 때 병렬 처리 작업을 활용하여 작업 시간을 단축시킬수 있다
  • 상황에 따라 다르지만 될 수 있으면 thread 모듈보단 고수준 비동기 concurrent.futures 모듈을 사용하자 병렬처리 작업의 결과물을 리턴 받는 등 다양한 함수를 제공한다 참조링크
자세히 보기

Beautifulsoup으로 이미지 크롤링 안될 때 다른 방법으로 크롤링하기

이미지 태그나 클래스명 등으로 크롤링 하려는데 안될 때 어떻게 해야할까?

여러가지 방법이 있겠지만 이번 글에서는 python, re(정규식) 모듈, requests 모듈을 이용해서 크롤링을 해보려고한다
다음 이미지에서 강아지를 검색하면 브라우저 검색창에 아래 url이 나온다

1
https://search.daum.net/search?w=img&nil_search=btn&DA=NTB&enc=utf8&q=%EA%B0%95%EC%95%84%EC%A7%80

이 url을 이용해서 python으로 request를 날려보자
아래 사진을 보면 이미지 불러오는 url이 보인다 저 url을 정규식으로 추줄해보자
더 좋은 정규식이 있을 텐데 저는 저렇게 해서 추출했습니다
(정규식 내용은 분량이 큽니다 관심이 있으면 따로 찾아보자)
code url

  1. 코드를 보자 정규식을 선언하고
  2. url을 뽑아내는 전처리 작업을하고
  3. 이미지를 저장하는 코드룰 볼 수 있다
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
import requests
import re
import time

t1 = time.time()
# 정규식 선언
reg = re.compile('oimgurl: ".{0,300}", cpid')

url = 'https://search.daum.net/search?w=img&nil_search=btn&DA=NTB&enc=utf8&q=%EA%B0%95%EC%95%84%EC%A7%80'
html = requests.get(url=url)
html_raw_data = str(html.content)

reg_iter = reg.finditer(html_raw_data)

#url 이미지를 저장하는 함수
def save_img(idx, img_url):
res = requests.get(img_url).content
with open(f'{idx}.jpg', 'wb') as f:
# 이미지 관련 작업은 pillow 모듈을 사용하는게 좋다
# 예제에서는 사용 안함 (따로 설치가 필요)
f.write(res)
print(f'save {idx}')

for idx, res_data in enumerate(reg_iter):
# url 을 뽑아내는 전처리 작업
img_url = res_data.group().split('oimgurl:')[1].split(', cpid')[0].replace('"', '')
save_img(idx, img_url)

t2 = time.time()

print(t2-t1)

이렇게 하면 이미지가 저장되는 걸 볼 수 있다
강아지 이미지
작업이 4초 넘게 걸렸는데 다음 포스트에서는 thread 와 비동기 함수를 사용하여
작업 시간을 단축시키는 포스트를 올려보겠습니다

속도를 높이는 파이썬 코드

기존 파이썬 코드를 빠르게 만드는 방법

  • 상황에 맞는 정확한 데이터 구조 사용
    (list, tuple, set, dict, etc…)
  • for 루프 대신 while 루프 사용
  • 컴프레헨션 사용
  • 복수 할당 사용 ( a, b = 1, 2)
  • 될 수 있으면 global 키워드를 줄이자 연산 작업에 많은 시간을 소요한다
  • 라이브러리를 활용
  • 제너레이터 사용
  • .사용 자제 (use sqart instead of math.sqart)
  • 무한 반복문에 1 사용 True 사용 보다 런타임 줄일수 있음
  • 큰 데이터 처리는 pandas, numpy, scipy 사용

참조링크