Pandas 데이터 <-> MySQL

이전 포스팅에서 만든 데이터프레임을 mySQL과 연동하기 위한 과정이다.

import pandas as pd
from sqlalchemy import create_engine
import pymysql

# db 연결
db = pymysql.connect(host='localhost', user='root', password='비번')
cursor = db.cursor()

# DataFrame을 MySQL 데이터베이스에 저장
try:
    engine = create_engine("mysql+pymysql://root:비번@localhost:포트번호/연결할db")
    데이터프레임이름.to_sql(name="테이블", con=engine, if_exists="replace", index=False)
    print("DataFrame이 성공적으로 MySQL 데이터베이스에 저장되었습니다.")
except Exception as e:
    print("에러 발생:", e)
finally:
    db.close()

 

데이터프레임의 정보가 그대로 mySQL에 저장


Mysql <-> node.js 연동

https://www.npmjs.com/package/mysql2

 

mysql2

fast mysql driver. Implements core protocol, prepared statements, ssl and compression in native JS. Latest version: 3.9.6, last published: 3 days ago. Start using mysql2 in your project by running `npm i mysql2`. There are 4319 other projects in the npm re

www.npmjs.com

여기에 기본적인 사용법이 나와있다.

// Get the client
const mysql = require('mysql2');

// Create the connection to database
const connection = mysql.createConnection({
  host: 'localhost',
  user: 'root',
  password: '비번',
  database: '연결할 db',
  dateStrings: true
});

// Using placeholders
connection.query(
  'SELECT * FROM `테이블`',
  ['Page', 45],
  function (err, results) {
    console.log(results);
    results.map((item)=>{
      console.log('key : ', item.key)
      console.log('title : ', item.title)
      console.log('sub_title : ', item.sub_title)
      console.log('img : ', item.img)
    })
  }
);

연결 후 테스트 로그를 찍어보면

잘 연결된 것을 볼 수 있다 !

크롤링하기 위해 필요한 라이브러리

from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

안양 청년 정책 웹 사이트 url을 얻어온다.

driver.get("https://www.anyang.go.kr/youth/contents.do?key=3567")

웹페이지에서 요소를 찾기 위해 암묵적으로 10초 대기

driver.implicitly_wait(10)

제목, URL 크롤링

해당 페이지에서 각 정책의 제목과 url 크롤링

해당 정책이 몇 개인지 고정되어있지 않기 때문에 값을 설정할 수 없어서 while문을 사용하여 정책 요소들을 순회한다.

순회하면서 해당 정책의 각 url의 key값, 정책 제목을 구한다.

구해온 리스트 안의 딕셔너리 형태로 저장한다. key: key, title: 정책 제목 형태

while True:
        try:
            # key
            url_element = driver.find_element(By.XPATH, '//*[@id="contents"]/div/div/div/ul/li[{}]/a'.format(i))
            title = driver.find_element(By.XPATH, '//*[@id="contents"]/div/div/div/ul/li[{}]/a/span[1]'.format(i))
            
            url = url_element.get_attribute('href')
            key = url.split('=')[-1]
            policyData.append({"key": key, "title": title.text})
            
            i += 1
        except NoSuchElementException:
            break  # 에러가 발생하면 반복문 종료

각 정책들이 리스트 안의 딕셔너리 형태로 저장됨.


데이터프레임으로 저장

이후 고유한 값은 해당 url의 key 값으로 지정하고, 정책 제목은 title로 지정하여 데이터 프레임으로 만든다.

데이터 프레임은 엑셀의 파이썬 버전이라고 생각하면 쉽다.

dfPolicy = pd.DataFrame(policyData) # 데이터 프레임으로 만들기

위 리스트와 같은 값이지만 데이터프레임으로 저장하면 데이터를 관리하기 쉬워진다.


데이터프레임 column 추가하기

현재 key, title만 설정되어있다. sub_title, img의 값들도 추가할 것이기 때문이기 때문에 컬럼명부터 설정한다.

dfPolicy['sub_title'] = None
dfPolicy['img'] = None


위에서 구한 Key 값으로 나머지 sub_title / img 구하기

url의 key 값으로 정책의 이미지와 사업 목적을 구해올 것이다.

정책 페이지 순회

먼저 데이터 프레임 안에 있는 키값을 이용하여 정책 페이지를 순회한다

for index, row in dfPolicy.iterrows():
        policy_key = row['key']
        driver.get('https://www.anyang.go.kr/youth/contents.do?key={}'.format(policy_key))

