주유소 데이터를 저장할 테이블을 다음의 구조로 생성하세요
import mysql.connector
remote = mysql.connector.connect(
host = {} ,
port = 3306,
user = "admin",
password = {},
database = "oneday"
)
sql = "CREATE TABLE GAS_BRAND(id INT, name VARCHAR(16), PRIMARY KEY (id))"
remote.cursor().execute(sql)
sql = '''
CREATE TABLE GAS_STATION (
id INT auto_increment,
brand INT,
name VARCHAR(64),
city char(2),
gu VARCHAR(10),
address VARCHAR(128),
gasoline INT,
diesel INT,
self BOOLEAN,
car_wash BOOLEAN,
charging_station BOOLEAN,
car_maintenance BOOLEAN,
convenience_store BOOLEAN,
24_hours BOOLEAN,
lat DECIMAL(16,14),
lng DECIMAL(17,14),
PRIMARY KEY (id),
CONSTRAINT PK_GAS FOREIGN KEY (brand) REFERENCES GAS_BRAND (id))
'''
remote.cursor().execute(sql)
📌 BOOLEAN타입이란
- Ture or False / 1,0 으로 반환
- 내부적으로는 Boolean 타입 자체가 없기때문에 tinyint(1) 으로 생성됨
- 1,0이외의 값도 유효성 체크없이 데이터가 삽입이 되므로 주의해야함.
- 참고링크 : https://yscho03.tistory.com/305
문제 2. Python 코드로 GAS_BRAND 데이터를 다음과 같이 입력하고 확인하세요.
data = [(1, "SK에너지"),(2, "현대오일뱅크"),(3, "GS칼텍스"),(4, "S-OIL"),(5, "알뜰주유소"),(6, "자가상표")]
sql = "INSERT INTO GAS_BRAND VALUES (%s,%s)"
for i in data :
remote.cursor(buffered=True).execute(sql, i)
print(i)
remote.commit()
문제 3. 다음의 함수와 그 함수를 테스트하는 코드를 작성하세요.
def money(str) :
result = int(str.replace(",",""))
return result
replace 메서드를 사용하여 ,를 제거하고
int로 변환하는 원초적인 함수를 만들었다 🥲
def brand_to_id(str) :
sql = f"SELECT id FROM GAS_BRAND WHERE name LIKE '%{str}%'"
cursor = remote.cursor()
cursor.execute(sql)
result = cursor.fetchone()
if result :
return result [0]
else :
return None
📌 fetchall() / fetchone()
둘 다 select 쿼리의 결과를 가져오는 메서드이다.
다만 차이점이 있다면,
- fetchall() : 모든행을 가져와서 리스트로 반환
일치하는 데이터가 없다면 빈 리스트 반환- fetchone() : 첫번째 행만 가져와서 튜플로 반환
일치하는 데이터가 없다면 None 반환
if 구문에서 result 값을 넣었는데,
fetchone()으로 인해 None값이 반환되면 False,
데이터가 있다면, True가 되므로
튜플값이 return 되는 것을 막았다.
def ads(str):
raw = str.split()
result = raw[1]
return result
🤨 다른 형태의 주소가 들어오면 안될것같은데..
라는 고민은 들었지만, 일단은 split으로 자른 후 구에 해당하는 값만 return 시킴
def lan_lng(str):
import googlemaps
gmaps_key = "{개인키값}"
gmaps = googlemaps.Client(key = gmaps_key)
lat = gmaps.geocode(str, language = "ko")[0].get("geometry")["location"]["lat"]
lng = gmaps.geocode(str, language = "ko")[0].get("geometry")["location"]["lng"]
return lat, lng
문제 4. Python 코드에서 주유소 페이지에서 데이터를 가져올때, GAS_STATION 테이블에 바로 입력하도록 수정하세요. (앞서 생
성한 함수활용)
📌 페이지 접근 코드는 생략하고 입력코드만 기재함
from bs4 import BeautifulSoup
import time
# 구 이름 넣기
for gu in tqdm_notebook(gu_list) :
gu_raw = driver.find_element(By.CSS_SELECTOR,"#SIGUNGU_NM0")
gu_raw.send_keys(gu)
html = driver.page_source
soup = BeautifulSoup(html,"html.parser")
cnt = int(soup.find(id = "totCnt").text)
for n in range(1,cnt+1) :
driver.find_element(By.CSS_SELECTOR,f'#body1 > tr:nth-child({n}) > td.rlist > a').click()
html = driver.page_source
soup = BeautifulSoup(html,"html.parser")
data = []
# brand
bd = soup.find(id = "poll_div_nm").text
brand = brand_to_id(bd)
data.append(brand)
# name
name = soup.find(id = "os_nm").text
data.append(name)
# city
city = "서울"
data.append(city)
# address / gu
address = soup.find(id = "rd_addr").text
gu = ads(address)
data.append(gu)
data.append(address)
#gasoline
gs = soup.find(id = "b027_p").text
gasoline = money(gs)
data.append(gasoline)
#diesel
ds = soup.find(id = "d047_p").text
diesel = money(ds)
data.append(diesel)
#self
if soup.find('img', {'alt': '셀프주유소', 'class': 'bul'}):
self = "1"
else:
self = "0"
data.append(self)
# car_wash
if "off" in soup.find(id = "cwsh_yn").get("src") :
wash = "0"
else:
wash = "1"
data.append(wash)
#charging_station
if "off" in soup.find(id = "lpg_yn").get("src") :
charging = "0"
else:
charging = "1"
data.append(charging)
#car_maintenance
if "off" in soup.find(id = "maint_yn").get("src"):
center = "0"
else:
center = "1"
data.append(center)
# convenience_store
if "off" in soup.find(id = "cvs_yn").get("src") :
store = "0"
else:
store = "1"
data.append(store)
# 24_hours
if "off" in soup.find(id = "sel24_yn").get("src") :
night = "0"
else:
night = "1"
data.append(night)
# lat / lat
try :
lat = lan_lng(address)[0]
lng = lan_lng(address)[1]
except :
lat = ''
lng = ''
data.append(lat)
data.append(lng)
sql = "INSERT INTO GAS_STATION (brand,name,city,gu,address,gasoline,diesel,self,car_wash,charging_station,car_maintenance,convenience_store,24_hours,lat,lng) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) "
cursor = remote.cursor(buffered=True)
cursor.execute(sql,tuple(data))
print(tuple(data))
remote.commit()
- 위도경도 에러는 try~except 구문으로 처리
GAS_STATION의 primary key인 id는 auto라 코드가 잘못되어 다시 for문을 돌릴 때마다 drop하고 다시 create 하였다는 슬픈 스토리..ㅎ
문제 5. 시각화 프로젝트를 위하여 다음의 규칙으로 쿼리하여 CSV 파일로 저장합니다. (Python 코드로 작성)
sql = '''
SELECT gs.id, gb.name, gs.name, city, gu, address, gasoline, diesel, self, car_wash, charging_station, car_maintenance, convenience_store, 24_hours, lat, lng
FROM GAS_STATION gs LEFT OUTER JOIN GAS_BRAND gb
ON gs.brand = gb.id
ORDER BY gs.id
'''
cursor = remote.cursor(buffered=True)
cursor.execute(sql)
result = cursor.fetchall()
import pandas as pd
df = pd.DataFrame(result)
df.columns = ["id", "brand", "name", "city", "gu", "address", "gasoline", "diesel", "self", "car_wash", "charging_station", "car_maintenance", "convenience_store", "24_hours", "lat", "lng"]
df.to_csv('./Oil Price Analysis.csv',index = False, encoding = "euc-kr")
GAS_STATION 기존 컬럼에서 brand 번호만 GAS_STORE의 name으로 변경하면 되서 left outer join을 사용했다.
문제 6. 위도, 경도 정보를 이용하여 미왕빌딩에서 1킬로 이내에 위치한 주유소 정보를 검색하세요.
sql = '''
SELECT id, brand, name, address, ROUND((6371*acos(cos(radians(37.495535))*cos(radians(lat))*cos(radians(lng) -radians(127.029357))+sin(radians(37.495535))*sin(radians(lat)))), 2) AS distance
FROM GAS_STATION HAVING distance < 1
ORDER BY distance;
'''
cursor.execute(sql)
result = cursor.fetchall()
for i in result:
print(i)
📌 답 기록
- https://yusang.tistory.com/48
풀이식은 모르겠고;
링크보고 그대로 따라 적었다.
- 미왕빌딩 좌표값
target_lat = 37.495535
target_lng = 127.029357
📌 추가시도기록
- https://mizykk.tistory.com/21
해당 내용 참고하여
haversine 라이브러리 install 한뒤 아래처럼 풀었으나,
거리까지 출력되게하려면 임시로 테이블을 하나 만들어서 INSERT한뒤 SELECT 해야할것아서
다른방법을 찾게되었다.... ㅜ.ㅜfrom haversine import haversine target = (37.495535, 127.029357) target = (37.495535, 127.029357) sql = 'SELECT id, lat, lng FROM GAS_STATION' cursor = remote.cursor(buffered=True) cursor.execute(sql) result = cursor.fetchall() for i in result : temp = tuple(float(x) for x in i) temp_id = int(temp[0]) temp_lat_lng = temp[1:] if haversine(target, temp_lat_lng, unit='km') <= 1 : sql = f'SELECT GAS_STATION.id, GAS_BRAND.name, GAS_STATION.name, GAS_STATION.address FROM GAS_STATION LEFT OUTER JOIN GAS_BRAND ON GAS_STATION.brand = GAS_BRAND.id WHERE GAS_STATION.id = {temp_id} ' cursor = remote.cursor(buffered=True) cursor.execute(sql) result = cursor.fetchall() for i in result : print(i)
추가로 위에서 temp = tuple(float(x) for x in i)는 위도 경도가 Decimal('37.46715650000000'), Decimal('127.11870810000000') 처럼 타입까지 출력되서 GPT에 물어봤다.
문제 7. 위도, 경도 정보를 이용하여 미왕빌딩에서 셀프주유가 가능하고 24시간이면서 편의점이 있는 가장 가까운 주유소 10개를 휘발유 가격이 가장 저렴한 순으로 정렬하여 조회하세요.
sql = '''
SELECT gs.id, gb.name, gs.name, address, gasoline, self, 24_hours, convenience_store, ROUND((6371*acos(cos(radians(37.495535))*cos(radians(lat))*cos(radians(lng) -radians(127.029357))+sin(radians(37.495535))*sin(radians(lat)))), 2) AS distance
FROM GAS_STATION gs LEFT OUTER JOIN GAS_BRAND gb
ON gs.brand = gb.id
ORDER BY distance, gasoline
LIMIT 10
'''
cursor.execute(sql)
result = cursor.fetchall()
for i in result:
print(i)
6번풀면서 뇌정지가 와서
가까운 주유소 10개를 뽑고 휘발유 저렴한 순이면
LIMIT에 조건을 걸어야하나..? 🥲
라는 바보같은 생각으로 10여분을 보내고 ㅋㅋㅋ
order by에 거리를 먼저 걸어주면 된다 ㅎㅅㅎ!
문제 8. 구별로 주유소 브랜드 별 휘발유 평균가격을 조회하여 저렴한 순으로 출력하세요.
sql = '''
SELECT gs.gu, gb.name, avg(gasoline)
FROM GAS_STATION gs LEFT OUTER JOIN GAS_BRAND gb
ON gs.brand = gb.id
GROUP BY gs.gu, gb.name
ORDER BY avg(gasoline)
'''
cursor.execute(sql)
result = cursor.fetchall()
for i in result:
gu, name, avg_gasoline = i
avg_gasoline = round(avg_gasoline,2)
print(gu, name, avg_gasoline)