设为首页收藏本站

Crossin的编程教室

 找回密码
 立即加入
查看: 52001|回复: 66
打印 上一主题 下一主题

Python 实战(5):拿来主义

[复制链接]

174

主题

45

好友

11万

积分

管理员

Rank: 9Rank: 9Rank: 9

跳转到指定楼层
楼主
发表于 2015-5-17 23:32:10 |只看该作者 |正序浏览
someweb-05.png
有了列表,有了详细信息,有了搜索,这个电影网站已经有了基本的结构。现在要做的是:获取更多的内容。

我们没有必要也不可能自己去生产数量庞大的电影信息,互联网上的资源已足够满足我们的需求。(不过如果你要使用这些资源进行商业用途,请尊重内容来源方的版权。)

这个项目里,我将用豆瓣电影的 API 来获取内容。不要问我如何知道豆瓣有 API 可以做这样的事。我只是觉得它应该有,然后就去搜索引擎里搜索“豆瓣 api”,结果真的有。大概扫了下文档和示例,发现还挺好用的,于是就它了。

类似的情况还有之前的“查天气”系列课程(微信回复 43 可见),有人问是怎么知道获取天气的 API 地址,另外最初的地址现在已失效,如何更换新地址。对于这些,我也并不比各位了解更多,只是在需要的时候去网上搜索,发掘想要的答案。作为一名程序员,正确使用搜索引擎是最基本的技能。

现在许多内容型网站都将其数据开放了 API 供开发者使用,包括天气预报、电影、图书、地图、商户信息等等。对于没有 API 的网站,也可以通过直接抓取网页上的内容获得数据,也就是通常说的“爬虫”。API 和爬虫的区别在于,API 是内容提供方将信息整理好主动提供给你,数据有标准的格式,但使用时会受一定的限制;爬虫则是你直接从网页上的展现内容里去分析并提取你要的信息,一般来说是未经授权的。从实现上来说,API 会比爬虫简单许多,只要按照接口规范就很容易获取数据。

豆瓣 API 有比较详细的文档,各位可自行查阅。在文档中,我看到了两个比较合适的接口:

Top250(/v2/movie/top250),获取豆瓣电影排行榜前 250 部电影列表;
电影条目信息(/v2/movie/subject/:id),获取一部电影的详细信息。

所以我打算在抓取信息代码中,首先通过 Top250 接口获取 250 部电影的 id,之后再根据 id 查询每一部的详细信息。

这个代码并不作为网站功能的一部,而是直接通过命令行运行。如果你想在网页上实现此功能,会有一个问题,就是抓取过程是个很耗时的事情,但一个网页请求并不能等待很久,如果一段时间未返回,这个请求就会关闭。暂时我们还不需要去解决这个问题。

首先获取 Top250。使用最基本的 urllib 请求 API:

import urllib
response = urllib.urlopen('http://api.douban.com/v2/movie/top250')
data = response.read()
print data

可以看到结果是一长串 json 格式的文本,这就是我们想要的结果。建议在浏览器中打开此 API 地址,并且用 json 插件或工具查看返回信息,这样可以更直观地看到数据的结构。

someweb-05-1.png

将 json 格式转换成 dict 对象:

import json
data_json = json.loads(data)

dict 类型的结果中,subjects 对应的是影片 list:

movie250 = data_json['subjects']
for movie in movie250:
    print movie['id'], movie['title']

打印出结果,发现只有 20 条。又查了下文档,原来 Top250 接口还有两个可选参数:start 和 count,表示从第几位开始取,取多少条。

那么把代码调整下,外面增加一层循环。为了避免连续请求太快,在每次循环中,通过  time.sleep 方法停顿 3 秒钟。另外,把取到的电影 id 都存入一个变量中:

import urllib
import json
import time

movie_ids = []
for index in range(0, 250 ,50):
    print index
    response = urllib.urlopen('http://api.douban.com/v2/movie/top250?start=%d&count=50' % index)
    data = response.read()
    # print data

    data_json = json.loads(data)
    movie250 = data_json['subjects']
    for movie in movie250:
        movie_ids.append(movie['id'])
        print movie['id'], movie['title']
    time.sleep(3)
print movie_ids