이미지 구하기

이미지는 로딩이 느릴 수도 있으므로 로드될 때까지 기다린 후 로딩이 끝나면 요소를 구해온 후 리스트로 저장

# 이미지 로드될 때까지 기다림
element = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.XPATH, '//*[@id="contents"]/div/div/div[1]/div/div[1]/div/div/div/div/div/img')))
img = element.get_attribute('src')
policy_img_lst.append(img)

사업목적 구하기

이후 사업목적도 동일한 방식으로 구해온 후 리스트로 저장

sub_title = driver.find_element(By.XPATH, '//*[@id="contents"]/div/div/div[2]/div/ul/li[1]/p')
sub_title_lst.append(sub_title.text)

데이터 프레임에 저장

dfPolicy['img'] = policy_img_lst
dfPolicy['sub_title'] = sub_title_lst

리스트 안의 값들을 데이터프레임 img, sub_title 열 안에 저장한다.

여러 줄로 잘렸지마나 데이터프레임에 key, title, sub_title, img가 잘 들어가있는 것을 볼 수 있다!

  • 회원가입 - POST /signup
  • 로그인 - POST /signin
  • 회원 개별 조회 - GET /users/:id
  • 회원 개별 탈퇴 - DELETE /users/:id

회원가입 : POST

app.post('/signup', (req, res) => {
  if (req.body == {}) {
    res.status(400).json({
      message: '입력 값을 다시 확인해주세요.'
    })
  } else {
    db.set(id++, req.body)

    res.status(201).json({
      message: `${db.get(id-1).name}님 환영합니다.`
    })
  }
})

요청 바디에는 id, pwd, name 등이 올 수 있다.

요청 바디로 들어온 회원 정보를 db에 저장하고, 성공했다는 응답 코드를 돌려준다.

id++ 을 하는 이유는 자동으로 1을 올려주기 위함 (하나의 객체를 유니크하게 구별하기 위함)

아직 db랑 연결을 안 하고 설계하는 과정이라 수동으로 하고 있다 . ㅎㅎ


회원 개별 조회 : GET

app.get('/users/:id', (req, res) => {
  const id = parseInt(req.params.id)
  const user = db.get(id)

  if (user == undefined) {
    res.status(404).json({
      message: "회원 정보가 없습니다."
    })
  } else {
    res.status(200).json({
      userId: user.userId,
      name: user.name,
    })
  }
})

 

조회하고 싶은 회원의 id를 params로 받는다.

params는 모두 문자열로 들어오기 때문에 parseInt로 정수로 변환한다.

회원의 id를 db에서 찾고,

존재하지 않는다면 상태코드 404와 회원정보가 없다는 메세지를 보냄

존재한다면 상태코드 200과, id와 일치하는 회원 정보를 응답으로 넘겨준다.


회원 개별 탈퇴

// 회원 개별 탈퇴
app.delete('/users/:id', (req, res) => {
  const id = parseInt(req.params.id)
  const user = db.get(id)

  if (user == undefined) {
    res.status(404).json({
      message: "회원 정보가 없습니다."
    })
  } else {
    db.delete(id)
    res.status(200).json({
      message: `${user.name}님, 다음에 또 뵙겠습니다.`
    })
  }
})

 

회원 개별 조회와 거의 비슷하다.

params로 회원의 id를 받고 있다면 db에서 삭제, 없다면 상태코드 404와 에러 메세지 반환


route를 활용한 코드 리팩토링 - 회원 개별 조회 / 회원 개별 탈퇴

개별조회와 개별탈퇴는 http method만 다르고 url이 같다.

route를 이용해 중복 코드를 줄일 수 있다.

app
  .route('/users/:id')
  .get()
  .delete()

 

중복되는 url을 묶고, 아래 메서드들로 분리 할 수 있다.

각 메서드 괄호 안에는 해당 요청이 들어왔을 때의 실행할 콜백 함수만 넣어주면 된다.

app
  .route('/users/:id')
  // 회원 개별 조회
  .get((req, res) => {
    const id = parseInt(req.params.id)
    const user = db.get(id)
  
    if (user == undefined) {
      res.status(404).json({
        message: "회원 정보가 없습니다."
      })
    } else {
      res.status(200).json({
        userId: user.userId,
        name: user.name,
      })
    }
  })
  // 회원 개별 탈퇴
  .delete((req, res) => {
    const id = parseInt(req.params.id)
    const user = db.get(id)
  
    if (user == undefined) {
      res.status(404).json({
        message: "회원 정보가 없습니다."
      })
    } else {
      db.delete(id)
      res.status(200).json({
        message: `${user.name}님, 다음에 또 뵙겠습니다.`
      })
    }
  })

