이번에는 제가 원하는 정적으로 동작하는 웹 사이트에서 제품명, 제품유형, 제조사, 인증일, 인증만료일 등 데이터를 추출하는 프로그램을 파이썬으로 구현해보았습니다.

항상 크롤링에서 중요한 것은, 파싱하고자 하는 웹 사이트의 HTML에서 원하는 경로를 바로 파악하는 능력이 필요하다고 생각합니다.

(내가 원하는 데이터가 어떤 div 태그영역에 있는지 확인하고, div 태그 내에서 class 값 또는 id 값 등을 이용해 해당 데이터를 추출할 수 있는지가 필요)

아래는 제가 원하는 사이트에서 html 내 태그값을 이용하여 제품명, 분류, 제조사, 인증일, 인증만료일, 상태값을 추출하는 코드입니다.

import requests, openpyxl
from bs4 import BeautifulSoup

wb = openpyxl.Workbook()
sheet = wb.active

sheet.append(["제품명", "분류", "제조사", "인증일", "인증만료일", "상태값"])

#크롤링할 사이트 직접입력하였으며, URL 변수로 별도 선언 후 input() 을 통해 크롤링할 URL을 입력받도록 만들어도 됨.
for i in range(1, 10+1, 1):
    raw = requests.get(
       "https://www.bsi.bund.de/SiteGlobals/Forms/IT-Sicherheitskennzeichen/EN/IT-Sicherheitskennzeichen_Formular.html?gtp=980004_list%253D{}".format(
          (i)))
    html = BeautifulSoup(raw.text, 'html.parser')

    #크롤링 할 영역을 container로 저장
    container = html.select(".c-search-results > ul > li")

    for c in container:
       #제품명 추출
       product_name = c.select_one(".c-search-result-teaser__headline").text.strip()

       #본문(카테고리, 제조사, 인증일, 인증만료일)의 데이터를 추출하여 line별로 list에 저장
       product_data = c.select_one(".c-search-result-teaser__content").text.strip()
       product_data_list = product_data.splitlines()

       #리스트로부터 제품의 카테고리, 제조사, 인증일, 인증만료일 데이터를 추출
       category = product_data_list[0].replace("Category: ", "")
       manufacturer = product_data_list[1].replace("Manufacturer: ", "")
       Date_of_issue = product_data_list[2].replace("Date of issue: ", "")
       end_of_term = product_data_list[3].replace("End of term: ", "")

       #제품의 상태값은 해당 제품들의 링크로 들어가서 추출해야하므로, 링크값(a태그) 추출 후, 한번 더 BeautifulSoup를 이용해 Status 값을 추출 
       product_status_link = c.find('a')['href']

       raw_status = requests.get('https://www.bsi.bund.de/' + product_status_link)
       html_status = BeautifulSoup(raw_status.text, 'html.parser')

       product_status = html_status.select_one('.c-product-stage__security-status').text.strip()
       
       #엑셀 시트에 저장
       sheet.append([product_name, category, manufacturer, Date_of_issue, end_of_term, product_status])
       
wb.save("C:/Users/user/Documents/bsi_product_search.xlsx")

 

결과

 

위 코드에서 포인트는 A웹사이트에서, A->A' 사이트(A'는 각 제품의 상세페이지)로 한번 더 들어가서 제품의 현재 상태값(status)을 추출해주는 부분입니다.

각 제품들의 링크로 들어가서 한번 더 beautifulsoup를 이용해 파싱하고, 상태값을 불러오는 부분에서 상당한 딜레이가 발생하는 것을 확인하였습니다.

어떻게 성능을 개선할지가 다음 과제인것 같습니다.

좋은 의견 있으실 경우 남겨주시면 언제든 환영입니다.


WRITTEN BY
SiriusJ

,

최근에 파이썬으로 프로그램을 만들어보고 있는데, pycharm으로 개발하면서 실행파일로 누구나 사용할 수 있도록 하기 위해 배포하려고 해서 관련 포스팅을 남깁니다.

 

