[python] 파이썬 웹 크롤링(5): 데이터 저장하기

업데이트:

파이썬 웹 크롤링(5): 데이터 저장하기

본 포스팅은 Web Scraping with Python by Ryan Mitchell을 참고하였습니다.

참고 링크

Chapter 5: 데이터 저장하기

5.1 데이터 저장의 필요성

데이터를 크롤링해서 바로 print로 확인하는 것은 재밌지만 수많은 데이터를 취합하고 분석하기에는 적절하지 않습니다. 여러분이 직접 개입하지 않고 웹 크롤링을 유용하게 하려면 데이터를 따로 저장할 필요가 있습니다. 이번 챕터에서는 데이터 관리에 대해 알아보겠습니다. 여러분은 혹시 백엔드나 여러분이 스스로 만든 API가 필요하시나요? 아마 크롤러가 여러분의 데이터베이스에 바로 쓰는(write)하는 것을 원하실지도 모르겠습니다. 인터넷이 연결되어 있지 않은 경우에도 데이터를 보고 싶다면 파일 스트림을 만들어야겠지요. 그리고 경고창이나 하루에 한번 취합 정보를 받아보고 싶다면 여러분 이메일주소로 이메일을 보내게끔 하면 됩니다.

5.2 미디어, 이미지 파일 저장하기

미디어 파일(media file)을 저장하는 방법에는 두가지 방법이 있습니다. 참조하거나 다운로드 받는 것입니다. 참조하는 방법은 그저 미디어의 URL을 저장하면 됩니다. 참조 방식의 장점은 아래와 같은 것들이 있습니다.

  • 크롤러가 빠르게 작동합니다. 파일 전체를 다운로드 받을 필요가 없기 때문입니다.
  • 하드디스크 용량을 아낄 수 있습니다.
  • 호스트 서버에 부하를 주지 않습니다.

반대로 참조방식의 단점은 아래와 같습니다.

  • 여러분의 웹사이트나 애플리케이션에 URL 링크를 걸어놨다면 여러분의 웹사이트나 애플리케이션이 해당 URL을 위한 하나의 경로가 됩니다.
  • 여러분의 애플리케이션이 URL 호스트 서버와 계속 통신하는 서버 사이클에 속하게 됩니다.
  • 참조 URL은 바뀔 수 있습니다. 이 경우 해당 URL은 더이상 사용할 수 없고 변경을 해줘야합니다.

파이썬 3에서는 아래 코드로 URL로부터 파일을 다운로드할 수 있습니다.

urllib.request.urlretrieve
from urllib.request import urlretrieve
from urllib.request import urlopen
from bs4 import BeautifulSoup

html = urlopen("http://www.pythonscraping.com")
bsObj = BeautifulSoup(html)
imageLocation = bsObj.find("a", {"id": "logo"}).find("img")["src"]
urlretrieve (imageLocation, "logo.jpg")

위 코드는 http://www.pythonscraping.com 라는 주소에서 logo.jpg파일을 저장하라는 뜻입니다. 이때 저장 위치는 위 코드가 돌아가는 위치입니다. 이런 방법은 단 하나의 파일을 다운로드할 때 유용합니다. 하지만 대부분의 크롤러는 단 하나의 파일반 받는 경우는 거의 없습니다. 아래 코드는 사이트에 존재하는 모든 내부 파일을 다운로드 합니다. 이때 어느 종류의 태그인지에 상관없이 src 속성에 해당하는 파일을 받습니다.

import os
from urllib.request import urlretrieve
from urllib.request import urlopen
from bs4 import BeautifulSoup

downloadDirectory = "downloaded"
baseUrl = "http://pythonscraping.com"

def getAbsoluteURL(baseUrl, source):
  if source.startswith("http://www."):
    url = "http://"+source[11:]
  elif source.startswith("http://"):
    url = source
  elif source.startswith("www."):
    url = source[4:]
    url = "http://"+source
  else:
    url = baseUrl+"/"+source
  if baseUrl not in url:
    return None
  return url

def getDownloadPath(baseUrl, absoluteUrl, downloadDirectory):
  path = absoluteUrl.replace("www.", "")
  path = path.replace(baseUrl, "")
  path = downloadDirectory+path
  directory = os.path.dirname(path)

  if not os.path.exists(directory):
    os.makedirs(directory)

  return path

html = urlopen("http://www.pythonscraping.com")
bsObj = BeautifulSoup(html)
downloadList = bsObj.findAll(src=True)

for download in downloadList:
  fileUrl = getAbsoluteURL(baseUrl, download["src"])
  if fileUrl is not None:
    print(fileUrl)

urlretrieve(fileUrl, getDownloadPath(baseUrl, fileUrl, downloadDirectory))

5.3 CSV 파일 형태로 저장하기

CSV(comma-separated values) 파일은 아마 스프레드시트 데이터를 저장할 때 쓰이는 가장 유명한 파일일 것입니다. 아래와 같은 형태가 CSV 파일을 의미합니다.

fruit,cost
apple,1.00
banana,0.30
pear,1.25

이번에는 파이썬으로 CSV 파일 만드는 방법을 살펴보겠습니다.

import csv

csvFile = open("../files/test.csv", 'wt')

try:
  writer = csv.writer(csvFile)
  writer.writerow(('number', 'number plus 2', 'number times 2'))
  for i in range(10):
    writer.writerow( (i, i+2, i*2))
finally:
  csvFile.close()

위 코드를 설명하자면, 해당 경로에 CSV 파일이 존재하지 않으면 파이썬이 해당 디렉터리를 만들고 파일을 자동으로 생성합니다. 만약 해당 파일이 이미 존재한다면 text.csv라는 새로운 파일을 덮어씁니다.

HTML 테이블을 CSV 파일 형태로 저장해보겠습니다.

import csv
from urllib.request import urlopen
from bs4 import BeautifulSoup

html = urlopen("http://en.wikipedia.org/wiki/Comparison_of_text_editors")
bsObj = BeautifulSoup(html)
#The main comparison table is currently the first table on the page
table = bsObj.findAll("table",{"class":"wikitable"})[0]
rows = table.findAll("tr")

csvFile = open("../files/editors.csv", 'wt')
writer = csv.writer(csvFile)
try:
  for row in rows:
  csvRow = []
  for cell in row.findAll(['td', 'th']):
    csvRow.append(cell.get_text())
    writer.writerow(csvRow)
finally:
  csvFile.close()

5.4 MySQL

MySQL은 데이터베이스를 관리하는 아주 유명한 오픈 소스입니다.

5.4.1 MySQL 설치하기

일반적인 리눅스라면 아래와 같이 설치합니다.

$ sudo apt-get install mysql-server

맥 유저라면 아래와 같이 설치합니다.

$ brew install mysql

5.4.2 기본 명령어

나중에 따로 SQL 명령어에 대해서 정리하겠습니다.

5.5 파이썬과 결합시키기

파이썬 mysql 연동을 참고해주세요.

5.6 이메일

웹페이지는 HTTP 형식으로 전송하지만, 이메일은 SMTP(Simple Mail Transfer Protocol) 형식으로 보냅니다. 파이썬을 이용해서 이메일을 보내는건 쉽지만, 서버가 SMTP 형식으로 보낼 수 있도록 접근해야합니다. 당신의 서버 혹은 로컬 컴퓨터에 SMTP 클라이언트를 세팅하거나 이게 가능하도록 여러가지 다른 도구를 사용할 수 있습니다.

import smtplib
from email.mime.text import MIMEText

msg = MIMEText("The body of the email is here")

msg['Subject'] = "An Email Alert"
msg['From'] = "ryan@pythonscraping.com"
msg['To'] = "webmaster@pythonscraping.com"

s = smtplib.SMTP('localhost')
s.send_message(msg)
s.quit()

파이썬의 email 모듈에는 유용한 함수가 많이 존재합니다. MiMEText 객체는 고수준의 SMTP 연결을 위해 저수준의 MIME(Multipurpose Internet Mail Extensions) 프로토콜을 위해 빈 이메일을 생성합니다. MIMEText 객체인 msg는 이메일 주소 뿐만 아니라 body와 header를 포함합니다.

smtplib 패키지는 서버와 연결하기 위한 정보를 포함합니다. 이를 이용해 MySQL 서버와 연결할 수 있습니다.

import smtplib
from email.mime.text import MIMEText
from bs4 import BeautifulSoup
from urllib.request import urlopen
import time

def sendMail(subject, body):
  msg = MIMEText(body)
  msg['Subject'] = subject
  msg['From'] = "christmas_alerts@pythonscraping.com"
  msg['To'] = "ryan@pythonscraping.com"

s = smtplib.SMTP('localhost')
s.send_message(msg)
s.quit()

bsObj = BeautifulSoup(urlopen("https://isitchristmas.com/"))
while(bsObj.find("a", {"id":"answer"}).attrs['title'] == "NO"):
  print("It is not Christmas yet.")
  time.sleep(3600)
bsObj = BeautifulSoup(urlopen("https://isitchristmas.com/"))
sendMail("It's Christmas!",
          "According to http://itischristmas.com, it is Christmas!")

위 코드는 https://isitchristmas.com/ 에 한시간에 한번씩 접속합니다. 만약 NO이외에 다른 무언가를 발견한다면 당신의 이메일에 경고를 보냅니다.