로그인

1. db를 순회하면서, 요청받은 userId가 db에 있는지 확인

2. 있다면 해당 객체의 정보를 loginUser에 담는다. (없으면 loginUser는 빈 값이 됨)

3. loginUser가 빈 값인지 확인하고, (빈 값이 아니면 > userId가 일치한다는 것)

4. 요청받은 pwd가 해당 userId의 pwd와 같은지 확인

// 로그인
app.post('/signin', (req, res) => {
  var loginUser = {}
  const { userId, password } = req.body

  // userId가 db에 저장된 회원인지 확인
  db.forEach((user, idx)=>{ // data, idx, totalData
    if (user.userId === userId) { // 요청데이터 userId와 db에 있는 userId가 같은지 확인
      loginUser = user 
    }
  })
  
  // userId 값을 못 찾았으면
  if(isExisted(loginUser)) {
    // pwd도 맞는지 비교
    if (loginUser.password === password) {
      console.log('비번도 같아')
    } else {
      console.log('틀렸다.')
    }
  } else {
    console.log('없는 아이디입니다.') 
  }
})

 

 

Object.keys(obj)의 길이가 0이라면, 비어있는 것 (속성 이름이 하나도 존재하지 않는다는 것이기 때문)

Object.keys() > 객체의 속성 이름을 배열로 얻을 수 있는 메서드

// 객체가 비었는지 확인하는 함수
function isExisted(obj) {
  if (Object.keys(obj).length) {
    return true
  } else {
    return false
  }
}

'졸업작품 > Node.js' 카테고리의 다른 글

Express - params, query(쿼리 문자열)  (0) 2024.04.18
RESTful API 및 HTTP 프로토콜 기본 개념  (0) 2024.04.17

Params : 동적인 URL 경로 처리

app.get(`/products/:n`, function(req, res) {
  res.json({
    num: req.params.n
  })
})

클라이언트에서 /products 뒤에 입력한 값을 req.params로 받을 수 있음.

/products/:n 경로에 접속하면 :n자리에 오는 값이 req.params.n에 저장되어 해당 값을 JSON 형식으로 반환.

n에 200 전달 > req.params.n으로 받을 수 있음.


쿼리 문자열

  • 쿼리 문자열은 URL의 끝에 '?'를 사용하여 추가적인 데이터를 전달하는 데 사용
  • 쿼리 문자열은 'key=value' 쌍으로 이루어져 있음
  • '&'를 사용하여 여러 개의 쌍을 연결할 수 있음
  • Express에서는 req.query를 통해 이러한 쿼리 문자열을 읽어올 수 있다.
// https://www.youtube.com/watch?v=2KheQK5srCc&t=2339s
app.get(`/watch`, function(req, res) {
  const query = req.query
  res.json({
    video: query.v,
    timeline: query.t
  })
})

/watch?v=2KheQK5srCc&t==2339s 에 접속하면

watch경로 뒤에 오는 쿼리문자열을 req.query로 받을 수 있다.

현재 v=value / t=value 형태로 두 쌍이 존재하며, 이 두 쌍을 JSON 형태로 반환할 수 있다.

더 깊이 접근하고 싶다면, req.query.v / req.query.t > 객체 표기법으로 각 키의 값에도 직접 접근할 수 있다.


+ TIP! 자바스크립트 객체, 배열 비구조화 할당

코드를 간결하고 가독성 있게 사용할 수 있음.

객체

  • 객체 비구조화 할당에서는 변수의 이름과, 객체의 속성명이 반드시 같아야 한다.
  • 변수의 이름이 객체의 속성명과 일치하지 않으면 값이 할당되지 않는다.

배열

  • 배열은 속성명이 따로 존재하지 않기 때문에, 배열의 순서대로 할당된다.
  • 선언된 변수의 순서와, 배열의 요소 순서가 일치해야 한다.
  • 중간에 필요없는 값이 끼여있다면 , ,로 생략 처리를 해주어야 한다.

'졸업작품 > Node.js' 카테고리의 다른 글

[Node.js] 회원 API 기초 설계  (0) 2024.04.19
RESTful API 및 HTTP 프로토콜 기본 개념  (0) 2024.04.17

클라이언트와 서버 간의 약속, 프로토콜(Protocol)

서로 정보를 주고 받을 때 지켜야 하는 약속이 존재한다.

