작업 디렉토리로 이동 합니다.

 

git bash를 실행 합니다.

디렉토리 공간에서 오른쪽 마우스 클릭 Git Bash Here 선택

git bash 창이 뜨는지 확인

git bash here가 안보인다면 git 설치 부터 해야 한다.

git clone 대상을 복사해서 git bash 에서 clone 해 온다.

git clone https://git

hub.com/사용자id/레퍼지토리id

clone 해 온 디렉토리가 생성 되었는지 확인 한다.

ls -lrt

디렉토리로 이동 한다.

main 이라는 브런치로 clone 해 온걸 확인 할 수 있다.

git hub 에서 master 라는 브런치를 생성 한다.

이 master가 최상위 브런치가 된다.

없다면 master 브런치를 생성 한다.

git hub 들어가서  

branches 탭을 클릭 하면 branch 관리 탭으로 이동 하게 되고 new branch 누르게 되면 신규 브런치를 생성 하게 된다.

다시 git bash 에서 git switch master 를 입력 하면 브런치를 변경 하게 된다.

git pull 명령어를 이용하여 원격 저장소(git hub)의 데이터를 동기화 한다.

test 로 test.txt 파일을 만들어서 commit , push 를 진행 해 본다.

그전에 feature 브런치를 만들어서 휘발성 브런치를 이용해 본다.

git branch feature/feature_20230215

로컬에 feature/feature_20230215 브런치가 생성 되었다.

다시 feature/feature_20230215 로 브런치를 변경해 본다.

test.txt 파일을 생성 해 본다.

git bash 가 리눅스 기반이기에 vi 툴을 사용 할 수 있다.

"a" 를 클릭 하면 에디터를 할 수 있게 되고

"test git hub" 를 입력 하고 저장 해 본다.

저장은 esc 키를 누르고 :wq 를ㄹ 누르면 저장 된다.

파일 생성 된 것을 확인 한다.

git status로 git 의 상태를 확인 한다.

먼저 add 명령어로 변경된 파일을 로컬 레퍼지토리에 추가를 해야 하는데

git add [파일명] 

이렇게 하나씩 하는 방법이 있고

git add .

이렇게 변경된 파일 전부를 추가 하는 방법이 있다.

경고가 나왔다. 이 경고 내용은 줄바꿈 언어를 명확히 해달라고 하는건데

window는 

git config --global core.autocrlf true

 

mac, linux 는 

git config --global core.autocrlf input

 

설정 한다.

다시 git add . 명령어를 수행 하면 추가 된다.

git status 로 상태 확인 한다.

정상 으로 추가 된 것이 확인 된다.

커밋을 해본다.

git commit -m "테스트 커밋"

push 해 본다.

git push origin feature/feature_20230215

git hun 에 가보자

feature/feature_20230215 로 test.txt 파일이 신규 생성 된 것이 보인다.

Compare & pull request 버튼을 클릭 해서 master 와 병합 한다.

신규 pull requests 탭에 테스트 커밋 내용의 pr 이 생성 된 것이 확인 된다.

테스트 커밋을 누르면

하단에 "Merge pull request" 가 보인다.

누르면 Confirm merge 버튼이 생성이 되고 이걸 누르면

feature 브런치를 삭제 할지 선택 한다.

머지도 완료 되었고 삭제도 완료 되었다.

code 탭에서 반영 된 것을 확인 할 수 있다.

자 이제 vscode 에서 사용 하는 방법을 보자.

소스코딩을 위 디렉토리에서 했다고 하자.

이것 저것 소스들이 추가가 되었다.

vscode 로 open folder 로 작업 폴더를 열고 왼쪽 메뉴중 Source Control 메뉴에 가면 변경된 소스 대상들이 보이게 된다.

"..." 버튼 클릭 "Branch" -> "Create Branch" 를 클릭 한다.

브런치 이름을 입력 한다.

