使用 scrapy 爬取豆瓣电影 Top250

最近在学习使用 python 做数据分析,正好今天学了下 scrapy ,来写个爬虫练练手吧。因为是第一次写爬虫,先来个简单点的,那就爬豆瓣电影 Top250 吧。

因为这次我是在 windows 上写的,之前由于 window 的开发环境配置实在是不省心,各种问题搞得很烦躁。最近试了下 Anconda 这个 python 的科学计算环境发行包,它集成了很多常用的科学计算 package,省去了很多配置环境变量的步骤,能做到开箱即用。而且自带了非常简单易用的虚拟环境,python 版本之间可以随意切换,互不干扰。

环境配置

首先使用 conda 新建一个环境(基于 python3):

conda create -n spider python=3

激活 spider 这个环境:

activate spider

接着安装相关依赖:

pip install scrapy beautifulsoup peewee
  • scrapy 是我们写爬虫需要用到的框架;
  • beautifulsoup 是解析 html 用到的框架;
  • peewee 是一款数据库 ORM 库,可以很方便的帮你把对象和数据库表进行映射,从而让你不用写一句 SQL 就能操作数据库,我们爬到的数据需要写到 mysql 数据库中。

然后使用 scrapy 创建爬虫项目:

scrapy startproject douban

定制爬虫

首先我们先建一个 MySQL数据库 douban,设置好用户名和密码。

接着我们需要定义一个想要爬取的数据 Item,它继承了 scrapy.Item 这个类,包含了 name(电影名称)、url(详情链接)、score(电影评分)这三个数据。为了能将这些数据写入到 MySQL 数据库,我们还使用 peewee 定义了一个 Movie 类,继承于 Model 类(该类属于 peewee),并定义了相关的属性。

import scrapy
from peewee import *

db = MySQLDatabase("douban", host='localhost', user="root", passwd="123456", charset="utf8")

class DoubanMovieItem(scrapy.Item):
    name = scrapy.Field()
    url = scrapy.Field()
    score = scrapy.Field()

class Movie(Model):
    id = PrimaryKeyField()
    name = CharField(verbose_name="电影名称", max_length=50, null=False, unique=False)
    url = CharField(verbose_name="详情链接", null=False)
    score = FloatField(verbose_name="评分", null=False, unique=False)

    class Meta:
        database = db

数据 Model 定义好了之后,我们需要在 spiders 这个目录下自定义一个 spider,这个 spider 需要继承 Spider 这个类。在该类中,我们需要设置 spider 的 name,以及 header(添加 header 能够模拟正常的请求,不加 header 很容易造成请求失败),在 start_requests 方法中定义开始的 url,并返回这个请求。

parse 这个方法中,我们使用 BeautifulSoup 来解析获取到的 response 对象,具体参数需要去豆瓣电影的 html 源码中去找。

import scrapy
from scrapy import Spider
from spider.items import DoubanMovieItem
from bs4 import BeautifulSoup

class DoubanTopMovieSpider(Spider):
    name = 'DoubanTopMovie'
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.110 Safari/537.36'
    }

    def start_requests(self):
        url = 'https://movie.douban.com/top250'
        yield scrapy.Request(url, headers=self.headers)

    def parse(self, response):
        item = DoubanMovieItem()

        soup = BeautifulSoup(response.text, 'lxml')
        movies = soup.findAll('div', {"class": 'item'})
        for movie in movies:
            item['name'] = movie.find('span', {'class': 'title'}).get_text()
            item['url'] = movie.find('a')['href']
            item['score'] = movie.find('span', {'class': 'rating_num'}).get_text()

            yield item

        next_url = soup.find('span', {'class': 'next'}).find('a')['href']
        if next_url:
            url = 'https://movie.douban.com/top250' + next_url
            yield scrapy.Request(url, headers=self.headers)

末尾,我们需要获取 next_url,继续进行下一页的爬取操作。

存储到 MySQL

爬到数据之后,我们需要在 pipelines 这个 module 中将数据保存到数据库,因为我们使用的是 orm,可以看到我们没有写任何 SQL 语句,就能很方便的把数据写到数据库中。

from spider.items import Movie

class SpiderPipeline(object):
    def process_item(self, item, spider):

        if Movie.table_exists() == False:
            Movie.create_table()

        mv = Movie(name=item['name'], url=item['url'], score=item['score'])
        mv.save()

        return item

最后上一张爬取到的数据截图: