2013년 12월 4일 수요일

[python] Scrapy - 네이버 영화 파싱


Scrapy is a fast high-level screen scraping and web crawling framework, used to crawl websites and extract structured data from their pages. It can be used for a wide range of purposes, from data mining to monitoring and automated testing.

Scrapy documentation : http://doc.scrapy.org/en/0.20/


공식 페이지의 설명에서 볼 수 있듯이 Scrapy는 python 기반의 파싱 프레임워크다.

Scrapy를 알기 전에는 urllib2와 beatifulsoup로 파싱을 해 왔으나, Scrapy를 쓰면 훨씬 빠른 속도로 crawling을 할 수 있었다.


자세한 정보는 공식 튜토리얼을 통해서 얻을 수 있으며, Scrapy를 사용하기 위해서는 lxml, OpenSSL이 필요하다.

Installation : http://doc.scrapy.org/en/latest/intro/install.html

Tutorial : http://doc.scrapy.org/en/latest/intro/tutorial.html#crawling


간단하게 프로젝트를 시작하는 방법을 설명해 보면,

scrapy startproject scrapy_sample

cd scrapy_sample
vi scrapy_sample/spiders/spider.py
로 새로운 프로젝트를 하나 만들고, spider.py 파일을 만든다.


Scrapy 공식 홈페이지에 나와있는 example 소스는 아래와 같다.

from scrapy.spider import BaseSpider
from scrapy.selector import HtmlXPathSelector
from scrapy.item import Item, Field

class Website(Item): // parse 된 정보를 저장할 class
    name = Field() // Field()에는 숫자와 string 모두 저장할 수 있음
    description = Field()
    url = Field()

class DmozSpider(BaseSpider):
    name = "dmoz" // spider의 이름을 나타낸다
    allowed_domains = ["dmoz.org"] // dmoz.org 이외의 redirect는 무시된다.
    start_urls = [
        "http://www.dmoz.org/Computers/Programming/Languages/Python/Books/",
        "http://www.dmoz.org/Computers/Programming/Languages/Python/Resources/",
    ] // start_urls 안에 들어있는 url이 parse 된다

    def parse(self, response):
        hxs = HtmlXPathSelector(response)

        // css selector 문법을 통해 dom element 를 찾을 수 있다.
        sites = hxs.select('//ul[@class="directory-url"]/li') 
        items = []

        for site in sites:
            item = Website()
            item['name'] = site.select('a/text()').extract()
            item['url'] = site.select('a/@href').extract()
            item['description'] = site.select('text()').re('-\s([^\n]*?)\\n')
            items.append(item)
            
        return items

이후 project 의 root 디렉토리에서 아래와 같은 명령어를 입력하면 된다.

scrapy crwal dmoz // spiders 폴더 내에 있는 소스 코드 중 dmoz를 이름으로 가지고 있는 spider를 실행
scrapy crawl dmoz -o some.json -t json 2> result.txt // item을 json형태로 txt 파일에 저장
 
rm result.txt // 크롤 하기 전에 result.txt는 매번 지워주어야 함
 
scrapy shell spider.py // scrapy를 shell 형태로 실행
scrapy shell "http://www.dmoz.org/Computers/Programming/Languages/Python/Books/"

selector 에 대한 정보는

http://doc.scrapy.org/en/latest/topics/selectors.html

여기서 찾을 수 있다.


Scrapy에 대한 한국어 문서가 없어서 제대로된 사용법을 찾는데 시간이 조금 걸렸지만,

다른분들은 이 글을 읽으시고 시간낭비를 하지 않으시길 바란다.


아래의 예제는 네이버 영화 정보를 파싱하는 spider다.

영화 제목과 개봉시기, 장르 등의 정보를 파싱하며 위에 나온 명령어들을 사용하면 쉽게 json 파일을 얻을 수 있을 것이다.


__author__ = 'carpedm20'
__date__ = '2013.11.14'

from scrapy.spider import BaseSpider
from scrapy.selector import HtmlXPathSelector
from scrapy.http.request import Request
from scrapy.item import Item, Field

import sys, os

class ScrapyItem(Item):
    title = Field()
    url = Field()
    year = Field()
    open1 = Field() # 2013
    open2 = Field() # 06.12
    country = Field()
    genre = Field()
    form = Field()
    grade = Field()

YEAR = "2015"
START_PAGE = 10 # 0
START_PAGE = 0 # 0
MAX_LOOP = 4 #-1
MAX_LOOP = -1 #-1
PMOD = True
PMOD = False

