ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • SKN Family AI 15기 6월 1주차 회고
    기록../SKN Family AI 15기 2025. 6. 8. 19:30

    KLT


    Keep

    1. 웹 크롤링은 잘 도전해보지 않은 영역이었는데, 이번주 동안 다른 페이지를 접근하는 방식으로 두 개 이상의 페이지를 크롤링 하는 방법을 익힐 수 있었다.

    Lacked

    1. 실습 위주의 강의를 듣다보니 이론 위주의 수업을 필기할 때에 비해 필기 내용이 다소 부정확해지는 면이 있는 것 같다. 이 코드들은 다 나중에도 사용할 테니까 코드에 주석을 좀 더 꼼꼼하게 다는 연습도 필요할 것 같다.
    2. 페이지를 넘기는 부분을 구현하는 것이 너무 어렵다... Selenium은 좀 더 쉬운 API인데도...! 그리고 DB와 연결할 때 자꾸 오류가 발생하는데 이 이 유를 꼭 한 번 확인해봐야겠다.

    Try

    1. 도움 없이 지역 코드를 활용하여 전국 데이터를 가져오는 방법을 익혔다. 웹 크롤링에 점점 익숙해지고 개발자 도구를 통해 웹의 상태를 확인하는 방법을 잘 익히게 된 것 같아 뿌듯하다.
    2. 다만 위 과정을 하드코딩으로 진행하고 있는 중인지라... 좀 더 테크닉을 익혀서 웹 크롤링을 쉽게 할 수 있으면 좋겠다. 속성자와 클래스 네임을 꼭 확인하고, 이를 BeautifulSoup를 통해 파싱하는 법을 좀 더 잘 익힐 수 있게 힘내자!

    확률과 통계


    가설검정

    • 통계적 추정은 일상 생활에서 접하는 엄청나게 많은 데이터 세트 중에서 겨우 몇 개의 데이터를 관측하는 일에서 출발함
    • 관측한 데이터(표본 집단)를 통해 모든 데이터(모집단)를 추리하는 것을 통계적 추정이라고 함
    • 정규 분포를 하고 있는 모집단의 모수에서 그 모수가 어떤 수인지를 파악하는 것을 가설검정이라고 함.
    • 통계 추정이 타당하지 않으면 가설을 기각하고 성립하면 가설을 채택함.

    귀무가설

    • 통계에서 가설 검정은 측정된 두 현상 간에 관련이 없다는 귀무 가설
    • 관련이 없다는 형태의 가설임

    대립가설

    • 두 현상에 관련이 있다고 보는 것을 대립가설이라고 함
    • 같지 않다, 작다, 크다 세 가지 형태로 나타낼 수 있다.

    일반화

    • AI 모델은 주어진 데이터에 너무 딱 맞아서도, 혹은 주어진 데이터에 대해 아무런 설명도 할 수 없어서도 안 된다.
    • AI 모델을 $y=ax+b$로 표현한다면, a는 가중치, b는 편향이라고 부르며, 이런 직선형의 모델을 회귀(Regression)라고 부른다.
    • 가설을 응용하여 모델을 올바르게 일반화 할 수 있다.

     

     SQL


    FOREIGN KEY

    • 외래키. 다른 테이블의 키를 참조하는 키이다.
    • 테이블 생성 단계에서 다음과 같이 설정할 수 있다.
    CREATE TABLE customers (
      customerNumber int,
      customerName varchar(50) NOT NULL,
      phone varchar(50) NOT NULL,
      ......
      salesRepEmployeeNumber int DEFAULT NULL,
      creditLimit decimal(10,2) DEFAULT NULL,
      PRIMARY KEY (customerNumber),
      **FOREIGN KEY (salesRepEmployeeNumber) REFERENCES employees (employeeNumber)**);
    

    JOIN

    • 관계형 데이터 베이스에서 가장 핵심적인 기능 중 하나
    • 두 개 이상의 테이블에 나뉘어 저장된 데이터를 공통 데이터나 외래 키를 통하여 합하는 것
    • INNER JOIN(내부 조인)
      • 조인 조건을 만족하는 행 끼리만 조인함.
    SELECT
        table1.column1, table1.column2, table2.column_name, ...
    FROM
        table1
    INNER JOIN  -- 또는 그냥 JOIN 이라고만 써도 INNER JOIN으로 동작합니다.
        table2 ON table1.common_column = table2.common_column;
    
    --- 실전 응용은 다음과 같습니다.
    SELECT employees.employeeNumber, employees.lastName, employees.firstName, offices.phone 
    FROM employees 
    INNER JOIN offices ON employees.officeCode = offices.officeCode;
    
    --- 별명을 설정해 줄 수도 있습니다.
    SELECT a.employeeNumber, a.lastName, a.firstName, b.phone
    FROM employees a 
    INNER JOIN offices b ON a.officeCode = b.officeCode;
    # employees를 a, offices를 b로 설정해서 코드를 간편하게 고칠 수 있습니다.
    
    • LEFT JOIN(혹은 왼쪽 외부 조인)
      • LEFT JOIN은 왼쪽 테이블(먼저 명시된 테이블, FROM 절 바로 뒤)의 모든 로우를 결과에 포함시키고, 오른쪽 테이블에서 조인 조건을 만족하는 로우를 결합함
      • 일치하는 row가 없다면, null 값으로 채운다.
    SELECT
        table1.column1, table1.column2, table2.column_name, ...
    FROM
        table1  -- 왼쪽 테이블
    LEFT JOIN   -- 또는 LEFT OUTER JOIN
        table2 ON table1.common_column = table2.common_column; -- 오른쪽 테이블
    
    • RIGHT JOIN(혹은 오른쪽 외부 조인)
      • LEFT JOIN과 반대로, 오른쪽 테이블(두 번째로 명시된 테이블, JOIN 키워드 바로 뒤)의 모든 로우를 결과에 포함시키고, 왼쪽 테이블에서 조인 조건을 만족하는 로우를 결합함
    --- 일별 판매량 확인
    SELECT orders.orderDate, orderdetails.priceEach * orderdetails.quantityOrdered AS totals
    FROM orders
    LEFT JOIN orderdetails ON orders.orderNumber = orderdetails.orderNumber;
    
    • 한번에 세 개 이상의 테이블을 조인하는 것또한 가능합니다. 외래키는 반드시 확인해주세요.
    SELECT *
    FROM orders a
    LEFT JOIN orderdetails b
    ON a.orderNumber = b.orderNumber
    LEFT JOIN customers c
    ON c.customerName = a.customerNumber
    

    GROUP BY

    • 위 코드를 실행하면, 한 날짜에 다양한 상품이 팔렸을 경우 값 데이터가 상품 별로 따로따로 나오게 됩니다. 일괄적으로 한 날짜에 팔린 총액을 확인하려면 어떻게 해야할까요?
    • GROUP BY 절은 테이블의 로우(row)들을 특정 컬럼(들)의 값이 동일한 것끼리 그룹으로 묶어줌
    SELECT  a.orderDate, **sum(b.quantityOrdered * b.priceEach**) AS total
    --- 위와 똑같이 작성하면 오류가 납니다. 꼭 sum을 붙여주세요!
    FROM orders a 
    LEFT JOIN orderdetails b 
    ON a.ordernumber = b.orderNumber **GROUP BY a.orderDate**
    
    • 만약 조건을 걸고 싶다면, HAVING 절을 사용합니다.
    GROUP BY a.orderDate HAVING a.orderDate <= '2023-01-31';
    

    유용한 함수

    • SUBSTRING(n,m): 문자열을 n부터 m개까지 추출합니다. 이때, list의 인덱스와는 다르게 1부터 시작한다는 점에 유의해야 합니다.
    • COUNT : 행의 개수를 세어 출력합니다.
    • DISTINCT(’column name’): 해당하는 열에서 중복값을 제거하고 select 하게 됩니다,
    • ORDER BY DESC/ASC : 각각 내림차순 / 오름차순으로 정렬할 수 있습니다. 키워드가 딱히 없다면 기본 오름차순으로 정렬됩니다.
    • RANK(): 순서대로 순위를 매겨서 정렬한 후에 사용할 수 있는 함수로, 랭킹을 매긴 열을 생성합니다.
    • MAX(): 가장 큰 데이터를 가져옵니다. 이때, GROUP BY와 함께 사용하면 각 그룹별로 가장 큰 값을 가져옵니다.(날짜 데이터의 경우, 가장 최신 날짜가 됩니다.)
    • DATEDIFF(): 날짜 간의 간격을 계산하여 반환합니다. DATEDIFF(날짜, 대상 데이터)로 사용할 수 있습니다.
    • CASE WHEN ~then ~ else~ end: 일종의 조건문처럼 사용할 수 있습니다. 조건에 따라 분류하는 열을 삽입합니다.

    뷰(VIEW)

    • 뷰란 가상의 테이블을 의미하며, 논리적으로만 존재하고 물리적으로는 존재하지 않음.
    • CREATE VIEW 뷰 이름 코드를 통해 만들 수 있음.
    CREATE VIEW master_code AS 
    SELECT CONCAT('A',isu_srt_cd) st_code, isu_abbrv, list_dd, mkt_tp_nm, ist_shrs FROM sk17.st_master;
    
    

     

    Python - 크롤링


    Selenium을 통해 웹 크롤링 하기

    Selenium

    • 브라우저를 자동화할 수 있는 도구
    • Chrome을 웹 브라우저로 사용하고 있기 때문에, Chrome 드라이버를 다운받아 사용한다.

    Selenium을 통해 웹 사이트에 접속하기

    #윈도우 환경에서 설치
    pip install selenium
    pip install webdriver_manager
    
    from selenium import webdriver
    from selenium.webdriver.chrome.service import Service as ChromeService
    from webdriver_manager.chrome import ChromeDriverManager
    
    driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()))
    
    driver.get("<https://www.naver.com>")
    #해당 코드를 이용하면 네이버 사이트로 이동한다.
    
    

    Selenium을 통해 검색창으로 검색하기

    from selenium.webdriver.common.by import By
    
    click = '#query' #해당 선택자는 속성에서 "선택자 복사"를 클릭하면 얻어올 수 있다.
    
    driver.find_element(By.CSS_SELECTOR, click).click()
    #해당하는 선택자 요소를 찾아서 클릭
    driver.find_element(By.CSS_SELECTOR, click).send_keys("손흥민")
    #해당 키워드를 통해 검색하라는 뜻
    
    from selenium.webdriver.common.keys import Keys
    driver.find_element(By.CSS_SELECTOR, click).send_keys(Keys.ENTER)
    #Enter키를 입력하여 검색 완료!
    

    페이지 내에서 야구 선수의 정보를 가져오는 코드를 응용하여, 전체 페이지의 야구 선수 정보를 가져와봅시다.

    응용할 코드

    total = list()
    for data in bs.find('table', class_='tEx').find_all('tr')[1:]:
    #해당 페이지의 테이블을 순회하며 모든 열의 정보를 가져옵니다.
        play_list = []
        for idx, x in enumerate(data.find_all("td")):
            if idx == 1:
            #선수의 이름 정보의 경우, 선수의 아이디를 함께 가져오기 위해 이러한 코드를 적용했습니다.
            #선수의 아이디는 a href에서 playerid="XXXXXX" 양식으로 적혀 있습니다,
                play_list.extend([x.text,x.find('a')['href'].split("=")[-1]])
                # print(x.text, x.find('a')['href'].split("=")[-1])
            else:
                play_list.append(x.text)
    
        total.append(play_list)
    

    페이지를 오가면서 해당 페이지에서 코드를 반복하는 것으로 데이터를 파싱해올 수 있습니다.

    from selenium import webdriver
    from selenium.webdriver.chrome.service import Service as ChromeService
    from webdriver_manager.chrome import ChromeDriverManager
    from selenium.webdriver.common.keys import Keys
    from selenium.webdriver.common.by import By
    from bs4 import BeautifulSoup\\
    
    import time
    team_selected = "#cphContents_cphContents_cphContents_ddlTeam > option:nth-child({})"
    page_selected = "#cphContents_cphContents_cphContents_ucPager_btnNo{}"
    total_result = []
    for numb in range(2, 12):
        driver.find_element(By.CSS_SELECTOR, team_selected.format(numb)).click()
        time.sleep(3)
        last_number = max([int(x.text) for x in BeautifulSoup(driver.page_source).find("div", class_='paging').find_all("a") if x.text != ''])
        # print(f"{numb} --> last number : {last_number}")
        total_result.extend(get_page_info(BeautifulSoup(driver.page_source)))
        for page in range(2,last_number+1):
            driver.find_element(By.CSS_SELECTOR, page_selected.format(page)).click()
            time.sleep(3)
            total_result.extend(get_page_info(BeautifulSoup(driver.page_source)))
    

     

     

    크롤링 실습


    전국 편의점 데이터 가져오기

    def find_si_gugun(si_code) :
        results = []
        si_url =f"<https://gs25.gsretail.com/gscvs/ko/gsapi/gis/searchGungu?&stb1={si_code}&_=1749088239953>"
        si_r = requests.get(si_url)
        si_data = si_r.json()['result']
        for s in si_data:
            results.append(s[0])
        return results
    
    payload= {"pageNum" : "1",
    "pageSize" : "50000",
    "searchShopName" : "",
    "searchSido" : "11",
    "searchGugun" : "1168",
    "searchDong" : "",
    "searchType" : "",
    "searchTypeService" : "0",
    "searchTypeToto" : "0",
    "searchTypeCafe25" : "0",
    "searchTypeInstant" : "0",
    "searchTypeDrug" : "0",
    "searchTypeSelf25" : "0",
    "searchTypePost" : "0",
    "searchTypeATM" : "0",
    "searchTypeWithdrawal" : "0",
    "searchTypeTaxrefund" : "0",
    "searchTypeSmartAtm" : "0",
    "searchTypeSelfCookingUtensils" : "0",
    "searchTypeDeliveryService" : "0",
    "searchTypeParcelService" : "0",
    "searchTypePotatoes" : "0",
    "searchTypeCardiacDefi" : "0",
    "searchTypeFishShapedBun" : "0",
    "searchTypeWine25" : "0",
    "searchTypeGoPizza" : "0",
    "searchTypeSpiritWine" : "0",
    "searchTypeFreshGanghw" : "0",
    "searchTypeMusinsa" : "0",
    "searchTypePosa" : "0",}
    
    import json
    import pandas as pd
    
    si_code = [11,26,27,29,28,30,31,36,41,51,44, 43,48,47,46,52,50]
    gs25_result = []
    url = "<https://gs25.gsretail.com/gscvs/ko/store-services/locationList?**CSRFToken={}**>"
    
    with **requests.Session**() as s:
        r = s.get(gs25_url)
        bs = BeautifulSoup(r.text)
        csrf = bs.find("form", id='CSRFForm').find("input")['value']
        for si in si_code:
            gu_list = find_si_gugun(si)
            for gu in gu_list:
                payload["searchSido"] = si
                payload["searchGugun"] = gu
                **r2 = s.post(url.format(csrf), data=payload, headers=header)**
                data= json.loads(r2.json())['results']
                for value in data:
                    gs25_result.append(value)
                    
    
    pd.DataFrame(gs25_result)
    
Designed by Tistory.