<repo_name>Sai628/moviejie-api<file_sep>/etc/gunicorn_conf.py
# Gunicorn configuration file.
# http://docs.gunicorn.org/en/0.16.0/configure.html


import multiprocessing

bind = '127.0.0.1:5001'
backlog = 2048  # The maximum number of pending connections

workers = multiprocessing.cpu_count() * 2 + 1
worker_connections = 10240  # The maximum number of simultaneous clients. This setting only affects the Eventlet and Gevent worker types.
max_requests = 10240  # The maximum number of requests a worker will process before restarting.
keepalive = 2  # The number of seconds to wait for requests on a Keep-Alive connection

secure_scheme_headers = {
    'X-SCHEME': 'https',
}
x_forwarded_for_header = 'X-FORWARDED-FOR'

loglevel = 'info'
accesslog = '/var/log/moviejie-api/gunicorn_stdout.log'
errorlog = '/var/log/moviejie-api/gunicorn_stdout.log'

reload = True  # Restart workers when code changes
<file_sep>/controllers/index.py
# coding=utf-8

from flask_restful import Resource

from app import cache
from util.helper import *
from config import config
from model import ResourceInfo, NewInfo, HotInfo


class Index(Resource):
    @cache.memoize(timeout=config.CACHE_EXPIRE_TIME)
    def get(self):
        index_soup = get_html_soup(config.API_INDEX)

        # 解析更新资源
        new_info_div = index_soup.find('div', class_='link_list')
        new_cat_titles = [tag.text for tag in new_info_div.find_all('h4')]

        new_infos = []
        tbody_tags = [tag for tag in new_info_div.find_all('tbody')[:len(new_cat_titles)]]
        for index, tbody_tag in enumerate(tbody_tags):
            resources = []
            for tr in tbody_tag.find_all('tr'):
                # 获取资源(电影/电视剧)的标题
                title = tr.find('span', class_='restitle').text.strip()
                title = title.replace('【电影】', '').strip()
                title_a_tag = tr.find('span', class_='restitle').a
                if title_a_tag is not None:
                    title_a_text = title_a_tag.text.strip()
                    title = title.replace(title_a_text, '').strip()

                # 获取资源的大小
                size = tr.find('td', class_='size').text

                # 获取资源的下载链接页面地址
                link_tag = tr.find('td', class_='link').a
                if link_tag is not None:
                    link = link_tag['href']

                # 获取资源的合集页面地址
                movie_link_tag = tr.find('span', class_='restitle').a
                movie_link = movie_link_tag['href'] if movie_link_tag is not None else ''

                resource_info = ResourceInfo(title=title, movie_link=movie_link, link=link, size=size)
                resources.append(resource_info.info())

            new_info = NewInfo(category=new_cat_titles[index], resources=resources)
            new_infos.append(new_info.info())

        # 解析热门/最新资源
        hot_info_divs = index_soup.find_all('div', class_='movie_list')
        hot_infos = []
        for div in hot_info_divs:
            resources = []
            for li in div.find_all('li'):
                title_a_tag = li.find('div', class_='infos').a
                title = title_a_tag.text.strip()  # 获取资源标题
                movie_link = title_a_tag['href']  # 获取资源链接

                # 获取资源评分
                rating_tag = li.find('span', class_='star')
                rating = rating_tag.text if rating_tag is not None else ""

                resource_info = ResourceInfo(title=title, movie_link=movie_link, rating=rating)
                resources.append(resource_info.info())

            hot_cat_title = div.find('h4').text
            hot_info = HotInfo(category=hot_cat_title, resources=resources)
            hot_infos.append(hot_info.info())

        return success({
            "news": new_infos,
            "hots": hot_infos
        })

    def __repr__(self):
        return "%s" % self.__class__.__name__
<file_sep>/model/MovieInfo.py
# coding=utf-8

from util.common import DictObj


