2-10. 고윳값과 고유벡터 찾기

고유벡터: 정방 행렬 A를 선형 변환으로 봤을 때, 선형 변환 A에 의한 변환 결과가 자기 자신의 상수 배가 되는 0이 아닌 벡터

고유값: 이때의 상수배 값

Av: v라는 열 벡터에 선형 변환 A를 해주었다

Av = λv: v라는 열 벡터에 선형 변환 A를 해준 결과가 열 벡터v의 상수 배(λ)와동일

               즉, 벡터 v에 대해 선형 변환 A를 해주었을 때, 벡터 v의 방향은 변하지 않고 크기만 변함

그림 1) 출처: 공돌이의 수학정리노트 유튜브
그림 2) 출처: 공돌이의 수학정리노트 유튜브

그림 2)는 그림 1)을 선형변환한 것

파란색 화살표는 크기는 변했지만 방향은 그대로이므로 변한 크기 비율인 λ를 고유값으로하는 고유벡터

분홍색 화살표도 방향은 그대로이며 크기도 그대로이므로 λ=1인 고유벡터

빨간색 화살표는 방향과 크기가 모두 변했으므로 고유벡터가 아님

 

 

고유벡터의 성질로 det(A - λI)=0이어야하며 이를 이용해 고유값과 고유벡터를 구할 수 있음

A는 주어진 행렬

λ는 구해야하는 고유값

I는 A와 같은 크기의 단위행렬

행렬식을 이용해 λ를 구하면 Av = λv식에 λ를 대입하여 v 구하기

 

파이썬에서는 linalg.eig 함수 사용

import numpy as np

matrix=np.array([[1, -1, 3],
                          [1, 1, 6],
                          [3, 8, 9]])

eigenvalues, eigenvectors=np.linalg.eig(matrix)
print('고유값: ', eigenvalues)
print('고유벡터: ', eigenvectors)

### 결과 ###
고유값: array([13.55075847, 0.74003145, -3.29078992])
고유벡터: array([[-0.17511017, -0.96677403, -0.53373322],
                        [-0.435951  , 0.2053623  , -0.64324848],
                        [-0.88254925, 0.15223105 , 0.54896288]])

대칭행렬일 경우 np.linalg.eigh(matrix)를 사용하면 더 빨리 계산(대칭행렬 여부를 확인하지는 않음)

 

 

 

2-11. 점곱 계산하기

점곱은 행렬의 같은 위치의 성분끼리 곱해준 것

import numpy as np

vector_a=np.array([1,2,3])
vector_b=np.array([4,5,6])

np.dot(vector_a,vector_b)

### 결과 ###
32


# np.dot 대산에 @를 사용할 수도 있음
vector_a @ vector_b

### 결과 ###
32

vector_a와 vector_b의 점곱은 1×4 + 2×5 + 3×6=32

 

 

 

2-12. 행렬 곱셈

행렬의 곱셈

첫번째 행렬 첫번째 행 × 두번째 행렬 첫번째 열      첫번째 행렬 첫번째 행 × 두번째 행렬 두번째 열

첫번째 행렬 두번째 행 × 두번째 행렬 첫번째 열      첫번째 행렬 두번째 행 × 두번째 행렬 두번째 열

 

파이썬에서는 np.dot사용

import numpy as np

matrix_a=np.array([[1,1],
                              [1,2]])

matrix_b=np.array([[1,3],
                              [1,2]])

np.dot(matrix_a,matrix_b)

### 결과 ###
array([[2,5],
          [3,7]])


# np.dot 대신에 @ 사용 가능
matrix_a @ matrix_b

### 결과 ###
array([[2,5],
          [3,7]])


# 원소별 곱셈을 하려면 * 사용
matrix_a * matrix_b

### 결과 ###
array([[1,3],
          [1,4]])

 

 

※ np.dot과 np.matmul의 차이

 -(a,b,c,D) 크기의 4차원 배열은 c행 D열의 행렬 b개의 있는 행렬이 a개 있는 행렬

 -(e,f,D,h)크기의 4차원 배열은 D행 h열의 행렬 f개 있는 행렬이 e개 있는 행렬

 -np.dot은 (a,b,c,D) 크기의 배열과 (e,f,D,h) 크기의 배열의 곱셈의 결과로 (a,b,c,e,f,h)크기의 배열을 만듦

 -@(np.matmul)는 (a,b,c,D) 크기의 배열과 (e,f,D,h) 크기의 배열의 곱셈의 결과로 (a',b',c,h)크기의 배열을 만듦

    →a와 e는 같거나 둘 중 하나는 1, b와 f는 같거나 둘 중 하나는 1이어야 함

    →a'와 b'는 각각 a와 e, b와 f 중 1이 아닌 값

 

 

 

2-13. 역행렬

정방행렬의 역행렬은 정방행렬과 곱했을 때 단위행렬 I가 되는 행렬

파이썬에서는 np.linalg.inv 사용

