안녕하세요. 보랏입니다.
오늘 새로운 한 주를 시작하면서 또 많은 내용을 배웠습니다.
정규식 표현과 이를 활용한 편의점 크롤링, Pandas데이터분석을 진행하였습니다.
복습 바로 시작하겠습니다.
1. 정규식 표현
- 특정한 규칙을 가진 문자열 집합을 표현하는데 사용하는 언어(특정 규칙을 가진 문자열 검색, 추출, 제거)
- import re(기존 파이썬 라이브러리에 설치)
- 파이썬 raw string : 문자열 앞에 r이 붙으면 해당 문자열이 구성딘 그대로 문자열로 변환
- 기본 표현 방법
. | 모든 문자 포함 |
^ | 맨 처음 시작인 단어 잡기 |
$ | 맨 마지막 단어 잡기 (앞 뒤로 띄어쓰기 되어있으면 검색 불가) |
[ab] | 집합(a,b만 인정) # 김[도동]현 : 김도현, 김동현 찾기 |
(a|b) | or의 개념 # 김(도|동)현 : 김도현 or 김동현 찾기 |
\d | 모든 숫자(0~9) |
\D | 모든 문자 |
\w | 모든 문자(영어만, 한글 x) |
\s | 모든 띄어쓰기 찾기 |
\S | 띄어쓰기가 아닌 것들만 찾기 |
[a-zA-Z] | 모든 영어 표시 |
[가-힣] | 모든 한글 표시 |
- 반복패턴 : 해당 패턴이 반복적으로 존재하는지 검사
+ | 1번 이상의 패턴 발생 # a[a-z]+b : a로 시작하고 b로 끝나는 반복패턴 문자 찾기 |
* | 0번 이상의 패턴 발생 |
? | 0 or 1번의 패턴 발생 # 패턴이 존재하거나 존재하지 않는 것도 확인, 김[가-힣]?현 : 김도현, 김동현, 김현(가운데 글자가 없어도 (0,1)라서 검색 가능 |
{0,1} | 반복횟수 범위 지정 # 0~1 |
2. CU 홈페이지 크롤링(정규식 활용)
- 지난주까지 CU홈페이지 복구 작업이 한창이어서 해당 페이지를 크롤링을 못했었는데 이제 복구가 되어 오늘 수업은 CU홈페이지를 활용하여 전국에 위치한 CU편의점에 대한 정보를 크롤링하였습니다.
- 우선 Payload를 활용해 데이터 소스를 가져왔습니다.
cu_url = "https://cu.bgfretail.com/store/list_Ajax.do"
payload = {"pageIndex" : "1",
"listType" : "",
"jumpoCode" : "",
"jumpoLotto" : "",
"jumpoToto" : "",
"jumpoCash" : "",
"jumpoHour" : "",
"jumpoCafe" : "",
"jumpoDelivery" : "",
"jumpoBakery" : "",
"jumpoFry" : "",
"jumpoMultiDevice" : "",
"jumpoPosCash" : "",
"jumpoBattery" : "",
"jumpoAdderss" : "",
"jumpoSido" : "경기도",
"jumpoGugun" : "가평군",
"jumpodong" : "가평읍",
"user_id" : "",
"jumpoName" : "",}
r = requests.post(cu_url,data=payload)
r.text
pd.read_html(r.text) # list형태로 변환
pd.read_html(r.text)[0] #테이블 형태로 변환
- 이제 CU의 서비스를 확인하여 위의 테이블에 삽입하려고 합니다.
- 우선 크롤링 데이터에서 서비스를 추출해보도록 하겠습니다.
bs = BeautifulSoup(r.text)
tmp = bs.find("div", class_="detail_store").findAll("tr")[1]
p = re.compile("sevice[0-9]{2} on") #sevice 00~99 on으로된 객체를 컴파일
p2 = re.compile("_([0-9a-zA-Z]+)\.png") #_(0~9, a~z, A~Z)\.png로 구성된 문자열이 1번이라도 반복되는 문자 패턴 찾기
tmp.findAll("li", p) # "li" detail_store클래스에서 'tr'로된 클래스 객체에서 p정규표현식의 문자열 모두 찾기
p2.findall(str(tmp.findAll("li", p)[3]))[0].split(".")[0] # p2정규표현식 활용 서비스 문자열 찾기 및 추출
service = [p2.findall(str(x))[0] for x in tmp.findAll("li", p)]
service
- 이제 가게별 서비스를 반환하도록 하겠습니다.
for y in bs.find("div", class_="detail_store").findAll("tr")[1:]:
print([p2.findall(str(x))[0] for x in y.findAll("li", p)])
service_total=[]
for y in bs.find("div", class_="detail_store").findAll("tr")[1:]:
service_total.append([p2.findall(str(x))[0] for x in y.findAll("li", p)])
store['서비스'] = service_total
phone = re.compile("[0-9]{2,3}-[0-9]{3,4}-[0-9]{3,4}")
store['연락처'] = store['매장명 / 연락처'].apply(lambda x : phone.findall(x)[0] if len(phone.findall(x)) > 0 else None)
store_p = re.compile("[가-힣]+점")
store['매장명'] = store['매장명 / 연락처'].apply(lambda x : store_p.findall(x)[0])
store
- 이렇게 만든 데이터를 컬럼명과 컬럼순서를 정리하였고, 향후 크롤링 데이터 text입력시 데이터프레임으로 변환할 수 있도록 함수로 만들었습니다.
store.rename(columns = {"주소 / 매장 유형 및 서비스" : "주소"}, inplace=True) #컬럼명 변경
store = store[['매장명', '주소', '연락처', '서비스']] #컬럼 순서 변경
#함수 전환
def get_df(text):
store = pd.read_html(r.text)[0]
bs = BeautifulSoup(text)
phone = re.compile("[0-9]{2,3}-[0-9]{3,4}-[0-9]{3,4}")
p = re.compile("sevice[0-9]{2} on")
p2 = re.compile("_([0-9a-zA-Z]+)\.png")
service = [p2.findall(str(x))[0] for x in tmp.findAll("li", p)]
service_t = []
for y in bs.find("div", class_="detail_store").findAll("tr")[1:]:
service_t.append([p2.findall(str(x))[0] for x in y.findAll("li", p)])
store['서비스'] = service_t
store['연락처'] = store['매장명 / 연락처'].apply(lambda x : phone.findall(x)[0] if len(phone.findall(x)) > 0 else None)
store['매장명'] = store['매장명 / 연락처'].apply(lambda x : store_p.findall(x)[0])
store.drop('매장명 / 연락처', axis=1, inplace=True)
store.rename(columns={"주소 / 매장 유형 및 서비스" : "주소"}, inplace=True)
return store[['매장명','연락처', '주소', '서비스', ]]
3. 5개 편의점 데이터 분석
- cu, gs25, 세븐일레븐, 이마트24, 미니스탑 5개 편의점에 전체 데이터를 합친 데이터를 활용하여 데이터 분석을 진행하였습니다.
- 하지만 각 편의점마다 데이터 내용이 모두 상이하여 우선적으로 전처리부터 진행하였습니다. 주소(address)를 확인하니 주소에 대한 내용이 모두 달라 하나로 통일해주는 작업을 진행하였습니다.
df = pd.read_pickle("./5store.pkl") # 5개 편의점 데이터 불러오기
df.isnull().sum() # 결측치 확인
df['brand'].value_counts() # 컬럼 기준 갯수 세기
df['address'].apply(lambda x : x.split()[0]).unique() # 주소별 유니크값 도출
df['address'] = df['address'].apply(lambda x : x.strip()) #우선적으로 양쪽 공백제거
df.loc[df['address'].str.find("경기") == 0, '시'] = '경기도'
df.loc[df['address'].str.find("서울") == 0, '시'] = "서울특별시"
df.loc[df['address'].str.find("대전") == 0, '시'] = "대전광역시"
df.loc[df['address'].str.find("강원") == 0, '시'] = "강원도"
df.loc[df['address'].str.find("대구") == 0, '시'] = "대구광역시"
df.loc[(df['address'].str.find("충북") == 0) | (df['address'].str.find("충청북도") == 0) , '시'] = "충청북도"
df.loc[(df['address'].str.find("경북") == 0) | (df['address'].str.find("경상북도") == 0) , '시'] = "경상북도"
df.loc[(df['address'].str.find("경남") == 0) | (df['address'].str.find("경상남도") == 0) , '시'] = "경상남도" # or
df.loc[df['address'].str.find("인천") == 0, '시'] = "인천광역시"
df.loc[df['address'].str.find("부산") == 0, '시'] = "부산광역시"
df.loc[df['address'].str.find("광주") == 0, '시'] = "광주광역시"
df.loc[df['address'].str.find("울산") == 0, '시'] = "울산광역시"
df.loc[df['address'].str.find("세종") == 0, '시'] = "세종특별자치시"
df.loc[df['address'].str.find("제주") == 0, '시'] = "제주특별자치도"
df.loc[(df['address'].str.find("충남") == 0) | (df['address'].str.find("충청남도") == 0) , '시'] = "충청남도"
df.loc[(df['address'].str.find("전남") == 0) | (df['address'].str.find("전라남도") == 0) , '시'] = "전라남도"
df.loc[(df['address'].str.find("전북") == 0) | (df['address'].str.find("전라북도") == 0) , '시'] = "전라북도"
df['시'].value_counts()
- 이렇게 진행했는데도 아직 시도로 전처리가 되지않은 컬럼들이 있어 이 부분은 하드코딩으로 직접 바꾸었습니다.
df.loc[24549,'시'] = "서울특별시"
df.loc[30712, '시'] = '경기도'
df.loc[33001, '시'] = '부산광역시'
df.loc[33723, '시'] = '충청남도'
df.loc[33985, '시'] = '충청남도'
- 이렇게 데이터 전처리가 완료가 되서 각 시도별 편의점 비율과 인구통계별 비율을 분석하였습니다.
df['시'].value_counts(normalize=True) #비율
#인구통계별 비율
stat_url = "https://jumin.mois.go.kr/ageStatMonth.do"
pay = {"tableChart": "T",
"sltOrgType": "1",
"sltOrgLvl1": "A",
"sltOrgLvl2": "A",
"sltUndefType": "",
"nowYear": "2023",
"searchYearMonth": "year",
"searchYearStart": "2022",
"searchMonthStart": "12",
"searchYearEnd": "2022",
"searchMonthEnd": "12",
"sum": "sum",
"gender": "gender",
"sltOrderType": "1",
"sltOrderValue": "ASC",
"sltArgTypes": "10",
"sltArgTypeA": "0",
"sltArgTypeB": "100",}
r = requests.post(stat_url, data=pay)
korea = pd.read_html(r.text)[2]
korea.columns = [x[0] for x in korea.columns] #컬럼 출력
인구 = korea.iloc[1:, [0,1]] #컬럼 0,1 출력(행정기관, 인구 수)
편의점 = pd.DataFrame(df['시'].value_counts())
인구.set_index("행정기관", inplace=True)
인구대비 = pd.merge(인구, 편의점, left_index=True, right_index=True, how='left') #조인방법 : right index, left index, how = 'left'
인구대비.columns = ['인구수', '편의점수']
인구대비['비율'] = 인구대비['인구수'] / 인구대비['편의점수']
인구대비.sort_values(by=['비율'], ascending = False)
- 마지막으로 서울 내 자치구별 편의점 개수, 가장 많은 브랜드를 분석하였습니다.
# 자치구별 편의점 개수
seoul = df.query("시 == '서울특별시'").copy()
seoul['구'] = seoul['address'].apply(lambda x : x.split()[1])
seoul.groupby(['구'])['구'].count().sort_values(ascending=False)
#자치구별 가장 많은 편의점 브랜드
seoul_2 = seoul.groupby(['구','brand'], as_index=False)[['shopName']].count()
seoul_2.sort_values(by=['shopName'], ascending=False).groupby(['구']).first()
#자치구별 가장 수가 적은 편의점 브랜드
# nth(0) : 1위, nth(1) : 2위, nth(-1) : 꼴등
seoul_2.sort_values(by=['shopName'], ascending=False).groupby(['구']).nth(-1)
이렇게 오늘은 정규식 표현과 이를 활용한 Pandas데이터분석을 진행하였습니다.
오늘도 복습할 내용이 많아 더 공부하러 가보겠습니다.
감사합니다.
'DB 공부하기' 카테고리의 다른 글
230306_DB복습 (0) | 2023.03.06 |
---|---|
230228_DB복습 (0) | 2023.02.28 |
230224_DB 복습 (0) | 2023.02.24 |
230222_DB복습 (0) | 2023.02.22 |
230221_DB복습 (0) | 2023.02.21 |