1. 시카고 매거진
1.1 시카고 매거진
https://www.chicagomag.com/Chicago-Magazine/November-2012/Best-Sandwiches-Chicago/
- 미국 시카고 매거진의 베스트 50개의 샌드위치 맛집 리스트
- 메뉴와 가게 이름이 정리
1.2 가게 상세 페이지
- 각각소개한 50개의 페이지에 들어가면 가게 주소와 대표메뉴의 가격
- 즉, 총 51개의 페이지에서, 가게이름, 대표메뉴, 대표메뉴의 가격, 가게주소를 수집
2. 기본 페이지
2.1 기본 페이지 파싱
1
2
3
4
5
6
7
8
9
10
11
12
from bs4 import BeautifulSoup
from urllib.request import urlopen
import pandas as pd
url_base = 'https://www.chicagomag.com'
url_sub = '/Chicago-Magazine/November-2012/Best-Sandwiches-Chicago/'
url = url_base + url_sub
html = urlopen(url)
soup = BeautifulSoup(html, 'html.parser')
soup
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!DOCTYPE doctype html>
<html lang="en">
<head>
<!-- Urbis magnitudo. Fabulas magnitudo. -->
<meta charset="utf-8"/>
<style>a.edit_from_site {display: none !important;}</style>
<title>
The 50 Best Sandwiches in Chicago |
Chicago magazine
| November 2012
</title>
...
</script>
<!--[if lt IE 9]>
<script type="text/javascript" language="JavaScript" src="/core/media/themes/Respond/js/respond.js?ver=1473876729"></script>
<![endif]-->
<script language="JavaScript" src="/core/media/js/base.js?ver=1473876728" type="text/javascript"></script>
<script language="JavaScript" src="/core/media/themes/Respond/js/bootstrap.min.js?ver=1473876729" type="text/javascript"></script>
<script language="JavaScript" src="//maps.googleapis.com/maps/api/js?v=3.exp&sensor=false" type="text/javascript"></script>
<script language="JavaScript" src="/theme_overrides/Respond/js/interstitial.js?ver=1524154906" type="text/javascript"></script>
<script language="JavaScript" src="/theme_overrides/Respond/js/newsletter-subscribe.js?ver=1524850607" type="text/javascript"></script>
<script language="JavaScript" src="/theme_overrides/Respond/js/RivistaGoogleDFP.js?ver=1447178886" type="text/javascript"></script>
<!-- godengo-monitor --></body>
</html>
- 기본 시카고 매거진의 url과 2012년 11월 시카고 매거진의 50개 샌드위치 url을 합쳐서 완성된 url을 만듬
- 이후 Beautifulsoup을 사용하여 html로 파싱함
2.2 50개 가게 정보 가져오기
1
len(soup.find_all('div', 'sammy'))
1
50
- div의 sammy 클래스를 이용해 총 50개의 가게 정보를 가져옴
2.3 가게 정보
1
print(soup.find_all('div', 'sammy')[0])
1
2
3
4
5
6
<div class="sammy" style="position: relative;">
<div class="sammyRank">1</div>
<div class="sammyListing"><a href="/Chicago-Magazine/November-2012/Best-Sandwiches-in-Chicago-Old-Oak-Tap-BLT/"><b>BLT</b><br>
Old Oak Tap<br>
<em>Read more</em> </br></br></a></div>
</div>
- 각 가게 정보에 랭킹, 가게이름, 메뉴, 상세페이지로연결되는 url 이 포함 되어있음
2.4 랭킹 가져오기
1
2
tmp_one = soup.find_all('div', 'sammy')[0]
tmp_one.find(class_ = 'sammyRank').get_text()
1
'1'
- find 메소드와 sammyRank 클래스를 이용하여 랭킹을 확보
2.5 가게이름과 메뉴 가져오기
1
tmp_one.find(class_ = 'sammyListing').get_text()
1
'BLT\r\nOld Oak Tap\nRead more '
- 가게이름(Old Oak Tap)과 메뉴이름(BLT)은 sammyListing 클래스에 한번에 있음
1
2
3
4
5
6
import re
tmp_string = tmp_one.find(class_ = 'sammyListing').get_text()
print(re.split(('\n|\r\n'), tmp_string)[0])
print(re.split(('\n|\r\n'), tmp_string)[1])
print(re.split(('\n|\r\n'), tmp_string))
1
2
3
BLT
Old Oak Tap
['BLT', 'Old Oak Tap', 'Read more ']
- 가게이름과 메뉴이름은 re 모듈의 split으로 간단히 구분 가능
2.6 상세페이지
1
tmp_one.find('a')['href']
1
'/Chicago-Magazine/November-2012/Best-Sandwiches-in-Chicago-Old-Oak-Tap-BLT/'
- 가게의 상세페이지로 가는 url은 상대 경로로, href로 찾으면 됨
1
2
print(url_base)
print(tmp_one.find('a')['href'])
1
2
https://www.chicagomag.com
/Chicago-Magazine/November-2012/Best-Sandwiches-in-Chicago-Old-Oak-Tap-BLT/
- 처음에 설정한 url_base를 사용하여 가게 상세 url과 합치면 될듯 하다.
1
2
from urllib.parse import urljoin
urljoin(url_base, tmp_one.find('a')['href'])
1
'https://www.chicagomag.com/Chicago-Magazine/November-2012/Best-Sandwiches-in-Chicago-Old-Oak-Tap-BLT/'
- urljoin을 사용하여 url_base와 가게 상제 페이지 주소를 합쳐줌(절대 주소)
1
2
3
tmp_one = soup.find_all('div', 'sammy')[10].find('a')['href']
print(tmp_one)
urljoin(url_base, tmp_one)
1
2
3
4
http://www.chicagomag.com/Chicago-Magazine/November-2012/Best-Sandwiches-in-Chicago-Lula-Cafe-Ham-and-Raclette-Panino/
'http://www.chicagomag.com/Chicago-Magazine/November-2012/Best-Sandwiches-in-Chicago-Lula-Cafe-Ham-and-Raclette-Panino/'
- 11번쨰의 주소도 잘 합쳐짐을 확인함
2.7 50개 메뉴에 적용하기
1
2
3
4
5
6
7
8
9
10
11
rank = []
main_menu = []
cafe_name = []
url_add = []
for item in soup.find_all('div', 'sammy'):
rank.append(item.find(class_ = 'sammyRank').get_text())
tmp_string = item.find(class_ = 'sammyListing').get_text()
main_menu.append(re.split(('\n|\r\n'), tmp_string)[0])
cafe_name.append(re.split(('\n|\r\n'), tmp_string)[1])
url_add.append(urljoin(url_base, item.find('a')['href']))
1
2
3
4
print(len(rank), len(main_menu), len(cafe_name), len(url_add))
print(main_menu[:5])
print(cafe_name[:5])
url_add[:5]
1
2
3
4
5
6
7
8
50 50 50 50
['BLT', 'Fried Bologna', 'Woodland Mushroom', 'Roast Beef', 'PB&L']
['Old Oak Tap', 'Au Cheval', 'Xoco', 'Al’s Deli', 'Publican Quality Meats']
['https://www.chicagomag.com/Chicago-Magazine/November-2012/Best-Sandwiches-in-Chicago-Old-Oak-Tap-BLT/',
'https://www.chicagomag.com/Chicago-Magazine/November-2012/Best-Sandwiches-in-Chicago-Au-Cheval-Fried-Bologna/',
'https://www.chicagomag.com/Chicago-Magazine/November-2012/Best-Sandwiches-in-Chicago-Xoco-Woodland-Mushroom/',
'https://www.chicagomag.com/Chicago-Magazine/November-2012/Best-Sandwiches-in-Chicago-Als-Deli-Roast-Beef/',
'https://www.chicagomag.com/Chicago-Magazine/November-2012/Best-Sandwiches-in-Chicago-Publican-Quality-Meats-PB-L/']
- 전체 50개로 잘 가져와 진것 같음
2.8 데이터프레임 생성
1
2
3
4
5
import pandas as pd
data = {'Rank': rank, 'Menu': main_menu, 'Cafe': cafe_name, 'URL': url_add}
df = pd.DataFrame(data, columns=['Rank', 'Cafe', 'Menu', 'URL'])
df.tail()
Rank | Cafe | Menu | URL | |
---|---|---|---|---|
45 | 46 | Chickpea | Kufta | http://www.chicagomag.com/Chicago-Magazine/Nov... |
46 | 47 | The Goddess and Grocer | Debbie’s Egg Salad | http://www.chicagomag.com/Chicago-Magazine/Nov... |
47 | 48 | Zenwich | Beef Curry | http://www.chicagomag.com/Chicago-Magazine/Nov... |
48 | 49 | Toni Patisserie | Le Végétarien | http://www.chicagomag.com/Chicago-Magazine/Nov... |
49 | 50 | Phoebe’s Bakery | The Gatsby | http://www.chicagomag.com/Chicago-Magazine/Nov... |
- 50개 자료에 대해 이름, 메뉴 , 상세페이지 URL까지 모두 정리 완료
1
df.to_csv('./data/best_sandwiches_list.csv', sep =',', encoding = 'utf-8')
- csv 파일로 저장완료
3. 상세페이지
3.1 Data Load
1
2
df = pd.read_csv('https://raw.githubusercontent.com/hmkim312/datas/main/chicagosandwiches/best_sandwiches_list.csv', index_col=0)
df.head()
Rank | Cafe | Menu | URL | |
---|---|---|---|---|
0 | 1 | Old Oak Tap | BLT | https://www.chicagomag.com/Chicago-Magazine/No... |
1 | 2 | Au Cheval | Fried Bologna | https://www.chicagomag.com/Chicago-Magazine/No... |
2 | 3 | Xoco | Woodland Mushroom | https://www.chicagomag.com/Chicago-Magazine/No... |
3 | 4 | Al’s Deli | Roast Beef | https://www.chicagomag.com/Chicago-Magazine/No... |
4 | 5 | Publican Quality Meats | PB&L | https://www.chicagomag.com/Chicago-Magazine/No... |
- 데이터 불러옴
3.2 상세페이지 파싱
1
2
3
html = urlopen(df['URL'][0])
soup_tmp = BeautifulSoup(html, 'html.parser')
soup_tmp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!DOCTYPE doctype html>
<html lang="en">
<head>
<!-- Urbis magnitudo. Fabulas magnitudo. -->
<meta charset="utf-8"/>
<style>a.edit_from_site {display: none !important;}</style>
<title>
1. Old Oak Tap BLT |
Chicago magazine
| November 2012
</title>
...
</script>
<!--[if lt IE 9]>
<script type="text/javascript" language="JavaScript" src="/core/media/themes/Respond/js/respond.js?ver=1473876729"></script>
<![endif]-->
<script language="JavaScript" src="/core/media/js/base.js?ver=1473876728" type="text/javascript"></script>
<script language="JavaScript" src="/core/media/themes/Respond/js/bootstrap.min.js?ver=1473876729" type="text/javascript"></script>
<script language="JavaScript" src="//maps.googleapis.com/maps/api/js?v=3.exp&sensor=false" type="text/javascript"></script>
<script language="JavaScript" src="/theme_overrides/Respond/js/interstitial.js?ver=1524154906" type="text/javascript"></script>
<script language="JavaScript" src="/theme_overrides/Respond/js/newsletter-subscribe.js?ver=1524850607" type="text/javascript"></script>
<script language="JavaScript" src="/theme_overrides/Respond/js/RivistaGoogleDFP.js?ver=1447178886" type="text/javascript"></script>
<!-- godengo-monitor --></body>
</html>
- 하나의 페이지 파싱
- p태그에 addy 클래스에 정보가 있는듯 하다
3.3 가격 및 주소 정보 가져오기
1
2
price_tmp = soup_tmp.find('p', 'addy').get_text()
price_tmp
1
'\n$10. 2109 W. Chicago Ave., 773-772-0406, theoldoaktap.com'
- 가격, 주소, 전화번호, 홈페이지 정보가 한번에 있음
1
2
price_tmp = re.split('.,', price_tmp)[0]
price_tmp
1
'\n$10. 2109 W. Chicago Ave'
- 미국은 주소 뒤에 .,가 붙으니, 그걸 기준으로 split하여 가격과 주소만 가져옴
1
re.search('\$\d+\.(\d+)?', price_tmp).group()
1
'$10.'
- 숫자로 시작하다가 꼭 .을 만나고 그 뒤에 숫자가 있을수도 있고 없을수도 있는 정규표현식으로 가격만 가져옴
1
2
end = re.search('\$\d+\.(\d+)?', price_tmp).end()
price_tmp[end+1:]
1
'2109 W. Chicago Ave'
- 가격이 끝나는 지점의 위치를 이용해서 그 뒤는 주소로 저장
3.4 3개에 시범삼아 적용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
price = []
address = []
for idx, row in df[:3].iterrows():
html = urlopen(row['URL'])
soup_tmp = BeautifulSoup(html, 'lxml')
gettings = soup_tmp.find('p', 'addy').get_text()
price_tmp = re.split('.,', gettings)[0]
tmp = re.search('\$\d+\.(\d+)?', price_tmp).group()
price.append(tmp)
end = re.search('\$\d+\.(\d+)?', price_tmp).end()
address.append(price_tmp[end+1:])
print(row['Rank'])
1
2
3
1
2
3
1
price, address
1
2
(['$10.', '$9.', '$9.50'],
['2109 W. Chicago Ave', '800 W. Randolph St', ' 445 N. Clark St'])
- 잘되는듯 하다.
3.5 50개 모두 돌리기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import time
price = []
address = []
for idx, row in df.iterrows():
html = urlopen(row['URL'])
soup_tmp = BeautifulSoup(html, 'lxml')
gettings = soup_tmp.find('p', 'addy').get_text()
price_tmp = re.split('.,', gettings)[0]
tmp = re.search('\$\d+\.(\d+)?', price_tmp).group()
price.append(tmp)
end = re.search('\$\d+\.(\d+)?', price_tmp).end()
address.append(price_tmp[end+1:])
time.sleep(0.5)
print(row['Rank'], ' / ', '50')
1
2
3
4
5
6
7
8
1 / 50
2 / 50
3 / 50
...
47 / 50
48 / 50
49 / 50
50 / 50
1
len(price), len(address), len(df)
1
(50, 50, 50)
- 잘 된듯 하다
3.6 데이터 프레임에 추가
1
2
3
4
5
6
df['Price'] = price
df['Address'] = address
df = df.loc[:, ['Rank', 'Cafe', 'Menu', 'Price', 'Address']]
df.set_index('Rank', inplace = True)
df.head()
Cafe | Menu | Price | Address | |
---|---|---|---|---|
Rank | ||||
1 | Old Oak Tap | BLT | $10. | 2109 W. Chicago Ave |
2 | Au Cheval | Fried Bologna | $9. | 800 W. Randolph St |
3 | Xoco | Woodland Mushroom | $9.50 | 445 N. Clark St |
4 | Al’s Deli | Roast Beef | $9.40 | 914 Noyes St |
5 | Publican Quality Meats | PB&L | $10. | 825 W. Fulton Mkt |
- 완료되었음.
3.7 csv 저장
1
df.to_csv('./data/best_sandwiches_list2.csv', sep =',', encoding = 'utf-8')