import numpy as np
matrix=np.array([[1,4],
                          [2,5]])

np.linalg.inv(matrix)

### 결과 ###
array([[-1.66666667,  1.33333333],
          [ 0.66666667, -0.33333333]])


# 행렬과 역행렬을 곱하면 대각원소가 전부 1이고 나머지 원소는 0인 단위행렬이 나옴
matrix @ np.linalg.inv(matrix)

### 결과 ###
array([[1., 0.],
          [0., 1.]])

 

정방행렬이 아닌 행렬의 역행렬을 유사 역행렬이라고 부르며 계산할 수 있음, np.linalg.pinv() 사용

import numpy as np

matrix=np.array([[1,4,7],
                          [2,5,8]])

np.linalg.pinv(matrix)

### 결과 ###
array([[-1.6666667,  1.],
          [-0.3333333,  0.3333333],
          [ 0.5      , -0.3333333]])

'Python > 기본문법' 카테고리의 다른 글

데이터 랭글링 (2)  (0) 2023.06.26
데이터 랭글링 (1)  (0) 2023.06.23
데이터 적재  (0) 2023.06.22
Numpy로 배열, 벡터, 행렬 이해하기 (2)  (0) 2022.10.19
Numpy로 배열, 벡터, 행렬 이해하기 (1)  (0) 2022.10.17

2-5. 행렬 전치

행렬의 전치: 행과 열의 인덱스를 바꿈(1열→1행, 2열→2행, ..., 1행→1열, 2행→2열, ...), T 또는 transpose() 사용

import numpy as np
matrix=np.array([[1,2,3],
                          [4,5,6],
                          [7,8,9]])

# 행렬 전치
matrix.T

### 결과 ###
array([[1,4,7],
          [2,5,8],
          [3,6,9]])

 

벡터의 전치는 행벡터를 열벡터로 또는 열벡터를 행벡터로 바꾸는 것

벡터는 값의 모음이기 때문에 원래 기술적으로 전치할 수 없지만 대괄호를 두번 사용하면 전치 가능

matrix=np.array([[1,2,3,4,5,6]])

matrix.T

### 결과 ###
array([[1],
       [2],
       [3],
       [4],
       [5],
       [6]])

 

 

T 대신 transpose() 사용가능, transpose((차원의 튜플))을 통해 바꿀 차원을 직접 지정도 가능

# transpose() 사용
matrix=np.array([[1,2,3],
                          [4,5,6],
                          [7,8,9]])

# 행렬 전치
matrix.transpose()

### 결과 ###
array([[1,4,7],
          [2,5,8],
          [3,6,9]])


# transpose((차원 튜플)) 사용
matrix=np.array([[[1,2],
                            [3,4],
                            [5,6]],
                  
                           [[7,8],
                            [9,10],
                            [11,12]]])

matrix.transpose((0,2,1))

### 결과 ###
array([[[1,3,5],
            [2,4,6]],
           [[7,9,11],
            [8,10,12]]])

2×3×2 행렬을 transpose((0,2,1))을 적용하면 2×2×3 행렬로 전치됨, (0,2,1)은 차원의 순서를 나타내는 것으로 원래 차원의 (첫번째 숫자. 세번째 숫자, 두번째 숫자)를 의미

 

 

2-6. 행렬 펼치기

행렬을 1차원 배열로 펼치기, flatten()사용

matrix=np.array([[1,2,3],
                          [4,5,6],
                          [7,8,9]])

# 행렬 펼치기
matrix.flatten()

### 결과 ###
array([1,2,3,4,5,6,7,8,9])



# reshape() 사용으로도 똑같은 결과를 만들 수 있음
matrix.reshape(1,-1)

### 결과 ###
array([1,2,3,4,5,6,7,8,9])

reshape()는 원본의 뷰를 반환하여 원본의 값을 바꾸면 reshape()로 바꾼 배열의 값도 바뀜

flatten()은 새로운 배열을 만드는 것이므로 원본의 값을 바꿔도 값이 바뀌지 않음

 

 

 

2-7. 행렬의 랭크

행렬의 차원 수를 뜻함

 

 

 

2-8. 행렬식 계산

행렬식은 행렬을 대표하는 값으로 다음과 같이 계산

 

2×2 행렬의 행렬식

 

3×3 행렬의 행렬식

 

파이썬에서는 numpy의 선형대수 메서드 det 사용

import numpy as np
matrix=np.array([[1,5,0],
                          [2,4,-1],
                          [0,-2,0]])

# 행렬식 계산
np.linalg.det(matrix)

### 결과 ###
-1.9999999999999998

 

 

 

2-9. 행렬의 대각원소

정방행렬에서 대각선에 위치한 원소(1행1열, 2행2열, 3행3열...에 해당하는 원소), diagnoal() 사용

import numpy as np
matrix=np.array([[1,2,3],
                          [2,4,6],
                          [3,8,9]])

# 행렬의 대각원소 반환
matrix.diagonal()