print "==================="
print YEAR
print "==================="

url = "http://movie.naver.com/movie/sdb/browsing/bmovie.nhn?year="+YEAR+"&page=" + str(START_PAGE)

index = start_page = START_PAGE
old_index = -1
old_title = ""
loop = 0
pmod = PMOD
max_loop = MAX_LOOP

class ScrapyOrgSpider(BaseSpider):
    name = "naver"
    allowed_domains = ["movie.naver.com"]
    start_urls = [url]

    def parse(self, response):
        global start_page, index, loop, old_title, pmod, max_loop

        hxs = HtmlXPathSelector(response)
        items = []

        loop += 1

        next_page = ["http://movie.naver.com/movie/sdb/browsing/bmovie.nhn?year="+YEAR+"&page="+str(loop + start_page)]

        if max_loop != -1:
          if loop >= max_loop:
              next_page = []

        posts = hxs.select("//ul[@class='directory_list']/li")
        title = posts[0].select("a/text()")[0].extract()

        if old_title == title and loop > 2:
          next_page = []
        else:
          old_title = title

        if not not next_page:
            yield Request(next_page[0], self.parse)

        #posts = hxs.select("//tr")

        count = 0
        print "[ " + str(loop) + " ] index : " + str(index) + ", len(posts) : " + str(len(posts))

        for post in posts:
            try:
              title = post.select("a/text()")[0].extract()
              if pmod: print " [ " + str(count) + " ] TITLE : " + title
              url = post.select("a/@href")[0].extract()
              if pmod: print " [ " + str(count) + " ] URL : " + url
              year = post.select("ul[@class='detail']/li/a")[0].select("b/text()")[0].extract()
              if pmod: print " [ " + str(count) + " ] YEAR : " + year

              open1 = ""
              open2 = ""
              country = ""
              genre = ""
              form = ""
              grade = ""

              open_count = 0
              lis = post.select("ul[@class='detail']/li/a")

              for li in lis:
                h = li.select('@href')[0].extract()
                href = h[h.find('&')+1:h.rfind('=')]
                if pmod: print " [*] HREF : " + href

                if href == '?year':
                  if pmod: print "  [-] HREF SKIP : " + h
                  continue

                if href == 'open' and open_count == 0:
                  open1 = li.select('text()')[0].extract()
                  open_count += 1
                  if pmod: print " [*] open1 : " + open1
                elif  href == 'open' and open_count == 1:
                  try:
                    open2 = li.select('text()')[0].extract()
                    if pmod: print " [*] open2 : " + open2
                  except:
                    z = 123
                elif href == 'nation':
                  country = li.select('text()')[0].extract()
                  if pmod: print " [*] country : " + country
                elif href == 'genre':
                  genre = li.select('text()')[0].extract()
                  if pmod: print " [*] genre : " + genre
                elif href == 'form':
                  form = li.select('text()')[0].extract()
                  if pmod: print " [*] form : " + form
                elif href == 'grade':
                  grade = li.select('text()')[0].extract()
                  if pmod: print " [*] grade : " + grade
                else:
                  print " [^] Found NEW href : " + h
                  return

              count += 1
            except Exception as e:
              #for frame in traceback.extract_tb(sys.exc_info()[2]):
              #  fname,lineno,fn,text = frame
              #  print "Error in %s on line %d" % (fname, lineno)
              exc_type, exc_obj, exc_tb = sys.exc_info()
              fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
              print(exc_type, fname, exc_tb.tb_lineno)
              #for e in sys.exc_info():
              #  print e
              continue

            item = ScrapyItem()
            item["title"] = title
            item["url"] = url
            item["year"] = year
            item["open1"] = open1
            item["open2"] = open2
            item["country"] = country
            item["genre"] = genre
            item["form"] = form
            item["grade"] = grade
            items.append(item)

        for item in items:
            yield item

        old_index = index
        index += count

ps.


댓글 3개:

  1. 실행 안되는데요? @@ scrapy 에 요즘 빠져있는 1인입니다. 혹시 시간되시면 도움 주실수 있나요? 한국에서는 님이 고수인듯... ㅠ.ㅠ 도와주세요~

    답글삭제
    답글
    1. 얼마전에 메일 드렸었는데... 바쁜신가봐요~ ^^;

      삭제
  2. 파싱한 내용을 한글로 변환할 수 있는 방법좀 알려주세요 ㅠㅠ

    답글삭제