Python 101:如何从RottenTomatoes爬取数据

今天,我们将研究如何从热门电影网站Rotten Tomatoes爬取数据。你需要在这里注册一个API key。当你拿到key时,记下你的使用限制(如每分钟限制的爬取次数)。你不要对API进行超限调用,这可能会使key失效。最后,阅读你将要使用的API的文档是一个好办法。这里有几个链接:

你可以把这些文档保存起来稍后再看,现在我们继续。

开始

Rotten Tomatoes的API提供了一套可以从中提取数据的json模板。我们将使用requestssimplejson来获取数据并处理它。让我们写一个可以获取当前正在播放的电影小脚本。

# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - - -- -- -- -- -- -- -- -- -- -- 
def getInTheaterMovies ( ) : 
    "" "
    Get a list of movies in theaters .  
    "" "
    key =  "你的API key" 
    url =  "http://api.rottentomatoes.com/api/public/v1.0/lists/movies/in_theaters.json?apikey=%s" 
    res = requests . get ( url % key )
 
    data = res . content
 
    js = simplejson . loads ( data )
 
    movies = js [ "movies" ] 
    for movie in movies : 
        print movie [ "title" ]    #这里用的是Pyhon2,需要适当的修改来兼容Python3
 
# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - - -- -- -- -- -- -- -- -- -- -- 
if __name__ ==  "__main__" : 
    getInTheaterMovies ( )

如果你运行这个代码,你会看到一个打印出来的电影列表。我得到了以下输出:

Free Birds

Gravity

Ender's Game

Jackass Presents: Bad Grandpa

Last Vegas

The Counselor

Cloudy with a Chance of Meatballs 2

Captain Phillips

Carrie

Escape Plan

Enough Said

Insidious: Chapter 2

12 Years a Slave

We're The Millers

Prisoners

Baggage Claim

在上面的代码中,我们使用API key构建了一个URL并获得数据。然后我们将数据加载到Python嵌套字典的simplejson中。接下来,我们循环遍历电影字典(dictionary)并打印出每部电影的标题。现在我们准备创建一个新功能,从Rotten Tomatoes中提取关于这些电影中的每一个附加信息。

import requests
import simplejson
import urllib
 
# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - - -- -- -- -- -- -- -- -- -- -- 
def getMovieDetails ( key , title ) : 
    "" "
    Get additional movie details
    "" "
     if  " "  in title : 
        parts = title . split ( " " ) 
        title =  "+" . join ( parts )
 
    link =  "http://api.rottentomatoes.com/api/public/v1.0/movies.json" 
    url =  "%s?apikey=%s&q=%s&page_limit=1" 
    url = url %  ( link , key , title ) 
    res = requests . get ( url ) 
    js = simplejson . loads ( res . content )
 
    for movie in js [ "movies" ] : 
        print "rated: %s"  % movie [ "mpaa_rating" ] 
        print "movie synopsis: "  + movie [ "synopsis" ] 
        print "critics_consensus: "  + movie [ "critics_consensus" ]
 
        print "Major cast:" 
        for actor in movie [ "abridged_cast" ] : 
            print "%s as %s"  %  ( actor [ "name" ] , actor [ "characters" ] [ 0 ] )
 
        ratings = movie [ "ratings" ] 
        print "runtime: %s"   % movie [ "runtime" ] 
        print "critics score: %s"  % ratings [ "critics_score" ] 
        print "audience score: %s"  % ratings [ "audience_score " ] 
        print "for more information: %s"  % movie [ "links" ] [ "alternate" ] 
    print "-" *  40
    print
 
# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - - -- -- -- -- -- -- -- -- -- -- 
def getInTheaterMovies ( ) : 
    "" "
    Get a list of movies in theaters .  
    "" "
    key =  "你的 API CODE" 
    url =  "http://api.rottentomatoes.com/api/public/v1.0/lists/movies/in_theaters.json?apikey=%s" 
    res = requests . get ( url % key )
 
    data = res . content
 
    js = simplejson . loads ( data )
 
    movies = js [ "movies" ] 
    for movie in movies : 
        print movie [ "title" ] 
        getMovieDetails ( key , movie [ "title" ] ) 
    print
 
# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - - -- -- -- -- -- -- -- -- -- -- 
if __name__ ==  "__main__" : 
    getInTheaterMovies ( )

这个新代码提取了关于每部电影的大量数据,但json的返回包含了更多信息,我们没有全部展示出来。只需将js字典输出到stdout 即可看到还有什么没显示出来,或者你可以在Rotten Tomatoes 文档页面看到一个返回json的示例。如果你仔细观察,你就会发现Rotten Tomatoes API并没有涵盖他们网站上的全部数据。例如,没有办法获取电影的演员信息。例如,如果我们想知道Jim Carrey参演过的电影,没有公开的API可供利用。你也不能查看演出表中的其他人,如导演或制片人。这些信息网站上都有,API没有被公开。为此,我们不得不求助于互联网电影数据库(IMDB),在这里我们队这个问题不会继续讨论。

让我们花点时间改进这个例子。一个简单的改进是将API key放入配置文件中(这样就不会很容易地被别人一眼就看到)。另一个存储我们爬取到的信息。第三个改进是添加一些代码来检查我们是否已经下载了今天的全部电影,因为实际上没有理由每天下载一次全部的数据!

添加配置文件

我更喜欢并推荐ConfigObj来处理配置文件。让我们用下面的代码创建一个简单的“config.ini”文件:

api_key = API KEY
last_downloaded =

现在来改变我们的代码导入ConfigObj并更改getInTheaterMovies函数来使用它:

import requests
import simplejson
import urllib
 
from configobj import ConfigObj
 
# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - - -- -- -- -- -- -- -- -- -- -- 
def getInTheaterMovies ( ) : 
    "" "
    Get a list of movies in theaters .  
    "" "
    config =  ConfigObj ( "config.ini" ) 
    key = config [ "Settings" ] [ "api_key" ] 
    url =  "http://api.rottentomatoes.com/api/public/v1.0/lists/movies/in_theaters. json?apikey=%s" 
    res = requests . get ( url % key )
 
    data = res . content
 
    js = simplejson . loads ( data )
 
    movies = js [ "movies" ] 
    for movie in movies : 
        print movie [ "title" ] 
        getMovieDetails ( key , movie [ "title" ] ) 
    print
 
# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - - -- -- -- -- -- -- -- -- -- -- 
if __name__ ==  "__main__" : 
    getInTheaterMovies ( )

正如你所看到的,我们导入configobj并将Key等信息载入。您也可以使用绝对路径。接下来我们提取api_key的值并在我们的URL中使用它。由于我们的配置中有一个last_downloaded值,因此我们应该将其添加到我们的代码中,以防止我们每天下载重复数据。

import datetime
import requests
import simplejson
import urllib
 
from configobj import ConfigObj
 
# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - - -- -- -- -- -- -- -- -- -- -- 
def getInTheaterMovies ( ) : 
    "" "
    Get a list of movies in theaters .  
    "" "
    today = datetime . datetime . today ( ) . strftime ( "%Y%m%d" ) 
    config =  ConfigObj ( "config.ini" )
 
    if today != config [ "Settings" ] [ "last_downloaded" ] : 
        config [ "Settings" ] [ "last_downloaded" ]  = today
 
        try :  
            with  open ( "config.ini" ,  "w" )  as cfg : 
                config . write ( cfg ) 
        except IOError : 
            print "Error writing file!" 
            return
 
        key = config [ "Settings" ] [ "api_key" ] 
        url =  "http://api.rottentomatoes.com/api/public/v1.0/lists/movies/in_theaters.json?apikey=%s" 
        res = requests . get ( url % key )
 
        data = res . content
 
        js = simplejson . loads ( data )
 
        movies = js [ "movies" ] 
        for movie in movies : 
            print movie [ "title" ] 
            getMovieDetails ( key , movie [ "title" ] ) 
        print
 
# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - - -- -- -- -- -- -- -- -- -- -- 
if __name__ ==  "__main__" : 
    getInTheaterMovies ( )

在这里,我们导入Python的日期时间(datetime)模块,并使用如下格式获取今天的日期:YYYYMMDD。接下来我们检查配置文件的last_downloaded值是否等于今天的日期。如果相等,我们什么都不做。但是,如果它们不匹配,我们将last_downloaded设置为今天的日期,然后我们下载电影数据。现在我们准备了解如何将数据保存到数据库。

把数据保存到SQLite数据库

自2.5版本起,Python支持原生SQLite数据库,因此除非您使用的是旧版本的Python,否则您应该顺利地完成这一部分。大致上,我们只需要添加一个可以创建数据库并将数据保存到其中的函数。函数如下:

# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - - -- -- -- -- -- -- -- -- -- -- 
def saveData ( movie ) : 
    "" "
    Save the data to a SQLite database
    "" "
     if not os . path . exists ( "movies.db" ) :
        # create the database
        conn = sqlite3 . connect ( "movies.db" )
 
        cursor = conn . cursor ( )
 
        cursor . execute ( "" "CREATE TABLE movies  
        ( title text , rated text , movie_synopsis text , 
        critics_consensus text , runtime integer , 
        critics_score integer , audience_score integer ) "" " )
 
        cursor . execute ( "" "
        CREATE TABLE cast 
        ( actor text ,  
        character text ) 
        "" " )
 
        cursor . execute ( "" "
        CREATE TABLE movie_cast 
        ( movie_id integer ,  
        cast_id integer , 
        FOREIGN KEY ( movie_id ) REFERENCES movie ( id ) , 
        FOREIGN KEY ( cast_id ) REFERENCES cast ( id ) 
        ) 
        "" " ) 
    else : 
        conn = sqlite3 . connect ( "movies.db" ) 
        cursor = conn .cursor ( )
 
    # insert the data
    print
    sql =  "INSERT INTO movies VALUES(?, ?, ?, ?, ?, ?, ?)" 
    cursor . execute ( sql ,  ( movie [ "title" ] , 
                         movie [ "mpaa_rating" ] , 
                         movie [ "synopsis" ] , 
                         movie [ "critics_consensus" ] , 
                         movie [ "runtime" ] , 
                         movie [ "ratings"] ["critics_score" ] , 
                         movie [ "ratings" ] [ "audience_score" ] 
                         ) 
                   ) 
    movie_id = cursor . lastrowid
 
    for actor in movie [ "abridged_cast" ] : 
        print "%s as %s"  %  ( actor [ "name" ] , actor [ "characters" ] [ 0 ] ) 
        sql =  "INSERT INTO cast VALUES(?, ?)" 
        cursor . execute ( sql ,  ( actor [ "name" ] , 
                             actor [ "characters"] [ 0 ]
                             ) 
                       ) 
        cast_id = cursor . lastrowid
 
        sql =  "INSERT INTO movie_cast VALUES(?, ?)" 
        cursor . execute ( sql ,  ( movie_id , cast_id )  )
 
    conn . commit ( ) 
    conn . close ( )

该代码首先检查数据库文件是否已经存在。如果不存在,那么它将创建1个数据库以及3个表。否则,saveData函数将创建一个数据库连接和一个Cursor(游标)对象。接下来,它将把影片字典数据插入数据库。我们将调用该函数并从getMovieDetails函数传递电影字典。最后,我们将数据提交到数据库并关闭连接。

您可能想知道完整的代码是什么样子。那么:

import datetime
import os
import requests
import simplejson
import sqlite3
import urllib
 
from configobj import ConfigObj
 
# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - - -- -- -- -- -- -- -- -- -- -- 
def getMovieDetails ( key , title ) : 
    "" "
    Get additional movie details
    "" "
     if  " "  in title : 
        parts = title . split ( " " ) 
        title =  "+" . join ( parts )
 
    link =  "http://api.rottentomatoes.com/api/public/v1.0/movies.json" 
    url =  "%s?apikey=%s&q=%s&page_limit=1" 
    url = url %  ( link , key , title ) 
    res = requests . get ( url ) 
    js = simplejson . loads ( res . content )
 
    for movie in js [ "movies" ] : 
        print "rated: %s"  % movie [ "mpaa_rating" ] 
        print "movie synopsis: "  + movie [ "synopsis" ] 
        print "critics_consensus: "  + movie [ "critics_consensus" ]
 
        print "Major cast:" 
        for actor in movie [ "abridged_cast" ] : 
            print "%s as %s"  %  ( actor [ "name" ] , actor [ "characters" ] [ 0 ] )
 
        ratings = movie [ "ratings" ] 
        print "runtime: %s"   % movie [ "runtime" ] 
        print "critics score: %s"  % ratings [ "critics_score" ] 
        print "audience score: %s"  % ratings [ "audience_score " ] 
        print "for more information: %s"  % movie [ "links" ] [ "alternate"] 
        saveData ( movie ) 
    print"-"  *  40
    print
 
# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - - -- -- -- -- -- -- -- -- -- -- 
def getInTheaterMovies ( ) : 
    "" "
    Get a list of movies in theaters .  
    "" "
    today = datetime . datetime . today ( ) . strftime ( "%Y%m%d" ) 
    config =  ConfigObj ( "config.ini" )
 
    if today != config [ "Settings" ] [ "last_downloaded" ] : 
        config [ "Settings" ] [ "last_downloaded" ]  = today
 
        try :  
            with  open ( "config.ini" ,  "w" )  as cfg : 
                config . write ( cfg ) 
        except IOError : 
            print "Error writing file!" 
            return
 
        key = config [ "Settings" ] [ "api_key" ] 
        url =  "http://api.rottentomatoes.com/api/public/v1.0/lists/movies/in_theaters.json?apikey=%s" 
        res = requests . get ( url % key )
 
        data = res . content
 
        js = simplejson . loads ( data )
 
        movies = js [ "movies" ] 
        for movie in movies : 
            print movie [ "title" ] 
            getMovieDetails ( key , movie [ "title" ] ) 
        print
 
# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - - -- -- -- -- -- -- -- -- -- -- 
def saveData ( movie ) : 
    "" "
    Save the data to a SQLite database
    "" "
     if not os . path . exists ( "movies.db" ) :
        # create the database
        conn = sqlite3 . connect ( "movies.db" )
 
        cursor = conn . cursor ( )
 
        cursor . execute ( "" "CREATE TABLE movies  
        ( title text , rated text , movie_synopsis text , 
        critics_consensus text , runtime integer , 
        critics_score integer , audience_score integer ) "" " )
 
        cursor . execute ( "" "
        CREATE TABLE cast 
        ( actor text ,  
        character text ) 
        "" " )
 
        cursor . execute ( "" "
        CREATE TABLE movie_cast 
        ( movie_id integer ,  
        cast_id integer , 
        FOREIGN KEY ( movie_id ) REFERENCES movie ( id ) , 
        FOREIGN KEY ( cast_id ) REFERENCES cast ( id ) 
        ) 
        "" " ) 
    else : 
        conn = sqlite3 . connect ( "movies.db" ) 
        cursor = conn .cursor ( )
 
    # insert the data
    print
    sql =  "INSERT INTO movies VALUES(?, ?, ?, ?, ?, ?, ?)" 
    cursor . execute ( sql ,  ( movie [ "title" ] , 
                         movie [ "mpaa_rating" ] , 
                         movie [ "synopsis" ] , 
                         movie [ "critics_consensus" ] , 
                         movie [ "runtime" ] , 
                         movie [ "ratings"] ["critics_score" ] , 
                         movie [ "ratings" ] [ "audience_score" ] 
                         ) 
                   ) 
    movie_id = cursor . lastrowid
 
    for actor in movie [ "abridged_cast" ] : 
        print "%s as %s"  %  ( actor [ "name" ] , actor [ "characters" ] [ 0 ] ) 
        sql =  "INSERT INTO cast VALUES(?, ?)" 
        cursor . execute ( sql ,  ( actor [ "name" ] , 
                             actor [ "characters"] [ 0 ]
                             ) 
                       ) 
        cast_id = cursor . lastrowid
 
        sql =  "INSERT INTO movie_cast VALUES(?, ?)" 
        cursor . execute ( sql ,  ( movie_id , cast_id )  )
 
    conn . commit ( ) 
    conn . close ( )
 
# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - - -- -- -- -- -- -- -- -- -- -- 
if __name__ ==  "__main__" : 
    getInTheaterMovies ( )

如果您使用Firefox游览器,那么可以使用名为SQLite Manager的插件可视化我们创建的数据库。以下是本文实验产生的截图:

数据库可视化

总结

还有很多功能应该被添加。例如,我们需要getInTheaterMovies函数中的一些代码,如果我们已经获得当前数据,它将从数据库加载详细信息。我们还需要向数据库添加一些逻辑,以防止我们多次添加相同的演员或电影。如果我们有某种图形用户界面或网络界面,那将会很好。这些都是你可以添加的一些有趣的小练习。

顺便说一句,这篇文章的灵感来自于Michael Herman的Real Python for the Web一书。它有很多的想法和例子,你可以在这里查看

本文的版权归 轻吻晴雯 所有,如需转载请联系作者。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏liuchengxu

在 Golang 开发中使用 Makefile

使用 Golang 已经有一阵了,在 Golang 的开发过程中,我已经习惯于不断重复地手动执行 go build 和 go test 这两个命令. 不过,现...

1471
来自专栏文渊之博

SQL Server内存

背景 最近一个客户找到我说是所有的SQL Server 服务器的内存都被用光了,然后截图给我看了一台服务器的任务管理器。如图 ? 这里要说明一下任务管理器不会完...

3347
来自专栏信安之路

利用DNS协议回显数据

这个问题已经是去年提出的了,之前也看到过,在 CTF 题目环境中利用过却对原理不慎了解,在公司大佬们的帮助下成功了理解了一波原理。

1490
来自专栏PingCAP的专栏

TiDB 2.1 GA Release Notes

2018 年 11 月 30 日,TiDB 发布 2.1 GA 版。相比 2.0 版本,该版本对系统稳定性、性能、兼容性、易用性做了大量改进。

1570
来自专栏杨建荣的学习笔记

关于修改分区表的准备和操作细则(r3笔记26天)

在之前的博文中,讨论过一个根据分区键值发现性能问题的案例。90%以上的数据都分布在了一个分区上,其它的分区要么没有数据要么数据很少,这是很明显的分区问题。当然这...

3056
来自专栏菩提树下的杨过

利用Lucene打造站内搜索引擎的思路

1.为什么要用Lucene,而不用直接从数据库里搜索记录? 主要是考虑到几个因素:(1)性能问题,Lucene是基于文件索引的搜索机制,性能要比数据库里检索更...

1945
来自专栏MongoDB中文社区

MongoDB事务模型分析

在了解写操作的事务性之前,需要先了解mongo层的每一个table,是如何与wiredtiger层的table(btree)对应的。mongo层一个最简单的ta...

2402
来自专栏乐沙弥的世界

Oracle Time Model Statistics(时间模型统计)

下图为 DB Time in Overall User Response Time

1092
来自专栏IT笔记

我用Python实现了一个小说网站雏形

前段时间做了一个爬取妹子套图的小功能,小伙伴们似乎很有兴趣,为了还特意组建了一个Python兴趣学习小组,来一起学习。十个python九个爬,在大家的印象中好像...

2332
来自专栏java一日一条

单机数据库优化的一些实践

数据库优化有很多可以讲,按照支撑的数据量来分可以分为两个阶段:单机数据库和分库分表,前者一般可以支撑500W或者10G以内的数据,超过这个值则需要考虑分库分表。...

952

扫码关注云+社区

领取腾讯云代金券