### 결과 ###
array([1,4,9])

offset 매개변수를 이용하면 대각원소 하나 위 또는 아나 아래의 원소를 반환

matrix=np.array([[1,2,3],
                          [2,4,6],
                          [3,8,9]])

# 행렬의 대각원소 하나 위의 원소 반환
matrix.diagonal(offset=1)

### 결과 ###
array([2,6])


# 행렬의 대각원소 하나 아래의 원소 반환
matrix.diagonal(offset=-1)

### 결과 ###
array([2,8])

 

 

np.diag()에서 ()에 1차원 배열을 넣으면 1차원 배열을 대각원소로 하는 2차원 대각행렬을 생성

a=np.diagonal(matrix)
print(a)

### 결과 ###
array([1,4,9])


# 1차원 배열의 대각원소 사용하여 2차원 대각행렬 만들기
np.diag(a)

### 결과 ###
array([[1,0,0],
          [0,4,0],
          [0,0,9]])

 

 

trace()를 사용하여 대각원소의 합인 대각합 계산

matrix=np.array([[1,2,3],
                          [2,4,6],
                          [3,8,9]])

matrix.traace()

### 결과 ###
14
# matrix의 대각원소는 [1,4,9]이므로 1+4+9=14

'Python > 기본문법' 카테고리의 다른 글

데이터 랭글링 (2)  (0) 2023.06.26
데이터 랭글링 (1)  (0) 2023.06.23
데이터 적재  (0) 2023.06.22
Numpy로 배열, 벡터, 행렬 이해하기 (3)  (0) 2022.10.20
Numpy로 배열, 벡터, 행렬 이해하기 (1)  (0) 2022.10.17

1. 사용데이터

KTDB 국가교통데이터베이스(https://www.ktdb.go.kr/www/index.do)에서 철도망, 철도역, 행정경계 데이터 다운로드

 

-자료신청 및 다운로드 방법

 1) KTDB 국가교통데이터베이스 회원가입

 2) 상단 메뉴에서 정보공개 > 자료신청

 

 

 3) 교통분석자료 신청

 

 

 4) 다음과 같이 원하는 자료를 선택

 

 

 5) 신청서 작성을 완료하고 몇 분 뒤 신청이 승인되었다는 문자가 오면

     마이페이지 > 자료신청내역에서 데이터 다운로드

 

 

2. QGIS에서 SHP파일 GeoJSON파일로 변환하기

ktdb에서 받은 파일은 shp 형태이고 folium에서 지도를 시각화하기 위해서 GeoJSON파일이 필요

따라서, QGIS에서 shp파일을 GeoJSON으로 변환

 

2-1. QGIS에서 shp파일 불러오기

 

 

 

2-2. 불러온 레이어 우클릭 > 내보내기 > 객체를 다른 이름으로 저장

 

2-3. 포맷을 GeoJSON으로 설정 후 이름 작성하고 좌표계는 EPSG: 4326-WGS 84로 설정

2-4. 확인 누른 뒤 rail_route_station에도 똑같이 적용하면 shp파일을 geojson파일로 저장 완료

2-5. 행정경계 파일도 위의 과정을 거쳐 GeoJSON파일로 변환

 

3. 파이썬에서 GeoJSON파일 불러오기

import json

# 철도망
rail_route='rail_route_geojson.geojson'
rail_route_str=json.load(open(rail_route,encoding='utf-8'))

# 철도역
rail_route_station='rail_route_station_geojson.geojson'
rail_route_station_str=json.load(open(rail_route_station,encoding='utf-8'))

 

 

 

4. 파이썬에서 folium으로 지도 불러오기

import folium

m=folium.Map(location=[36.3435957365,127.7828455523],zoom_start=7)

 -location 내부의 좌표는 우리나라의 중심점 좌표

 -좌표를 중심으로 zoom을 7로 맞추어 지도의 처음 화면이 우리나라 전체를 보여줄 수 있도록 조정

 

 

5. folium 지도에 GeoJSON파일의 데이터 적용하기

import folium

m=folium.Map(location=[36.3435957365,127.7828455523],zoom_start=7)

# folium 지도 m에 앞서 불러온 도시철도 노선과 도시철도 역의 GeoJSON파일 적용시키기
folium.GeoJson(rail_route_str, name="도시철도 노선").add_to(m)
folium.GeoJson(rail_route_station_str, name="도시철도 역").add_to(m)

# 범례와 비슷한 역할로 지도위에 표시된 요소들을 알 수 있고 요소들을 끄고 켤수 있는 기능
folium.LayerControl().add_to(m)

# 지도 출력
m

 

확대해보면 다음과 같이 철도 노선과 철도 역들이 전부 표시되어있음

1. 벡터

1-1. 벡터는 1행 또는 1열로 이루어진 1차원 배열

import numpy as np

vector_row=np.array([1,2,3])    # 1행으로 이루어진 행벡터
vector_column=np.array([[1],    # 1열로 이루어진 열벡터
                        [2],
                        [3]])

 

