Python爬虫之爬豆瓣电影数据

0x000 前言

最近和公司的IOS谈起,以前我们是做移动端,现在已经和前端混为一谈了。 也是 , 做了一年多的Android ,一直都是在写界面展示数据,写交互 ,存储简单的数据 ,最主要的业务数据与业务逻辑都在服务器端,这让我有种离了服务端,我们只能玩玩单机和联机了 。作为曾经的web开发,深知服务端的重要性,我们要想做一款好的产品,没有服务端是万万不能的 , 而我们做移动端的更是要知晓一些服务端的知识 。

我有一个梦想,做一款属于自己的产品 ,但苦于没有足够的数据,于是就开始了我的Python爬虫之旅 。(如果有时间,我会写如何从零收集数据-->服务器端的建立 --> 客户端的编写 , 一整套教程)

0x001 Python基础

Python是一门很简洁的语言 , 因为市面上介绍Python语法的教程很多 , 大多数都很好 ,我这里就不啰嗦了 , 下面贴出我看过的网站:

廖雪峰 - Python教程
简明Python教程

0x002 网络爬虫

网络爬虫是一个自动提取网页的程序,它为搜索引擎从万维网上下载网页,是搜索引擎的重要组成。传统爬虫从一个或若干初始网页的URL开始,获得初始网页上的URL,在抓取网页的过程中,不断从当前页面上抽取新的URL放入队列,直到满足系统的一定停止条件。[来自百度]

我们通过URL让爬虫来爬取我们想要的内容 , 根据我们的规则进行深入爬取 , 比如说 , 我们想要爬取正在上映的电影数据 , 并下载电影海报,其他的内容我们忽略掉,那么我们就需要根据网页内容的规则来制订,哪些内容是我们需要的,哪些是我们不需要的。

0x003 爬取豆瓣电影数据

第一步 : 打开豆瓣电影 , 分析网页结构

我们爬取的网址是 https://movie.douban.com/cinema/nowplaying/shenzhen/ ,里面有一个的模块叫正在上映 , 我们就解析这个模块,其他的内容我们忽略掉。chrome(firefox)按f12查看网页结构。

网页结构

我们可以用元素选择工具,选中我们要分析的数据,firebug控制台就会将选中html结构展示出来。我们通过分析 , 我们想要的数据是在一个ul标签里面,每一个li标签就是一部电影的数据,我们只需要取出里面的li中的数据即可。豆瓣的前台使用了数据绑定的技术 ,这为我们获取数据,方便了不少 ,我们直接取出li中的属性就可以将电影信息获取到了 。

第二步 : 下载网页内容

我们需要将网页中的数据提取出去 , 就需要先将网页内容下载下来,写Java程序的朋友应该知道 , 如果使用Java内置网络请求,将网页数据下载下来,那将要写多少行代码,实话说,Java的网络请求真的很不友好,幸而Java社区异常强大,涌现了一批又一批比较好用的网络请求框架,如OkHttp,liteHttp等等,大大简化了我们的工作。但是,在Python中,将一个网页数据下载下来,只需要一行 。

with request.urlopen('https://movie.douban.com/cinema/nowplaying/shenzhen/') as response:

这是Python内置的urllib下的requests对象,将下载好的二进制数据封装在response对象里面,使用readlines方法就可以将其读取出来 。

from urllib import request
from urllib import parse

# 打开链接,并得到返回值
with request.urlopen('https://movie.douban.com/cinema/nowplaying/shenzhen/') as response:
      conent_list = response.readlines() # 得到一个byte类型的list
      #打印内容
      for content in conent_list:
          print(content .decode()) # 因为content是byte类型,所以需要解码成str类型

虽然内置urlib可以完成我们的需求,但是我们不会用系统内置,因为还是比较麻烦,我们将使用富有盛名的requets库,虽然名字和系统内置的差不多,但是确实截然不同的库 。系统库是在urllib模块下,而requests就在requests模块下 。

安装requests

pip install requests

example

# url 请求地址
# headers 请求头
url = 'https://movie.douban.com/cinema/nowplaying/shenzhen/'
res = requests.get(url,headers=headers)
res.text() # 得到请求的文本内容

好戏,马上开场

第三步 : 解析网页内容

解析是使用的Python内置的Html解析器,类似Java的jsoup.jar提供的api 。都是通过遍历Html dom树来进行分析,判断需要的tag ,然后进行属性解析。Python正在强大的Html解析器是,XPath解析,也是scrapy爬虫库内置的解析器 ,当然还是有beautifulsoup 。

1.引入HTMLPaser

from html.parser import HTMLParser

2.新建解析类,继承HTMLPaser

class MoviesParser(HTMLParser):

3.overload handle_starttag方法 , 解析标签