가장 일반적인 실행파일 생성 방법은 PyInstaller 또는 **cx_Freeze**와 같은 패키지를 사용하는 것입니다. 여기서는 가장 많이 사용되는 **PyInstaller**를 활용하여 파이썬 스크립트를 실행 파일로 만드는 방법을 다루겠습니다.

1. PyInstaller 설치

PyInstaller는 파이썬 스크립트를 단일 실행 파일로 변환해주는 도구입니다. 터미널이나 명령 프롬프트에서 다음 명령어로 설치할 수 있습니다.

(windows : cmd 창)

> pip install pyinstaller

 * 혹시라도 pip가 설치되어있지 않다고 할 경우에는, python을 제어판에서 삭제했다가 다시 설치하고, 설치할 때 환경변수 등 관련 체크박스가 해제되어있는 부분을 확인하고 체크해주신 후에 설치하면 pip가 자동으로 설치되게 됩니다. 이후에는 pip를 이용한 install 등 가능하게 됩니다.

 

2. PyInstaller 사용 방법

설치 후, 파이썬 스크립트를 실행 파일로 변환하려면 터미널 또는 명령 프롬프트에서 다음 명령을 입력합니다:

> pyinstaller --onefile 'test.py'

여기서 'test.py'는 실행 파일로 만들고자 하는 본인의 파이썬 파일 이름입니다.

주요 옵션:

  • --onefile: 하나의 실행 파일로 만듭니다. 기본적으로 PyInstaller는 여러 파일을 생성하는데, 이 옵션을 사용하면 단일 파일로 묶어줍니다.
  • --noconsole: 프로그램을 콘솔 창 없이 GUI 형태로 실행하고 싶을 때 사용합니다. (GUI 프로그램인 경우에만 유용)

 

3. 실행 파일 생성 과정

PyInstaller가 실행되면, 다음과 같은 작업을 수행합니다:

  • dist/ 폴더에 실행 파일이 생성됩니다.
  • build/ 폴더에 빌드와 관련된 파일이 생성됩니다.
  • .spec 파일이 생성되며, 이는 빌드 구성을 저장하는 파일입니다.

 

4. 실행 파일 배포

dist/ 폴더에 생성된 .exe 파일을 다른 사용자에게 배포할 수 있습니다. 배포할 때는 해당 파일만 전달하면 됩니다.

 

5. 참고 사항

  • 윈도우에서 만든 실행 파일은 윈도우에서만 실행 가능하고, macOS 또는 리눅스에서 만들면 그 운영체제에서만 실행 가능합니다. 각 운영체제에서 해당 OS에 맞게 빌드해야 합니다.
  • 실행 파일 크기가 커질 수 있으므로 불필요한 패키지는 제외하는 것이 좋습니다. 이를 위해 .spec 파일을 편집할 수 있습니다.

 

6. 실행 파일로 만들 때 발생할 수 있는 문제 해결

  • 외부 모듈이 포함되지 않는 문제: 일부 외부 모듈이 자동으로 포함되지 않을 수 있습니다. 이 경우, hidden-import 옵션을 사용하여 추가합니다.

> pyinstaller --onefile --hidden-import <module_name> 'test.py'

 

7. PyInstaller 외 다른 도구

  • cx_Freeze: PyInstaller와 유사하게 파이썬 스크립트를 실행 파일로 만들 수 있는 도구입니다. GUI 애플리케이션을 제작할 때 주로 사용됩니다.

> pip install cx_Freeze

> cxfreeze your_script.py --target-dir dist


WRITTEN BY
SiriusJ

,

[ 정렬들(Selection, Insertion, Bubble, Quick, Merge, Heap, Radix)에 대하여

수행시간, 비교 횟수, Swap횟수를 통한 비교 ]

 

1) Selection Sort :

Best Case

Worst Case

Stable

In-place

비교 횟수

Swap 횟수

O( n^2 )

O( n^2 )

X

O

O( n^2 )

O( n )

분석 : 데이터의 개수가 n개 일 때, 최소값을 선택하는 데 걸리는 시간 = O( n )

-> 최솟값을 선택하여 리스트의 첫 번째 요소와 자리를 바꾼다.

안정성을 만족하지 않고, 전체 시간복잡도는 O( n^2 )을 따른다.

데이터를 0부터 n까지 매번 비교해야 하므로, n(n-1)/2 번 비교하게 되며 O( n^2 ) 이 되고,

Swap횟수는 찾은 최소값과 리스트의 첫 번째 요소와 바꿔주면 되므로, n-1번 수행되어 O( n ) 이 된다.

 

2) Insertion Sort :

Best Case

Worst Case

Stable

In-place

비교 횟수

Swap 횟수

O( n )

O( n^2 )

O

O

O( n^2 )

O( n^2 )

분석 : 리스트의 값들을 순회하며 리스트에서 정렬이 필요한 데이터는 적당한 곳에 삽입해나간다.

정렬되어 있는 부분에 새로운 레코드를 적절한 위치에 삽입해야하는 과정을 반복해야 한다.

안정성이 보장되고 이미 정렬되어있으면 O(n)이 되어 효율적이지만, 삽입하는 과정이 있으므로 데이터가 많고 정렬이 되어있지 않다면 비효율적이다.

Worst Case는 데이터가 역순으로 정렬되어 있는 경우이며, 비교 횟수는 매번 데이터들의 값을 비교하여 위치를 옮겨주는 과정을 수행해야 하므로, n(n-1)/2 번 비교하며,

Swap은 데이터를 매번 비교하여 이동하는 횟수( n(n-1)/2 ) , 데이터가 n개일 때, 처음 인덱스에 대하여 n-1번 만큼 수행해야 하므로 n(n-1)/2 +(n-1) = O( n^2 ) 이 되고, 결과적으로 비교 횟수와 Swap횟수 둘 다 O( n^2 )이 된다.

 

3) Bubble Sort :

Best Case

Worst Case

Stable

In-place

비교 횟수

Swap 횟수

O( n^2 )

O( n^2 )

O

O

O( n^2 )

O( n^2 )

분석 : 인접한 리스트의 요소를 매번 비교해야 하고, 비교 시마다 인접한 요소가 정렬이 되어 있지 않다면 Swap을 해야하므로, 비교 횟수의 경우에는 항상 n(n-1)/2 번 비교하게 되며,

Swap의 경우에는 Best Case의 경우에는 이미 정렬되어 Swap하지 않아도 되는 경우이므로 0이지만, Worst Case의 경우일 때에는 비교와 같이 n(n-1)/2 번 수행해야 하므로, 비교 횟수와 Swap횟수 둘 다 O( n^2 ) 이 된다.

 

4) Quick Sort :

Best Case

Worst Case

Stable

In-place

비교 횟수

Swap 횟수

O( nlogn )

O( n^2 )

X

O

O( n^2 )

O( n )

분석 : 평균적으로 가장 빠르다고 알려져 있는 정렬방법이다. 리스트를 O( n )만큼 추가적으로 할당하여 정렬하는 방법이 있긴 하지만 Stable하고 In-place하지 않으므로 보통은 추가적인 메모리를 더 필요로 하지 않는 방법을 사용한다.

pivot값을 세우고, pivot값을 기준으로 왼쪽과 오른쪽을 분할하여 각각의 부분을 다시 재귀적으로 정렬하는 방법이다.

(pivot 값을 median, 즉 리스트의 중간 값으로 설정하게 되면 Worst case를 피할 수 있는 확률이 더 높아지기 때문에 효율이 높아지게 된다.)