1-2. asarray함수를 사용하여 배열을 만들수 있음

import numpy as np

new_row=np.asarray([1,2,3])    # np.array와 똑같이 1행으로 이루어진 행벡터 생성

 

np.asarray(넘파이 배열)은 새로운 배열 생성X

np.array(넘파이 배열)은 새로운 배열 생성

# array 사용

vector_row=np.array([1,2,3])    # 1행으로 이루어진 행벡터
new_row=np.array(vector_row)    # array의 copy 매개변수는 배열을 복사할지 선택, 
                                # 기본값은 True이므로 new_row에는 vector_row의 복사본 저장
                                
new_row[0]=10   # new_row의 첫번째 값 변경
print('new_row:',new_row)
print('vector_row:',vector_row)

### 결과 ###
new_row: [10,2,3]     # 복사본인 자기 자신에게만 영향을 주고
vector_row: [1,2,3]   # 원래 배열에는 영향을 주지 않음


# asarray 사용

new_row=np.asarray(vector_row)    # asarray는 새로운 배열을 생성하지 않고 원래의 배열 그 자체가 됨

new_row[0]=10   # new_row의 첫번째 값 변경
print('new_row:',new_row)
print('vector_row:',vector_row)

### 결과 ###
new_row: [10,2,3]      # 자기 자신에게 영향을 주고
vector_row: [10,2,3]   # 원래 배열에도 똑같이 영향을 줌

 

 

 

2. 행렬

2-1. 1개 이상의 행과 열로 이루어진 2차원 배열

import numpy as np

matrix=np.array([[1,2],    # 3행 2열의 행렬
                 [1,2],
                 [1,2]])

matrix_object=np.mat([[1,2],    # 3행 2열의 행렬, numpy의 행렬에 특화된 데이터 구조
                      [1,2],
                      [1,2]])

np.array()를 사용해 행렬 생성

np.mat가 행렬에 특화되어 있긴 하지만

   -numpy의 표준 데이터 구조는 배열이고

   -대부분의 넘파이 함수는 행렬 객체가 아닌 배열을 반환하므로

np.mat()보다는 np.array()를 사용

 

 

2-2. 희소행렬

데이터에서 0이 아닌 값이 매우 적을 때 이 데이터를 효율적으로 표현하는 방법

0이 아닌 값의 행과 열의 번호와 그 값만 저장됨

import numpy as np
from scipy import sparse

matrix=np.array([[0,0],
                 [0,1],
                 [3,0]])

matrix_sparse=sparse.csr_matrix(matrix)
print(matrix_sparse)

### 결과 ###
(1,1) 1    # 두번째 행, 두번째 열의 값 1
(2,0) 3    # 세번째 행, 첫번째 열의 값 3

위에서 사용한 것은 CSR(Compressed Sparse Row)이고

이외에도 CSC(Compressed Sparse Column), 리스트의 리스트, 키의 딕셔너리 등 여러 종류의 희소 행렬이 존재

가장 좋은 희소 행렬은 없고 각 희소 행렬 사이의 유의미한 차이를 통해 어떤 것을 적용하면 좋을 지 결정

    -키의 딕셔너리(Dictionary of Keys, DOK): 행렬에서 0이 아닌 값의 (행번호, 열번호)를 키, 행렬값을 값으로 하는 딕셔너리

    -리스트의 리스트(List of lists, LIL): 링크드 리스트 알고리즘 이용하여 추가와 삭제가 용이하지만 CSR과 CSC에 비해 메모리 낭비

    -좌표 리스트(Coordinate list, COO): (행, 열, 값)의 튜플로 저장, 임의 엑세스 시간을 향상시키기 위해 행 인덱스→열 인덱스 순으로 정렬 가능, 점진적 행렬 구성에 유용

    -CSR: 가로 순서대로 재정렬(행에 관해 정리 압축)

        →데이터(A): 0이 아닌 행렬 값과 값의 (행 번호, 열 번호)가 저장

        →열 인덱스 값(JA): 0행에서 값이 있는 열은 0, 3

                                          1행에서 값이 있는 열은 2, 4

                                          2행에서 값이 있는 열은 1

                                          3행에서 값이 있는 열은 2, 4

        →행 압축 정보(IA): (최초 시작행 번호, 시작행에 있는 데이터 개수, 두번째 행까지 데이터 누적 개수,..., 마지막행까지 데이터 누적 개수)

 

    -CSC: 열에 관해 정렬한 것, CSR과 저장 알고리즘은 동일, LIL에 비해 저장 메모리 70% 이상 줄일 수 있지만 추가와 삭제가 용이하지 않음

 

# toarray
print(matrix_sparse.toarray())

### 결과 ###
[[0,0],
 [0,1],
 [3,0]]    # 희소 행렬을 다시 밀집 배열로 변환
 
 
 # todense
 print(matrix_sparse.todense())
 
 ### 결과 ###
 matrix([[0,0],
         [0,1],
         [3,0]])    # 희소 행렬을 np.matrix 객체로 변환

 

 