一切顺利,拿到 250 个 id,接下来就可以进行第二步,获取影片的详细信息了。这里,你可以把打印出的 movie_ids 保存下来,避免后续过程中的重复抓取。

someweb-05-2.png
为了能把抓取到的数据保存下来,先对我们之前的数据库做一些改动。结合文档中对条目信息的说明,决定在数据库中记录以下字段:

id - 影片 id
title - 中文名
origin - 原名
url - 影片豆瓣链接
rating - 评分
image - 海报图片地址
directors - 导演
casts - 主演
year - 年代
genres - 类型
countries - 制片国家/地区
summary - 简介

在数据库中执行:

create table movie (id, title, origin, url, rating, image, directors, casts, year, genres, countries, summary);

添加数据的 sql 语句不需要了,我们将通过抓取程序来添加。

直接复用 web.py 里的数据库方法:

import web
db = web.database(dbn='sqlite', db='MovieSite.db')

观察一下条目信息 API 的格式,增加一个将此格式数据存入数据库的方法:

def add_movie(data):
    movie = json.loads(data)
    print movie['title']
    db.insert('movie',
        id=int(movie['id']),
        title=movie['title'],
        origin=movie['original_title'],
        url=movie['alt'],
        rating=movie['rating']['average'],
        image=movie['images']['large'],
        directors=','.join([d['name'] for d in movie['directors']]),
        casts=','.join([c['name'] for c in movie['casts']]),
        year=movie['year'],
        genres=','.join(movie['genres']),
        countries=','.join(movie['countries']),
        summary=movie['summary'],
    )

之后,就是开始让程序反复地去请求、转换、存储。同样,用 sleep 保持节奏,另外 print 出一些信息,以便于了解抓取的进度。这么做也是为了在程序意外中断后,可以手动从中断处开始继续抓取。

count = 0
for mid in movie_ids:
    print count, mid
    response = urllib.urlopen('http://api.douban.com/v2/movie/subject/%s' % mid)
    data = response.read()
    add_movie(data)
    count += 1
    time.sleep(3)

一切就绪,奔跑吧,程序!向着 250 部电影数据。

现在再运行网站,数据已经比较丰富了。不过因为数据库表名的变动,详细数据页会有错误。这个小修改就留给你们自己了。

get_movie.py (4.28 KB, 下载次数: 339)



#==== Crossin的编程教室 ====#
微信ID:crossincode
网站:http://crossincode.com
回复

使用道具 举报

174

主题

45

好友

11万

积分

管理员

Rank: 9Rank: 9Rank: 9

67#
发表于 2019-8-20 22:23:49 |只看该作者
boat 发表于 2019-8-18 08:41
这节课没啥问题,主要是两个地方注意一下。
一是python3里要用urllib.request.urlopen();
二是目前豆瓣API ...

赞一个
#==== Crossin的编程教室 ====#
微信ID:crossincode
网站:http://crossincode.com
回复

使用道具 举报

0

主题

0

好友

146

积分

注册会员

Rank: 2

66#
发表于 2019-8-18 08:41:01 |只看该作者
这节课没啥问题,主要是两个地方注意一下。
一是python3里要用urllib.request.urlopen();
二是目前豆瓣API已经关闭了,可以尝试通过下面两种进行连接
http://www.imooc.com/qadetail/319172
https://www.xiaojianjian.net/archives/4559
回复

使用道具 举报

174

主题

45

好友

11万

积分

管理员

Rank: 9Rank: 9Rank: 9

65#
发表于 2018-5-21 22:57:17 |只看该作者
风扇很响 发表于 2018-5-21 20:26
好了,现在差不多了,把movie_ids的结果放到一个文件里,每次从文件读取,如果文件不存在再做get_movie_i ...

insert之前先做一次select
#==== Crossin的编程教室 ====#
微信ID:crossincode
网站:http://crossincode.com
回复

使用道具 举报

2

主题

0

好友

476

积分

中级会员

Rank: 3Rank: 3

64#
发表于 2018-5-21 20:26:50 |只看该作者
crossin先生 发表于 2018-5-21 10:57
movie_ids = get_movie_ids(30, 10)
store_movie(movie_ids)