반드시 이 약속을 지켜서 통신해야 하며, 우리는 이 약속을 프로토콜이라 부른다.

 

인터넷으로 연결된 클라이언트와 서버는 웹 프로토콜인 HTTP를 사용하여 데이터를 주고 받는다.

웹(=인터넷망 속 가상 공간) 개발자 > 인터넷을 돌아다니기 위한 규약을 지켜야만 함 > HTTP를 지켜야만 한다.

 

HTTP 형식을 엄격하게 준수하지 않아도 통신을 가능하다. 하지만 규약 따르면 효율이 극대화!


RESTful API : HTTP 규약을 매우 잘 따른 API, REST의 원칙을 완벽하게 준수하는 API

RESTful API 쓰는 이유

  • 웹에서 일반적으로 사용하는 표준 프로토콜 활용 > 다양한 플랫폼 및 언어에서 API를 손쉽게 사용하고 통합할 수 있다.
  • API 사용이 직관적 > GET, POST, PUT, DELETE < 명확한 의미를 가지고 있어 이해하고 사용하기가 쉽다.
  • 자원 중심적으로 URL을 통해 고유하게 식별할 수 있다. 일반적으로 CRUD 작업 지원(Create, Read, Update, Delete)

HTTP 에 담아 보내야하는 것들

인터넷 상에서 공유/전달하고 싶은 모든 것들은 다 http에 넣어서 보내야 한다

HTTP 요청과 응답은 Head / Body 로 나누어져있음.

 

Header(헤더)

  • 통신 상태가 어떤지 알려주는 HTTP code(상태 코드, 200, 404, 500 등)
  • 응답이 어떤 형태인지.

Body(바디)

  • 응답의 본문 데이터(요청 성공, 요청 실패에 대한 정보 등)
  • post, put의 경우, 전달하려는 데이터

HTTP 에 담아보내는 나의 목적 = HTTP method

  • 생성(=등록) : POST
  • 조회 : GET
  • 수정 : PUT(덮어쓰기) / PATCH(일부 변경)
  • 삭제 : DELETE

REST API URL 규칙

  • 대문자 X, 소문자 O
  • 언더바(_) X, 하이픈(-) O
  • 마지막에 / 포함 X
  • 목적을 포함하지 않음 X
  • 복수형 O

URL + HTTP method (API 설계)

http://localhost:8888 : 내 컴퓨터 주소(local host)

 

잘못된 예

http://localhost:8888 상품 "등록"=> /post_product

http://localhost:8888 전체상품 "조회" => /select_all_products

http://localhost:8888 전체 상품 "삭제" => /DeleteAllProducts

 

올바른 예

http://localhost:8888 상품 "등록"=> "POST" /product

http://localhost:8888 전체 상품 "삭제" => "DELETE" /products

http://localhost:8888 전체 상품 "조회" => "GET" /products

 

http://localhost:8888 상품 " 개별 조회" => "GET" /products/{id}

http://localhost:8888/products/1
http://localhost:8888
/products/2
http://localhost:8888
/products/3

'졸업작품 > Node.js' 카테고리의 다른 글

[Node.js] 회원 API 기초 설계  (0) 2024.04.19
Express - params, query(쿼리 문자열)  (0) 2024.04.18
전 포스팅인 카카오 로그인 iOS 편에서 기본 설정은 마쳤으므로 프로젝트 생성 및 라이브러리 설치는 생략되었습니다.

1. 카카오 플랫폼 등록

https://developers.kakao.com/

 

Kakao Developers

카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.

developers.kakao.com

 

1-1. 패키지명 등록

프로젝트/android/app/src/main/java/com/프로젝트명/MainActivity.kt 파일 안의 1번째줄 > package com.프로젝트명

패키지명 : com~ 등록


프로젝트/android/app/src/main/AndroidManifest.xml 파일의 use-permission 태그의 패키지 속성 추가

<uses-permission android:name="android.permission.INTERNET" package="com.native01" />

 

1-2. 키 해시 등록

프로젝트/android/app/ 폴더로 이동한 뒤 터미널에 해당 코드 입력

keytool -exportcert -alias androiddebugkey -keystore debug.keystore -storepass android -keypass android | openssl sha1 -binary | openssl base64

코드 입력시 키 해시 값 나옴

 

패키지명, 키 해시 등록으로 안드로이드 플랫폼 등록 완료

2. Android 설정

2-1. KAKAO SDK 적용

android/build.gradle 파일 안에 코드 추가