class MovieInfo:
    """
    电影/电视剧信息model. 对应URL: /movie/<movie_id>/ 页面中的数据结构
    """
    def __init__(self, title=None, banner=None, directors=None, writers=None, stars=None, genres=None,
                 country=None, release_date=None, runtime=None, akaname=None, star=None,  story=None,
                 episode_filters=None, links=None, related_resources=None, recommended_resources=None):
        self.title = title  # 标题
        self.banner = banner  # 封面图URL
        self.directors = directors  # 导演
        self.writers = writers  # 编剧
        self.stars = stars  # 主演
        self.genres = genres  # 类型
        self.country = country  # 国家/地区
        self.release_date = release_date  # 上映日期
        self.runtime = runtime  # 片长
        self.akaname = akaname  # 又名
        self.star = star  # 评分
        self.story = story  # 剧情简介
        self.episode_filters = episode_filters  # 分集查看过滤列表
        self.links = links  # 下载页面链接列表
        self.related_resources = related_resources  # 相关资源列表
        self.recommended_resources = recommended_resources  # 推荐资源列表

    def info(self):
        return DictObj({
            "title": self.title,
            "banner": self.banner,
            "directors": self.directors,
            "writers": self.writers,
            "stars": self.stars,
            "genres": self.genres,
            "country": self.country,
            "release_date": self.release_date,
            "runtime": self.runtime,
            "akaname": self.akaname,
            "star": self.star,
            "story": self.story,
            "episode_filters": self.episode_filters,
            "links": self.links,
            "related_resources": self.related_resources,
            "recommended_resources": self.recommended_resources,
        })
<file_sep>/util/common.py
# coding=utf-8


class DictObj(dict):
    """A dict that allows for object-like property access syntax."""

    def __init__(self, *args, **kwargs):
        super(DictObj, self).__init__(*args, **kwargs)
        for k, v in args[0].items():
            if type(v) == dict:
                self[k] = DictObj(v)
            elif type(v) == list:
                self[k] = [DictObj(value) if type(value) == dict else value for value in v]

    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            return None

    def __setattr__(self, key, value):
        self[key] = value


def get_string(content, default_value=""):
    if content is None:
        return default_value
    return content


def not_none_number(content, default_value=0):
    if content is None:
        return default_value
    return content
<file_sep>/model/HotInfo.py
# coding=utf-8

from util.common import DictObj


class HotInfo:
    """
    热门信息model. 对应于首页中的热门剧集/电影
    """
    def __init__(self, category, resources):
        self.category = category
        self.resources = resources

    def info(self):
        return DictObj({
            "category": self.category,
            "resources": self.resources,
        })
<file_sep>/model/MovieSimpleInfo.py
# coding=utf-8

from util.common import DictObj


class MovieSimpleInfo:
    """
    电影/电视剧简略信息model. 对应URL: /new/movie/ 与 /new/tv/ 页面中的数据结构
    """
    def __init__(self, title=None, movie_link=None, banner=None, genres=None, country=None, star=None):
        self.title = title  # 标题
        self.movie_link = movie_link  # 电影详情页面链接
        self.banner = banner  # 封面图URL
        self.genres = genres  # 类型
        self.country = country  # 国家/地区
        self.star = star  # 评分

    def info(self):
        return DictObj({
            "title": self.title,
            "movie_link": self.movie_link,
            "banner": self.banner,
            "genres": self.genres,
            "country": self.country,
            "star": self.star,
        })
<file_sep>/controllers/movie.py
# coding=utf-8

from flask_restful import Resource

from app import cache
from config import config
from model import MovieInfo, LinkInfo, ResourceInfo
from util.helper import *