2-3. 벡터화 연산

배열의 여러 원소에 어떤 함수 적용하기

import numpy as np

matrix=np.array([[1,2,3],
                 [4,5,6],
                 [7,8,9]])

# 100을 더하는 함수
add_100=lambda i:i+100

# 함수를 np.vectorize(함수)를 통해 벡터화 시킴
vectorized_add_100=np.vectorize(add_100)

# 행렬에 벡터화된 함수를 적용시키면 행렬의 모든 원소에 함수 적용
vectorized_add_100(matrix)

### 결과 ###
array([[101,102,103],
       [104,105,106],
       [107,108,109]])

벡터화 연산은 기본적으로 for 루프를 구현한 것이므로 성능이 향상되지는 않음

 

 

2-4. 브로드캐스팅

넘파이 배열이 차원이 달라도 배열간 연산을 수행할 수  있음을 이용한 브로드캐스팅을 사용하면 더 간단하게 배열의 각 원소에 연산 적용 가능

# 1. 모든 원소에 100을 더함
matrix+100

### 결과 ###
array([[101,102,103],
       [104,105,106],
       [107,108,109]])


# 2. 행을 따라 더해짐
matrix+[100,100,10]

### 결과 ###
array([[101,102,13],
       [104,105,16],
       [107,108,19]])


# 3. 열을 따라 더해짐
matrix+[[100],[100],[10]]

### 결과 ###
array([[101,102,103],
       [104,105,106],
       [17,18,19]])

 

'Python > 기본문법' 카테고리의 다른 글

데이터 랭글링 (2)  (0) 2023.06.26
데이터 랭글링 (1)  (0) 2023.06.23
데이터 적재  (0) 2023.06.22
Numpy로 배열, 벡터, 행렬 이해하기 (3)  (0) 2022.10.20
Numpy로 배열, 벡터, 행렬 이해하기 (2)  (0) 2022.10.19

