零基础Python爬虫实战:豆瓣电影TOP250

我们曾经抓取过猫眼电影TOP100,并进行了简单的分析。但是众所周知,豆瓣的用户比较小众、比较独特,那么豆瓣的TOP250又会是哪些电影呢?

我在整理代码的时候突然发现一年多以前的爬虫代码竟然还能使用……那今天就用它来演示下,如何通过urllib+BeautifulSoup来快速抓取解析豆瓣电影TOP250。

一、观察网页地址

首先我们观察url地址,连续点击几页之后我们发现,豆瓣电影TOP250一共分10页,每页有25部电影,每页的url地址的格式为https://movie.douban.com/top250?start={0}&filter=,大括号中的部分用这一页的第一部电影的编号代替,编号从0开始一直到249。

因此我们可以通过格式化字符串来生成所有的url地址:

url_init = 'https://movie.douban.com/top250?start={0}&filter='
urls = [url_init.format(x * 25) for x in range(10)]
print(urls)

输出为:

['https://movie.douban.com/top250?start=0&filter=',
 'https://movie.douban.com/top250?start=25&filter=',
 'https://movie.douban.com/top250?start=50&filter=',
 'https://movie.douban.com/top250?start=75&filter=',
 'https://movie.douban.com/top250?start=100&filter=',
 'https://movie.douban.com/top250?start=125&filter=',
 'https://movie.douban.com/top250?start=150&filter=',
 'https://movie.douban.com/top250?start=175&filter=',
 'https://movie.douban.com/top250?start=200&filter=',
 'https://movie.douban.com/top250?start=225&filter=']

当然,这个地址未必就是我们需要请求的地址。我们先打开TOP250的第一页,右键检查,单击进入网络(Network)选项卡,刷新一下,可以看到出现了一大堆请求的返回结果,我们先打开第一个document类型的请求。

我们切换到Headers标签,可以看到,在General下,Request URL的取值与网页地址一样。也就是说,我们上边生成的10条url,正是我们需要请求的地址。

二、定位信息位置

接下来我们看一下电影的信息藏在哪里。我们切换到Response标签下,搜索网页中的内容,比如我们先搜一下榜首的名称:肖申克的救赎。