maven { url 'https://devrepo.kakao.com/nexus/content/groups/public/' }

repositories 안에 추가~

2-2. Redirect URL 설정

android:allowBackup의 값을 true로 변경

android:allowBackup="true"

AndroidManifest.xml 파일에 코드 추가

- application 태그 안에 삽입

- 실제 자신의 카카오 네이티브 앱 키 삽입

<activity
   android:name="com.kakao.sdk.auth.AuthCodeHandlerActivity"
   android:exported="true">
  <intent-filter>
      <action android:name="android.intent.action.VIEW" />
      <category android:name="android.intent.category.DEFAULT" />
      <category android:name="android.intent.category.BROWSABLE" />

      <data android:host="oauth"
          android:scheme="kakao{카카오 네이티브 앱 key를 입력해주세요}" />
  </intent-filter>
</activity>

3. Strings.xml

/android/app/src/main/res/values/strings.xml 파일에도 네이티브 앱 키 등록

4. android 실행

react-native run-android

흑 ... 성공 .... 감격스럽다

참고한 라이브러리 : https://github.com/crossplatformkorea/react-native-kakao-login

 

GitHub - crossplatformkorea/react-native-kakao-login: react-native native module for Kakao sign in.

react-native native module for Kakao sign in. Contribute to crossplatformkorea/react-native-kakao-login development by creating an account on GitHub.

github.com

1. 리액트 네이티브 프로젝트 생성

npx react-native init 프로젝트명

2. 앱 실행

npx react-native run-ios

3. 카카오 라이브러리 설치

npm add @react-native-seoul/kakao-login
npx pod install

 pod 라이브러리 설치

4. pod에서 iOS deployment target 11.0 이상으로 설정

> platform : ios, '11.0.0' 변경

5. 카카오 개발자 설정

https://developers.kakao.com/

 

Kakao Developers

카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.

developers.kakao.com


5-1. 플랫폼 설정

내 애플리케이션 > 애플리케이션 추가하기 > 앱이름, 사업자명 적고 저장하기

앱설정 > 플랫폼 > iOS 플랫폼 등록 > 번들 ID 작성

XCODE > Signing & Capabilities > Bundle Identifier 값 등록


5-2. 동의 설정

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

6. 프로젝트 설정

https://developers.kakao.com/docs/latest/ko/ios/getting-started

6-1. 앱 실행 허용 목록 설정

Info.plist 파일에 앱 실행 허용 목록(Allowlist)을 설정

 <key>LSApplicationQueriesSchemes</key>
  <array>
      <!-- 카카오톡으로 로그인 -->
      <string>kakaokompassauth</string>
      <!-- 카카오톡 공유 -->
      <string>kakaolink</string>
      <!-- 카카오톡 채널 -->
      <string>kakaoplus</string>
  </array>

6-2. 커스텀 URL 스킴 설정

[Info] > [URL Types] > [URL Schemes] 항목에 네이티브 앱 키(Native App Key)를 kakao${NATIVE_APP_KEY} 형식으로 등록

xcode에서 url 스킴 등록하면 자동으로 생성 됨


위의 두가지 설정 후 최종 info.plist 추가되는 코드

7. AppDelegate.m 파일 설정

#import <RNKakaoLogins.h>

- (BOOL)application:(UIApplication *)app
     openURL:(NSURL *)url
     options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
 if([RNKakaoLogins isKakaoTalkLoginUrl:url]) {
    return [RNKakaoLogins handleOpenUrl: url];
 }

 return NO;
}

import~ 코드는 위쪽에 그리고 밑 bool~ 코드는

@implementation AppDelegate 아래에 적어줘야한다.

8. Podfile

Podfile 파일 안의 target 프로젝트명 do ~ end 사이에 코드 추가

pod 'KakaoSDK'

9. 예제 코드

App.tsx

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 *
 * Generated with the TypeScript template
 * https://github.com/react-native-community/react-native-template-typescript
 *
 * @format
 */

import Intro from './pages/Intro';
import React from 'react';
import {
  SafeAreaView,
} from 'react-native';

const App = () => {

  return (
    <SafeAreaView >
      <Intro />
    </SafeAreaView>
  );
};

export default App;

Pages/Intro.tsx

import { Pressable, StyleSheet, Text, View } from 'react-native';
import React, { useState } from 'react';
import { login, logout, getProfile as getKakaoProfile, shippingAddresses as getKakaoShippingAddresses, unlink } from '@react-native-seoul/kakao-login';
import ResultView from './IntroView';