임신 육아 종합포털 아이사랑(https://www.childcare.go.kr/cpin/main1.jsp) > 어린이집 찾기 > 경상남도에 있는 모든 어린이집 정보 크롤링하기

 

크롤링할 정보: 기관명 / 설립유형 / 주소 / 제공서비스

 

 

 

1. 크롤링하려면 사이트의 주소가 필요

확인해야하는 사이트는 '요약'과 '기본현황' 두 개의 사이트이며 각 사이트의 주소는

>요약: http://info.childcare.go.kr/info/pnis/search/preview/SummaryInfoSlPu.jsp?flag=YJ&STCODE_POP='유치원코드'
>기본현황: http://info.childcare.go.kr/info/pnis/search/preview/BasisPresentConditionSlPu.jsp?flag=GH&STCODE_POP='유치원코드'

 

이며 유치원코드는 지역코드(5자리)+고유번호(6자리)로 이루어짐

# 시군구 코드: [48120,48121,48123,48125,48127,48129,48170,48220,48240,48250,48270,48310,48330,48720,48730,48740,48820,48840,48850,48860,48870,48880,48890]

# 고유번호는 규칙도 모르겠고 중간에 폐지된 시설도 있지만 세자리를 넘어가는 코드는 없는 것으로 보임

 

 

 

2. 해당 규칙에 따라 다음과 같이 url을 얻는 코드 작성

# 경상남도의 모든 시군구 코드 리스트
gn_cd_list=['48120','48121','48123','48125','48127','48129','48170','48220','48240','48250','48270','48310',
            '48330','48720','48730','48740','48820','48840','48850','48860','48870','48880','48890']

# 각 시군구 코드를 주소에 적용
for gn_cd in range(gn_cd_list):
    # 주소가 문자형이므로 세자리의 고유번호를 000~999 형태로 넣고 싶었지만 000을 0으로 자동으로 바꾸기에
    # 자리수에 따라 0~9, 10~99, 100~999 세번의 for문 사용
    
    for i in range(10):
        # 기관명과 주소를 확인할 수 있는 '요약' 사이트의 url
        name_juso_url = 'http://info.childcare.go.kr/info/pnis/search/preview/SummaryInfoSlPu.jsp?flag=YJ&STCODE_POP=%s00000%d'%(gn_cd,i)
        # 설립유형과 제공 서비스를 확인할 수 있는 '기본현황' 사이트의 url
        type_service_url='http://info.childcare.go.kr/info/pnis/search/preview/BasisPresentConditionSlPu.jsp?flag=GH&STCODE_POP=%s00000%d'%(gn_cd,i)
    
    for i in range(10,100):
        name_juso_url = 'http://info.childcare.go.kr/info/pnis/search/preview/SummaryInfoSlPu.jsp?flag=YJ&STCODE_POP=%s0000%d'%(gn_cd,i)
        type_service_url='http://info.childcare.go.kr/info/pnis/search/preview/BasisPresentConditionSlPu.jsp?flag=GH&STCODE_POP=%s0000%d'%(gn_cd,i)
    
    for i in range(100,1000):
        name_juso_url = 'http://info.childcare.go.kr/info/pnis/search/preview/SummaryInfoSlPu.jsp?flag=YJ&STCODE_POP=%s000%d'%(gn_cd,i)
        type_service_url='http://info.childcare.go.kr/info/pnis/search/preview/BasisPresentConditionSlPu.jsp?flag=GH&STCODE_POP=%s000%d'%(gn_cd,i)
 

 

 

3. 사이트에서 f12를 눌러 개발자 도구 확인

오른쪽 마우스 사용이 불가능한 사이트라 직접 화살표를 눌러가며 기관명, 주소, 설립유형, 제공서비스의 경로를 찾아 XPath를 복사

 

복사한 XPath를 fromstring(request.get(url).text).xpath('XPath')문에 적용하여 해당 정보 크롤링

참고로, 복사한 XPath문 맨 뒤에 '/text()'를 붙여줘야 XPath 주소에 해당하는 부분의 텍스트를 출력할 수 있음

 

import requests
import lxml.html
from lxml.html import fromstring

# 기관명의 XPath를 통해 사이트 내의 기관명 크롤링
name_li= fromstring(requests.get(name_juso_url).text).xpath('//*[@id="popWrap2"]/div/div/div/table/tbody/tr[1]/td[1]/text()')

# 주소의 XPath를 통해 사이트 내의 주소 크롤링
juso_li = fromstring(requests.get(name_juso_url).text).xpath('//*[@id="popWrap2"]/div/div/div/table/tbody/tr[10]/td/text()')

# 설립유형의 XPath를 통해 사이트 내의 설립유형 크롤링
type_li=fromstring(requests.get(type_service_url).text).xpath('//*[@id="popWrap2"]/div/div/div/table[1]/tbody/tr[1]/td[2]/text()')

# 제공 서비스의 XPath를 통해 사이트 내의 제공 서비스 크롤링
service_li=fromstring(requests.get(type_service_url).text).xpath('//*[@id="popWrap2"]/div/div/div/table[1]/tbody/tr[3]/td[1]/text()')

print(name_li)
print(juso_li)
print(type_li)
print(service_li)

### 실행 결과 ###
['\n              BNK창원어린이집\n              \n            ']
['\n                    \t(51366)\n                    \t경상남도 창원시 마산회원구 양덕로 135\xa0(양덕동)\n\t\t\t\t\t\t']
['직장']
['일반']

이름과 주소에서 필요없는 띄어쓰기와 '\n', '\t', '\xa0' 등의 문자가 나타나므로 replace(' ' ,''), replace('\n','') 등으로 없애기

 

 

4. 전체 코드

import requests
from lxml.html import fromstring
import pandas as pd

# 각 정보를 크롤링해와서 넣어 놓을 리스트
name=[]    # 기관명
juso=[]    # 주소
type=[]    # 설립유형
service=[] # 제공서비스

# 경상남도 시군구 코드
gn_cd_list=['48120','48121','48123','48125','48127','48129','48170','48220','48240','48250','48270','48310',
            '48330','48720','48730','48740','48820','48840','48850','48860','48870','48880','48890']

# 경상남도의 각 시군구 코드+한 자리수 코드(000~009)/두 자리수 코드(010~099)/세 자리수 코드(100~999)의 주소 크롤링
for gn_cd in gn_cd_list:
    # 한 자리수 코드(000~009)
    for i in range(10):
        name_juso_url = 'http://info.childcare.go.kr/info/pnis/search/preview/SummaryInfoSlPu.jsp?flag=YJ&STCODE_POP=%s00000%d'%(gn_cd,i)
        type_service_url='http://info.childcare.go.kr/info/pnis/search/preview/BasisPresentConditionSlPu.jsp?flag=GH&STCODE_POP=%s00000%d'%(gn_cd,i)
        name_li= fromstring(requests.get(name_juso_url).text).xpath('//*[@id="popWrap2"]/div/div/div/table/tbody/tr[1]/td[1]')
        juso_li = fromstring(requests.get(name_juso_url).text).xpath('//*[@id="popWrap2"]/div/div/div/table/tbody/tr[10]/td/text()')
        type_li=fromstring(requests.get(type_service_url).text).xpath('//*[@id="popWrap2"]/div/div/div/table[1]/tbody/tr[1]/td[2]')
        service_li=fromstring(requests.get(type_service_url).text).xpath('//*[@id="popWrap2"]/div/div/div/table[1]/tbody/tr[3]/td[1]')
        name.append(name_li[0].text.replace('\xa0','').replace('\t','').replace('\n','').replace('  ',''))  # 필요없는 단어 제외후 리스트에 추가하여 저장
        juso.append(juso_li[0].replace('\xa0','').replace('\t','').replace('\n','').replace('  ',''))       # 필요없는 단어 제외후 리스트에 추가하여 저장
        type.append(type_li[0].text)                                                                        # 리스트에 추가하여 저장
        service.append(service_li[0].text)                                                                  # 리스트에 추가하여 저장

    # 두 자리수 코드(010~099)
    for j in range(10,100):
        name_juso_url = 'http://info.childcare.go.kr/info/pnis/search/preview/SummaryInfoSlPu.jsp?flag=YJ&STCODE_POP=%s0000%d'%(gn_cd,j)
        type_service_url='http://info.childcare.go.kr/info/pnis/search/preview/BasisPresentConditionSlPu.jsp?flag=GH&STCODE_POP=%s0000%d'%(gn_cd,j)
        name_li= fromstring(requests.get(name_juso_url).text).xpath('//*[@id="popWrap2"]/div/div/div/table/tbody/tr[1]/td[1]')
        juso_li = fromstring(requests.get(name_juso_url).text).xpath('//*[@id="popWrap2"]/div/div/div/table/tbody/tr[10]/td/text()')
        type_li=fromstring(requests.get(type_service_url).text).xpath('//*[@id="popWrap2"]/div/div/div/table[1]/tbody/tr[1]/td[2]')
        service_li=fromstring(requests.get(type_service_url).text).xpath('//*[@id="popWrap2"]/div/div/div/table[1]/tbody/tr[3]/td[1]')
        name.append(name_li[0].text.replace('\xa0','').replace('\t','').replace('\n','').replace('  ',''))  # 필요없는 단어 제외후 리스트에 추가하여 저장
        juso.append(juso_li[0].replace('\xa0','').replace('\t','').replace('\n','').replace('  ',''))       # 필요없는 단어 제외후 리스트에 추가하여 저장
        type.append(type_li[0].text)                                                                        # 리스트에 추가하여 저장
        service.append(service_li[0].text)                                                                  # 리스트에 추가하여 저장

    # 세 자리수 코드(100~999)
    for k in range(100,1000):
        name_juso_url = 'http://info.childcare.go.kr/info/pnis/search/preview/SummaryInfoSlPu.jsp?flag=YJ&STCODE_POP=%s000%d'%(gn_cd,k)
        type_service_url='http://info.childcare.go.kr/info/pnis/search/preview/BasisPresentConditionSlPu.jsp?flag=GH&STCODE_POP=%s000%d'%(gn_cd,k)
        name_li= fromstring(requests.get(name_juso_url).text).xpath('//*[@id="popWrap2"]/div/div/div/table/tbody/tr[1]/td[1]')
        juso_li = fromstring(requests.get(name_juso_url).text).xpath('//*[@id="popWrap2"]/div/div/div/table/tbody/tr[10]/td/text()')
        type_li=fromstring(requests.get(type_service_url).text).xpath('//*[@id="popWrap2"]/div/div/div/table[1]/tbody/tr[1]/td[2]')
        service_li=fromstring(requests.get(type_service_url).text).xpath('//*[@id="popWrap2"]/div/div/div/table[1]/tbody/tr[3]/td[1]')
        name.append(name_li[0].text.replace('\xa0','').replace('\t','').replace('\n','').replace('  ',''))  # 필요없는 단어 제외후 리스트에 추가하여 저장
        juso.append(juso_li[0].replace('\xa0','').replace('\t','').replace('\n','').replace('  ',''))       # 필요없는 단어 제외후 리스트에 추가하여 저장
        type.append(type_li[0].text)                                                                        # 리스트에 추가하여 저장
        service.append(service_li[0].text)                                                                  # 리스트에 추가하여 저장
    print(gn_cd) # 몇번째 시군구까지 작업을 완료했는지 알아보기 위함

# 각 리스트를 하나의 데이터프레임으로 생성
df=pd.DataFrame({'기관명':name,'주소':juso,'설립유형':type,'제공서비스':service})
df.drop(df[df['기관명']==''].index,inplace=True)  # 위에서 고유코드가 없었던 경우도 빈 행으로 데이터 프레임에 포함되었기 때문에 빈 행 제거
df.reset_index().iloc[:,1:]

# 마지막으로 생성한 데이터프레임을 csv파일로 저장
df.to_csv('어린이집_크롤링.csv',encoding='cp949')

 

각 어린이집의 고유코드에 대한 정보를 알지 못해서 무작정 000~999까지 넣어 크롤링하여 실제 어린이집 보다 많은 수의 어린이집이 크롤링됨(임신육아종합포털에 올라와있지 않은 어린이집도 있는 듯)

지오코딩이란?

고유명칭(주소나 산, 호수의 이름 등)을 가지고 위도, 경도의 좌표를 얻는 것

 

카카오 api를 이용하여 지오코딩 하기

1. Kakao Developers 사이트(https://developers.kakao.com/)에 회원가입 후 로그인

 

2. 내 애플리케이션 > 애플리케이션 추가(사진 필요X, 앱 이름, 사업자명은 아무거나)

이렇게 한 뒤 생성된 애플리케이션에 들어가보면 다음과 같이 키가 발급되어 있음

키는 메모장에 복사해두기

 

 

3. 내 애플리케이션 > 제품 설정 > 카카오 로그인 > 활성화 상태 On

 

 

4. 내 애플리케이션 > 제품 설정 > 카카오 로그인 > 맨 아래에 Redirect Url 추가

각자 사용환경에 맞게 등록(주피터 노트북의 경우 http://localhost:8888로 등록)

 

 

5. 코드 받기

https://kauth.kakao.com/oauth/authorize?client_id=REST API키&redirect_uri=http://localhost:8888&response_type=code&response_type=code

각자 발급받았던 REST API 키와 사용환경에 맞게 등록한 Redirect Url 주소를 알맞게 넣어 주소창에 입력

개인정보 동의를 누르면 자신의 개발환경 창으로 접속

주소창에는 http://localhost:8888/tree?code=코드값 의 형태로 코드가 함께 나타남

 

 

6. 토큰 받기

https://kauth.kakao.com/oauth/token?grant_type=authorization_code&client_id=REST API키&redirect_uri=http://localhost:8888&code=드값

발급받은 REST API, 개발환경에 맞는 Redirect Url, 발급받은 코드를 각각 알맞게 붙여서 주소창에 입력

위와 같이 {"access token":"~", "token_type":"bearer", "refresh_token":"~",...}등 토큰이 발급됨

토큰은 기억할 필요X

 

 

7. 지오코딩 코드 작성

import json
import requests
import pandas as pd
import numpy as np
import warnings
warnings.filterwarnings('ignore')

REST_API='발급받은 Rest Api 키'

def Geocoding(주소):
    url = 'https://dapi.kakao.com/v2/local/search/address.json?query=' + 주소
    headers = {"Authorization": "KakaoAK "+ REST_API}
    result = json.loads(str(requests.get(url, headers=headers).text))
    match_first = result['documents'][0]['address']
    return float(match_first['y']), float(match_first['x'])
    
Geocoding('지오코딩 하고 싶은 주소')

# 결과는 해당 주소의 (위도,경도)로 반환됨

 

 

8. 엑셀 파일에 입력된 주소 여러 개를 통째로 지오코딩하기(+없거나 변경된 주소에 대한 대처)

import os
import lxml.html
from lxml.html import fromstring
import pandas as pd
from bs4 import BeautifulSoup

# 주소가 입력되어 있는 엑셀파일 불러오기
df=pd.read_csv('엑셀파일명')

# 위도와 경도가 입력될 열 생성
df['latitude']=0
df['longitude']=0

# 지오코딩 함수는 위의 함수 그대로 사용

# 엑셀의 주소 변환 작업 시작
for i in range(len(df)):
    try:
        try:
            df['latitude'][i]=Geocoding(df['주소'][i])[0]     # (위도, 경도)로 반환되는 결과 중 위도 부분
            df['longitude'][i]=Geocoding(df['주소'][i])[1]    #(위도, 경도)로 반환되는 결과 중 경도 부분

        # 주소가 변경되었다면 주소정보누리집에서 주소를 검색하여 새로운 주소를 크롤링
        except:
            juso=df['주소'][i].replace(' ','+')
            url=f'https://www.juso.go.kr/support/AddressMainSearch.do?firstSort=none&ablYn=N&aotYn=N&fillterHiddenValue=&searchKeyword={juso}&dsgubuntext=&dscity1text=&dscounty1text=&dsemd1text=&dsri1text=&dssan1text=&dsrd_nm1text=&searchType=HSTRY&dssearchType1=road&dscity1=&dscounty1=&dsrd_nm_idx1=%EA%B0%80_%EB%82%98&dsrd_nm1=&dsma=&dssb=&dstown1=&dsri1=&dsbun1=&dsbun2=&dstown2=&dsbuilding1='
            res=requests.get(url)
            p=fromstring(res.text)
            li=p.xpath('//*[@id="list1"]/div[2]/span[2]')

            # 크롤링한 변경된 주소에서 불필요한 부분 삭제 후 원래 주소 자리에 대체
            df['주소'][i]=li[0].text.replace('\r','').replace('\t','').replace('\n','')
            df['latitude'][i]=getLatLng(df['LTNO_ADDR'][i])[0]
            df['longitude'][i]=getLatLng(df['LTNO_ADDR'][i])[1]
    
    # 주소가 변경된 것이 아니라 카카오맵에 없는 것이라면 위 방법으로도 오류를 해결할 수 없으므로
    # 일단 위도와 경도 모두 0으로 대체 후 따로 확인해보기
    except:
        df['latitude'][i]=0
        df['longitude'][i]=0

하루에 할 수 있는 주소 개수에 제한이 있어서 49000개~50000개 정도만 되는 것 같음

+ Recent posts