커밋 메시지 입력 후 commit & push 를 클릭 한다.

원격 저장소에 해당 브런치가 없다고 한다. "OK, Don't Ask Again" 클릭 한다.

알아서 생성 하고 사용 하게 된다.

pull request 를 생성 할거냐고 묻는다.

"Create Pull Request" 버튼클릭 한다.

왼쪽에 PR 내용이 출력이 된다.

create 누른다.

git hub 내용이 출력 된다.

"Merge Pull Request" 버튼 클릭 한다.

"Create Merge Commit" 클릭 한다.

delete branch 클릭 한다.

로컬과 원격 브런치 삭제 할지 확인 한다.

git hub 들어가서 머지 확인 하면 정상 적으로 된 것을 확인 할 수 있다.

Posted by 블로그하는프로그래머
,

현제 내가 작성하고 있는 포스팅 에서는 도커 유틸중 Portainer를 통해서 이미지를 다운받고 컨테이너를 돌려 보려고 한다.

컨테이너 생성 주기는 아래와 같이 이루어 진다.

1. 이미지 다운로드

2. 볼륨생성

3. 컨테이너 생성

 

1. 이미지 다운로드 방법

- 첫번째 왼쪽 메뉴중 "Images"를 클릭 한다.

- 두번째 이미지 테그를 넣어 준다.

- 이미지 테그를 모를 경우 세번째 도커 허브를 확인 해서 넣어 줘야 합니다.

저는 아래와 같이 테그들로 이미지 대상들을 따운로드 했습니다.

jenkins:2.60.3

mariadb:latest -> 마지막 버전은 latest 로 테그가 생성 됩니다.

node:lts-alpine3.17

openjdk:20-ea-17-jdk

 

2. 볼륨생성

볼륨을 생성 하는 이유는 컨테이너의 저장공간? 과 로컬 서버의 저장 공간의 데이터를 연결 하는 공간으로 이해 하면 될 것 같다.

컨테이너의 경우 바로 삭제가 가능 하기에 컨테이너 안의 데이터가 휘발성으로 삭제가 된다면 복구 하기 어려운 부분이 있다, 이런경우 볼륨을 생성 해서 연결을 하고 로컬에서 관리가 되는 데이터가 있다면 삭제가 안된 데이터를 바로 연결이 가능 한 편의성을 제공 한다.

왼쪽 메뉴중 "Volumes" 메뉴를 클릭 한다.

오른쪽 버튼 중 "Add volume" 클릭 하여 볼륨을 생성 합니다.

필수 값을 입력 하고 "Create the volume" 버튼을 클릭 한다.

생성 된 내용을 확인 한다.

3. 컨테이너 생성

자 이제 직접적으로 서비스가 실행이 되는 컨테이너를 생성 한다.

왼쪽 메뉴 중 "Containers" 클릭 후 "Add container" 버튼을 클릭 하여 컨테이너를 생성 한다.

 

1. 컨테이너 이름

2. 이미지 테그

3. "pulish a new network port" 버튼을 클릭 한다.

로컬 포트와 도커 포트를 연결 하는 기능으로 이해 한다.

4, 5. 포트 번호를 입력 한다. (mariadb 의 경우 기본 포트가 3306 이기에 둘다 3306을 입력 한다.)

 

- "Volumes" 탭을 클릭 한다.

volume 에 이전에 생성한 "mariadbVal - local" 대상을 선택 한다.

 

- "Restart policy" 탭을 클릭 하고 "Restart policy" - "Always" 를 선택 한다.

도커가 재시작 될때 컨테이너도 시작이 되도록 설정 하는 기능 이다.

- "Deploy the container" 버튼 클릭 하여 컨테이너를 생성 한다.

"Container list" 에서 "Quick Actions" 기능은 차례데로 아래와 같은 기능을 지원 한다.

  • Log: 컨테이너 로그
  • Inspect: 상세정보
  • Stats: 자원 사용량(메모리, CPU, 네트워크)
  • Exec Console: 해당 컨테이너 shell 연결

1편 Ubuntu 20.04 에 도커 설치 하기

Posted by 블로그하는프로그래머
,

파이썬에서 셀레니움을 사용하는 방법 2번째 

전 시간에는 파이썬에서 셀레니움의 라이브러리를 불러와 드라이브를 호출 하여 네이버를 연결하는 작동법 까지 포스팅을 해 봤습니다.

이전 포스팅은 이 포스팅 제일 아래 링크를 연결해 두겠습니다.

이번 포스팅 에서는 검색어를 입력 후 조회 된 페이지를 크롤링 하여 필요한 데이터를 수집하여 데이터화 하는 방법을 구현해 보도록 하겠습니다.

대신 한정적으로 "VIEW" 탭의 첫 페이지만 크롤링 해 보도록 하겠습니다.

수집된 데이터는 엑셀 파일로 데이터 화가 되게 할 것이고, 검색할 단어와 수집할 단어는

검색어 : "나이키"

수집할 단어 : "트레비스스캇"

조회를 하게 코딩 해 봅니다.

1. 네이버 메일 화면에서 검색 어 입력하는 input 박스의 name 또는 id 를 확인 합니다.

1-1. 네이버 웹사이트 화면에서 F12 를 눌러 개발자 툴을 엽니다.

개발자 툴에서 왼쪽 편에 보면 아래오 같은 아이콘이 생성이 되고 화면에서 검색어 창을 선택 할 수 있게 된다.

마우스 클릭을 하게 되면 검생어 입력 input 의 정보를 개발자 툴에서 보여 준다.

 

id는 query 

name 은 query 

둘다 같습니다.

이제 셀레니움에서 이 input에 "나이키" 를 입력 하게 합니다.

import selenium
from selenium import webdriver
from selenium.webdriver import ActionChains

from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By

from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import Select
from selenium.webdriver.support.ui import WebDriverWait

#접속 url
#검색어
SEARCHWORD = "나이키"
#찾을 단어
MACHINGWORD = "트레비스스캇"

driver = webdriver.Firefox(executable_path='C:\dev\python\geckodriver.exe')
#driver = webdriver.Chrome(executable_path='C:\dev\python\chromedriver.exe')

#네이버로 이동
driver.get(url=URL)

#사이트이름 매칭 확인
assert "NAVER" in driver.title

#input id = query, name = query -> "나이키 입력"
#searchName = driver.find_element_by_id("query")
searchName = driver.find_element_by_xpath("//*[@id='query']")
searchName.send_keys(SEARCHWORD)
searchName.submit()

 

위에 프로그램을 실행 하게 되면 아래 처럼 나이키를 검색 하게 됩니다.

 

이제 VIEW탭으로 이동을 해서 조회된 첫 페이지 대상들을 변수에 담아 보겠습니다.

뷰 탭으로 이동 할때는 xpath 를이용한 이동을 해보도록 하겠습니다.

어려운거 없이 개발자 도구에서 view 탭의 xpath를 복사해서 셀레니움에 복사한 xpath 를 넣어 주기만 하면 이동이 됩니다.

 

초록생경로가 xpath 경로 입니다.

#view 탭으로 이동 한다.
driver.implicitly_wait(100)
viewChage = driver.find_element_by_xpath("/html/body/div[3]/div[1]/div/div[2]/div[1]/div/ul/li[2]/a")
print(viewChage.text)
viewChage.click()

자 이제 view 탭의 첫 페이지 리스트 대상을 변수에 입력해 보겠습니다.

view 탭은 sc_new sp_nreview _au_view_tab _svp_list _prs_rvw 이란 큰 클래스에 각 포스팅들이 나열이 되어 있습니다.

그 아래로 list 클래스로 묶인 포스팅 리스트가 있습니다.