const Intro = () => {
  const [result, setResult] = useState<string>('');

  const signInWithKakao = async (): Promise<void> => {
    try {
      const token = await login();
      setResult(JSON.stringify(token));
    } catch (err) {
      console.error('login err', err);
    }
  };

  const signOutWithKakao = async (): Promise<void> => {
    try {
      const message = await logout();

      setResult(message);
    } catch (err) {
      console.error('signOut error', err);
    }
  };

  const getProfile = async (): Promise<void> => {
    try {
      const profile = await getKakaoProfile();

      setResult(JSON.stringify(profile));
    } catch (err) {
      console.error('signOut error', err);
    }
  };

  const getShippingAddresses = async (): Promise<void> => {
    try {
      const shippingAddresses = await getKakaoShippingAddresses();

      setResult(JSON.stringify(shippingAddresses));
    } catch (err) {
      console.error('signOut error', err);
    }
  };

  const unlinkKakao = async (): Promise<void> => {
    try {
      const message = await unlink();

      setResult(message);
    } catch (err) {
      console.error('signOut error', err);
    }
  };

  return (
    <View style={styles.container}>
      <ResultView result={result} />
      <Pressable
        style={styles.button}
        onPress={() => {
          signInWithKakao();
        }}
      >
        <Text style={styles.text}>
          카카오 로그인
        </Text>
      </Pressable>
      <Pressable
        style={styles.button}
        onPress={() => getProfile()}
      >
        <Text style={styles.text}>
          프로필 조회
        </Text>
      </Pressable>
      <Pressable
        style={styles.button}
        onPress={() => getShippingAddresses()}
      >
        <Text style={styles.text}>
          배송주소록 조회
        </Text>
      </Pressable>
      <Pressable
        style={styles.button}
        onPress={() => unlinkKakao()}
      >
        <Text style={styles.text}>
          링크 해제
        </Text>
      </Pressable>
      <Pressable
        style={styles.button}
        onPress={() => signOutWithKakao()}
      >
        <Text style={styles.text}>
          카카오 로그아웃
        </Text>
      </Pressable>
    </View>
  );
};

export default Intro;

const styles = StyleSheet.create({
  container: {
    height: "100%",
    justifyContent: "flex-end",
    alignItems: 'center',
    paddingBottom: 100
  },
  button: {
    backgroundColor: '#FEE500',
    borderRadius: 40,
    borderWidth: 1,
    width: 250,
    height: 40,
    paddingHorizontal: 20,
    paddingVertical: 10,
    marginTop: 10
  },
  text: {
    textAlign: "center"
  }
});

IntroView.tsx

import { ScrollView, StyleSheet, Text, View } from 'react-native';

import React from 'react';

type Props = {
  result: string;
};

function IntroView({ result }: Props): React.ReactElement {
  return (
    <View style={styles.container}>
      <ScrollView>
        <Text>{result}</Text>
        <View style={{ height: 100 }} />
      </ScrollView>
    </View>
  );
}

export default IntroView;

const styles = StyleSheet.create({
  container: {
    flexDirection: "column",
    width: "100%",
    padding: 24,
  }
});

여기까지 다 하면 ~~~

iOS 카카오 로그인 성공 ㅠㅠㅠ

이거 때문에 프로젝트 몇 개를 생성했는지 모르겠다 .... 

천천히 하나하나 해보니까 됨 ..... 예 !!!

시도하는 모든 사람들 성공하시길 바랍니당

크롤링한 데이터(정책) > 엑셀 > 데이터베이스 > 스프링 > 프론트에 띄우기 ==> 일단 이렇게 생각하고 작업 중이다.

데이터 양이 많아서 스프링 애플리케이션 내에서 메모리에 모두 유지하는 것보다는

데이터베이스에 저장하여 관리하는 것이 좋을 것 같아서 저러한 과정을 거치기로 한 것.

 

크롤링한 데이터를 엑셀 파일로 저장하는 과정은 생각보다 어렵지 않았다

1. 먼저 필요한 라이브러리 설치

  • pip install openpyxl 

2. 엑셀파일 만들고 저장

# 엑셀 만들기
import openpyxl

wb = openpyxl.Workbook()

ws = wb.create_sheet('주거정책')

ws['A1'] = 'number'
ws['B1'] = 'name'

ws['A2'] = 1
ws['B2'] = '홍길동'

wb.save(r'C:\Users\tmdgm\Desktop\pyex\주거정책_data.xlsx')

엑셀파일 만들기 > openpyxl.Workbook()

