Cyxapeff Blog

Пишем Tv программу на Django. Часть 2

Ну вот, модель мы создали. Таблицы создали. Теперь было бы их не плохо заполнить.

Заполнять будем парсером афиши@mail.ru Поэтому теперь мы будем злобными пиратами, ворующими чужой контент. Кому это не интересно можете пропускать эту запись сразу, а наполнять базу можно и какими-то легальными способами.

Мой способ - парсить майл.ру каждое воскресенье, доставая оттуда телепрограмму на следующую неделю. То есть сейчас необходимо написать какой-то скриптик, который потом нужно будет прописать в cron. И после этого мы всегда будем иметь актуальную информацию в нашей бд.

И так зайдём на http://pda.tv.mail.ru и выберем какой-нибудь канал. Смотрим урл, например: http://pda.tv.mail.ru/?gosetup=1&channel=365. 365 это тот Num который мы создавали в модели. Но как видно отображаются лишь актуальные телепередачи, поэтому переключимся в вид "весь день". Но теперь отображается программа на сегодня, а нам бы нужно бы иметь возможность получать программу на разные дни. Выберем какой-нибудь другой день... Замечательно! Спасибо мэйлу, нам необходимо только циклом ходить по страничкам вида http://pda.tv.mail.ru/?date=[date]&gosetup=1&period=3&channel=[Num] и парсить их.

Посмотрим исходный код... Шикарно, ещё один респект мэйлу от меня. Нужная нам информация находится в блоке между <!-- START: Programm Content --> и <!-- END: Programm Content --> Там простая табличка, которую не составит большого труда отпарсить, тем более программы с описанием выделены тегом "b". Описание фильмов находится в теге "p" с классом t75. Ну вроде бы в устройстве сайта разобрались, Парсим!

Создаём новый файлик, я его назвал core.py Подключаем стандартные питоновские модули HTMLParser и urllib, ну и дальше по мануалу (хотя наверняка можно написать гораздо красивее):

class ChanalHTMLParser(HTMLParser):
  """ Класс парсера для канала, создаём на основе HTMLParser """
  def __init__(self):
      HTMLParser.__init__(self)
      self.content=0 # несколько служебных переменных - переключателей
      self.flag=0
      self.result=[]
      self.buffer=[]
      self.flag2=0
  def handle_comment(self, data):
      """ Ищет в комментах необходимые строки """
      if "START: Programm Content" in data:
          self.content=1 # говорим что начались полезные данные
      elif "END: Programm Content" in data:
          self.content=0 # а теперь закончились

  def handle_starttag(self, tag, attrs):
      """ Функция вызывается при нахождении открывающего тега """
      if self.content==1: # Если мы внутри интересующий нас информации, то ищим в тегах
          if tag=="td":
              if len(attrs)>0:
                  if attrs[1][0]=="class" and attrs[1][1]=="time":
                      self.flag=1 # последующие данные, до закрытия тега будут временем
              else: # иначе ссылкой и названием программы
                  if self.flag==2: # конечно если тег со временем уже закрыт
                      self.flag=3 # говорим, что нашли строку с названием
          elif tag=="b" and self.flag==3: # встретили b - верный признак того, чтоо есть описание
              self.flag2=1 # сообщаем об этом
          elif tag=="a" and self.flag2==1 and self.flag==3: # а вот если встречаем a, то внутренность его нужна только если есть описание
              self.buffer.append(attrs[0][1]) # если всё ok, то добавляем ссылку в буффер

  def handle_data(self, data):
      """ Выполняется каждый раз когда оказывается 'внутри' тегов """
      if self.flag==1 or self.flag==3: # если там время или название
          self.buffer.append(data) # то добавляем их в буффер

  def handle_endtag(self, tag):
      """ Ну а эта функция соответсвенно проверяет закрывающие теги """
      if self.content==1: # снова делаем проверку
          if tag=="b":
              if self.flag2==1 and self.flag==3:
                  self.flag2=0 # обнуляем сообщение о описании
          if tag=="td":
              if self.flag==1: # если сейчас было 'время'
                  self.flag=2 # то сообщаем о том, что оно кончилось
              elif self.flag==3: # если было название
                  self.result.append(self.buffer) # до добавляем буфер к результату
                  self.buffer=[] # очищаем его
                  self.flag=0 # и обнуляем флаг