class MoviesParser(HTMLParser):
    def __init__(self):
        HTMLParser.__init__(self)
        # 电影列表list集合
        self.movies = []
        # 标签是否在li标签中的标识
        self.img_into_movie = False
        self.a_into_movie = False

    def handle_starttag(self, tag, attrs):
        # 因为属性都是key=val形式 , 所以根据属性名称取值
        def _attr(attrs,attr_name):
            for attr in attrs:
                if attr[0] == attr_name:
                    return attr[1]
            return None

        # 取出属性值 , 根据每个Item的特征取值 ,因为有些Item的属性可能会重复 , 所以要尽量找出差异性,这样才能保证数据的准确性
        if tag == 'li' and _attr(attrs,'data-title') and _attr(attrs,'data-category') == 'nowplaying':
            movie = {}
            movie['title'] = _attr(attrs,'data-title')
            movie['score'] = _attr(attrs,'data-score')
            movie['star'] = _attr(attrs,'data-star')
            movie['duration'] = _attr(attrs,'data-duration')
            movie['region'] = _attr(attrs,'data-region')
            movie['director'] = _attr(attrs,'data-director')
            movie['actors'] = _attr(attrs,'data-actors')
            self.movies.append(movie)
            self.img_into_movie = True
            self.a_into_movie = True

        #获取海报图片
        if tag == 'img' and self.img_into_movie:
            self.img_into_movie = False
            img_src = _attr(attrs,'src')
            movie = self.movies[len(self.movies) -1]
            movie['poster_url'] = img_src
            # 下载图片
            donwload_poster_url(img_src)
        
        # 解析a标签,提取电影详情页的Url
        if tag == 'a' and self.a_into_movie:
            if _attr(attrs,'data-psource') == 'title':
                self.a_into_movie = False
                movie_url = _attr(attrs,'href')
                movie = self.movies[len(self.movies) -1]
                movie['movie_url'] = movie_url

这是爬虫中最关键的部分,数据解析是保证数据正确的性的地方,解析没做好,就可能存在很多脏数据,这是我们应当避免的 。

第四步 : 下载图片

# 下载图片
def donwload_poster_url(url):
    res = requests.get(url)
    file_name = str.split(url,'/')[-1]
    file_path = 'poster_img/' + file_name
    print('download img file_path = ',file_path)
    with open(file_path,'wb') as f:
        f.write(res.content)

我们直接使用requests库,get图片地址,得到图片的二进制数据,再见二进制数据写入到文件中,这样我们的图片文件就下载好了。

完整源码

# 使用requests爬豆瓣正在上映的电影
from html.parser import HTMLParser
import requests


class MoviesParser(HTMLParser):
    def __init__(self):
        HTMLParser.__init__(self)
        self.movies = []
        self.img_into_movie = False
        self.a_into_movie = False

    def handle_starttag(self, tag, attrs):
        # 根据属性名称取值
        def _attr(attrs,attr_name):
            for attr in attrs:
                if attr[0] == attr_name:
                    return attr[1]
            return None
        # 取出属性值
        if tag == 'li' and _attr(attrs,'data-title') and _attr(attrs,'data-category') == 'nowplaying':
            movie = {}
            movie['title'] = _attr(attrs,'data-title')
            movie['score'] = _attr(attrs,'data-score')
            movie['star'] = _attr(attrs,'data-star')
            movie['duration'] = _attr(attrs,'data-duration')
            movie['region'] = _attr(attrs,'data-region')
            movie['director'] = _attr(attrs,'data-director')
            movie['actors'] = _attr(attrs,'data-actors')
            self.movies.append(movie)
            self.img_into_movie = True
            self.a_into_movie = True

        #获取海报图片
        if tag == 'img' and self.img_into_movie:
            self.img_into_movie = False
            img_src = _attr(attrs,'src')
            movie = self.movies[len(self.movies) -1]
            movie['poster_url'] = img_src
            donwload_poster_url(img_src)

        if tag == 'a' and self.a_into_movie:
            if _attr(attrs,'data-psource') == 'title':
                self.a_into_movie = False
                movie_url = _attr(attrs,'href')
                movie = self.movies[len(self.movies) -1]
                movie['movie_url'] = movie_url


# 下载图片
def donwload_poster_url(url):
    res = requests.get(url)
    file_name = str.split(url,'/')[-1]
    file_path = 'poster_img/' + file_name
    print('download img file_path = ',file_path)
    with open(file_path,'wb') as f:
        f.write(res.content)


def douban_movies(url):
    #首先构建请求头 ,模拟浏览器请求头
    headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36'}
    # # 打开链接,并得到返回值
    res = requests.get(url,headers=headers)
    # 创建Html解析器
    moviesParser = MoviesParser()
    # 解析Html数据
    moviesParser.feed(res.text)
    return moviesParser.movies

if __name__ == '__main__':
    url_str = 'https://movie.douban.com/cinema/nowplaying/shenzhen/'
    movies_res = douban_movies(url_str)

    import json
    json_str = json.dumps(movies_res,sort_keys=True,indent=4,separators=(',',': '))
    # 打印json数据
    print(json_str)

从下载数据到解析Html,只用了七十多行 ,包含了注释和空格,真是人生苦短,我用Python

0x004 结语

Python确实是一个比较简洁的语言,学起来也相对比较轻松,各种库应有尽有,可以作为获取数据的不二选择 。其实,解析Html 还可以简化,使用Xpath更加简洁,几行代码就可以搞定,在后续的文章中,我会逐一介绍 。

有时候感觉文字的表现力真是太弱了,特别是对于技术文章,因为涉及的多,很多时候写着写着就啰嗦了,然后删掉 。 俗话说:子不如表 , 表不如图,图不如视频 。最近关注了一个简书的作者,他做了一系列的视频,讲得挺好的,我思考着,要不要出个爬虫的视频教程 。

推荐阅读更多精彩内容