Best Case는 왼쪽과 오른쪽 분할된 부분의 크기가 거의 균등하게 분할되는 경우이며, 분할된 Level의 크기는 logn 이며, 각 분할된 부분에 대하여 비교를 수행하는 횟수는 n 이므로, 비교 횟수는 O(logn) 이 된다.

그러나 Worst Case의 경우에는 왼쪽과 오른쪽이 불균등하게(한쪽은 1, 한쪽은 n-1) 분할되는 경우이며,

 

분할된 Level의 크기가 n 이 되고, 분할된 부분에 대하여 비교를 수행하는 횟수는 n 이므로 총 비교 횟수는 O( n^2 ) 이 되고, Swap횟수는 분할된 부분에 대하여만 Swap을 수행하면 되므로 비교 횟수보다 항상 적다.

 

5) Merge Sort :

Best Case

Worst Case

Stable

In-place

비교 횟수

Swap 횟수

O( nlogn )

O( nlogn )

O

X

O( nlogn )

O( nlogn )

분석 : 리스트를 두 개로 분할하는 과정을 재귀적으로 수행한 후, 다시 올라오며 각각을 정렬하며 합쳐지는 과정을 수행한다. 분할할 때에는 정렬을 하지 않으며, 전부 분할 한 후에 다시 분할된 부분을 합치며 정렬을 수행하는 방식이다.

Best Case, Worst Case에 관계없이 퀵 정렬의 이상적인 분배를 위하여 항상 균등 분할을 하게 되며, 분할된 Level의 크기는 logn 이 되며, 각 분할된 부분에서 n 번의 비교가 수행된다. 따라서 비교 횟수는 O(nlogn) 이 된다.

Swap의 경우 각 분할된 부분에서 ( n *2)번의 Swap이 수행되므로 비교 횟수는 O(nlogn) 이 된다.

 

6) Heap Sort:

Best Case

Worst Case

Stable

In-place

비교 횟수

Swap 횟수

O( nlogn )

O( nlogn )

X

O

O( nlogn )

O( nlogn )

분석 : Heap에서는 각 노드는 자식 노드의 값보다 작으면 안 된다는 성질을 가지고 있다.

n개의 노드에 대하여 완전이진트리는 log(n+1) 의 Level을 가지므로 평균 시간복잡도는 O( nlogn )이 되며,

최악의 경우에 비교는 매번 Heapify를 통해 힙을 재구성 하는 부분에서 Level의 크기( logn )에 대하여 n번 비교를 하게 되므로, O( nlogn )이 되고, Swap횟수는 heapify에서 매번 Swap이 일어나는 경우에, 루트노드와 마지막 노드를 바꿔주는 수행 횟수도 더해주어야 하므로, O( nlogn ) + O(n) = O( nlogn ) 이 된다.

 

7) Radix Sort:

Best Case

Worst Case

Stable

In-place

비교 횟수

Swap 횟수

O( d*n )

O( d*n )

O

X

O

O

분석 : 리스트의 전체를 비교하는 것이 아니라, 자리 수를 이용하여 정렬하는 방법이다.

현재 일반적인 정렬 방법의 가장 효율적인 시간복잡도는 O( nlogn )이지만, 이것보다 더 좋은 효율을 낼 수 있는 방법이기도 하지만, 정렬할 수 있는 타입이 숫자와 같이 제한적이라는 단점이 있고, 큐 방식을 이용하여 자릿수에 따라 분할하기 때문에 비교나 Swap횟수는 따로 수행하지 않아 0이 된다.


아래의 그림은 테스트 데이터(정수)의 갯수 n을 각각 100개, 1000개, 10000개, 50000개로 했을 때의 Swap횟수와 비교횟수, 수행시간의 데이터이다.

n = 100

n = 1000 

 

 

 n = 10000

n = 50000 

 

 



WRITTEN BY
SiriusJ

,

C Shell Sort로 오름차순 정렬하는 방식입니다.

시간복잡도 : O(n^2) - Worst case