и

class FilmHTMLParser(HTMLParser):
  """ Класс парсера для описаний, создаём на основе HTMLParser """
  content=0
  flag=0
  result=""
  def handle_comment(self, data):
      """ Ищем комменты """
      if "BEGIN: Main table" in data:
          self.content=1
      elif "END: Main table" in data:
          self.content=0

  def handle_starttag(self, tag, attrs):
      """ нужный тег с нужным классом """
      if self.content==1:
          if tag=="p":
              if len(attrs)==1:
                  if attrs[0][1]=="t75":
                      self.flag=1

  def handle_data(self, data):
      """ добавляем содержимое """
      if self.flag==1:
          self.result+=data

  def handle_endtag(self, tag):
      if self.content==1:
          if tag=="p":
              self.flag=0

Теперь скормив этим классам странички с программой или описанием будем получать только нужную информацию. делается это вот так:

p = ChanalHTMLParser()
sock = urllib.urlopen("http://pda.tv.mail.ru/?date="+date+"&channel="+str(chanal.Num)+"&period=3")
htmlSource = sock.read()
htmlSource = unicode(htmlSource, "cp1251") # переводим страничку в юникод. Советую все данный поступающие от пользователя или ещё откуда-нибудь сразу же переводить в unicode. Меньше проблем будет.
sock.close()
p.feed(htmlSource)
print p.result

Для описаний аналогично.

Это всё конечно замечательно, но надо это всё ещё и в бд запихнуть. Для этого нужно подключить к нашему скрипту django'вский ORM. Делается это довольно просто. А именно:

from django.core.management import setup_environ
sys.path.append("Путь до проекта") # т.к core.py у меня лежит в папке с приложением, которое обычно находится внутри папки проекта, то в место прямого пути я написал вот такую штукенцию os.path.split(os.path.dirname(os.path.abspath(__file__)))[0]
import settings
setup_environ(settings)

Ну вот, теперь мы можем использовать всю мощь Django в своём скрипте. Подключаем из нашей недавно созданный модели классы каналы, программы и описания:

from mysite.tv.models import Chanal, Program, Description

для работы с бд я написал отдельный класс, хотя можно было обойтись и без этого:

class DataBase2:
  def get_chanals(self):
      """ Функция просто получает все каналы """
      chanals = Chanal.objects.all()
      return chanals

  def add_programm(self, chanal, ptime, title):
      """ Функция добавляет программу """
      if len(Program.objects.filter(Title=title, Time=ptime, Chanal=chanal))==0: # проверяем нет ли уже такой в бд
          p = Program(Title=title, Time=ptime, Chanal=chanal)
          p.save()
          return p
      else:
          return None

  def add_description(self, programm, content):
      """ Функция добавляет описание """
      if len(Description.objects.filter(Text=content, Program=programm))==0: # опять проверка на клонов
          Description(Text=content, Program=programm).save()

Вобщем-то всё. Осталось написать простой цикл обхода всех страниц и скрипт готов.

Взять его как всегда можно из моего svn: svn checkout http://tvwatcher.googlecode.com/svn/tags/post2/ tvwatcher

Запускаем django сервер, заходим в админку, добавляем какой-нибудь канал, указывая в Num номер канала на мэйле. Запускаем core.py с ключом w, снова идём в админку и видим, что добавились программы и описания этого канала на неделю.

Комментарии:

Ссылка http://cyxapeff.org/blog/pishem-tv-programmu-na-django-chast-1/, которая указана на первую часть не работает. Хорошо было бы увеличить шрифт листингов на 0.1 em, чтобы выглядели без искажений.

dicos 17.11.2009 - 05:46 #
Markdown syntax:

> цитата           *курсив*
> цитата           **жирный**

* список           1. список
* список           2. список
* список           3. список

отступ в 4 пробела:
    def some_code():
        return "code"
    print some_code()

[ссылка](http://example.com/)
Ваш ник:
E-mail:
или OpenID
Оставьте свой комментарий:
Получать уведомления о новых комментариях