엑셀 워크시트 만들기 > wb.create_sheet('엑셀시트이름')

행, 열에 들어갈 데이터 추가한 후 wb.save('저장할 경로') 로 저장하면 해당 폴더에 엑셀 시트가 만들어지는 것을 볼 수 있다.

 

이 코드를 데이터 크롤링하는 코드와 합쳐보면

# 가져온 데이터 엑셀파일로 저장
import requests
from bs4 import BeautifulSoup
import openpyxl

fpath = r'C:\Users\tmdgm\Desktop\pyex\주거정책_data.xlsx'

wb = openpyxl.load_workbook(fpath)
ws = wb.active # 현재 활성화된 시트 선택 - 기본시트 선택

row = 2
for i in range(1, 5):
  response = requests.get(f'https://youth.incheon.go.kr/youthpolicy/youthPolicyInfoList.do?menudiv=dwelling&pgno={i}')
  html = response.text
  soup = BeautifulSoup(html, 'html.parser')
  titles = soup.select(".boardList .con-box .tit") # 정책 title
  links = soup.select(".boardList .btn-box .btn:first-child") # 정책 url
  
  # for link in links:
  #   url = link.attrs['href']
  #   print(f'https://youth.incheon.go.kr{url}')
    
  for title in titles:
    print(title.text.strip())
    ws[f'B{row}'] = title.text
    row += 1
    
wb.save(fpath)

 

1. 엑셀 파일을 불러와서

openpyxl.load_workbook(엑셀파일저장되어있는경로) 

2. 현재 활성화된 시트를 선택하고

wb.active

3. 크롤링한 데이터들을 행에 알맞게 저장한다.

ws.[f'B{rows}'] = title.text

B행에 쭉 저장되겠죠 ?

4. 저장하면 끝.

wb.save(fpath)

 

전 포스팅에서 공공데이터 API를 활용하여 정책들을 받아왔었다.

그거 하는 것도 꽤나 애먹었는데 내가 필요한 정보들이 아니었다.

그래서 여러 사이트를 검색해보던 중 청년 정책들을 잘 소개해주는 사이트를 발견했다.

카테고리 주거분야로 들어가면 주거 관련 정책들만 쫙 모아주니 여기서 데이터를 받아오면 좋겠다 생각했다.

그래서 이번에는 웹 크롤링을 시도해봤다. 이것저것 정말 많이 해보는 ...  (vsCode 사용했습니다.)

 

내가 할 것은
1. 위의 사이트에서 정책 제목들을 가져오는 것
2. 해당 정책의 상세보기에 접근할 수 있는 '사업 안내' 버튼 URL을 가져오는 것

1. 필요한 라이브러리 설치

크롤링에 기본이 되는 라이브러리들이다.

  • pip install requests
  • pip install beautifulsoup

파이썬 버전 문제때문에 시간을 또 잡아먹었는데

https://youtu.be/eJ7kqK18afY?si=cD5xM1nliGlPtFhW

이거보고 해결함 ㅠㅠ 

2. 크롤링하고 싶은 URL 가져오기

# 크롤링 기본
import requests
from bs4 import BeautifulSoup

# 크롤링하고 싶은 url get해오기
response = requests.get("https://youth.incheon.go.kr/youthpolicy/youthPolicyInfoList.do?")
html = response.text # html 전체 코드 들어있음.
soup = BeautifulSoup(html, 'html.parser') # html 번역기
# soup.select > 여러개 / soup.select_one > 한 개 선택
titles = soup.select(".tit")

for title in titles:
  print(title.text.strip())

 

설치한 라이브러리들을 import 해준다.

requests.get("URL")로 내가 크롤링하고 싶은 주소를 넣는다.

성공적으로 가져와졌다면 response 출력시 <Response [200]> 이 뜰 것이다.

이후 html번역기인 html.parser를 활용

soup.select() < css 선택자를 활용하여 가져오고 싶은 태그 선택

  • select > 한 개 선택
  • select_one > 여러 개 선택

.tit가 여러 개라면 리스트 형태로 titles에 들어온다.

반복문으로 title를 출력

성공 나이쓰 !

3. 링크 가져오기 

import requests
from bs4 import BeautifulSoup

response = requests.get("https://youth.incheon.go.kr/youthpolicy/youthPolicyInfoList.do?menudiv=dwelling")
html = response.text
soup = BeautifulSoup(html, 'html.parser')
titles = soup.select(".boardList .con-box .tit") # 정책 title
links = soup.select(".boardList .btn-box .btn:first-child") # 해당 정책 URL 접근