Best case : O(n), Average Case : O(n^1.25)


#include <stdio.h>


void printArray(int value[], int count) {

int i = 0;

for (i = 0; i < count; i++)

printf("%d ", value[i]);

printf("\n");

}


void shellInsertionSort(int value[], int start, int end, int interval) {

int i = 0, item = 0, index = 0;

for (i = start + interval; i <= end; i = i + interval) {

item = value[i];

index = i - interval;

while ((index >= start) && item < value[index]) {

value[index + interval] = value[index];

index = index - interval;

}

value[index + interval] = item;

}

}


void shellSort(int value[], int count) {

int i = 0, interval = 0;

interval = count / 2;

while (interval >= 1) {

for (i = 0; i < interval; i++)

shellInsertionSort(value, i, count - 1, interval);

printf("Interval-%d, ", interval);

printArray(value, count);

interval = interval / 2;

}

}


int main(int argc, char *argv[]) {

int values[] = { 80, 50, 70, 10, 60, 20, 40, 30 };

printf("Before shellSort\n");

printArray(values, 8);


shellSort(values, 8);


printf("\nAfter Sort\n");

printArray(values, 8);


return 0;

}


WRITTEN BY
SiriusJ

,

C Merge Sort로 오름차순 정렬하는 방식입니다.

시간복잡도 : O(nlogn)


#include <stdio.h>

#include <stdlib.h>


void printArray(int value[], int count) {

int i = 0;

for (i = 0; i < count; i++)

printf("%d ", value[i]);

printf("\n");

}


void mergeSortInternal(int value[], int buffer[], int start, int middle, int end) {

int i, j, k, t;

i = start;

j = middle + 1;

k = start;

while (i <= middle && j <= end) {

if (value[i] <= value[j]) {

buffer[k] = value[i];

i++;

}

else {

buffer[k] = value[j];

j++;

}

k++;

}


if (i > middle) {

for (t = j; t <= end; t++, k++)

buffer[k] = value[t];

}

else {

for (t = i; t <= middle; t++, k++)

buffer[k] = value[t];

}


for (t = start; t <= end; t++)

value[t] = buffer[t];


printf("start-%d, middle-%d, end-%d, ", start, middle, end);


for (t = start; t <= end; t++)

printf("%d ", value[t]);

printf("\n");

}


void mergeSort(int value[], int buffer[], int start, int end) {

int middle = 0;

if (start < end) {

middle = (start + end) / 2;

mergeSort(value, buffer, start, middle);

mergeSort(value, buffer, middle + 1, end);

mergeSortInternal(value, buffer, start, middle, end);

}

}


int main(int argc, char *argv[]) {

int values[] = { 80, 50, 70, 10, 60, 20, 40, 30 };

int *pBuffer = NULL;

pBuffer = (int *)malloc(sizeof(values));

printf("Before mergeSort\n");

printArray(values, 8);


if (pBuffer != NULL) {

mergeSort(values, pBuffer, 0, 7);

free(pBuffer);

}


printf("\nAfter Sort\n");

printArray(values, 8);


return 0;

}


WRITTEN BY
SiriusJ

,

C Quick Sort로 오름차순 정렬하는 방식입니다.

시간복잡도 : O(n^2) - Worst Case 일 때 이지만,

일반적으로는 가장 빠른 정렬으로 시간복잡도를 O(nlogn) 으로 봅니다.


#include <stdio.h>


void printArray(int value[], int count) {

int i = 0;

for (i = 0; i < count; i++)

printf("%d ", value[i]);

printf("\n");

}

void quickSort(int value[], int start, int end) {

int pivot = 0;

if (start < end) {

pivot = partitionQuickSort(value, start, end);

quickSort(value, start, pivot - 1);

quickSort(value, pivot + 1, end);

}

}