可以看到,所有的电影信息都在一个class="grid_view"

    (有序列表)标签下,每一部电影是一个
  1. 标签。

    在每个

  2. 下:

    • 标题藏在一个class="title"标签下;
    • 导演、主演、上映年份、地区、类型藏在一个
      的子标签

      中;

    • 得分和评分人数在

    好,接下来我们就开始抓取并解析这些内容。

    三、抓取并解析

    首先我们定义一个函数,用来打开url并返回BeautifulSoup对象。

    # -*- coding:utf-8 -*-
    
    from urllib.request import urlopen
    from bs4 import BeautifulSoup
    from collections import defaultdict
    import pandas as pd
    import time
    import re
    
    
    class DoubanMovieTop():
        def __init__(self):
            self.top_urls = ['https://movie.douban.com/top250?start={0}&filter='.format(x*25) for x in range(10)]
            self.data = defaultdict(list)
            self.columns = ['title', 'link', 'score', 'score_cnt', 'top_no', 'director', 'writers', 'actors', 'types',
                            'edit_location', 'language', 'dates', 'play_location', 'length', 'rating_per', 'betters',
                            'had_seen', 'want_see', 'tags', 'short_review', 'review', 'ask', 'discussion']
            self.df = None
    
        def get_bsobj(self, url):
            html = urlopen(url).read().decode('utf-8')
            bsobj = BeautifulSoup(html, 'lxml')
            return bsobj
    

    在这个函数中,我们使用urllib.requests.urlopen来发起请求,对返回的响应结果,我们使用.read()方法来读取内容。但是这里读取到的内容是字节(bytes),我们要将其转化为字符串,所以我们要再使用字节对象的.decode('utf-8')方法进行转化;然后我们使用bs4.BeautifulSoup()从字符串中生成BeautifulSoup对象。

    这里我们为什么选择utf-8编码进行解码呢?这是因为这个网页的编码正是utf-8。一般情况下,我们可以从网页的中找到编码信息,这样我们就可以对于不同编码的网页进行针对性的解码了。

    接下来我们开始解析电影的信息。我们定义一个函数,以上边get_bsobj(url)函数输出的BeautifulSoup对象为输入,以数据列表为输出。

    def get_info(self):
        for url in self.top_urls:
            bsobj = self.get_bsobj(url)
            main = bsobj.find('ol', {'class': 'grid_view'})
    
            # 标题及链接信息
            title_objs = main.findAll('div', {'class': 'hd'})
            titles = [i.find('span').text for i in title_objs]
            links = [i.find('a')['href'] for i in title_objs]
    
            # 评分信息
            score_objs = main.findAll('div', {'class': 'star'})
            scores = [i.find('span', {'class': 'rating_num'}).text for i in score_objs]
            score_cnts = [i.findAll('span')[-1].text for i in score_objs]
    
            for title, link, score, score_cnt in zip(titles, links, scores, score_cnts):
                self.data[title].extend([title, link, score, score_cnt])
                bsobj_more = self.get_bsobj(link)
                more_data = self.get_more_info(bsobj_more)
                self.data[title].extend(more_data)
                print(self.data[title])
                print(len(self.data))
                time.sleep(1)
    

    我们在榜单列表页面直接获取了标题、电影详情页面地址、评分、评分人数信息。

    接下来,可以看到我对所有的电影详情页面进行了一个循环抓取解析,这是因为在榜单页面中信息展示不全,且这里的信息不够丰富,在详情页中,我们可以获取非常丰富的数据,包括导演、编剧、演员、上映时间和地区、语言、别名、短评数、影评数、多少人想看、多少人看过……

    获得了这么多信息之后,我们可以进行更加深入的分析,因此我们选择进入详情页进一步抓取更多信息。因此我们需要定义一个函数,用来解析详情页。

    豆瓣的页面抓取难度较小,不过我们这里定义了较多的字段,因此这个函数会显得比较长。这个函数里我们使用了两个try...except...来应对异常,这是因为有些电影没有编剧或者主演,这会导致抓取异常,针对这种情况,我们直接将该字段留空即可。

    每个字段抓取的表达式都是根据返回的源码得到的,BeautifulSoup的使用非常简单,几分钟就可以上手,半小时就可以入门。事实上我现在更喜欢使用XPath表达式,更灵活、更强大,对XPath的使用不了解的同学可以去看我的另一篇抓取网易云音乐的文章。在这个例子中,有些BeautifulSoup不太容易实现的部分,我们结合了re正则表达式来完成。

    关于BeautifulSoup的使用,可以参考这份教程:https://docs.pythontab.com/beautifulsoup4/。至于学习程度,仍然是按照我们的二八法则,不需深究,学习最少的内容,覆盖最多的应用即可,剩下的在实战中遇到了再去检索学习即可。

    def get_more_info(self, bsobj):
        # 榜单排名
        top_no = bsobj.find('span', {'class': 'top250-no'}).text.split('.')[1]
    
        # 更多信息
        main = bsobj.find('div', {'id': 'info'})
    
        # 导演
        dire_obj = main.findAll('a', {'rel': 'v:directedBy'})
        director = [i.text for i in dire_obj]
    
        # 编剧
        try:
            writer_obj = main.findAll('span', {'class': 'attrs'})[1]
            writers = [i.text for i in writer_obj.findAll('a')]
        except Exception as e:
            writers = []
            print(e)
    
        # 主演
        try:
            actor_obj = main.findAll('a', {'rel': 'v:starring'})
            actors = [i.text for i in actor_obj]
        except Exception as e:
            actors = []
            print(e)
    
        # 类型
        type_obj = main.findAll('span', {'property': 'v:genre'})
        types = [i.text for i in type_obj]
    
        # 制片地区
        pattern = re.compile('地区: (.*?)\n语言', re.S)
        edit_location = re.findall(pattern, main.text)[0]
    
        # 语言
        pattern2 = re.compile('语言: (.*?)\n上映日期')
        language = re.findall(pattern2, main.text)[0]
    
        # 上映日期/地区
        date_obj = main.findAll('span', {'property': 'v:initialReleaseDate'})
        dates = [i.text.split('(')[0][:4] for i in date_obj]
        play_location = [i.text.split('(')[1][:-1] for i in date_obj]
    
        # 片长
        length = main.find('span', {'property': 'v:runtime'})['content']
    
        # 5星到1星比例
        rating_obj = bsobj.findAll('span', {'class': 'rating_per'})
        rating_per = [i.text for i in rating_obj]
    
        # 好于
        better_obj = bsobj.find('div', {'class': 'rating_betterthan'})
        betters = [i.text for i in better_obj.findAll('a')]
    
        # 想看/看过
        watch_obj = bsobj.find('div', {'class': 'subject-others-interests-ft'})
        had_seen = watch_obj.find('a').text[:-3]
        want_see = watch_obj.findAll('a')[-1].text[:-3]
    
        # 标签
        tag_obj = bsobj.find('div', {'class': 'tags-body'}).findAll('a')
        tags = [i.text for i in tag_obj]
    
        # 短评
        short_obj = bsobj.find('div', {'id': 'comments-section'})
        short_review = short_obj.find('div').find('span', {'class': 'pl'}).find('a').text.split(' ')[1]
    
        # 影评
        review = bsobj.find('a', {'href': 'reviews'}).text.split(' ')[1]
    
        # 问题
        ask_obj = bsobj.find('div', {'id': 'askmatrix'})
        ask = ask_obj.find('h2').find('a').text.strip()[2:-1]
    
        # 讨论
        discuss_obj = bsobj.find('p', {'class': 'pl', 'align': 'right'}).find('a')
        discussion = discuss_obj.text.strip().split('(')[1][2:-2]
    
        more_data = [top_no, director, writers, actors, types, edit_location, language, dates, play_location,
                     length, rating_per, betters, had_seen, want_see, tags, short_review, review, ask, discussion]
    
        return more_data
    

    成功抓取之后,我们还需要定义一个函数,用来将数据缓存到本地或其他途径(比如数据库),用于后续分析。

    def dump_data(self):
        data = []
        for title, value in self.data.items():
            data.append(value)
        self.df = pd.DataFrame(data, columns=self.columns)
        self.df.to_csv('douban_top250.csv', index=False)
    

    好了,一个针对豆瓣电影TOP250的爬虫就写完了,接下来我们执行抓取。

    if __name__ == '__main__':
        douban = DoubanMovieTop()
        douban.get_info()
        douban.dump_data()
    

    抓取完成后,我们就可以看到我们的数据了。

    那么有人可能会问,我们抓取到数据就结束了吗?

    当然没有,在下一篇文章中,我们会实战演练如何对我们得到的数据进行数据清洗和分析挖掘。

你可能感兴趣的:(互联网拾遗)