class Movie(Resource):
    @cache.memoize(timeout=config.CACHE_EXPIRE_TIME)
    def get(self, movie_id):
        movie_soup = get_html_soup('%s/movie/%s' % (config.API_DOMAIN, movie_id))

        # "电影/电视剧"标题
        title_div = movie_soup.find('div', id='movie_title')
        if title_div is None:  # 当不存在标题项时, 表示无法查看电影的详情页(很可能是因为用户权限问题). 这里直接返回None
            return success({"movie": None})

        title = movie_soup.find('div', id='movie_title').text.strip()

        # 基本信息字段
        infos_div = movie_soup.find('div', id='movie_info')
        banner = infos_div.find('img')['src']
        directors = get_p_text(infos_div, 'directors')
        writers = get_p_text(infos_div, 'writers')
        stars = get_p_text(infos_div, 'stars')
        genres = get_p_text(infos_div, 'genres')
        country = get_p_text(infos_div, 'country')
        release_date = get_p_text(infos_div, 'release_date')
        runtime = get_p_text(infos_div, 'runtime')
        akaname = get_p_text(infos_div, 'akaname')
        star = get_p_text(infos_div, 'star')

        # 分集查看过滤列表
        filters_div = movie_soup.find('div', id='episode_filter')
        episode_filters = [{'episode': li_tag.a['data-ep'], 'text': li_tag.text.strip()}
                            for li_tag in filters_div.find_all('li')] if filters_div is not None else []

        # 下载链接信息列表
        link_infos = []
        for tr in movie_soup.find('tbody').find_all('tr'):
            episode = tr['data-episode']
            name_tag = tr.find('td', class_='movie_name')
            if name_tag is None:
                continue

            td_tags = tr.find_all('td')
            name = td_tags[0].text.strip()
            size = td_tags[1].text.strip()
            dimen = td_tags[2].text.strip()
            format = td_tags[3].text.strip()
            link = td_tags[4].a['href'] if td_tags[4].a is not None else ''

            link_info = LinkInfo(name=name, size=size, dimen=dimen, format=format, link=link, episode=episode)
            link_infos.append(link_info.info())

        # 相关影视/猜你喜欢列表
        related_resources = []
        recommended_resources = []
        for movie_rel_div in movie_soup.find_all('div', class_='movie_rel'):
            ul_tag = movie_rel_div.find('ul', id='related_movies')
            if ul_tag is None:
                continue

            li_tags = ul_tag.find_all('li')
            if li_tags is None or len(li_tags) <= 0:
                continue

            rel_resources = []
            for li in li_tags:
                resource_title = li.text
                resouces_movie_link = li.a['href'] if li.a is not None else ''
                resouces_info = ResourceInfo(title=resource_title, movie_link=resouces_movie_link)
                rel_resources.append(resouces_info.info())

            rel_cat_title = movie_rel_div.find('h2').text
            if rel_cat_title == '相关影视':
                related_resources = rel_resources
            elif rel_cat_title == '猜你喜欢':
                recommended_resources = rel_resources

        # 剧情简介
        story_tag = movie_soup.find('p', id='movie_info')
        story = story_tag.text.strip('\n').strip() if story_tag is not None else ''
        story = '\n'.join([line.strip('\n').strip() for line in story.split('\n')])

        movie_info = MovieInfo(title=title, banner=banner, directors=directors, writers=writers, stars=stars,
                               genres=genres, country=country, release_date=release_date, runtime=runtime,
                               akaname=akaname, star=star, story=story,
                               episode_filters=episode_filters, links=link_infos,
                               related_resources=related_resources, recommended_resources=recommended_resources)

        return success({
            "movie": movie_info.info()
        })

    def __repr__(self):
        return "%s" % self.__class__.__name__
<file_sep>/util/const.py
# coding=utf-8


STATUS_SUCCESS = 1
STATUS_FAIL = 0
<file_sep>/controllers/search.py
# coding=utf-8

from flask_restful import Resource

from app import cache
from config import config
from model import MovieSimpleInfo
from util.helper import *


class Search(Resource):
    @cache.memoize(timeout=config.CACHE_EXPIRE_TIME)
    def get(self, keyword, page):
        movies = get_movie_simple_infos('%s/%s/%s' % (config.API_SEARCH, keyword, page))
        return success({
            "movies": movies
        })

    def __repr__(self):
        return "%s" % self.__class__.__name__