그 아래로 _more_contents_event_base 클랙스

ul 테그 lst_total _list_base 클래스

제일 중요한 li 테그의 bx _svp_item 클래스 의 data-cr-rank 속성들이 1부터 증가를 하는 것으로 확인이 됩니다.

1페이지당 30포스팅 까지 발생을 하고 추가적으로 발생되는건 + 30건씩 조회가 되는 것을 확인 했습니다.

그러면 반복문을 1부터 30까지 발생시켜 해당 url을 추출 하고 이동하여 테스트를 추출 합니다.

텍스트를 추출 하는 건 beautifulsoup 를 이용하여 진행 합니다.

웹상의 택스트를 정형화 하여 추출 하는것을 도와 주는 모듈 입니다.

pip install beautifulsoup4

url 접속을 도와주는 requests 모듈도 설치 한다.

pip install requests

 

전체 소스 코드는 아래와 같습니다.

카페, 블로그, 포스트 로 이루어진 VIEW 탭은 각기 별로 분기처리하는 로직이 필요하여 소스가 지저분해져 있습니다.

정형화 하고 함수로 정의 하는 부분은 나중에 정리해서 따로 포스팅 하도록 하겠습니다.

from cgitb import text
import selenium
from selenium import webdriver
from selenium.webdriver import ActionChains

from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By

from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import Select
from selenium.webdriver.support.ui import WebDriverWait

from time import sleep
from bs4 import BeautifulSoup
from urllib.parse import quote
import requests
import time


#접속 url
URL_TMP = ''
#검색어
SEARCHWORD = "나이키"
#찾을 단어
MACHINGWORD = "버블검"
# 조회 한 페이지의 피드 리스트 저장할 리스트 선언
POSTINGLIST = list()

# 저장할 파일
f = open('C:/dev/test.txt', 'w', encoding='utf-8')

driver = webdriver.Firefox(executable_path='C:\dev\python\geckodriver.exe')
#driver = webdriver.Chrome(executable_path='C:\dev\python\chromedriver.exe')

driver.set_window_position(0, 0)
driver.set_window_size(100, 1000)

#네이버로 이동
driver.get(url=URL)

#사이트이름 매칭 확인
assert "NAVER" in driver.title

#input id = query, name = query -> "나이키 입력"
#searchName = driver.find_element_by_id("query")
searchName = driver.find_element_by_xpath("//*[@id='query']")
searchName.send_keys(SEARCHWORD)
searchName.submit()

#view 탭으로 이동 한다.
driver.implicitly_wait(100)
viewChage = driver.find_element_by_xpath("/html/body/div[3]/div[1]/div/div[2]/div[1]/div/ul/li[2]/a")
print(viewChage.text)
viewChage.click()

#반복
# 첫페이지에서 조회된 대상을 변수에 저장한다.
# 첫 페이지 대상은 1부터 30까지의 대상이다.
# 20210117 기준 xpath 는 /html/body/div[3]/div[2]/div/div[1]/section/div/div[2]/panel-list/div/more-contents/div/ul/li[1]/div[1]/div/a
# 여기서 li의 [n]번 값이 증가 한다.
#for n in range(1, 31):
for n in range(1, 31):
    pth_tmp = "/html/body/div[3]/div[2]/div/div[1]/section/div/div[2]/panel-list/div/more-contents/div/ul/li[{0}]/div[1]/div/a".format(n)
    #print(pth_tmp)
    POSTINGLIST.append(pth_tmp)

print("-"*50)

