예제코드분석 - moabogey/docs GitHub Wiki
코딩의 시작은 다른 사람이 만든 코드를 보고 따라하는 것입니다. 여기서는 예제 봇의 소스 코드를 분석하여 자신만의 봇을 만드는데 도움을 드리고자 합니다.
모아보기 봇의 구조는 3개의 파일로 구성되어 있습니다.
수집한 데이터를 저장하고, 저장된 데이터와 비교하는 기능을 가지고 있는 파일입니다. 이 파일은 내용을 변경하지 않고 그냥 가져다가 사용하면 됩니다.
봇의 고유값을 저장하는 파일입니다. 우리가 인터넷 커뮤니티에서 다른 사람과 닉네임이 중복되지 않는 것처럼 봇은 고유의 ID를 가지고 있습니다. 고유값은 일반적으로 봇의 파일 이름을 그대로 사용합니다.
사이트에 들어가서 데이터를 수집하는 핵심적인 기능을 수행하는 파일입니다. 파일 이름은 모아보기 앱에서 일반 사용자에게 보여지기 때문에 알기 쉽도록 만들어 져야 합니다. 영어로만 표기가 되어야 하고 특수 문자등을 사용할 수 없습니다. 예제 봇들의 이름은 앞부분은 주제
를 표시하고 뒷부분은 사이트
를 알려 주고 있습니다. 예를 들어서 best_on_ruliweb이라는 이름은 루리웹에서 베스트를 모아주는 봇이라는 뜻을 가지고 있습니다.
분석에 사용되는 소스 코드는 bts_army_on_twitter.py를 사용합니다. bts_army_on_twitter는 BTS ARMY의 트윗을 모아주는 봇입니다.
봇의 소스 코드는 크게 세단계로 나눌 수 있습니다.
- 사이트의 HTML에서 데이터를 수집
- 포스트의 HTML에서 데이터를 수집
- 데이터 저장
데이터를 수집할 사이트의 정보와 주소를 설정합니다. 예제에서는 https://twitter.com/BTS_ARMY 에서 데이터를 수집합니다.
# 사이트 이름
site_name = 'twitter'
# 사이트에서 가져올 주제
subject_name = 'BTS_ARMY'
# 사이트 주소
site_url = 'https://twitter.com/BTS_ARMY'
requests와 beautifulsoup4를 이용해서 사이트의 HTML을 가져오고 파일로 저장합니다.
# 사이트에 대한 정보를 요청하고 응답을 받는다.
response = requests.get(site_url)
# 응답에서 HTML을 변수로 빼낸다.
html_source = response.text
# HTML을 html해석기로 해석하고 결과를 변수로 빼낸다.
soup = BeautifulSoup(html_source, 'html.parser')
# 해석 결과를 보기 좋게 만들어서 파일로 저장한다.
with open(file_name, 'w', encoding='utf-8') as f:
f.write(soup.prettify())
저장된 HTML파일 (예제에서는 twitter_source.html)을 열어 봅니다. 여기서 우리는 "포스트의 리스트"를 표현하는 구간을 찾을 것입니다. 포스트는 제목, 내용, 이미지, 작성자, 작성 날짜 및 페이지 위치(URL)를 가지고 있는 하나의 문서(여기서는 트윗)를 나타내는 용어로 사용합니다.
+-------------+ +------> <div class="content">
| Post 1 |
| (Tweet 1) |
+-------------+ +------> </div>
+-------------+ +------> <div class="content">
| Post 2 |
| (Tweet 2) |
+-------------+ +------> </div>
+-------------+ +-------> <div class="content">
| Post3 |
| (Tweet 3) |
+-------------+ +------> </div>
예제에서 각각의 포스트는 <div class="content">
에서 시작 되고 </div>
로 끝난다는 것을 알아내는 것이 중요합니다. 이것은 사이트마다 다르기 때문에 이것을 찾아내는 것은 약간의 경험이 필요합니다.
# 반복해서 포스트의 목록을 하나씩 검색하며 데이터를 수집한다.
for post in soup.find_all('div', class_='content'):
# 포스트를 올린 작성자를 수집한다.
발견한 포스트에서 작성자, 올린 시간 및 포스트 위치(URL)을 찾습니다.
+-------------+
| Post 1 |
+-------------+
| createdBy | +-------> <strong class="fullname...> </strong>
| |
| post URL | +-------> <a class="tweet-timestamp... href=...>
| |
| createdAt | +-------> <a class="tweet-timestamp... title=...>
| |
+-------------+
# 포스트를 올린 작성자를 수집한다.
moa_createBy = post.find('strong', class_='fullname').text.strip()
# 포스트의 주소를 수집한다.
href = post.find('a', {"class": "tweet-timestamp", "href": True})
# 포스트를 올린 날짜를 수집한다.
post_time = post.find('a', class_="tweet-timestamp")['title'].replace('오전', 'AM').replace('오후', 'PM')
나머지 데이터를 수집하기 위해서 포스트 HTML로 이동합니다.
데이터를 수집할 포스트의 주소를 설정합니다.
href_url = 'https://twitter.com' + href['href']
requests와 beautifulsoup4를 이용해서 사이트의 HTML을 가져오고 파일로 저장합니다.
# 사이트에 대한 정보를 요청하고 응답을 받는다.
response = requests.get(href_url)
# 응답에서 HTML을 변수로 빼낸다.
subhtml_source = response.text
# HTML을 html해석기로 해석하고 결과를 변수로 빼낸다.
post = BeautifulSoup(subhtml_source, 'html.parser')
# 해석 결과를 보기 좋게 만들어서 파일로 저장한다.
with open(file_name, 'w', encoding='utf-8') as f:
f.write(post.prettify())
저장된 HTML파일 (예제에서는 twitter_post_source.html)을 열어 봅니다. 여기서 우리는 포스트의 제목, 요약, 이미지, 사이트 이름, 포스트 주소(URL), 수집 날짜 및 시간 데이터를 수집할 것입니다.
대부분의 사이트들은 우리가 수집할 데이터를 사이트의 첫머리에 미리 모아 놓고 있습니다. 이 규약(Protocol)은 사이트를 모두 분석하지 않고도 사이트의 내용을 파악하는데 도움이 됩니다.
아래와 같은 매타 태그를 사용합니다.
<head>
...
<meta content="..." property="og:url"/>
<meta content="..." property="og:title"/>
<meta content="..." property="og:image"/>
<meta content="..." property="og:description"/>
<meta content="..." property="og:site_name"/>
...
</head>
데이터를 수집합니다.
# 포스트 제목을 수집/가공 한다.
moa_title = post.find('meta', property="og:description")
# 포스트 요약을 수집/가공한다.
moa_desc = post.find('meta', property="og:description")
# 대표 이미지의 주소를 수집한다.
moa_image = post.find('meta', property="og:image")
# 사이트 이름을 수집한다.
moa_site_name = post.find('meta', property="og:site_name")
# 포스트의 주소를 가공한다.
moa_url = 'https://mobile.twitter.com' + href
# 현재 날짜와 시간을 수집한다.
moa_timeStamp = datetime.now()
수집한 데이터를 선별해서 중복되는 것을 제외하고 데이터베이스에 저장합니다. 모아보기 봇은 하루에 24번 이상 동작 하도록 되어 있기 때문에 한번에 모든 데이터를 수집하지 않고 가장 최근의 데이터 1~2개를 수집하는 것이 원칙입니다. 여기서는 데이터를 수집한 날짜와 동일한 날에 등록된 포스트만 수집하도록 프로그래밍되어 있습니다.
# 오늘 발행된 포스트만 선택한다.
delta = moa_timeStamp - moa_createdAt + timedelta(hours=9)
if delta.days <=0:
# 데이터베이스에 있는 포스트와 중복되는지를 확인한다.
if my_db.isNewItem('title', moa_title):
# JSON형식으로 수집한 데이터를 변환한다.
db_data = { 'title': moa_title,
'desc': moa_desc,
'url': moa_url,
'image': moa_image,
'siteName': moa_site_name,
'createdBy': moa_createBy,
'createdAt': moa_createdAt,
'timeStamp': moa_timeStamp
}
# 수집한 데이터를 데이터베이스에 전송한다.
my_db.insertTable(db_data)