def get_movie_simple_infos(url):
    movie_soup = get_html_soup(url)
    search_result_div = movie_soup.find('div', id='search_result')
    ul_tag = search_result_div.find('ul')

    movie_simple_infos = []
    for li_tag in ul_tag.find_all('li'):
        # 标题
        title_tag = li_tag.find('a', class_='name')
        title = title_tag.text.strip() if title_tag is not None else ''
        movie_link = title_tag['href'] if title_tag is not None else ''

        # 封面图片
        banner_img_tag = li_tag.find('img', class_='banner')
        banner = banner_img_tag['src'] if banner_img_tag is not None else ''

        genres = get_span_text(li_tag, 'genres')  # 类型
        country = get_span_text(li_tag, 'country')  # 国家/地区
        star = get_span_text(li_tag, 'star')  # 评分
        movie_simple_info = MovieSimpleInfo(title=title, movie_link=movie_link, banner=banner, genres=genres,
                                            country=country, star=star)
        movie_simple_infos.append(movie_simple_info.info())
    return movie_simple_infos
<file_sep>/deploy.sh
#!/bin/bash
set -e

cp ./etc/supervisor/moviejie_api.conf /etc/supervisor/conf.d/moviejie_api.conf
supervisord -c /etc/supervisor/supervisord.conf
<file_sep>/model/LinkInfo.py
# coding=utf-8

from util.common import DictObj


class LinkInfo:
    """
    下载链接信息model. 对应URL: /movie/<movie_id>/ 页面中下载链接列表每项的数据结构
    """
    def __init__(self, name=None, size=None, dimen=None, format=None, link=None, episode=None):
        self.name = name
        self.size = size
        self.dimen = dimen
        self.format = format
        self.link = link
        self.episode = episode

    def info(self):
        return DictObj({
            "name": self.name,
            "size": self.size,
            "dimen": self.dimen,
            "format": self.format,
            "link": self.link,
            "episode": self.episode
        })
<file_sep>/util/helper.py
# coding=utf-8

from __future__ import print_function
import json
import os

from bs4 import BeautifulSoup
import requests
import requests.packages.urllib3

from config import config
from util import const
from util import log


requests.packages.urllib3.disable_warnings()


def get_html(url):
    try:
        response = requests.get(url, verify=False, headers=config.SIMULATE_HEADERS, cookies=config.LOGIN_COOKIES)
        return response.text
    except Exception as e:
        print_error("Error: %s\n get_url_content -- %s" % (e, url))
        return None


def get_html_soup(url):
    html = get_html(url)
    return get_soup(html)


def get_soup(html):
    return BeautifulSoup(html, "lxml")


def send_request(url, json_data):
    try:
        headers = {"Content-Type": "application/json; charset=utf-8"}
        response = requests.post(url=url, json=json_data, headers=headers, verify=False)
        return response.text
    except Exception as e:
        print_error("Error: %s\nsend_post_requestion -- %s" % (e, url))
        return None


def save_content_to_file(file_path, content):
    f = open(file_path, 'a')
    f.write(content)
    f.close()


def remove_file(file_path):
    if os.path.exists(file_path):
        os.remove(file_path)


def print_error(text):
    print('\033[0;31;40m%s' % text)


def json_dumps(content):
    return json.dumps(content, ensure_ascii=False)


def success(info=None, max_log_len=2000):
    if info is None:
        info = {}
    info["status"] = const.STATUS_SUCCESS
    response_body = json_dumps(info)
    log.debug("response:%s", response_body[0:max_log_len])
    return json.loads(response_body)


def fail(info=None, max_log_len=2000):
    if info is None:
        info = {}
    info["status"] = const.STATUS_FAIL
    response_body = json_dumps(info)
    log.debug("response:%s", response_body[0:max_log_len])
    return json.loads(response_body)


def get_p_text(tag, class_name):
    return get_tag_child_text(tag, child_tag_name='p', class_name=class_name)


def get_span_text(tag, class_name):
    return get_tag_child_text(tag, child_tag_name='span', class_name=class_name)


def get_tag_child_text(tag, child_tag_name, class_name):
    child_tag = tag.find(child_tag_name, class_=class_name)
    if child_tag is not None:
        text_list = child_tag.text.split('：')[1:]
        return ''.join(text_list).strip()
    return ''
<file_sep>/controllers/new.py
# coding=utf-8

from flask_restful import Resource

from app import cache
from config import config
from model import OSTSimpleInfo
from util.helper import *
from .search import *


class NewMovie(Resource):
    @cache.memoize(timeout=config.CACHE_EXPIRE_TIME)
    def get(self, page):
        movies = get_movie_simple_infos('%s/%s' % (config.API_NEW_MOVIE, page))
        return success({
            "movies": movies
        })

    def __repr__(self):
        return "%s" % self.__class__.__name__


class NewTv(Resource):
    @cache.memoize(timeout=config.CACHE_EXPIRE_TIME)
    def get(self, page):
        movies = get_movie_simple_infos('%s/%s' % (config.API_NEW_TV, page))
        return success({
            "movies": movies
        })

    def __repr__(self):
        return "%s" % self.__class__.__name__


class NewOST(Resource):
    @cache.memoize(timeout=config.CACHE_EXPIRE_TIME)
    def get(self, page):
        ost_soup = get_html_soup('%s/%s' % (config.API_NEW_OST, page))
        search_result_div = ost_soup.find('div', id='search_result')
        ul_tag = search_result_div.find('ul')

        ost_simple_infos = []
        for li_tag in ul_tag.find_all('li'):
            # 标题
            title_tag = li_tag.find('p', class_='movie_name')
            movie_name = title_tag.text.strip() if title_tag is not None else ''

            # 原声大碟详情页面链接
            ost_link = title_tag.a['href'] if title_tag.a is not None else ''

            # 封面图片
            banner_img_tag = li_tag.find('img', class_='banner')
            banner = banner_img_tag['src'] if banner_img_tag is not None else ''

            res_name = get_p_text(li_tag, 'res_name')  # 资源名称
            res_size = get_p_text(li_tag, 'res_size')  # 资源大小
            country = get_span_text(li_tag, 'country')  # 地区/语言
            publish_time = get_span_text(li_tag, 'time')  # 发行时间
            file_type = get_span_text(li_tag, 'filetype')  # 资源格式
            ost_simple_info = OSTSimpleInfo(movie_name=movie_name, ost_link=ost_link, banner=banner,
                                            res_name=res_name, res_size=res_size, country=country,
                                            publish_time=publish_time, file_type=file_type)
            ost_simple_infos.append(ost_simple_info.info())
        return success({
            "ost_infos": ost_simple_infos
        })

    def __repr__(self):
        return "%s" % self.__class__.__name__
<file_sep>/model/ResourceInfo.py
# coding=utf-8

from util.common import DictObj


class ResourceInfo:
    """
    资源信息model. 对应于首页的"更新/热门"资源数据结构
    """
    def __init__(self, title, movie_link="", link="", size="", rating=""):
        self.title = title
        self.movie_link = movie_link
        self.link = link
        self.size = size
        self.rating = rating

    def info(self):
        return DictObj({
            "title": self.title,
            "movie_link": self.movie_link,
            "link": self.link,
            "size": self.size,
            "rating": self.rating,
        })
<file_sep>/model/OSTSimpleInfo.py
# coding=utf-8

from util.common import DictObj