好了,现在差不多了,把movie_ids的结果放到一个文件里,每次从文件读取,如果文件不存在再做get_movie_ids
不过MovieSite.db里仍然可能重复保存,浪费时间,而且弄不好又被豆瓣封掉了,我找找怎么样可以做db.insert之前先检测一下相同的id是否已经存在
回复

使用道具 举报

174

主题

45

好友

11万

积分

管理员

Rank: 9Rank: 9Rank: 9

63#
发表于 2018-5-21 10:57:30 |只看该作者
风扇很响 发表于 2018-5-20 20:29
一头雾水...
稍微改了下想减少点数量,调试方便一点
#-*-coding: utf-8 -*-

movie_ids = get_movie_ids(30, 10)
store_movie(movie_ids)

你这两句放在代码里,每次运行都会去执行抓取任务
#==== Crossin的编程教室 ====#
微信ID:crossincode
网站:http://crossincode.com
回复

使用道具 举报

2

主题

0

好友

476

积分

中级会员

Rank: 3Rank: 3

62#
发表于 2018-5-20 20:29:25 |只看该作者
crossin先生 发表于 2018-5-19 17:30
可能只是达到豆瓣接口限制被暂时拒绝了

你要调试的话,把输出的url 给 print 出来看一下 ...

一头雾水...
稍微改了下想减少点数量,调试方便一点
#-*-coding: utf-8 -*-
#D:\ProgramData\Anaconda3\envs\py36\python.exe code.py 127.0.0.1
import web
import urllib.request
import json
import time

urls = (
    '/', 'index',
    '/movie/(\d+)', 'movie',
)

render = web.template.render('templates/')

#web.py 连接 SQLite
db = web.database(dbn='sqlite', db='MovieSite.db')

def add_movie(data):
    movie = json.loads(data)
    #print (movie['title'])
    db.insert('movie',
        id = int(movie['id']),
        title = movie['title'],
        origin = movie['original_title'],
        url = movie['alt'],
        rating = movie['rating']['average'],
        image = movie['images']['large'],
        directors = ','.join([d['name'] for d in movie['directors']]),
        casts = ','.join([c['name'] for c in movie['casts']]),
        year = movie['year'],
        genres = ','.join(movie['genres']),
        countries = ','.join(movie['countries']),
        summary = movie['summary'],
    )

def movie_exist(data):
    movie = json.loads(data)
    n_id = int(movie['id'])
    pass

class index:
    def GET(self):
        movies = db.select('movie')
        return render.index(movies)

    def POST(self):
        data = web.input()
        condition = r'title like "%' + data.title + r'%"'
        movies = db.select('movie', where=condition)
        return render.index(movies)

class movie:
    def GET(self, movie_id):
        condition = 'id=' + movie_id
        movie = db.select('movie', where=condition)[0]
        return render.movie(movie)

def get_movie_ids(total_num, step_num):
    movie_ids = []
    for index in range(0, total_num, step_num):
        response = urllib.request.urlopen('http://api.douban.com/v2/movie/top250?start=%d&count=step_num' % index)
        data = response.read()
        data_json = json.loads(data)
        movie_total = data_json['subjects']
        #print(len(movie_total))
        for movie in movie_total:
            movie_ids.append(movie['id'])
            print(movie['id'], movie['title'])
        time.sleep(3)
    #print(movie_ids)
    #print(len(movie_ids))
    return  movie_ids

def store_movie(movie_ids):
    count = 0
    for mid in movie_ids:
        #print (count, mid)
        try:
            response = urllib.request.urlopen('http://api.douban.com/v2/movie/subject/%s' % mid)
            data = response.read()
            add_movie(data)
            count += 1
            time.sleep(3)
        except:
            print('movie %s is not found' % mid)

movie_ids = get_movie_ids(30, 10)
store_movie(movie_ids)

if __name__ == "__main__":
    app = web.application(urls, globals())
    app.run()

第一次运行,看到数据库已经导入记录成功了,终端里显示http://127.0.0.1:8080/
但是浏览器打开网页之后,页面一直没有显示,这时候看到终端里又自动重新运行了一遍,直到被豆瓣接口拒绝
老师能不能看下这是什么问题?
回复

使用道具 举报

174

主题

45

好友

11万

积分

管理员