int partitionQuickSort(int value[], int start, int end) {

int pivot = 0, temp = 0, left = 0, right = 0;

left = start;

right = end;

pivot = end;


while (left < right) {

while ((value[left] < value[pivot]) && (left < right))

left++;

while ((value[right] >= value[pivot]) && (left < right))

right--;

if (left < right) {

temp = value[left];

value[left] = value[right];

value[right] = temp;

printf("start-[%d], end-[%d], pivot-[%d], in-loop, ", start, end, value[pivot]);

printArray(value, end - start + 1);

}

}

temp = value[pivot];

value[pivot] = value[right];

value[right] = temp;

printf("start-[%d], end-[%d], pivot-[%d], out-loop, ", start, end, value[right]);


printArray(value, end - start + 1);

return right;

}


int main(int argc, char *argv[]) {

int values[] = { 80, 50, 70, 10, 60, 20, 40, 30 };

printf("Before QuickSort\n");

printArray(values, 8);


quickSort(values, 0, 7);


printf("\nAfter Sort\n");

printArray(values, 8);


return 0;

}


WRITTEN BY
SiriusJ

,

C Selection Sort로 오름차순 정렬하는 방식입니다.

시간복잡도 : O(n^2)


#include <stdio.h>


void printArray(int value[], int count) {

int i = 0;

for (i = 0; i < count; i++) {

printf("%d ", value[i]);

}

printf("\n");

}


void selectionSort(int value[], int count) {

int i = 0, j = 0, min = 0, temp = 0;

for (i = 0; i < count - 1; i++) {

min = i;

for (j = i + 1; j < count; j++) {

if (value[j] < value[min]) {

min = j;

}

}

temp = value[i];

value[i] = value[min];

value[min] = temp;

printf("Step-%d, ", i + 1);

printArray(value, count);

}

}


int main(int argc, char *argv[]) {

int values[] = { 80, 50, 70, 10, 60, 20, 40, 30 };

printf("Before Selction\n");

printArray(values, 8);


selectionSort(values, 8);


printf("\nAfter Sort\n");

printArray(values, 8);

return 0;

}

'Programming > C' 카테고리의 다른 글

Sorting(5) Merge Sort(합병 정렬)  (0) 2016.11.01
Sorting(4) Quick Sort(퀵 정렬)  (0) 2016.11.01
Sorting(2) Insertion Sort(삽입 정렬)  (0) 2016.11.01
Sorting(1) Bubble Sort(버블 정렬)  (0) 2016.11.01
(6) CircularList 사용하기  (0) 2016.10.30

WRITTEN BY
SiriusJ

,

C Insertion Sort로 오름차순 정렬하는 방식입니다.


시간복잡도 : O(n^2)


#include <stdio.h>


void printArray(int value[], int count) {

int i = 0;

for (i = 0; i < count; i++) {

printf("%d ", value[i]);

}

printf("\n");

}


void insertionSort(int value[], int count) {

int i = 0, j = 0, temp = 0;

for (i = 1; i < count; i++) {

temp = value[i];

j = i;

while ((j > 0) && value[j - 1] > temp) {

value[j] = value[j - 1];

j = j - 1;

}

value[j] = temp;

printf("Step-%d,", i);

printArray(value, count);

}

}

int main(int argc, char *argv[]) {

int values[] = { 80, 50, 70, 10, 60, 20, 40, 30 };

printf("Before InsertionSort\n");

printArray(values, 8);


insertionSort(values, 8);


printf("\nAfter Sort\n");

printArray(values, 8);


return 0;

}

'Programming > C' 카테고리의 다른 글

Sorting(4) Quick Sort(퀵 정렬)  (0) 2016.11.01
Sorting(3) Selection Sort(선택 정렬)  (0) 2016.11.01
Sorting(1) Bubble Sort(버블 정렬)  (0) 2016.11.01
(6) CircularList 사용하기  (0) 2016.10.30
(5) DoubleLinkedList 사용하기  (0) 2016.10.30

WRITTEN BY
SiriusJ

,