for link in links:
  url = link.attrs['href']
  print(f'https://youth.incheon.go.kr{url}')

위와 비슷한 코드이다.

다른 은 a태그 href 속성을 활용하여 url을 가져온 것

link에는 a 태그가 들어가 있다. 

link.attrs['href'] : 해당 링크의 url을 가져올 수 있다.

위의 '~ 인천시 청년월세 지원사업' 들의 해당 정책 상세보기 링크에 접근할 수 있는 것!

4. 여러 페이지 가져오기

정책들이 여러 페이지로 이루어져 있을 것이다.

페이지를 이동할 때마다 변경되는 url을 보고 활용하면 된다.

1페이지
2페이지

페이지가 이동될 때 pgno= 1, 2로 변경되는 것을 볼 수 있다.

# 여러 페이지 가져오기
import requests
from bs4 import BeautifulSoup

pageNum = 1 
for i in range(1, 10):
  print(f'{pageNum}페이지입니다.')
  response = requests.get(f'https://youth.incheon.go.kr/youthpolicy/youthPolicyInfoList.do?menudiv=dwelling&pgno={i}')
  html = response.text
  soup = BeautifulSoup(html, 'html.parser')
  titles = soup.select(".boardList .con-box .tit") # 정책 title
    
  for title in titles:
    print(title.text.strip())
    
  pageNum += 1

 

 

 

반복문으로 pgno={i} < 1~10페이지까지의 모든 정책 제목을 가져올 수 있었다.

간단하게 사이트 크롤링해보기 성공

앞으로의 여정은 멀고도 험하지만요 시작이 반이라고~

 

 

방학에 열심히 했어야했는데 발등에 불떨어진 ... 이제 졸업작품 열심히 준비해야겠다.

일단 내가 해야되는 부분은 공공데이터포털에서 청년 주거 정책을 가져오는 것이다.

 

https://www.data.go.kr/

 

공공데이터 포털

국가에서 보유하고 있는 다양한 데이터를『공공데이터의 제공 및 이용 활성화에 관한 법률(제11956호)』에 따라 개방하여 국민들이 보다 쉽고 용이하게 공유•활용할 수 있도록 공공데이터(Datase

www.data.go.kr

여기 들어가서 끌어오고 싶은 공공데이터 신청하기

활용신청에 웹서비스개발 , 활용목적 간단하게 쓰면 바로 승인해준다.

새벽에 신청했는데 바로 승인됨.


승인되면 개발계정 상세보기에 있는 값들 활용하면 된다.

여기서 겁나 헤맸는데 ...  생각보다 간단하게 해결 ㅠ

1. 일반 인증키(디코딩) 복사
2. 인증키 설정 누르고 복사한 인증키를 붙여넣는다.


그리고 아래 제공해주는 스웨거에서

API 실행준비 > OpenAPI 호출하면 Response가 돌아온다.
저기서 Request URL 활용하면 된다.

위의 Request URL 활용해서 아래 코드 apiUrl 작성

apiUrl에 요청 변수를 분할해서 엔드포인트와 인증키, 반환 형식 등을 지정해주면 끝 !


package com.example.CheerupYouth_Back.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;

@RestController
@Slf4j
public class ApiController {
    @GetMapping("/api")
    public String callApi() throws IOException {
        StringBuilder result = new StringBuilder();

        String apiUrl = "https://api.odcloud.kr/api/15038446/v1/uddi:4502e4a9-4db1-4169-bc21-feb0abb017a6?" +
                "pageNo=1" +
                "&serviceKey=인증키" + 
                "&perPage=100" +
                "&returnType=json"; // 반환 형식 json

        URL url = new URL(apiUrl);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("GET");

        int responseCode = connection.getResponseCode();

        if (responseCode == HttpURLConnection.HTTP_OK) {
            BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
            String line;
            while ((line = reader.readLine()) != null) {
                result.append(line);
            }
            reader.close();
        } else {
            result.append("API 호출 실패, 응답코드: ").append(responseCode);
        }

        log.info(result.toString());
        connection.disconnect();

        return result.toString();
    }
}

간단하게 API 받아오는 것까지는 성공.. .!


localhost:8080/api 로 접근하면 응답 데이터 들어와있는 것 확인!

 

 

성공은 했는데 .. 내가 필요한 정보들은 이게 아니라서 조금 당황했다. 

API가 아니가 주거정책 사이트를 크롤링 해와야하나 ....

+ Recent posts