Rank: 9Rank: 9Rank: 9

61#
发表于 2018-5-19 17:30:29 |只看该作者
风扇很响 发表于 2018-5-19 17:18
#-*-coding: utf-8 -*-
#D:\ProgramData\Anaconda3\envs\py36\python.exe code.py 127.0.0.1
import web

可能只是达到豆瓣接口限制被暂时拒绝了

你要调试的话,把输出的url 给 print 出来看一下
#==== Crossin的编程教室 ====#
微信ID:crossincode
网站:http://crossincode.com
回复

使用道具 举报

2

主题

0

好友

476

积分

中级会员

Rank: 3Rank: 3

60#
发表于 2018-5-19 17:18:55 |只看该作者
#-*-coding: utf-8 -*-
#D:\ProgramData\Anaconda3\envs\py36\python.exe code.py 127.0.0.1
import web
import urllib.request
import json
import time

urls = (
    '/', 'index',
    '/movie/(\d+)', 'movie',
)

render = web.template.render('templates/')

#web.py 连接 SQLite
db = web.database(dbn='sqlite', db='MovieSite.db')

def add_movie(data):
    movie = json.loads(data)
    #print (movie['title'])
db.insert('movie',
        id = int(movie['id']),
        title = movie['title'],
        origin = movie['original_title'],
        url = movie['alt'],
        rating = movie['rating']['average'],
        image = movie['images']['large'],
        directors = ','.join([d['name'] for d in movie['directors']]),
        casts = ','.join([c['name'] for c in movie['casts']]),
        year = movie['year'],
        genres = ','.join(movie['genres']),
        countries = ','.join(movie['countries']),
        summary = movie['summary'],
    )

def movie_exist(data):
    movie = json.loads(data)
    n_id = int(movie['id'])
    pass
class index:
    def GET(self):
        movies = db.select('movie')
        return render.index(movies)

    def POST(self):
        data = web.input()
        condition = r'title like "%' + data.title + r'%"'
movies = db.select('movie', where=condition)
        return render.index(movies)

class movie:
    def GET(self, movie_id):
        condition = 'id=' + movie_id
        movie = db.select('movie', where=condition)[0]
        return render.movie(movie)


movie_ids = []
for index in range(0, 250, 50):
    response = urllib.request.urlopen('http://api.douban.com/v2/movie/top250?start=%d&count=50' % index)
    data = response.read()
    data_json = json.loads(data)
    movie250 = data_json['subjects']
    for movie in movie250:
        movie_ids.append(movie['id'])
        #print(movie['id'], movie['title'])
time.sleep(3)
#print(movie_ids)
count = 0
for mid in movie_ids:
    #print (count, mid)
try:
        response = urllib.request.urlopen('http://api.douban.com/v2/movie/subject/%s' % mid)
        data = response.read()
        add_movie(data)
        count += 1
        time.sleep(3)
    except:
        print('movie %s is not found' % mid)

if __name__ == "__main__":
    app = web.application(urls, globals())
    app.run()

第一次从头开始sqlite3 MovieSite.db
create table movie (id, title, origin, url, rating, image, directors, casts, year, genres, countries, summary);然后运行上面的程序,进入网页报错
无标题5.jpg 无标题4.jpg
第二次把获取movie_ids和存入数据库的两段注释掉,再运行,就可以正常显示网页了(虽然也没全部抓取到,只有90多个)
麻烦老师看下这会是什么问题?



回复

使用道具 举报

174

主题

45

好友

11万

积分

管理员

Rank: 9Rank: 9Rank: 9

59#
发表于 2018-2-7 17:02:58 |只看该作者
qiqibaoer 发表于 2018-2-6 17:11
我也有遇到,抓取数据时会有找不到数据的情况,然后程序就停止了,这种不知道怎么处理下跳过该数据继续执 ...

异常处理
#==== Crossin的编程教室 ====#
微信ID:crossincode
网站:http://crossincode.com
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即加入

QQ|手机版|Archiver|Crossin的编程教室 ( 苏ICP备15063769号  

GMT+8, 2024-5-4 19:35 , Processed in 0.033478 second(s), 26 queries .

Powered by Discuz! X2.5

© 2001-2012 Comsenz Inc.

回顶部