class OSTSimpleInfo:
    """
    原声大碟简略信息model. 对应URL: /new/ost/ 页面中的数据结构
    """
    def __init__(self, movie_name=None, ost_link=None, banner=None, res_name=None,
                 res_size=None, country=None, publish_time=None, file_type=None):
        self.movie_name = movie_name  # 电影名称
        self.ost_link = ost_link  # 原声大碟详情页面链接
        self.banner = banner  # 封面图URL
        self.res_name = res_name  # 资源名称
        self.res_size = res_size  # 资源大小
        self.country = country  # 地区/语言
        self.publish_time = publish_time  # 发行时间
        self.file_type = file_type  # 资源格式

    def info(self):
        return DictObj({
            "movie_name": self.movie_name,
            "ost_link": self.ost_link,
            "banner": self.banner,
            "res_name": self.res_name,
            "res_size": self.res_size,
            "country": self.country,
            "publish_time": self.publish_time,
            "file_type": self.file_type,
        })
<file_sep>/model/LinkDetailInfo.py
# coding=utf-8

from util.common import DictObj


class LinkDetailInfo:
    """
    下载链接详情信息model. 对应URL: /link/<link_id>/ 页面中的数据结构
    """
    def __init__(self, movie_title=None, movie_link=None, name=None, size=None, download_link=None):
        self.movie_title = movie_title
        self.movie_link = movie_link
        self.name = name
        self.size = size
        self.download_link = download_link

    def info(self):
        return DictObj({
            "movie_title": self.movie_title,
            "movie_link": self.movie_link,
            "name": self.name,
            "size": self.size,
            "download_link": self.download_link
        })
<file_sep>/controllers/link.py
# coding=utf-8

import time

from flask_restful import Resource

from app import cache
from config import config
from model import LinkDetailInfo
from selenium import webdriver
from util.helper import *


class Link(Resource):
    @cache.memoize(timeout=config.CACHE_EXPIRE_TIME)
    def get(self, link_id):
        link_soup = get_html_soup('%s/link/%s' % (config.API_DOMAIN, link_id))

        # 电影页面链接
        movie_title = ''
        movie_link = ''
        movie_back_div = link_soup.find('div', class_='movie_back')
        if movie_back_div is not None:
            movie_back_a_tag = movie_back_div.a
            if movie_back_a_tag is not None:
                movie_title = movie_back_a_tag.text.strip()
                movie_link = movie_back_a_tag['href']

        # 下载资源名称
        link_info_div = link_soup.find('div', class_='link_info')
        p_tags = link_info_div.find_all('p')
        name = p_tags[0].text.split('：')[1].strip()
        size = p_tags[1].text.split('：')[1].strip()

        # 下载链接地址
        link_parsed_html = ''
        for script_tag in link_soup.find_all('script'):
            if '~[]' in script_tag.text:  # 包括 "~[]" 字符的 javascript 代码, 为生成下载链接的代码
                get_link_html = '<span id="link_text_span"></span>' + \
                                '<script type="text/javascript">' + script_tag.text + '</script>'
                temp_file_path = '%s-%.9f.html' % (link_id, time.time())
                save_content_to_file(temp_file_path, get_link_html)
                
                driver = webdriver.PhantomJS()
                driver.get(temp_file_path)
                link_parsed_html = driver.page_source
                driver.close()

                remove_file(temp_file_path)

        link_parsed_soup = get_soup(link_parsed_html)
        link_tag = link_parsed_soup.find('span', id='link_text_span')
        download_link = link_tag.text.strip() if link_tag is not None else ''
        link_detail_info = LinkDetailInfo(movie_title=movie_title, movie_link=movie_link, name=name, size=size,
                                          download_link=download_link)

        return success({
            "link_detail": link_detail_info.info()
        })

    def __repr__(self):
        return "%s" % self.__class__.__name__
<file_sep>/controllers/__init__.py
# coding=utf-8

from .index import Index
from .movie import Movie
from .link import Link
from .new import NewMovie, NewTv, NewOST
from .ost import OST
from .search import Search


__all__ = [
    Index,
    Movie,
    Link,
    NewMovie,
    NewTv,
    NewOST,
    OST,
    Search,
]
<file_sep>/config/config.py
# coding=utf-8

# 缓存到期时间(单位:秒)
CACHE_EXPIRE_TIME = 1800