for i in POSTINGLIST:
    driver.switch_to.window(driver.window_handles[0])
    postSearch = driver.find_element_by_xpath(i)    # 1~30까지 포스팅 페이지를 담는다.
    print(postSearch.get_attribute("href"))
    URL_TMP = postSearch.get_attribute("href")
    postSearch.click()                              # 이동한다.
    driver.implicitly_wait(100)                     # 로딩될때까지 대기
   
    webpage = requests.get(postSearch.get_attribute("href"))
    soup = BeautifulSoup(webpage.content, "html.parser")

    print("-"*50)

    #url이 블로그일 경우
    if "blog.naver" in postSearch.get_attribute("href"):
        print("블로그")
        print("https://blog.naver.com" + soup.iframe["src"])

        webpage = requests.get("https://blog.naver.com" + soup.iframe["src"])
        print("encoding = " + webpage.encoding)
        webpage.encoding = 'UTF-8'
        soup = BeautifulSoup(webpage.content, "html.parser")

        if soup.find("div", attrs={"class":"se-main-container"}):
            text = soup.find("div", attrs={"class":"se-main-container"}).get_text()
            text = text.replace("\n","") #공백 제거
            print("블로그")
            print(text)
        elif soup.find("p", attrs={"class":"se_textarea"}):
            text = soup.find("p", attrs={"class":"se_textarea"}).get_text()
            text = text.replace("\n","") #공백 제거
            print("블로그")
            print(text)
        elif soup.find("div", attrs={"class":"ArticleContentBox"}):
            text = soup.find("div", attrs={"class":"ArticleContentBox"}).get_text()
            text = text.replace("\n","") #공백 제거
            print("블로그")
            print(text)
        elif soup.find("div", attrs={"id":"postViewArea"}):
            text = soup.find("div", attrs={"id":"postViewArea"}).get_text()
            text = text.replace("\n","") #공백 제거
            print("블로그")
            print(text)
        else:
            print("블로그 확인불가")
            text = soup
            f.write('블로그\n')
            f.write("https://blog.naver.com" + soup.iframe["src"] + '\n')
            f.write(str(text))
            f.write('\n')

    #url이 post일 경우
    elif "post.naver" in postSearch.get_attribute("href"):
        print("포스트")
        #print(soup)
        print(postSearch.get_attribute("href"))
       
        webpage = requests.get(postSearch.get_attribute("href"))
        print("encoding = " + webpage.encoding)
        soup = BeautifulSoup(webpage.content, "html.parser", from_encoding='ANSI')
       
        f.write("포스트11111111111111111111111\n")
        f.write(postSearch.get_attribute("href") + "\n")
        f.write(str(webpage.encoding))
        f.write('\n')
        f.write("포스트22222222222222222222222\n")

        if soup.find("div", attrs={"class":"se-main-container"}):
            text = soup.find("div", attrs={"class":"se-main-container"}).get_text()
            text = text.replace("\n","") #공백 제거
            print("포스트1")
            print(text)
        elif soup.find("p", attrs={"class":"se_textarea"}):
            text = soup.find("p", attrs={"class":"se_textarea"}).get_text()
            text = text.replace("\n","") #공백 제거
            print("포스트2")
            print(text)
        elif soup.find("div", attrs={"class":"ArticleContentBox"}):
            text = soup.find("div", attrs={"class":"ArticleContentBox"}).get_text()
            text = text.replace("\n","") #공백 제거
            print("포스트3")
            print(text)
        elif soup.find("div", attrs={"id":"postViewArea"}):
            text = soup.find("div", attrs={"id":"postViewArea"}).get_text()
            text = text.replace("\n","") #공백 제거
            print("포스트4")
            print(text)
        elif soup.find("div", attrs={"class":"se_component_wrap"}):
            text = soup.find("div", attrs={"class":"se_component_wrap"}).get_text()
            text = text.replace("\n","") #공백 제거
            print("포스트5")
            print(text)
       
        elif soup.find("div", attrs={"id":"cont"}):
            text = soup.find("div", attrs={"id":"cont"}).get_text()
            text = text.replace("\n","") #공백 제거
            print("포스트6")
            print(text)
        else:
            print("포스트 전체 조회")
            text = soup
            print(text)
            f.write("포스트 확인불가\n")
            f.write(postSearch.get_attribute("href") + "\n")
            f.write(str(text))
            f.write('\n')

    #url이 cafe일 경우
    elif "cafe.naver" in postSearch.get_attribute("href"):
        print("카페")
        #print(soup)

        #탭이동
        driver.switch_to.window(driver.window_handles[1])

        print("tap1 이동 완료")
        #print(driver.page_source)
        #f.write("11111111111111111111111111111카페\n")
        #f.write(driver.page_source + "\n")
        #f.write('\n')

        print("cafe_main 로딩시작")
        #cafe_main 로딩 완료 대기
        #driver.implicitly_wait(10)
        time.sleep(10)
        print("cafe_main 로딩완료")

        driver.switch_to.frame("cafe_main")          # cafe_main iframe으로 이동
        print("cafe_main frame 이동")
        #print(driver.page_source)
        #f.write("22222222222222222222222222222카페\n")
        #f.write(driver.page_source + "\n")
        #f.write('\n')
       
        driver.implicitly_wait(1)          
        req_tmp = driver.page_source
        print("페이지 확인")
        #print(driver.page_source)
        #f.write("33333333333333333333333333카페\n")
        #f.write(driver.page_source + "\n")
        #f.write('\n')

        soup = BeautifulSoup(req_tmp, "html.parser", from_encoding='ANSI')
        #print(soup)
       
        #webpage = requests.get(postSearch.get_attribute("href"))
        #print("encoding = " + webpage.encoding)
       
        #soup = BeautifulSoup(webpage.content, "html.parser", from_encoding='ANSI')

        if soup.find("div", attrs={"class":"se-main-container"}):
            text = soup.find("div", attrs={"class":"se-main-container"}).get_text()
            text = text.replace("\n","") #공백 제거
            print("카페1")
            print(text)
        elif soup.find("p", attrs={"class":"se_textarea"}):
            text = soup.find("p", attrs={"class":"se_textarea"}).get_text()
            text = text.replace("\n","") #공백 제거
            print("카페2")
            print(text)
        elif soup.find("div", attrs={"class":"ArticleContentBox"}):
            text = soup.find("div", attrs={"class":"ArticleContentBox"}).get_text()
            text = text.replace("\n","") #공백 제거
            print("카페3")
            print(text)
        elif soup.find("div", attrs={"id":"postViewArea"}):
            text = soup.find("div", attrs={"id":"postViewArea"}).get_text()
            text = text.replace("\n","") #공백 제거
            print("카페4")
            print(text)
        elif soup.find("div", attrs={"class":"se-component-content"}):
            text = soup.find("div", attrs={"class":"se-component-content"}).get_text()
            text = text.replace("\n","") #공백 제거
            print("카페5")
            print(text)
        else:
            print("카페 확인불가")
            text = soup
            f.write("카페\n")
            f.write(postSearch.get_attribute("href") + "\n")
            f.write(str(text))
            f.write('\n')
   
    if MACHINGWORD in text:
        print("Found word " + MACHINGWORD)
        #print(postSearch.get_attribute("href"))
        driver.implicitly_wait(100)                     # 로딩될때까지 대기
        driver.switch_to.window(driver.window_handles[1])
        driver.close()
       
    else:
        print("Not Found word " + MACHINGWORD)
        #print(postSearch.get_attribute("href"))
        driver.implicitly_wait(100)                     # 로딩될때까지 대기
        driver.switch_to.window(driver.window_handles[1])
        driver.close()
       

    print("-"*50)

driver.quit()
f.close()

# 리스트의 url n번째 로 이동 하여 "트레비스스캇" 단어를 찾는다.
# 단어가 일치하는 대상이 있다면 변수에 url 을 담는다.
# 단어가 일치하면 count 를 한다.
# 다시 view 탭으로 이동한다.

파이썬 셀레니움 사용법 1

 

Posted by 블로그하는프로그래머
,