-
SKN Family AI 15기 6월 1주차 회고기록../SKN Family AI 15기 2025. 6. 8. 19:30
KLT
Keep
- 웹 크롤링은 잘 도전해보지 않은 영역이었는데, 이번주 동안 다른 페이지를 접근하는 방식으로 두 개 이상의 페이지를 크롤링 하는 방법을 익힐 수 있었다.
Lacked
- 실습 위주의 강의를 듣다보니 이론 위주의 수업을 필기할 때에 비해 필기 내용이 다소 부정확해지는 면이 있는 것 같다. 이 코드들은 다 나중에도 사용할 테니까 코드에 주석을 좀 더 꼼꼼하게 다는 연습도 필요할 것 같다.
- 페이지를 넘기는 부분을 구현하는 것이 너무 어렵다... Selenium은 좀 더 쉬운 API인데도...! 그리고 DB와 연결할 때 자꾸 오류가 발생하는데 이 이 유를 꼭 한 번 확인해봐야겠다.
Try
- 도움 없이 지역 코드를 활용하여 전국 데이터를 가져오는 방법을 익혔다. 웹 크롤링에 점점 익숙해지고 개발자 도구를 통해 웹의 상태를 확인하는 방법을 잘 익히게 된 것 같아 뿌듯하다.
- 다만 위 과정을 하드코딩으로 진행하고 있는 중인지라... 좀 더 테크닉을 익혀서 웹 크롤링을 쉽게 할 수 있으면 좋겠다. 속성자와 클래스 네임을 꼭 확인하고, 이를 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.customerNumberGROUP 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 resultspayload= {"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)'기록.. > SKN Family AI 15기' 카테고리의 다른 글
SKN Family AI 15기 6월 3주차 회고 (0) 2025.06.24 SKN Family AI 15기 6월 2주차 단위 프로젝트 회고 (0) 2025.06.15 SKN Family AI 15기 5월 5주차 회고 (0) 2025.05.27 SKN Family AI 15기 5월 4주차 회고(2) (0) 2025.05.23 SKN Family AI 15기 5월 4주차 회고(1) (0) 2025.05.20