# API地址
API_DOMAIN = 'http://moviejie.xyz'
API_INDEX = API_DOMAIN  # 首页地址
API_NEW_MOVIE = API_DOMAIN + "/new/movie"  # 最新电影地址
API_NEW_TV = API_DOMAIN + "/new/tv"  # 最新电视剧地址
API_NEW_OST = API_DOMAIN + "/new/ost"  # 原声大碟地址
API_NEW_ARTICLE = API_DOMAIN + "/new/article"  # 影评地址
API_SEARCH = API_DOMAIN + "/search"  # 搜索地址

# 模拟的Headers
SIMULATE_HEADERS = {
    'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36',
}

# 模拟已登录用户对应的Cookies
LOGIN_COOKIES = {
    'Hm_lvt_85abf7488e97713e5a0e152b2ab2316d': '1554270698',
    'user_nickname': '"2|1:0|10:1565254859|13:user_nickname|8:U2FpNjI4|5dada89845c0bf66ec67d06460bc981d4ba94db6a6d3e7a655968e9505c8fc83"',
    'user_slug': '"2|1:0|10:1565254859|9:user_slug|8:YzI3Yzc0|728464ad8897b87d0f1bd749a2a964605574747d6203978b233f483502f59723"',
    '_xsrf': '2|7fb418cd|c2bdacef396cf02f40f5354bea1e59d9|1564287607',
}
<file_sep>/model/__init__.py
# coding=utf-8

from .HotInfo import HotInfo
from .LinkInfo import LinkInfo
from .LinkDetailInfo import LinkDetailInfo
from .MovieInfo import MovieInfo
from .MovieSimpleInfo import MovieSimpleInfo
from .NewInfo import NewInfo
from .OSTInfo import OSTInfo
from .OSTSimpleInfo import OSTSimpleInfo
from .ResourceInfo import ResourceInfo


__all__ = [
    HotInfo,
    LinkInfo,
    LinkDetailInfo,
    MovieInfo,
    MovieSimpleInfo,
    NewInfo,
    OSTInfo,
    OSTSimpleInfo,
    ResourceInfo,
]
<file_sep>/model/OSTInfo.py
# coding=utf-8

from util.common import DictObj


class OSTInfo:
    """
    原声大碟信息model. 对应URL: /ost/<ost_id>/ 页面中的数据结构
    """
    def __init__(self, movie_name=None, movie_link=None, banner=None, res_name=None, country=None,
                 language=None, publish_time=None, file_type=None, track_list=None, links=None):
        self.movie_name = movie_name  # 电影名称
        self.movie_link = movie_link  # 电影详情页面链接
        self.banner = banner  # 封面图URL
        self.res_name = res_name  # 资源名称
        self.country = country  # 地区
        self.language = language  # 语言
        self.publish_time = publish_time  # 发行时间
        self.file_type = file_type  # 资源格式
        self.track_list = track_list  # 专辑曲目列表. 数据类型: [string]
        self.links = links  # 下载页面链接列表

    def info(self):
        return DictObj({
            "movie_name": self.movie_name,
            "movie_link": self.movie_link,
            "banner": self.banner,
            "res_name": self.res_name,
            "country": self.country,
            "language": self.language,
            "publish_time": self.publish_time,
            "file_type": self.file_type,
            "track_list": self.track_list,
            "links": self.links,
        })
<file_sep>/web.py
# coding=utf-8

from app import app


if __name__ == '__main__':
    app.run(host='localhost', port=5001, debug=True)
<file_sep>/app/__init__.py
# coding=utf-8

# import sys

from flask import Flask
from flask_cache import Cache
import flask_restful as restful


# 解决输出中文报错 UnicodeEncodeError 的问题
# reload(sys)
# sys.setdefaultencoding('utf-8')


app = Flask(__name__)
api = restful.Api(app)
cache = Cache(app, config={'CACHE_TYPE': 'simple'})


import controllers
api.add_resource(controllers.Index, '/')
api.add_resource(controllers.Movie, '/movie/<string:movie_id>/')
api.add_resource(controllers.Link, '/link/<string:link_id>/')
api.add_resource(controllers.OST, '/ost/<string:ost_id>/')
api.add_resource(controllers.NewMovie, '/new/movie/<string:page>/')
api.add_resource(controllers.NewTv, '/new/tv/<string:page>/')
api.add_resource(controllers.NewOST, '/new/ost/<string:page>/')
api.add_resource(controllers.Search, '/search/<string:keyword>/<string:page>/')
<file_sep>/requirements.txt
aniso8601==1.2.1
appdirs==1.4.3
beautifulsoup4==4.6.0
certifi==2018.10.15
chardet==3.0.4
click==6.7
Flask==1.0.0
Flask-Cache==0.13.1
Flask-RESTful==0.3.5
gunicorn==19.8.1
idna==2.7
itsdangerous==0.24
Jinja2==2.10.1
lxml==3.7.3
MarkupSafe==1.0
packaging==16.8
pyparsing==2.2.0
python-dateutil==2.6.0
pytz==2017.2
requests==2.20.0
selenium==3.6.0
six==1.10.0
urllib3==1.24.2
werkzeug==0.15.3
<file_sep>/util/log.py
# coding=utf-8

from app import app


debug = app.logger.debug
info = app.logger.info
warning = app.logger.warning
error = app.logger.error
<file_sep>/controllers/ost.py
# coding=utf-8

from flask_restful import Resource

from app import cache
from config import config
from model import OSTInfo, LinkInfo
from util.helper import *


class OST(Resource):
    @cache.memoize(timeout=config.CACHE_EXPIRE_TIME)
    def get(self, ost_id):
        ost_soup = get_html_soup('%s/ost/%s' % (config.API_DOMAIN, ost_id))

        # "电影/电视剧"标题
        title_div = ost_soup.find('div', id='ost_title')
        if title_div is not None:
            movie_name = title_div.a.text.strip() if title_div.a is not None else ''
            movie_link = title_div.a['href'] if title_div.a is not None else ''

        # 基本信息字段
        banner = ost_soup.find('img', class_='ost_cover')['src']
        res_name = get_tag_child_text(tag=ost_soup, child_tag_name='h2', class_name='res_title')
        country = get_span_text(ost_soup, 'country')
        language = get_span_text(ost_soup, 'language')
        publish_time = get_span_text(ost_soup, 'time')
        file_type = get_span_text(ost_soup, 'filetype')

        # 专辑曲目列表
        track_list = []
        track_tag = ost_soup.find('p', class_='inner_content')
        if track_tag is not None:
            # 去除曲目 html 中的 "<!--Wrap-head end-->" 与 "<!--Wrap-tail begin-->" 字符, 以及 "<br>" 标签
            tracks = filter(lambda x: x not in ['\n', 'Wrap-tail begin', 'Wrap-head end'] and str(x) != '<br/>',
                            track_tag.contents)
            track_list = ["".join(str(track).split('.')[1:]).strip() for track in tracks]  # 去除曲目前面的序号

        # 专辑曲目下载链接信息列表
        link_infos = []
        for tr in ost_soup.find('table').find_all('tr'):
            name_tag = tr.find('td', class_='movie_name')
            if name_tag is None:
                continue

            td_tags = tr.find_all('td')
            name = td_tags[0].text.strip()
            size = td_tags[1].text.strip()
            link = td_tags[2].a['href'] if td_tags[2].a is not None else ''

            link_info = LinkInfo(name=name, size=size, link=link)
            link_infos.append(link_info.info())

        ost_info = OSTInfo(movie_name=movie_name, movie_link=movie_link, banner=banner, res_name=res_name,
                           country=country, language=language, publish_time=publish_time, file_type=file_type,
                           track_list=track_list, links=link_infos)
        return success({
            "ost": ost_info.info()
        })

    def __repr__(self):
        return "%s" % self.__class__.__name__
